Zagens API Design

July 9, 2026 · View on GitHub

Zagens shell version: 0.7.5 (crates/desktop/Cargo.toml) | Document revision: 2026-06-12 | Authoritative implementation: commands.rs, runtime_proxy.rs, runtime_api/router.rs build_router, web-ui/src/api/client.ts

This document describes the dual-channel integration API of the Zagens desktop shell. Protocol types live in crates/protocol/; the HTTP routing SSOT is crates/runtime-server/src/runtime_api/router.rs (sidecar: deepseek-runtime).

OpenAPI 3.1 (D8): Checked-in contract openapi/zagens-runtime-v1.openapi.json; exported by export-runtime-openapi from Rust schemars + path table; web-ui TS generated via openapi-typescript (see adr/D8_OPENAPI_TS_GENERATION.md). Legacy RUNTIME_API.md has been removed. Runtime architecture diagrams: RUNTIME_ARCHITECTURE.md.

Agent behavior: The runtime prompt loaded by the same sidecar includes hallucination-control sub-rules (Capability / Architecture Claims) in crates/runtime-server/src/prompts/base.md. SSE diagrams and capability claims in this document are illustrative only — integrate against code and streamNormalize.ts.

Only production HTTP runtime: Zagens and headless scripts use crates/runtime-server/src/runtime_api/ (/v1/*); binary is deepseek-runtime. crates/app-server removed in D7; deepseek-tui / deepseek serve removed in D6 Phase B (see adr/D4_APPSERVER_DEPRECATED.md · adr/D6_PHASE_B_CLI_SUNSET.md).


1. Architecture Overview

Zagens uses a dual-channel API architecture: the WebView frontend and Rust backend communicate over two paths.

┌────────────────────────────────────────────────────────────┐
│                    Zagens (Tauri App)                      │
│                                                            │
│  ┌─────────────────────┐    ┌────────────────────────┐     │
│  │   WebView (React)   │    │    Rust Shell          │     │
│  │                     │    │                        │     │
│  │  Channel A:         │    │  commands.rs           │     │
│  │  Tauri invoke() ────┼───►│  runtime_proxy.rs      │     │
│  │  (native bridge,    │    │  sidecar.rs            │     │
│  │   ~64 commands)     │    │  main.rs               │     │
│  │                     │    │  ┌──────────────────┐  │     │
│  │  Channel B:         │    │  │  Sidecar Process │  │     │
│  │  via proxy or dev   │    │  │  deepseek-runtime│  │     │
│  │  fetch (127.0.0.1)  │───►│  │  (HTTP + SSE)    │  │     │
│  │  73 HTTP routes*    │    │  └──────────────────┘  │     │
│  └─────────────────────┘    └────────────────────────┘     │
└────────────────────────────────────────────────────────────┘
ChannelTransportPurpose
A — Tauri IPCinvoke() → Rust #[tauri::command]System-level ops: secrets, settings, binary I/O, runtime HTTP/SSE proxy (H06), terminal, multi-window
B — Runtime HTTPUnder Tauri via runtime_proxy; Vite dev connects directly to http://127.0.0.1:{port}Agent runtime: chat threads, sessions, streaming, MCP, tasks, usage

* §8 “Channel B” table counts 73 rows by method + path (including /health and /internal/probe); verify against runtime_api/router.rs + runtime-api/src/router.rs.

vs CLI / app-server: Zagens does not start crates/app-server; the desktop only spawns the bundled deepseek-runtime sidecar; the HTTP surface is runtime_api/. app-server is for other entry points (if any), not the Zagens data path.


2. Tauri IPC Commands (Channel A)

Protocol: JSON-RPC (Tauri built-in), invoked via @tauri-apps/api/core invoke(). Authentication: No extra auth — in-process method calls; security enforced by Tauri sandbox.

2.1 Runtime Connection

CommandParametersReturnDescription
get_runtime_portu16Sidecar listen port (default 7878)
runtime_httprequest: { method, path, body? }{ status, body }H06 proxy: REST /v1/*; Rust injects Bearer
runtime_post_streambody: String() + SSE eventsH06 proxy: POST /v1/stream; events pushed to WebView via Tauri
runtime_get_ssepath: String, thread_id?: String() + SSE eventsH06 proxy: GET …/events and other SSE. thread_id (multi-session P0.1) isolates the consumer per (window, thread); omit for legacy per-window behaviour
runtime_cancel_ssethread_id?: String()Cancel in-flight runtime_get_sse. With thread_id: cancel only that thread's consumer; without: cancel every in-flight SSE for the window (legacy global Stop)
get_platform_infoPlatformInfo{ os, arch, version }version is Zagens shell SemVer (CARGO_PKG_VERSION), not OS version
get_os_themeStringPlaceholder: currently fixed "dark"; system theme API not read
get_localeStringRead persisted app locale from ~/.deepseek/config.toml (zagens-config)
set_app_localelocale: String()Persist app locale to config
restart_sidecar()Trigger sidecar restart (reload config.toml)

2.1.1 Cancel / Interrupt Two-Layer Contract (D9)

Desktop Stop / Escape must execute both layers; disconnecting the stream alone does not stop the runtime turn loop.

LayerMechanismEffectIf skipped
Layer 1AbortSignal + IPC runtime_cancel_sseDisconnect WebView ↔ sidecar SSE / poll pipeTurn still running; LLM / tools continue
Layer 2HTTP POST /v1/threads/{id}/turns/{turn_id}/interruptruntime Op::InterruptCancellationTokenLocal UI still shows streaming

Web UI SSOT: crates/desktop/web-ui/src/api/turnControl.ts

  • disconnectThreadEventStream(signal?) — Layer 1 only (navigation leave, sidecar restart, etc.)
  • stopThreadTurn({ threadId, turnId, streamControl? })user Stop: Layer 2 then Layer 1 (ignore 409 if already finished)

Runtime narrative and broadcast details: RUNTIME_ARCHITECTURE.md §8.

2.1.2 Multi-Window Thread Ownership (D10)

IPCDescription
register_window_thread { threadId }Current WebView declares UI ownership of thread
thread_owned_by_window { threadId }Query whether this window is still owner

SSE filtering: Non-owner windows must ignore live GET …/events / runtime_get_sse increments (filterThreadStreamEvents + windowOwnsThreadForStream TTL cache). Same for approval.required. Historical replay (session load) is not limited.

get_runtime_token removed (2026-05-20 H06 security follow-up): Bearer no longer enters WebView; Tauri production path always goes through runtime_proxy. Full IPC list: crates/desktop/src/main.rs generate_handler! (64 commands as of 2026-06-12, including sandbox, LHT, hooks, terminal, multi-window, and app updater).

2.2 API Key Management

CommandParametersReturnDescription
get_api_key_statusApiKeyStatus { configured }Check if DeepSeek API Key is stored in OS keychain
save_deepseek_api_keykey: String()Write to OS keychain → clear plaintext in config.toml → restart sidecar
clear_deepseek_api_key()Remove key from keychain → restart sidecar
get_vision_bridge_statusVisionBridgeStatus { configured, base_url, model }Vision transcription bridge status
save_vision_bridgeapi_key, base_url, model()Save vision bridge config → restart sidecar
clear_vision_bridge()Clear vision bridge config → restart sidecar
vision_transcribe_imagedata_url: StringStringCall vision model to transcribe image to text

2.3 File / Workspace Operations

CommandParametersReturnDescription
read_thread_workspace_binarythread_id, relative_pathBinaryFileResponseRead thread workspace binary (see structure below); Runtime workspace/file is UTF-8 text only
read_workspace_binary_at_rootworkspace_root, relative_pathBinaryFileResponseSame, without thread context

BinaryFileResponse (commands.rs):

{
  "mime_type": "image/png",
  "base64": "...",
  "size": 12345,
  "truncated": false
}

| open_in_shell | path: String | () | Open path in system terminal | | open_with_system_app | path: String | () | Open file with system default app | | open_external_url | url: String | () | Open HTTPS/HTTP URL in system browser | | export_thread_json | thread_id: String | save dialog | Export thread conversation as JSON | | export_session_json | session_id: String | save dialog | Export session record as JSON |

2.4 System Settings

CommandParametersReturnDescription
get_system_settingsSystemSettingsRead full system settings
save_system_settingssettings: SystemSettings()Write settings → restart sidecar

SystemSettings structure:

default_model: string      reasoning_effort: string      cost_currency: string
allow_shell: bool           approval_policy: string        sandbox_mode: string
max_subagents: number       web_search: bool               subagents_enabled: bool
exec_policy: bool           memory_enabled: bool            lsp_enabled: bool
snapshots_enabled: bool     notify_method: string           session_file_mb: number

2.5 Project Rules & Symbol Index

CommandParametersReturnDescription
read_pick_rulesworkspace_root: StringStringRead .deepseek/pick-rules.md (empty if missing)
save_pick_rulesworkspace_root, content()Write project rules file
rebuild_symbol_indexworkspace: String()HTTP POST /v1/symbol-index/rebuild?workspace=… (query, no JSON body)
get_symbol_index_infoworkspace: StringSymbolIndexInfoIPC only: read .deepseek/symbols.json (status/size/count, stale detection)
delete_symbol_indexworkspace: String()Delete workspace .deepseek/symbols.json and related index files

2.6 Sandbox (Windows native + cross-platform overview)

See SANDBOX_CAPABILITY_MATRIX.md. Desktop Settings → Sandbox uses these IPC commands; runtime enforcement is in the sidecar (crates/runtime-server/src/sandbox/).

CommandParametersReturnDescription
get_windows_sandbox_statusWindowsSandboxStatusWindows-only: setup complete, backend (elevated / unelevated), configured mode
get_sandbox_platforms_overviewSandboxPlatformsOverviewPer-OS sandbox posture for Settings panel
get_sandbox_onboarding_stateSandboxOnboardingStateFirst-run onboarding completion
get_sandbox_settingsSandboxSettingsRead sandbox_mode, [windows] sandbox, private desktop flag
save_sandbox_settingssettings: SandboxSettings()Write sandbox section → restart sidecar
initialize_windows_sandboxmode: StringSandboxSettingsWindows UAC setup (elevated / unelevated); writes config markers

2.7 Long-Horizon Tasks (LHT) & Desktop Shell

CommandParametersReturnDescription
get_lht_composer_modeStringComposer tri-state: auto | strict | off
set_lht_composer_modemode: String()Persist composer LHT mode
get_lht_strictboolStrict harness gate flag
set_lht_strictstrict: bool()Persist strict flag
get_lht_settingsLhtSettingsFull LHT config block
save_lht_settingssettings: LhtSettings()Write LHT settings → restart sidecar
apply_lht_presetpreset_id: String()Apply named LHT preset from zagens-config
get_desktop_shell_prefsDesktopShellPrefsDesktop-only UI prefs (not sidecar system settings)
save_desktop_shell_prefsprefs: DesktopShellPrefs()Persist desktop shell prefs
default_composer_workspaceStringDefault workspace path for new composer sessions

2.8 Lifecycle Hooks

CommandParametersReturnDescription
get_hooks_settingsHooksSettingsRead [hooks] from config (see desktop/HOOKS.md)
save_hooks_settingssettings: HooksSettings()Write hooks config → restart sidecar

2.9 Embedded Terminal & Multi-Window

CommandCategory
spawn_terminal / write_terminal / resize_terminal / kill_terminalEmbedded PTY (terminal.rs)
get_window_label / get_window_workspace / create_agent_window / list_agent_windows / focus_agent_window / register_window_thread / thread_owned_by_window / close_current_windowMulti-window Agent windows (window_registry.rs)

2.10 Storage & App Updates

CommandParametersReturnDescription
get_storage_pressureworkspace_root?: StringStoragePressureSnapshotDisk guard / workspace size hints for UI warnings
get_update_statusUpdateStatusTauri updater: available version, download state
install_app_update()Install downloaded app update and relaunch

3. Runtime HTTP API (Channel B)

Server: sidecar runs deepseek-runtime --host 127.0.0.1 --port {port} (see sidecar.rs) Listen address: http://127.0.0.1:{port} (default 7878; use get_runtime_port) Sidecar env: DEEPSEEK_RUNTIME_TOKEN, DEEPSEEK_CLIENT_SURFACE=zagens, optional DEEPSEEK_API_KEY (keychain) CORS: sidecar allows http(s)://tauri.localhost (WebView cross-origin) Serialization: JSON (request/response bodies) Streaming protocol: SSE (Server-Sent Events) — for /v1/stream and /v1/threads/{id}/events

3.1 Authentication

When RuntimeApiState.runtime_token is configured, all /v1/* routes are validated by middleware; either:

Authorization: Bearer <runtime_token>

or

x-deepseek-runtime-token: <runtime_token>

runtime_token is UUID v4 from Tauri main.rs, passed to sidecar via DEEPSEEK_RUNTIME_TOKEN. WebView does not hold the token — Tauri production path uses runtime_proxy.rs to inject Authorization: Bearer … on the Rust side (H06). After initRuntimeConfig(), client.ts sets useTauriRuntimeProxy = true; REST uses runtime_http, streaming uses runtime_post_stream / runtime_get_sse.

Vite frontend-only dev (non-Tauri) may connect directly to sidecar with no Bearer (only when sidecar has no token configured).

If runtime_token is not configured (some test/dev paths), middleware allows all /v1/* — desktop production path always uses a token.

Public endpoints (no auth):

  • GET /health
  • GET /internal/probe

3.2 General Conventions

  • All request bodies: Content-Type: application/json
  • Retry policy: WebView retries transient network errors (TypeError: Failed to fetch, NetworkError) with exponential backoff (5 attempts, base delay 350ms)
  • Runtime readiness probe: poll GET /health on startup (then GET /v1/sessions after token ready), up to 90s
  • POST/PATCH with body: Content-Type: application/json; exception: symbol-index/rebuild uses query only (see §3.3.11)

3.2.1 SSE Stable Event Subset v1 (event: field)

Status: GA · event_schema_version = 2

Both POST /v1/stream and GET /v1/threads/{id}/events emit SSE. Each RuntimeEventRecord carries schema_version: 2; clients SHOULD ignore records where schema_version exceeds the known maximum. The table below is the exhaustive stable subset; implementation: map_compat_stream_event() (crates/runtime-server/src/runtime_api/stream.rs).

SSE event:SourceMeaning
turn.startedstream_turn (first frame)New turn started; includes thread_id / turn_id / model / mode / workspace
thinking.deltaitem.delta (thinking)Thinking/reasoning delta, data: { content }
message.deltaitem.delta (agent)Assistant text delta, data: { content }
tool.progressitem.delta (tool)Tool output stream, data: { output }
tool.starteditem.started (tool)Tool call started, data: { id, name, input }
tool.completeditem.completed / item.failedTool call finished, data: { id, success, output }
statusitem.completed (status)Status/notification, data: { message }
erroritem.completed (error)Error message, data: { message }
approval.requiredapproval.requiredUser approval required for tool execution, data: { id, tool_name, description, … }
sandbox.deniedsandbox.deniedSandbox policy denied
turn.completedturn.completedTurn finished, data: { usage, turn_summary? }
agent.spawnedagent.spawnedSub-agent started, data: { agent_id, prompt? }
agent.progressagent.progressSub-agent status update, data: { agent_id, status }
agent.completedagent.completedSub-agent finished, data: { agent_id, result }
agent.listagent.listSub-agent list, data: { agents }
craft.verdictcraft.verdictCRAFT structured verdict, data: { agent_id, agent_type, task_id?, verdict, summary?, items }
craft.board_updatedcraft.board_updatedCRAFT blackboard partition update, data: { task_id, partition, agent_id }
panel.checklistpanel.checklistChecklist panel data
panel.scratchpadpanel.scratchpadScratchpad panel data
panel.contextpanel.contextContext panel data
donestream_turn (last frame)Stream end, no data

turn_summary sub-object (only on turn.completed, optional):

{
  "step_count": 3,
  "tool_names": ["read_file", "grep_files"],
  "end_reason": null
}

Client guidance:

  • Unknown event names SHOULD be ignored (forward compatibility).
  • When schema_version increments, new fields are additive; clients MUST NOT fail on unknown fields.
  • WebView normalizes to UI events in streamNormalize.ts.

Desktop UI block mapping (Zagens web-ui timeline; live + replay):

SSE / normalizedTurnBlock.kindNotes
thinking.delta / thinking_deltathinkingBoundary after tool/message opens a new thinking block
message.delta / message_deltatextStreaming prose
message.segment / message_segmenttextCompletes a text segment; duplicate prose skipped
tool.started / tool_*toolCloses open thinking/text; explore/write may collapse in presentation
item.* (replay)same via normalize or item spinePrefer durable ThreadDetail.items + events (buildAssistantBlocksForTurn)

Presentation (AssistantTurnFrame) does not add block kinds: step grouping and compact tool rows are display-only.


3.3 Endpoint Reference

3.3.1 Health Checks

MethodPathAuthDescription
GET/healthNoRuntime liveness check

GET /health response:

{
  "status": "ok",
  "service": "zagens-runtime-api",
  "mode": "local",
  "event_schema_version": 2
}

| GET | /internal/probe | No | Internal readiness / troubleshooting (PID, start time, token fingerprint, sidecar version) |

GET /internal/probe response (summary):

{
  "status": "ok",
  "pid": 12345,
  "started_at_ms": 1710000000000,
  "token_fingerprint": "...",
  "version": "0.7.5"
}

(version is deepseek-runtime / runtime crate version, not Zagens shell version.)

3.3.2 Streaming Chat

MethodPathDescription
POST/v1/streamCore streaming endpoint — create anonymous thread and start one turn; SSE stream

Request body (StreamTurnRequest):

{
  "prompt": "User input text",
  "workspace": "/path/to/project",
  "mode": "agent",
  "model": "deepseek-v4-pro",
  "auto_approve": false,
  "trust_mode": false,
  "allow_shell": false,
  "route_intent": "coding"
}

SSE event stream: Each event is event: + data: (mostly JSON) blocks separated by \n\n. Event names: §3.2.1.

Two chat paths:

PathUse case
POST /v1/streamQuick single turn (anonymous thread + one turn)
POST /v1/threads + …/turns + GET …/eventsPersistent thread, multi-turn, approval/interrupt

3.3.3 Sessions

MethodPathDescription
GET/v1/sessionsList all sessions
GET/v1/sessions/{id}Session detail (messages, system prompt)
DELETE/v1/sessions/{id}Delete session (200 or 204)
POST/v1/sessions/{id}/resume-threadResume archived session as runtime thread

GET /v1/sessions/{id} response (SessionDetail):

{
  "metadata": { "id": "...", "name": "...", "title": "...", "created_at": 0, "updated_at": 0 },
  "messages": [
    { "role": "user", "content": [{ "type": "text", "text": "..." }] }
  ],
  "system_prompt": "..."
}

3.3.4 Threads — Core Conversation Unit

MethodPathDescription
GET/v1/threadsList all threads
POST/v1/threadsCreate new thread
GET/v1/threads/summaryThread summary list
GET/v1/threads/{id}Thread detail + latest_seq
PATCH/v1/threads/{id}Update thread properties
POST/v1/threads/{id}/resumeResume thread
POST/v1/threads/{id}/forkFork thread
POST/v1/threads/{id}/fork-at-user-messageFork at a specific user message
POST/v1/threads/{id}/edit-last-turnEdit and replay from last user turn
GET/v1/threads/{id}/contextThread context summary (tokens, compaction hints)
GET/v1/threads/{id}/harness/task-graphLHT harness task graph snapshot
GET/v1/threads/{id}/harness/cyclesLHT harness cycle status
GET/v1/threads/{id}/scratchpad/statusScratchpad agent status
POST/v1/threads/{id}/scratchpad/initInitialize scratchpad for thread
POST/v1/threads/{id}/compactCompact thread context
POST/v1/threads/{id}/turnsStart a turn on this thread
POST/v1/threads/{id}/turns/{turn_id}/steerSteer conversation direction
POST/v1/threads/{id}/turns/{turn_id}/resolve-approvalHandle tool approval (approve/deny)
POST/v1/threads/{id}/turns/{turn_id}/interruptInterrupt running turn
GET/v1/threads/{id}/checklistThread-associated checklist
GET/v1/threads/{id}/eventsSubscribe to thread event stream (SSE)
POST/v1/threads/{id}/persist-sessionPersist thread as archivable session
GET/v1/threads/{id}/snapshotsList thread git snapshots (?limit=N)
POST/v1/threads/{id}/snapshots/restoreRestore to snapshot ({ "n": N })

PATCH /v1/threads/{id} request body (PatchThreadBody):

{
  "archived": false,
  "allow_shell": true,
  "trust_mode": false,
  "auto_approve": true,
  "model": "deepseek-v4-flash",
  "mode": "agent",
  "title": "Refactor task",
  "system_prompt": "You are a Rust expert",
  "workspace": "/path/to/workspace"
}

POST /v1/threads/{id}/turns request body:

{
  "prompt": "Please fix clippy warnings",
  "model": "deepseek-v4-pro",
  "mode": "agent",
  "allow_shell": true,
  "trust_mode": false,
  "auto_approve": false,
  "route_intent": "coding"
}

POST /v1/threads/{id}/turns/{turn_id}/resolve-approval request body:

{
  "tool_call_id": "...",
  "decision": "approve"
}
Thread Workspace Browse
MethodPathDescription
GET/v1/threads/{id}/workspace/browseList thread workspace directory (?path=)
GET/v1/threads/{id}/workspace/fileRead thread workspace file (?path=)
Composer Workspace Browse (no thread context)
MethodPathDescription
GET/v1/workspace/browseList any workspace directory (?workspace=&path=)
GET/v1/workspace/fileRead any workspace file (?workspace=&path=)
GET/v1/workspace/statusWorkspace status
GET/v1/office/environmentOffice tooling environment probe (Python, bundled scripts)

GET ../workspace/browse response:

{
  "workspace": "/path/to/root",
  "path": "src",
  "entries": [
    { "name": "main.rs", "kind": "file", "size": 12400 },
    { "name": "components", "kind": "dir" }
  ]
}

GET ../workspace/file response:

{
  "path": "src/main.rs",
  "content": "fn main() { ... }",
  "truncated": false,
  "language_hint": "rust"
}

3.3.5 Tasks — Durable Background Work

MethodPathDescription
GET/v1/tasksList all tasks
POST/v1/tasksCreate task
POST/v1/tasks/clearClear completed/cancelled tasks
GET/v1/tasks/{id}Task detail
POST/v1/tasks/{id}/cancelCancel task
GET/v1/resume-tasks/{thread_id}Resume task for thread

3.3.6 Skills

MethodPathDescription
GET/v1/skillsList all skills
POST/v1/skillsCreate skill
POST/v1/skills/importImport skill from local path/archive
POST/v1/skills/installInstall skill from remote URL/registry

3.3.6a Blackboards & Topic Memory

MethodPathDescription
GET/v1/blackboardsList CRAFT blackboards
GET/v1/blackboards/{id}Blackboard detail
GET/v1/topic-memoryB2 topic memory snapshot

3.3.7 Automations

MethodPathDescription
GET/v1/automationsList automation rules
POST/v1/automationsCreate automation rule
GET/v1/automations/{id}Get automation
PATCH/v1/automations/{id}Update automation
DELETE/v1/automations/{id}Delete automation
POST/v1/automations/{id}/runRun automation
POST/v1/automations/{id}/pausePause automation
POST/v1/automations/{id}/resumeResume automation
GET/v1/automations/{id}/runsList automation run history

3.3.8 MCP (Model Context Protocol) Servers

MethodPathDescription
GET/v1/apps/mcp/serversList MCP servers
POST/v1/apps/mcp/serversAdd MCP server
GET/v1/apps/mcp/servers/{name}Get MCP server config
PUT/v1/apps/mcp/servers/{name}Update MCP server config
DELETE/v1/apps/mcp/servers/{name}Delete MCP server
GET/v1/apps/mcp/toolsList MCP tools (?server=)
POST/v1/apps/mcp/config/mergeMerge MCP config JSON into mcp.json
POST/v1/apps/mcp/reloadReload MCP pool from disk config
GET/v1/apps/mcp/discoverDiscover MCP servers (stdio/SSE probe)
GET/v1/apps/mcp/callsList recent MCP tool call records

POST /v1/apps/mcp/servers request body:

{
  "name": "my-server",
  "command": "npx",
  "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
  "url": null
}

3.3.9 Routing Rules

MethodPathDescription
GET/v1/apps/routing/rulesGet model routing rules
PUT/v1/apps/routing/rulesSet routing rules; body { "rules": [ ... ] }

3.3.10 Usage

MethodPathDescription
GET/v1/usageUsage statistics
?since=&until=&group_by=Aggregate by model/date

3.3.11 Symbol Index

MethodPathAuthDescription
POST/v1/symbol-index/rebuild?workspace=/abs/pathBearerRebuild index; query param only workspace, no request body

Success response example (JSON):

{
  "status": "ok",
  "path": "/path/to/.deepseek/symbols.json",
  "symbol_count": 42
}

Index status/size/stale via IPC get_symbol_index_info; no corresponding GET HTTP route.


4. Authentication Flow (H06)

┌──────────┐          ┌──────────┐          ┌──────────────┐
│  Tauri   │          │  WebView │          │   Sidecar    │
│  main.rs │          │   JS     │          │deepseek-runtime│
└────┬─────┘          └────┬─────┘          └──────┬───────┘
     │                     │                       │
     │ ① uuid v4 token     │                       │
     │  (AppState)         │                       │
     │ ② spawn sidecar     │                       │
     │  DEEPSEEK_RUNTIME_  │                       │
     │  TOKEN=zagens       │                       │
     │────────────────────────────────────────────►│
     │                     │                       │
     │                     │ ③ invoke runtime_http │
     │                     │   / runtime_post_     │
     │                     │   stream / runtime_   │
     │                     │   get_sse             │
     │                     │──────────────────────►│
     │  Rust injects Bearer│                       │
     │────────────────────────────────────────────►│
     │                     │      200 / SSE ◄──────│
  1. Tauri main.rs generates uuid v4 token, stores in AppState
  2. Sidecar starts with DEEPSEEK_RUNTIME_TOKEN env var
  3. WebView calls runtime_http / runtime_post_stream / runtime_get_sse (does not obtain token)
  4. runtime_proxy.rs injects Authorization: Bearer <token> on each request

Token security: Bearer does not enter WebView or localStorage / window; see CHANGELOG 2026-05-20 H06 follow-up.


5. Error Handling

5.1 Runtime HTTP Error Format

Failed responses are often plain text or short JSON (handler-dependent). WebView does not assume a fixed schema; uniformly await res.text() then construct Error.

WebView wraps as Error with status property:

// Unified pattern in client.ts
if (!res.ok) {
  const text = await res.text();
  const err = new Error(`HTTP ${res.status}: ${text}`);
  (err as Error & { status?: number }).status = res.status;
  throw err;
}

5.2 Transient Error Retry

isTransientRuntimeFetchError() detects:

  • TypeError (fetch failure, sidecar not ready)
  • Error message containing Failed to fetch / NetworkError / Load failed

On transient errors, auto-retry 5 times (exponential backoff, base delay 350ms).

5.3 Tauri IPC Errors

Returns Result<T, String>; Rust errors passed as strings, caught as Error on frontend.


6. Sidecar Lifecycle

┌───────────────┐
│  Tauri start  │
└───────┬───────┘

┌───────────────────────────────────────┐
│  sidecar.rs: start_and_monitor()       │
│                                        │
│  spawn deepseek-runtime --http         │
│  + health check polling                │
│  + crash auto-restart (backoff)        │
│  + API Key change → restart notify     │
│  + logs to sidecar.log /               │
│    supervisor.log                      │
└───────────────────────────────────────┘

Key parameters:

  • Startup probe: 60 retries, first 60ms, then 200ms, ~12s total
  • Health heartbeat: 5s interval
  • Crash restart: ≥3 rapid crashes within 60s → pause auto-restart, notify user
  • Connection refused threshold: 2 (treat port as dead immediately)
  • Busy timeout threshold: 12 (process alive but overloaded, grace wait)
  • API Key change: restart_sidecar triggers restart, 450ms debounce

7. Data Flow Diagrams

7.1 Starting a Conversation

WebView                          Sidecar                    LLM API
  │                                │                          │
  │ POST /v1/stream { prompt, ... }│                          │
  │───────────────────────────────►│                          │
  │                                │ POST /chat/completions   │
  │                                │─────────────────────────►│
  │                                │      SSE streaming ◄────│
  │◄── SSE: thinking.delta ───────│                          │
  │◄── SSE: tool.started ─────────│                          │
  │◄── SSE: message.delta ────────│                          │
  │◄── SSE: done ─────────────────│                          │

7.2 Tool Approval

WebView                          Sidecar
  │                                │
  │◄── SSE: approval.required ────│  (user approval required)
  │                                │
  │ POST …/resolve-approval        │
  │ { tool_call_id, decision }     │
  │───────────────────────────────►│
  │                                │  continue tool execution
  │◄── SSE: tool.completed ───────│

8. Endpoint Summary

Channel A: Tauri IPC

Full list: crates/desktop/src/main.rs generate_handler! (64 commands as of 2026-06-12). Excludes removed get_runtime_token. Grouped by category (order differs from registration):

CategoryCommands
Runtime proxyget_runtime_port, runtime_http, runtime_post_stream, runtime_get_sse, runtime_cancel_sse
Platform / localeget_platform_info, get_os_theme, get_locale, set_app_locale, restart_sidecar
Sandboxget_windows_sandbox_status, get_sandbox_platforms_overview, get_sandbox_onboarding_state, get_sandbox_settings, save_sandbox_settings, initialize_windows_sandbox
API key / visionget_api_key_status, save_deepseek_api_key, clear_deepseek_api_key, get_vision_bridge_status, save_vision_bridge, clear_vision_bridge, vision_transcribe_image
File / exportread_thread_workspace_binary, read_workspace_binary_at_root, open_in_shell, open_with_system_app, open_external_url, export_thread_json, export_session_json
System / LHT / hooksget_system_settings, save_system_settings, get_lht_*, set_lht_*, apply_lht_preset, get_hooks_settings, save_hooks_settings, get_desktop_shell_prefs, save_desktop_shell_prefs, default_composer_workspace
Rules / symbol indexread_pick_rules, save_pick_rules, rebuild_symbol_index, get_symbol_index_info, delete_symbol_index
Storageget_storage_pressure
Terminalspawn_terminal, write_terminal, resize_terminal, kill_terminal
Multi-windowget_window_label, get_window_workspace, create_agent_window, list_agent_windows, focus_agent_window, register_window_thread, thread_owned_by_window, close_current_window
App updaterget_update_status, install_app_update

Channel B: Runtime HTTP

#MethodPathCategory
1GET/healthHealth
2GET/internal/probeHealth
3POST/v1/streamStreaming chat
4GET/v1/sessionsSessions
5GET/v1/sessions/{id}Sessions
6DELETE/v1/sessions/{id}Sessions
7POST/v1/sessions/{id}/resume-threadSessions
8GET/v1/threadsThreads
9POST/v1/threadsThreads
10GET/v1/threads/summaryThreads
11GET/v1/threads/{id}Threads
12PATCH/v1/threads/{id}Threads
13POST/v1/threads/{id}/resumeThreads
14POST/v1/threads/{id}/forkThreads
15POST/v1/threads/{id}/fork-at-user-messageThreads
16POST/v1/threads/{id}/edit-last-turnThreads
17GET/v1/threads/{id}/contextThreads
18GET/v1/threads/{id}/harness/task-graphThreads (LHT)
19GET/v1/threads/{id}/harness/cyclesThreads (LHT)
20GET/v1/threads/{id}/scratchpad/statusThreads (scratchpad)
21POST/v1/threads/{id}/scratchpad/initThreads (scratchpad)
22POST/v1/threads/{id}/compactThreads
23POST/v1/threads/{id}/turnsThreads
24POST/v1/threads/{id}/turns/{turn_id}/steerThreads
25POST/v1/threads/{id}/turns/{turn_id}/resolve-approvalThreads
26POST/v1/threads/{id}/turns/{turn_id}/interruptThreads
27GET/v1/threads/{id}/checklistThreads
28GET/v1/threads/{id}/eventsThreads (SSE)
29POST/v1/threads/{id}/persist-sessionThreads
30GET/v1/threads/{id}/snapshotsThreads
31POST/v1/threads/{id}/snapshots/restoreThreads
32GET/v1/threads/{id}/workspace/browseWorkspace browse
33GET/v1/threads/{id}/workspace/fileWorkspace browse
34GET/v1/workspace/browseWorkspace browse
35GET/v1/workspace/fileWorkspace browse
36GET/v1/workspace/statusWorkspace browse
37GET/v1/office/environmentOffice environment
38GET/v1/tasksTasks
39POST/v1/tasksTasks
40POST/v1/tasks/clearTasks
41GET/v1/tasks/{id}Tasks
42POST/v1/tasks/{id}/cancelTasks
43GET/v1/resume-tasks/{thread_id}Tasks
44GET/v1/blackboardsBlackboards
45GET/v1/blackboards/{id}Blackboards
46GET/v1/topic-memoryTopic memory
47GET/v1/skillsSkills
48POST/v1/skillsSkills
49POST/v1/skills/importSkills
50POST/v1/skills/installSkills
51GET/v1/automationsAutomations
52POST/v1/automationsAutomations
53GET/v1/automations/{id}Automations
54PATCH/v1/automations/{id}Automations
55DELETE/v1/automations/{id}Automations
56POST/v1/automations/{id}/runAutomations
57POST/v1/automations/{id}/pauseAutomations
58POST/v1/automations/{id}/resumeAutomations
59GET/v1/automations/{id}/runsAutomations
60GET/v1/apps/mcp/serversMCP
61POST/v1/apps/mcp/serversMCP
62GET/v1/apps/mcp/servers/{name}MCP
63PUT/v1/apps/mcp/servers/{name}MCP
64DELETE/v1/apps/mcp/servers/{name}MCP
65GET/v1/apps/mcp/toolsMCP
66POST/v1/apps/mcp/config/mergeMCP
67POST/v1/apps/mcp/reloadMCP
68GET/v1/apps/mcp/discoverMCP
69GET/v1/apps/mcp/callsMCP
70GET/v1/apps/routing/rulesRouting
71PUT/v1/apps/routing/rulesRouting
72GET/v1/usageUsage
73POST/v1/symbol-index/rebuildSymbol index

9. Source File Index

ComponentFileDescription
Tauri command registrationcrates/desktop/src/main.rsgenerate_handler!64 IPC commands
Tauri command implcrates/desktop/src/commands.rsKeys, settings, binary preview, symbol index IPC
Runtime HTTP proxycrates/desktop/src/runtime_proxy.rsH06 — Bearer injection, SSE forwarding
Sidecar process mgmtcrates/desktop/src/sidecar.rsspawn deepseek-runtime, health check, restart
WebView HTTP clientcrates/desktop/web-ui/src/api/client.tsinitRuntimeConfig, proxy branch, readiness probe
SSE normalizationcrates/desktop/web-ui/src/api/streamNormalize.tswire event → UI events
Runtime HTTP routescrates/runtime-server/src/runtime_api/router.rsbuild_router
Protocol typescrates/protocol/src/Shared DTOs (if any)
Agent prompt (sidecar)crates/runtime-server/src/prompts/base.md etc.Includes hallucination-control sub-rules (Capability / Architecture Claims)

crates/app-server/ is for other monorepo entry points; Zagens desktop path does not use this crate. Line counts are approximate at time of writing; verify in repo.


DocumentContent
RUNTIME_ARCHITECTURE.mdThree-layer model, crate deps, dual persistence diagrams
SANDBOX_CAPABILITY_MATRIX.mdSandbox backends (incl. Windows native)
TOOLS_PRINCIPLES.mdTool system architecture and execution flow
Maintainer: doc_Private/docs/tech/RUNTIME_EVOLUTION_ROADMAP.mdEvolution roadmap §3 current snapshot
crates/runtime-server/src/prompts/base.mdHallucination-control sub-rules (Capability / Architecture claims)
Regression tests (maintainer)doc_Private/docs/tui/REGRESSION_TESTS.md — hallucination control and parallel scheduling regression cases
craft-v2-improvements.md · TOOLS_PRINCIPLES.md §3.7CRAFT improvements; sub-agent write path

Revision History

DateNotes
2026-06-12Align with 0.7.5: 64 IPC (sandbox, LHT, hooks, updater), 73 HTTP routes (harness, scratchpad, blackboards, topic-memory, MCP reload/discover/calls, office environment)
2026-05-26D6 Phase B: sidecar path → crates/runtime-server; remove deepseek-tui / CLI production narrative
2026-05-25Align with code: Zagens 0.4.3, runtime_api/router.rs, H06 proxy auth (remove get_runtime_token), IPC ~41, DEEPSEEK_CLIENT_SURFACE=zagens
2026-05-18Code review fixes: health/probe, BinaryFileResponse, SSE event names, symbol-index query, auth header, 25 IPC / 56 HTTP, app-server boundary
2026-05-18Initial version (Zagens dual-channel API)