3e6f856f23 · ZCode app 3.11.2 installed at /Applications/ZCode.appzcode app-serverVerdict: native ZCode integration in T3 Code is feasible, verified end-to-end, and architecturally aligned — recommended as a first-class provider built on zcode app-server --stdio.
What was proven (not inferred): the installed ZCode 3.11.2 ships a CLI runtime (0.16.5, plain-Node, no Electron dependency) that runs a line-delimited JSON-RPC server over stdio and self-identifies as {"protocol":{"name":"ZCode Protocol","version":1}}. A from-scratch 200-line client created a session, answered the server's mandatory reverse-calls, streamed a real GLM turn, received exact usage accounting, listed sessions, and fetched the typed message history — all without touching ZCode desktop (which itself is just another client of the same CLI). Resume was verified by a second probe recalling a prior turn under the same session id.
Why it fits T3: this is the fourth instance of a subprocess pattern T3 already has three adapters for (ACP stdio, Codex app-server, Claude SDK). T3's provider layer has no closed enum — a new zcode driver + snapshot makes it selectable in every picker on web/desktop/mobile automatically. Checkpoints, projections, receipts, and remote relay are provider-agnostic in T3 and come for free. ZCode's own feature surface covers every T3 capability the other providers expose: streaming deltas, bidirectional permission prompts, image attachments, dynamic model list with capability metadata, in-session model/mode switching, native compaction, resume, subagents, feedback, and even a purpose-built web-remote-replayable event delivery mode.
Cost: Grok-scale structure (~3.5k insertions shipped for Grok) with Codex-scale protocol ownership — an honest floor of ~4–6k server-side LOC plus ~10 UI files, because the zcode protocol is custom and must be owned by a first-party client package (the effect-codex-app-server pattern). No installer and no OAuth machinery needed — the CLI and its login already exist on the user's machine.
The two real risks are non-code: (1) the protocol is undocumented and already broke once at 0.15→0.16 — mitigate with version gating, tolerant schemas, protocol logging, and integration tests against the installed CLI; (2) z.ai's Coding Plan terms say usage is "strictly limited to officially supported tools" — headless use has community precedent (Paseo, Zed adapters, pi) but is a maintainer-level decision to accept and disclose.
| Property | Value | Evidence class |
|---|---|---|
| Product | Z.ai (Zhipu AI)'s first-party desktop coding agent — self-described "Agentic Development Environment (ADE)", "Official Harness for GLM-5.3". Bundles agent chat, file manager, terminal, git panel, live browser preview, MCP, skills/plugins, Goal Mode, remote dev (SSH/WSL/Docker), bot control (WeChat/Feishu/Telegram). | CONFIRMED zcode.z.ai |
| Installed version | App 3.11.2 (build 3.11.2.6792, Sep 4 2026 — latest per official changelog). Bundle id dev.zcode.app, URL scheme zcode://. Data dirs: ~/.zcode + ~/Library/Application Support/ai.z.zcode + …/ZCode. | LOCAL Info.plist |
| Models | GLM-5.3 / GLM-5.3-Flash / GLM-5.2 (open weights, Apache-2.0 on zai-org/GLM-5; the app/CLI is closed-source — no zai-org repo exists for ZCode itself). | CONFIRMED |
| Auth | GLM Coding Plan subscription (OAuth account-link or API key). Tiers Lite/Pro/Max ≈ 80/400/1600 prompts per 5h. Plan is "strictly limited to use within officially supported tools and products" — headless use of the bundled runtime is a ToS gray area all community bridges accept explicitly. | CONFIRMED docs.z.ai |
| CLI | No official standalone CLI, no public SDK, no daemon API. The de-facto surface is the undocumented bundled runtime /Applications/ZCode.app/Contents/Resources/glm/zcode.cjs (12.6 MB, run via node), driven headless by every community integration. Not on PATH. | REPORTED paseo#1670 |
| Runtime lineage | One bridge author characterizes the runtime as "OpenCode-derived"; another exposes it as an OpenCode-SDK-1.14.46-compatible HTTP/SSE server. Session/agent storage conventions (rollout/, projects/) and a codex/ dir inside ~/.zcode hint at mixed lineage. | REPORTED |
| App version | CLI runtime | Protocol changes |
|---|---|---|
| 3.1.4 | 0.14.8 | 0.15→0.16 boundary broke the wire protocol: envelopes omit the jsonrpc field; session/create param cwd → workspace:{workspacePath, workspaceKey}; session/subscribe requires deliveryKind; new mandatory reverse-call session/requestRuntimePreferences; removed steer, rewind*, prompt/enhance*. |
| 3.2.0–3.3.x | 0.15.0–0.15.2 | |
| 3.10.2 | 0.16.5 (host artifact zcode-host-3.10.2, protocol zcode-task-v1) | |
| 3.11.2 (installed) | ≥ 0.16.5 — exact version + method surface verified against the local bundle in §3 |
Sources: supermomongo/zcode-acp, tizerluo/zcode-open-bridge, ExMCP ZCode adapter docs.
| Project | How | Note |
|---|---|---|
| Paseo (closest analog to T3) | ZCode as user-defined ACP provider via supermomongo/zcode-acp (~/.paseo/config.json, extends: "acp"); shipped in v0.1.55 to close issue #1670 | Full arc documented: "no official interface" research → community adapter → shipped provider |
| Zed / JetBrains | ACP agent servers (ZCODE_BIN env → bundled zcode.cjs) | Zed ACP registry lists a separate "GLM Agent" (Coding Plan API direct, no ZCode runtime) |
| pi (earendil-works) | npm zcode-provider: zcode app-server → pi model provider; full streaming, permission dialogs bridged via interaction/requestUserInput, mid-turn steering (followupMode: queue|guide) | README documents streaming + steering in production use |
| ExMCP (Elixir) | First-class ExMCP.ACP.Adapters.ZCode.Protocol module | Documents the protocol surface in hexdocs |
| william0wang/zcode-acp | ACP server + remote hub daemon serving ZCode sessions to phone/browser over WebSocket, multi-client broadcast | A working mini-T3 for ZCode — validates the remote/multi-surface requirement |
model.streaming (kinds text_start|text_delta|text_end|reasoning_start|reasoning_delta|reasoning_end|tool_input_start|tool_input_delta|tool_input_end|tool_call),
tool.updated (kinds scheduled|started|result|batch),
turn.completed, terminal state.updated with reason prompt_completed|prompt_failed. Permissions flow through server→client requests interaction/requestPermission / interaction/requestUserInput.
zcode.cjs app-server --stdioZCode (Electron main)
├─ Renderer (iframe UI) ←→ host via Electron MessagePortMain (NOT network)
├─ utilityProcess forks:
│ ├─ "zcode-host-local-1" = out/host/index.js (from app.asar) — the service layer
│ │ └─ "zcode-cli" child = Resources/glm/zcode.cjs
│ │ spawned as: <electron-execPath> zcode.cjs app-server --stdio
│ │ env: ELECTRON_RUN_AS_NODE=1 (+ --surface desktop)
│ └─ "zcode-cron-scheduler" = automation cron
└─ ZCode Computer Use.app (CUA helper, unix socket + token file)
zcode-server.cjs = the SAME host layer packaged standalone, for REMOTE Linux hosts
(SSH push via ssh2 + CDN download). NOT an HTTP API — VSCode-style binary channels.
There is no meaningful HTTP API anywhere. UI→host is MessagePort IPC; host→agent is a stdio child. ZCode's own desktop app is "just another client" of the agent CLI — which is exactly the position T3 would occupy. The host even exposes env overrides for alternative hosts: ZCODE_AGENT_SERVER_COMMAND / ZCODE_AGENT_SERVER_ARGS_JSON (default ["app-server","--stdio"]) and GLM_BINARY_PATH, with runtime candidates $ZCODE_SERVER_RUNTIME_ROOT/glm/zcode.cjs, ~/.zcode/server/agents/glm/zcode.cjs, process.resourcesPath/glm/zcode.cjs.
Transport: newline-delimited JSON on the child's stdout (stderr → logs). Envelope is zod-.strict():
request {id, method, params?, trace?}
notification {method, params?, trace?}
response {id, result}
error {id, error: {code, …}}
— preceded on boot by:
→ stdout: {"type":"zcode-hello","version":…,"platform":…,"arch":…,"pid":…}
← stdin: {"type":"zcode-hello-ack","version":…,"clientId":…} (≤10s window)
Methods (V4_METHODS, exact strings from the host bundle):
v4/connection/flow backpressure {connectionId, state: saturated|drained|closed}
v4/controller/subscribe|resync|unsubscribe
v4/conversation/subscribe|resync|unsubscribe|frame|rowsRange|plans|fileChanges|
fileRewindPreview|usage
v4/attachment/begin|chunk|commit|abort|read|previewSource (chunks ≤512 KiB)
v4/commands/query v4/command v4/usage/stats v4/telemetry/event
v4/cua/permission-observation
Commands ride a CAS envelope (optimistic-concurrency, "10-protocol-spec §6.4") via v4/command:
{commandId: uuidv7 (stable across retries), clientId, sessionId|null,
baseRevision?, baseLogEpoch?, type, payload, issuedAt}
ack: accepted|duplicate|noop else error ZCODE_V4_COMMAND_REJECTED
26 command types: createSession, createSelectionSideSession, sendText, sendGoalCommand, stop, compact, forkAssistant, applyFileRewind, editUserQuery, retryTurn, setAssistantFeedback, sendQueuedNow, editQueueItem, reorderQueueItem, deleteQueueItem, setAutoDrain, resolveInteraction, revokeWorkspaceHookTrust, snoozeInteractionAutoResolution, switchModelConfig, switchCollaborationMode, setFollowupMode, pauseGoal, resumeGoal, cancelBackgroundWork, renameSession, deleteSession. CAS-gated (require baseRevision): file rewind, fork, queue edits, model/mode switches, goal pause/resume, feedback.
Representative payloads: createSession {workspaceId, firstInput:{text, attachments[]}, config?, runtimeModel?, mcpServers?} · sendText {text, attachments?, requestedDelivery: startNow|queue|guide, heldQueueDisposition: clearQueueAndSend|keepQueueAndSend} · stop {expectedForegroundExecutionId?} · editUserQuery {target, newText, workspaceMode: preserve|rewind}.
Streaming: v4/conversation/frame delivers snapshot + deltas. Conversation row kinds: turnHeader, userInput, assistantText, reasoning, toolCall, subagent, hookInvocation, timelineMarker. Delta event kinds: row.appended / row.delta / row.removed / row.upserted, turn.started / turn.terminal, tool.lifecycle, subagent.lifecycle, permission.lifecycle, usage.delta, stream.chunk, state.updated, compaction.terminal, config.updated, session.upserted / session.removed, task.upserted / task.removed, phase.completed / phase.error, checkpoint.created, mode.set/list, model.set/streaming, mcp.servers.
Approvals: permission.requested → permission.respond → permission.resolved, plus elicitation.respond. Images: v4/attachment/begin|chunk|commit (≤512 KiB chunks). Interrupt: stop command. Compaction: compact command + compaction.terminal event. Model switch: switchModelConfig (CAS-gated). Rewind/fork: applyFileRewind / forkAssistant + v4/conversation/fileRewindPreview.
session/create, session/send, session/setModel…) has evolved into the V4 command/CAS model above by 3.11.2 — the older method names still appear in community bridges pinned to older CLIs. The live-probe verification (§3.4) is therefore mandatory before writing an adapter: build against what the local 0.16.5 bundle actually speaks, and keep the protocol schema tolerance loose (unknown row/event kinds → ignore).
| Option | Verdict | Why |
|---|---|---|
1. Spawn zcode.cjs app-server --stdio, speak V4 | RECOMMENDED | Exactly what ZCode's own host does; only stable edge; maps 1:1 onto T3's adapter model (per-session child process, stdio JSON protocol, bidirectional permission requests). The CLI persists its own history/checkpoints/usage in ~/.zcode/cli/db/db.sqlite. Auth stays in the CLI's own files — T3 never touches ZCode credentials. Desktop and T3 each own their own agent processes → safe concurrent use. |
2. Talk to zcode-server.cjs | REJECTED | Same host layer exposed over a private VSCode-style binary protocol (13-byte frame header + protobufjs payloads, message types 100–204, ~37 service channels with unexported TS function names). No stable contract; normally only run on remote machines over SSH; nothing T3 needs lives there. |
| 3. Embed/link ZCode bundles | REJECTED | Minified ESM-flattened CJS with build constants; hard-wired to Electron utilityProcess.fork + MessageChannelMain; re-shipping 293 MB of asar internals is unmaintainable. |
| 4. GLM via Claude-endpoint env hack (status quo) | NOT ZCODE | Already working on this machine, but it's model routing through the Claude adapter — no ZCode agent, tools, sessions, checkpoints, or workflows. Not what "Zcode as a provider" means. |
Independent probe (driver written from scratch, not reusing sub-agent scripts) against the installed 3.11.2 bundle, run as plain system Node:
node /Applications/ZCode.app/Contents/Resources/glm/zcode.cjs app-server --stdio # cwd = workspace
Redacted transcript of the successful run (full logs: /tmp/zcode-t3-verify/run4.log):
>>> {"id":"c1","method":"session/create","params":{"workspace":{"workspaceKey":"<ws>","workspacePath":"<ws>"}}}
<<< {"id":"server-1","method":"session/requestRuntimePreferences","params":{"sessionId":"sess_…","scope":"runtime-materialization"}}
>>> {"id":"server-1","result":{"nativeSearchEnhancementsEnabled":false,"memoryEnabled":false,
"askUserQuestionAutoResolutionEnabled":true,"modelContextBudgetStrategy":"preflight-v1"}}
<<< {"id":"c1","result":{
"protocol":{"name":"ZCode Protocol","version":1},
"session":{"sessionId":"sess_74f1…","mode":"build","sessionKind":"interactive",
"model":{"modelId":"glm-5.1","providerId":"builtin:zai"}},
"settings":{"model":{"available":[
{"label":"glm-5.1","ref":{…},"supportsImages":false,"reasoning":{"levels":["0","1"]}},
{"label":"glm-5v-turbo","ref":{…},"supportsImages":true,…}, …]}},
"projection":{"status":…,"pendingPermissions":…,"contextWindow":200000,…}}}
>>> {"id":"c2","method":"session/subscribe","params":{"sessionId":"sess_74f1…","deliveryKind":"desktop-continuous"}}
<<< {"id":"c2","result":{"eventSeq":0,"events":[]}} ← replay cursor (remote reconnect support)
>>> {"id":"c3","method":"session/send","params":{"sessionId":"sess_74f1…","content":"Reply with exactly: T3_ZCODE_OK"}}
<<< {"method":"state.updated","params":{"patch":{"status":"running"},"reason":"prompt_started","revision":1}}
<<< {"id":"c3","result":{"accepted":true,"stateRevision":1}}
<<< {"method":"session/event","params":{"type":"session.titleUpdated","payload":{"source":"first_input",…}}}
<<< {"method":"session/event","params":{"type":"turn.started","payload":{"foregroundExecutionId":"runtime_command_1","queryId":"query_…"}}}
<<< {"method":"session/event","params":{"type":"session.updated","payload":{"messageCount":6,"toolCount":161,…}}}
<<< {"method":"v4/telemetry/event","params":{"kind":"model.request.status","status":"model_request_started",
"providerKind":"anthropic","providerHostname":"api.z.ai","transport":"sse","querySource":"main_turn","attempt":1,"maxAttempts":11}}
<<< {"method":"session/event","params":{"type":"session.updated","payload":{"content":"T3_ZCODE_OK","stopReason":"stop",
"usage":{"inputTokens":50809,"outputTokens":7,"totalTokens":50816,"cacheReadTokens":10368,"cacheWriteTokens":0},
"contextUsageBreakdown":[{"source":"system_prompt","chars":6663},{"source":"meta_user_context","chars":25194},
{"source":"skills","chars":8942},{"source":"system_tool_schemas","chars":86042},
{"source":"mcp_tool_schemas","chars":230308},{"source":"messages","chars":59}]}}}
<<< {"method":"session/event","params":{"type":"turn.completed","payload":{"response":"T3_ZCODE_OK",
"usage":{"source":"provider","modelRequestCount":1,"inputTokens":50809,"outputTokens":7,
"totalTokens":50816,"cacheReadTokens":10368,"reasoningTokens":0,
"webFetchRequests":0,"webSearchRequests":0},
"toolCallCount":0,"historyRoundCount":1,"duration":6769,"resultType":"success",
"cacheStats":{"totalMessages":7,"cachedMessages":6,"lastCacheHit":true}}}
>>> {"id":"c4","method":"session/messages","params":{"sessionId":"sess_74f1…"}}
<<< {"id":"c4","result":{"messages":[ {user msg with contextSnapshot.envInfo, tools:{Agent,Bash,Edit,Read,Write,
TodoWrite,TodoRead,WebFetch,WebSearch,Skill,SendMessage,TaskOutput,TaskStop,AskUserQuestion,
EnterPlanMode,ExitPlanMode,CronCreate,CronDelete,CronList,CronUpdate,ReadSessionContext,…},
parts:[{type:"text","text":"Reply with exactly: T3_ZCODE_OK",…}]}, {assistant msg with completed ts,…} ]}}
>>> {"id":"c5","method":"session/list","params":{}}
<<< {"id":"c5","result":{"sessions":[{sessionId,title,titleSource:"generated",status:"idle",mode,
workspace:{workspaceKey,workspacePath},createdAt,updatedAt}, …]}}
zcode-hello is the desktop host's transport; a JSON-RPC client connects by simply sending requests. My first probe stalled waiting for hello; driving directly works.deliveryKind returned -32602 enumerating the allowed values — "desktop-continuous" | "web-remote-replayable". The latter is a replayable event stream purpose-built for remote/replay consumers — an unusually good match for T3's relay/tunnel connection modes.session/subscribe, turn state still broadcasts (state.updated, telemetry) but conversation content events (session/event stream) flow to subscribers. The subscribe result returns an eventSeq cursor for replay.jsonrpc — sending it yields -32600 "Invalid ZCode Protocol message".~/.zcode/cli/config.json (provider apiKey) into a sandbox and completed real GLM calls (providerKind:"anthropic", providerHostname:"api.z.ai").ZCODE_STORAGE_DIR, the probe's sessions were written to the live ~/.zcode/cli/db/db.sqlite WAL (72 string hits for the probe session id; main-db query at immutable=1 missed them because they live in the WAL). Only rollout/ and plugins/ honored the redirect. Implications: (a) T3 threads and desktop threads interoperate in one history — a feature (start in T3, continue in ZCode desktop), and WAL multi-process writing is exactly how ZCode's own desktop multiplexes its zcode-cli children; (b) test isolation must use a HOME override, not ZCODE_STORAGE_DIR, until the true DB override knob is identified.contextUsageBreakdown in T3's context meter.~/ZCodeProject/artifacts/)| Mode | Invocation | Shape | Fit for T3 |
|---|---|---|---|
| A. One-shot prompt | node zcode.cjs --prompt "…" --json |
Runs to completion, prints exactly one JSON object: {sessionId, traceId, turnId?, response, usage?, eventCount, projection:{status, turnCount, totalTokenCount, contextUsed, contextWindow}}, exit 0/1. Final text only — no streaming, no tool I/O. |
Too lossless-poor for chat UI. OK for smoke tests. |
| B. app-server PRIOR-VERIFIED | node zcode.cjs app-server |
NDJSON JSON-RPC 2.0 over stdio (one JSON per line, no LSP framing, no initialize handshake). Bidirectional: server sends requests to the client (ids server-1…) for interaction/requestPermission / interaction/requestUserInput — client must answer or the turn blocks. |
Excellent — this is the T3 integration surface. Same pattern T3 already wraps for Codex (codex app-server). |
Prior-verified app-server method surface (v0.15.2):
session/create session/resume session/list
session/send session/stop session/messages
session/events session/fork session/compact
session/setMode session/setModel session/goal
v4/conversation/subscribe (start streaming)
v4/conversation/frame (server→client streaming frames)
interaction/requestPermission (server→client request)
interaction/requestUserInput (server→client request)
workspace/* mcp/list plugins/* automation/create
Prior-verified env vars (found in bundle):
| Env var | Purpose |
|---|---|
ZCODE_API_KEY / ZCODE_BASE_URL | Model auth/endpoint override (alternative to zcode login OAuth) |
ZCODE_PROJECT_DIR | = --cwd |
ZCODE_DATA_BASE_DIR / ZCODE_STORAGE_DIR | Session-DB override (default ~/.zcode/cli/db/db.sqlite) — enables isolated testing |
ZCODE_CUA_PERMISSION_BROKER_* | Unattended permission brokering (undocumented — flagged as fresh-forensics target) |
ZCODE_DEBUG / ZCODE_LOG_DIR | Diagnostics |
--mode <build|edit|plan|yolo> permission policy; default is yolo for --prompt (auto-approves everything; the branch is literally mode==="yolo" ? allow(...) "Yolo mode bypasses permission prompts"). plan is the safe headless mode.--help but rejected by the strict parser (verified live): --print, --max-turns, --allowed-tools, --permission-mode, --allow-main-worktree-yolo.--cwd, --disallowed-tools (the ONLY allow/deny flag), --attach (repeatable), --resume <sess_…>, -c/--continue, --target (= /goal), --json, --no-color, --verbose.~/.zcode/cli/db/db.sqlite — tables session, message, part, todo, session_target, usage. Grew 172 MB (Jul) → 731 MB (Aug) → 1.7 GB (now), ~495 sessions at first measurement.session.share_url column dead, relay wss://zcode.z.ai/ws is live remote-control only). STALE-3.4 — re-check on 3.11.2.~/.zcode/cli/rollout/ raw model-I/O JSONL is transient and embeds live Bearer tokens — never surface it in T3 UI.~/.zcode/v2/credentials.json (Z.AI OAuth+JWT), ~/.zcode/codex/auth.json, ~/.zcode/v2/acp-auth/, keys inside ~/.zcode/cli/config.json.session.directory, contextSnapshot.envInfo) — history import is readable cross-machine but -c resume is machine-local.~/.claude/settings.json rewires the claude CLI that T3 spawns to ANTHROPIC_BASE_URL=https://api.z.ai/api/anthropic with a z.ai Coding Plan token, models glm-5.2[1m] / glm-4.7. So George already runs GLM in T3 through the Claude adapter. This is model-level routing, not ZCode-the-agent integration: no ZCode sessions, tools, workflows, or permissions.wire_api="responses"; z.ai returns 404 on all /responses paths (200 on /chat/completions). Working recipe needs a LiteLLM bridge (responses_api_bridge: true, api_base must include /api/coding/paas/v4 — the /coding/ segment selects flat-rate Coding Plan billing; wrong path silently burns pay-as-you-go credit)./api/anthropic) and OpenAI-compatible endpoints, not Responses API.~/ZCodeProject/artifacts/t3-codex-glm-setup.html embeds a full live z.ai Coding Plan token in plaintext (ZAI_API_KEY=…) despite claiming masking elsewhere. Recommend rotating that token at z.ai and scrubbing the artifact. Token prefix for identification: 9dc51c4f (first 8 chars only, safe to display).
["*"] → resolves to 215 tools); background mode with task-IDs, steering, notifications — vocabulary that parallels T3's own task model (turn_started, model_streaming, tool_call_scheduled, checkpoint_created, turn_complete … in ~/.zcode/cli/agents/*/transcript.jsonl).checkpoint_created events observed in subagent transcripts) — relevant to T3's per-turn checkpoint feature; needs fresh verification of mechanics (git refs? internal?).mcp.servers map in ~/.zcode/v2/config.json with drift vs legacy ~/.zcode/cli/config.json; project .mcp.json supported.agent()/pipeline()/parallel()) shipped in 3.3.6 — out of scope for a T3 provider v1 but a differentiator for later.Full read-only sweep of the live install (SQLite opened mode=ro&immutable=1; keychain probed by attribute lookup only — no entries exist, ZCode auth is entirely file-based).
| Generation | Location | What it is |
|---|---|---|
| v1 (Apr 2026) | ~/Library/Application Support/ai.z.zcode/ | Electron app that wrapped OpenCode/Claude Code (zcode.db has agent_id + claude_code_session_id columns; .dat files are plain JSON). ~1 session. |
| v2 "tasks/ACP" era | ~/.zcode/v2/ + ~/.zcode/agent/ | Per-task session JSONs, tasks-index.sqlite (tasks/automations/cron), checkpoint metadata, ACP configs for sub-agent providers, credentials. |
| Current CLI era | ~/.zcode/cli/ | Relational SQLite session store (16 GB total dir). Schema migrations 0001→0018, runtime versions 0.2.0→0.16.5, UA self-reports ZCode/3.11.2. |
~/.zcode/cli/db/db.sqlitesession: id sess_<uuid>, project_id (proj_users-george-workspace-t3trade), absolute directory, title + title_source, task_type (interactive 493 / subagent_child 1536 / selection_side_chat), parent_id subagent linkage, compaction/archival timestamps, trace_id.message.data (JSON): user messages carry model:{providerID:"builtin:zai-coding-plan", modelID:"GLM-5.3", variant}, contextSnapshot.envInfo (cwd/platform/git), anchor:{turnId, sourceCommandId}; assistant messages carry tokens:{total,input,output,reasoning,cache:{read,write}}, finish, mode.part types: tool (callID/tool/state{status,input,output,title}/time), text, reasoning (with metadata.anthropic.signature — Anthropic reasoning blocks), step-start/step-finish, file, compaction (pre/post token counts, trigger, summaryMessageId).turn_usage (tokens + TTFT), model_usage (embeds anthropic.usage), tool_usage (top tools: Bash 22k, Edit 5k, Read 4.3k, Write, TodoWrite, mcp__playwright__*, Agent, SendMessage, Skill — read_only/destructive flags), todo, permission, session_target (objective/token budget), workflow_*.~/.zcode/cli/artifacts/<sess>/call_*.txt (join via callID); raw per-call stdout under cli/exec/<sess>/; raw model I/O captures under cli/rollout/ only when enabled (only 3 files exist).| Layer | Verdict | Evidence |
|---|---|---|
| Model wire protocol | Anthropic Messages | providers configured kind:"anthropic" vs https://api.z.ai/api/anthropic; model-io captures are verbatim Anthropic requests (system array, tools, thinking, stream); anthropic.signature on reasoning parts; anthropic.usage in model_usage |
| Agent/tool surface | Claude-Code-like | Tool names Bash/Edit/Read/Write/TodoWrite/Agent/Skill/SendMessage/mcp__server__tool; SKILL.md skills (25 user skills); installs from claude-plugins-official marketplace; ships its own Claude-Code history importer (claude-import-*.json, meta.migrationSource:"claudeCode") |
| Session storage | Custom SQLite (neither Claude *.jsonl nor Codex rollout) | Normalized relational schema as above |
Why ~/.zcode/codex/ exists | ZCode embeds Codex as a sub-agent via codex-acp, repointed at Z.ai | ~/.zcode/agents/codex/v0.9.4/codex-acp + Codex-format config.toml (wire_api="chat", base_url …/api/coding/paas/v4); also bundles claude-code 0.22.6, gemini-cli 0.36.0, opencode 1.1.48 as sub-agents |
~/.zcode/v2/checkpoints/<projhash>/<uuid>.json: {refName: "refs/zcode/checkpoints/<projhash>/<uuid>", commitOid} — hidden git refs, the same trick T3's per-turn checkpoints use. v2 task JSONs also carry turnCheckpoints. Checkpoint parity in a T3 adapter is architectural alignment, not new invention.
| File | Contents | Format |
|---|---|---|
~/.zcode/v2/credentials.json | .oauth:zai:access_token, .zcodejwttoken, per-MCP OAuth, relay pass-hash | flat JSON KV, chmod 600, live-written |
~/.zcode/cli/config.json | provider.builtin:zai.options.apiKey + baseURL + model catalog; 12+ MCP servers | plaintext JSON |
~/.zcode/agent/config.json | apiKey for …/api/coding/paas/v4 | plaintext JSON |
~/.zcode/v2/model-providers.json | provider registry with both endpoints.anthropic and endpoints.openai; Claude model-name aliases (haiku→glm-4.5-air, sonnet→glm-4.7, opus→glm-5.1) | JSON |
No macOS keychain entries exist (probed by attribute: none for ZCode/zcode/ai.z.zcode/zai/Z.ai). Detection heuristic for T3: provider considered "logged in" iff ~/.zcode/v2/credentials.json has a zai OAuth token or ~/.zcode/cli/config.json has a provider apiKey. Copy, never lock — the running app writes these files.
Everything T3 renders per thread/turn exists: session id, project directory, titles, typed message parts (text/reasoning/tool with inputs+outputs), per-turn token counts incl. cache, todos, compaction boundaries. Gaps: cost always 0 (subscription billing — display "—" or compute from tokens), offloaded tool outputs need the artifacts join, compacted-away history not retained. ZCode's own Claude-Code importer proves the symmetric pattern; a T3 ← ZCode importer maps session→project/thread, message+part→turns/events, turn_usage/model_usage→token accounting. v2/v1 history needs separate (simpler) importers.
apps/server/src/provider/
├── ProviderDriver.ts # Driver SPI + ProviderInstance record (plain values, no tags)
├── builtInDrivers.ts # BUILT_IN_DRIVERS array + BuiltInDriversEnv ← THE registration point
├── providerSnapshot.ts # shared probe helpers: buildServerProvider, spawnAndCollect…
├── makeManagedServerProvider.ts # generic snapshot lifecycle (probe/enrich/refresh/TTL)
├── Services/ # interfaces: ProviderAdapter, ProviderService, ProviderRegistry,
│ # ProviderInstanceRegistry(+Mutator), ProviderAuthService…
├── Layers/ # implementations + per-provider <X>Adapter.ts / <X>Provider.ts
├── Drivers/ # per-provider <X>Driver.ts (config schema → instance factory)
├── acp/ # shared ACP runtime: AcpSessionRuntime (1,294 ln), AcpRuntimeModel,
│ # AcpCoreRuntimeEvents, per-provider ACP glue
└── testFixtures/, testUtils/ # codex collab mock peer, fakeCli (acp-mock-agent)
packages/effect-acp/ # first-party ACP JSON-RPC-over-stdio client
packages/effect-codex-app-server # first-party Codex app-server JSON-RPC client
| SPI | Definition | Members |
|---|---|---|
| Adapter | Services/ProviderAdapter.ts:67-158 ProviderAdapterShape<TError> | provider, capabilities, startSession, sendTurn, compaction? (native vs slash-command), interruptTurn, respondToRequest, respondToUserInput, stopSession, listSessions, hasSession, readThread, rollbackThread, uploadFeedback?, stopAll, streamEvents: Stream<ProviderRuntimeEvent> — the single canonical event stream everything downstream consumes. Capabilities: sessionModelSwitch: "in-session"|"unsupported", promptlessTurnContinuation?, supportsConversationRollback?. |
| Driver | ProviderDriver.ts:134-172 ProviderDriver<Config,R> | driverKind, metadata (displayName, supportsMultipleInstances), configSchema, defaultConfig(), create(input) → Effect<ProviderInstance>. ProviderInstance = snapshot + snapshotForCwd?/refreshModels? + adapter + textGeneration + auth?. |
Materialization is provider-agnostic (no edits): ServerSettings → deriveProviderInstanceConfigMap (Layers/ProviderInstanceRegistryHydration.ts:73) → ProviderInstanceRegistryLive.buildEntry (:117) decodes config and runs driver.create; failures degrade to an "unavailable" shadow snapshot. ProviderAdapterRegistry resolves instanceId → adapter; ProviderService is the facade transports call.
| Family | Providers | Transport | Adapter size | Shared runtime |
|---|---|---|---|---|
| ACP | Grok, Cursor, Antigravity | JSON-RPC over stdio, ACP initialize handshake, session/new|resume, session/update notifications, permission requests | Grok: ~2,900 ln total (thinnest) | acp/AcpSessionRuntime.ts (spawn/env/cwd, handshake, cancel behavior, stderr) + AcpRuntimeModel + AcpCoreRuntimeEvents + packages/effect-acp |
| app-server JSON-RPC | Codex | spawns codex app-server; JSON-RPC via first-party effect-codex-app-server client | 2,739 ln | Layers/CodexSessionRuntime.ts |
| SDK stream-json | Claude | @anthropic-ai/claude-agent-sdk query(); SDK spawns CLI; canUseTool → approval/user-input requests blocked on Deferred | 5,149 ln (thickest) | Agent SDK + EventNdjsonLogger |
| HTTP/SSE | OpenCode | T3-managed OpenCode server via @opencode-ai/sdk/v2 with idle shutdown, or external serverUrl | 3,863 ln | OpenCodeServerOwner.ts + shared opencodeRuntime.ts |
app-server fits T3's architecture precisely. It is a fourth instance of a pattern T3 already has three times over: a long-lived subprocess speaking line-delimited JSON-RPC over stdio with bidirectional requests. The zcode protocol is not ACP and not Codex's method set, so it needs its own thin session-runtime module (like CodexSessionRuntime.ts) rather than reusing AcpSessionRuntime — unless the ACP community bridge is chosen instead (§7 trade-offs).
orchestration/Layers/CheckpointReactor.ts reacts to turn.completed/turn.aborted and captures hidden git refs via checkpointing/CheckpointStore.ts. Emit correct turn events → checkpoints work with zero adapter code.ProviderRuntimeIngestion projects adapter events into orchestration events/activities.RuntimeInstructions (the <runtime_info> block), ProviderEventLoggers (native+canonical NDJSON protocol capture), userInputAttachments, thread-scoped MCP (McpProviderSession).docs/internals/providers.md): if the provider can't rewind its conversation, set supportsConversationRollback = false so revert is rejected before files change. ZCode does have rewind (v2 checkpoints + session/fork) — verify before enabling.--version; auth detection = read credential files (§4.6), never trigger login.zcode (server side)packages/contracts/src/settings.ts — ZcodeSettings via makeProviderSettingsSchema (:516) + zcode: key in ServerSettings.providers (:1040) — required for settings UI + auto-hydration of the default instanceapps/server/src/provider/zcode/ZcodeSessionRuntime.ts (new) — spawn zcode.cjs app-server, NDJSON JSON-RPC framing (no jsonrpc field!), request/response correlation, reverse-call handling (session/requestRuntimePreferences, interaction/requestPermission) — model on Layers/CodexSessionRuntime.tsapps/server/src/provider/Layers/ZcodeProtocol.ts (new) — schema for the zcode method set + model.streaming/tool.updated/turn.completed/state.updated event vocabulary → ProviderRuntimeEvent mapping (model on acp/AcpRuntimeModel.ts + AcpCoreRuntimeEvents.ts)apps/server/src/provider/Layers/ZcodeAdapter.ts (new) — makeZcodeAdapter implementing ProviderAdapterShape (model on Layers/GrokAdapter.ts structure, Codex transport)apps/server/src/provider/Layers/ZcodeProvider.ts (new) — checkZcodeProviderStatus (bundle discovery + --version + credential-file auth detection + model catalog from provider config) — model on GrokProvider.ts:330apps/server/src/provider/Services/ZcodeAdapter.ts (new, ~16 ln) — shape anchor (copy Services/GrokAdapter.ts)apps/server/src/provider/Drivers/ZcodeDriver.ts (new) — the ProviderDriver value (copy Drivers/GrokDriver.ts)apps/server/src/textGeneration/ZcodeTextGeneration.ts (new) — text-gen closure (copy GrokTextGeneration.ts)apps/server/src/provider/builtInDrivers.ts:36-56 — add to BuiltInDriversEnv + BUILT_IN_DRIVERS — the single registration pointpackages/contracts/src/model.ts — display name + default models (+ §6 UI files)~/.zcode/skills/ is Claude-Code SKILL.md format — reuse pattern from Drivers/ClaudeSkills); history importer reading ~/.zcode/cli/db/db.sqlite read-only (§4.6); TextGenerationProvider union membershipserver.ts needs no edit unless the driver introduces a new shared service beyond BuiltInDriversEnv.
apps/server/src/provider/Layers/GrokAdapter.test.ts — full adapter lifecycle against testUtils/fakeCli.ts's acp-mock-agent (configurable via env flags like T3_ACP_EMIT_TOOL_CALLS). A zcode equivalent: a zcode-mock-app-server.ts script driven by env flags, exercising spawn/handshake/turn/interrupt/permission against TestClock.Layers/GrokProvider.test.ts — probe/parsing units + fake-CLI status tests (binary missing → installed:false; version parsing; auth states).ClaudeAdapter.test.ts injects a FakeClaudeQuery implements AsyncIterable<SDKMessage> — same idea as injecting a fake session-runtime factory (CodexAdapter.test.ts).| Codex | Claude | Cursor | Grok | OpenCode | Antigravity | |
|---|---|---|---|---|---|---|
| Process model | app-server child/thread | CLI child via SDK | ACP child/session | ACP child/session | T3-managed HTTP server (or external URL) | ACP child, T3-installed |
| Wire protocol | custom JSON-RPC (effect-codex-app-server) | stream-json (via agent SDK) | ACP | ACP + xAI ext. | HTTP + event stream | ACP + normalization |
| Interrupt | native | hard kill + resume | acp.cancel | acp.cancel+lock | session.abort | acp.cancel wait-for-prompt |
| Models | dynamic model/list | static manifest | dynamic ext. | dynamic CLI parse | dynamic HTTP | dynamic |
| Compaction | native | slash | slash | slash | native | slash |
| Non-test LOC | ~7.8k | ~7.7k | ~3.6k | ~3.5k | ~6.4k | ~5.8k (+auth+installer) |
Pattern: adapter size tracks protocol delegation, not feature count. The three ACP adapters share ~2,500 lines of runtime (AcpSessionRuntime 1,294 + AcpRuntimeModel 887 + AcpCoreRuntimeEvents 234) and each pays only for CLI quirks.
| Provider | Commit | Scope |
|---|---|---|
| Grok (best template) | 38ea6d483e 2026-06-09 #2809 "feat(grok): add Grok CLI provider via ACP" | 40 files, +3,673/−22 — the cheapest complete provider addition in repo history. New: Driver (153), Adapter (934+t), Provider (324+t), Services anchor (16), AcpSupport (104), XAiAcpExtension (173+t), TextGen (272+t), Skills (151+t). Wiring: builtInDrivers (+3), providerSnapshot (+4), serverSettings (+2), ProviderCommandReactor (+48), AcpSessionRuntime (+17). Contracts: settings (+32), model (+3), server (+1). Web: 10 files (~150 ln). Mock agent fixture (+86). Mobile: zero changes (icons followed 2 months later, 94331c58ec #4586). |
| Antigravity (most recent, 2026-09-03) | 06336460c9 #9348 | 169 files, +21,470; server+contracts subset 78 files +15,670 — because it added in-app OAuth (~1,100 ln + new providerSetup.ts contract) and a T3-managed CLI installer (950 ln). A CLI that already exists on the machine (like ZCode) needs neither. |
Shared-layer lineage: ACP+Cursor 9c64f12ea0 → OpenCode ce94feeea1 → multi-instance model 08e6d4cfbf → Grok 38ea6d483e → Antigravity 06336460c9.
app-server is custom NDJSON JSON-RPC (not ACP, not Codex's method set) → clone the Codex shape: a small first-party client package (the effect-codex-app-server pattern: client/protocol/errors, ~1–2k LOC) + a CodexSessionRuntime-style session layer. Honest floor ~4–8k LOC server-side — Grok-scale structure with Codex-scale protocol ownership. The zcode protocol is smaller than Codex's (fewer thread modes, no collab peers), so expect the low end if the 0.16.x surface verifies as documented. Do NOT settle for the one-shot --prompt --json mode — no streaming, no approvals, no steering: it cannot carry T3's feature set.
packages/contracts/src/providerRuntime.ts:19-28 — add a RuntimeEventRawSource literal (e.g. "zcode.jsonrpc") so native raw events round-trip through protocol logging.
ProviderDriverKind (packages/contracts/src/providerInstance.ts:70, pattern ^[a-zA-Z][a-zA-Z0-9_-]*$) is an intentionally open branded slug. "Membership" = a server-side driver registry entry + entries in a scatter of Partial<Record<ProviderDriverKind,…>> lookup maps. Every picker (composer, settings, command palette, mobile sheets) is snapshot-driven from ServerProvider[] over the wire (packages/contracts/src/server.ts:188-238: installed, auth, status, models, capability flags) — so once the server ships a driver, the lists render themselves. What does NOT render itself: icons, brand labels, settings schema, default models, per-driver capability branches.
| Surface | Where | Data-driven? |
|---|---|---|
| Web composer picker | ProviderModelPicker.tsx:29 (in ChatComposer.tsx:177) + rail ModelPickerSidebar.tsx:43 + ModelPickerContent.tsx/ModelListRow.tsx, fed by apps/web/src/providerInstances.ts:94 | ✅ pure wire projection — no change |
| Settings pickers (text-gen default, project defaults, source-control) | same ProviderModelPicker in SettingsPanels.tsx:57, ProjectSettingsPanel.tsx:80, ProjectDefaultsSettings.tsx:26, SourceControlWritingSettings.tsx:21 | ✅ no change |
| Settings → Providers | ProviderSettingsPanel.tsx:127 builds from DRIVER_OPTIONS (providerDriverMeta.ts:46-85); instances ProviderInstanceCard.tsx; add-instance dialog AddProviderInstanceDialog.tsx | ⚠️ needs ProviderClientDefinition entry |
| Command palette | CommandPalette.tsx:161,645-650 via deriveProviderInstanceEntries | ✅ no change |
| Keybindings | no provider-switch commands exist (packages/contracts/src/keybindings.ts) | ✅ nothing to do |
| Onboarding wizard | WelcomeWizard.tsx:609 hardcodes PRIMARY_AGENT_DRIVERS=["claudeAgent","codex"]; install/login commands providerReadiness.logic.ts:79-129 | ⚠️ decide membership + login command |
| Mobile thread sheet | ThreadSettingsSheet.tsx, catalog from config.providers grouped by modelOptions.ts:237; PRIMARY_PROVIDER_DRIVERS at :78 | ⚠️ label + icon + expand-set |
| Desktop | Electron shell is provider-agnostic (verified across apps/desktop/src — no bundled CLIs, no per-provider PATH, no tray items) | ✅ nothing to do |
apps/web/src/components/Icons.tsx (Antigravity precedent: base64 PNG wrapped in SVG <image>). Map lives in providerIconUtils.ts:12-19.apps/mobile/src/components/ProviderIcon.tsx:13 — inline react-native-svg branches with dark/light fills; PNG via expo-image precedent (assets/antigravity.png). Unknown drivers fall back to the OpenAI glyph — a branch is required or Zcode shows the wrong logo.providerStatus.ts:32: "Not found / CLI not detected on PATH", "Not authenticated", "Authenticated · label" from ServerProvider.installed/auth). No per-provider work.zcodeA. Contracts (packages/contracts)
src/settings.ts — add ZcodeSettings = makeProviderSettingsSchema({...}) (pattern at :530-842, makeBinaryPathSetting("zcode") at :470); add zcode: key to ServerSettings.providers (:1040-1047); add ZcodeSettingsPatch + key in update-patch record (:1166-1211, :1271-1276)src/model.ts — add zcode to PROVIDER_DISPLAY_NAMES (:217), DEFAULT_MODEL_BY_PROVIDER (:168), DEFAULT_TEXT_GENERATION_MODEL_BY_PROVIDER (:179), optionally MODEL_SLUG_ALIASES_BY_PROVIDER (:189)src/usage.ts:35 — extend closed UsageProviderKind ["claude","codex","grok"] only if Zcode reports usage/costs — compile-breaking across both clients until they're updated (C8/D4 below)B. Client-runtime / shared
providerInstanceDisplay.ts auto-consumes contracts' display names (falls back to humanizeSlug); packages/shared/src/model.ts legacy paths degrade to codex fallback.C. Web (apps/web)
src/components/Icons.tsx — add ZcodeIcon (reads at 16–20px, light+dark)src/components/settings/providerDriverMeta.ts:46-85 — add ProviderClientDefinition (value:"zcode", label, icon, settingsSchema: ZcodeSettings) — one entry feeds Settings→Providers, add-instance dialog, and the generic settings formsrc/components/chat/providerIconUtils.ts:12-19 — map driver → ZcodeIcon (composer trigger, picker rail, model rows, update notifications)src/onboarding/providerReadiness.logic.ts:79-129 — install/login commands (or rely on generic driver-slug fallback)src/components/onboarding/WelcomeWizard.tsx:609 — decide PRIMARY_AGENT_DRIVERS membership (+ sources typing :1483)src/modelSelection.ts:61,101-104,311-319 — dynamic-model behavior decisions (defaults pass through if untouched)src/components/settings/ProviderModelsSection.tsx:26-31 + customModelEditor.logic.ts:54-95 — custom-model placeholder/descriptors if custom models supportedsrc/components/usage/usageProviders.ts:16-33, UsageLimits.tsx:49-50 — only if UsageProviderKind extended (exhaustive maps)ModelPickerContent.tsx:70-94, ProviderModelPicker.tsx:80, TraitsPicker.tsx:147,505,600, composerProviderState.tsx:114, ContextWindowMeter.logic.ts:70, ChatView.logic.ts:423,481, ChatView.tsx:6539, ProviderStatusBanner.tsx:14-55, ProviderInstanceCard.tsx:472, ProviderSettingsPanel.tsx:593,903, ProviderSetupSection.tsx (in-app OAuth — Antigravity-only today, gated on setup.canAuthenticate)src/components/settings/AddProviderInstanceDialog.tsx:73-94 — optional COMING_SOON_DRIVER_OPTIONS teaser pre-launchD. Mobile (apps/mobile)
src/components/ProviderIcon.tsx:13-85 — add provider === "zcode" branch (else wrong fallback glyph)src/lib/modelOptions.ts:31-40 — providerDisplayLabel branch ("Zcode") so sheet header doesn't show raw instanceIdsrc/features/threads/ThreadSettingsSheet.tsx:78-82 — decide PRIMARY_PROVIDER_DRIVERS membershipfeatures/usage/usageProviders.ts:8-27, UsageLimitsPooled.tsx:28,239, UsageLimitsSection.tsx:37, ComposerUsageLimits.tsx:8src/state/use-thread-composer-state.ts:346 — only for codex-style feedback-command parsingE. Desktop — nothing (provider-agnostic shell). F. Server — see §5. G. Docs — docs/user/providers-zcode.md only if setup has user-facing quirks (pattern: providers-{claude,codex,opencode,antigravity}.md).
Every capability T3's other providers expose, mapped to the verified zcode surface. LIVE = observed in the verification probe or sub-agent live captures; DOC = in the bundle method table / community docs, not yet exercised; DECIDE = product decision required.
| T3 feature | ZCode surface | Evidence | Adapter effort |
|---|---|---|---|
| Selectable provider (composer, settings, palette, mobile) | Driver + ServerProvider snapshot (installed/version/auth/models) | ARCH §5/§6 | Wiring only — pickers are snapshot-driven |
| Threads/sessions | session/create (workspace-keyed), session/list, session/resume, session/messages, session/read | LIVE | Small |
| Turns + streaming | session/send → session/event stream (turn.started, session.updated w/ content+stopReason, turn.completed); thought/text deltas via stream.chunk telemetry; one-shot mode has full model.streaming text_delta|reasoning_delta events | LIVE | Medium — event mapping to ProviderRuntimeEvent |
| Interrupt | session/stop, session/cancelBackgroundTask, v4 stop {expectedForegroundExecutionId} | DOC | Small |
| Approvals / permissions UI | Reverse-call interaction/requestPermission {requestId, toolName, toolCallId, reason, riskLevel, input, options:[{optionId,kind,name}]} → answer {optionId, response}; modes build|edit|plan|yolo (+ acceptEdits|bypassPermissions|dontAsk in enum) | DOC | Small — direct map to T3 approval cards; mode maps to RuntimeMode |
| User-input questions | interaction/requestUserInput reverse-call | DOC | Small |
| Images in / out | session/send {attachments}; v4 attachment/begin|chunk|commit (≤512 KiB); models advertise supportsImages (glm-5v-turbo true); image gen via workspace tools | DOC + model flags LIVE | Small–medium |
| Model picker (dynamic) | settings.model.available from session/create / workspace/readState: label, ref, supportsImages, reasoning levels, contextWindow | LIVE | Small |
| In-session model switch | session/setModel, session/setThoughtLevel, session/setMode; v4 switchModelConfig (CAS) | DOC | Small → sessionModelSwitch: "in-session" |
| Compaction | session/compact — native (vs Grok's slash-command) | DOC | Trivial: compaction: {type:"native"} |
| Resume/continue | session/resume, --resume sess_…, -c | LIVE (sub-agent probe: model recalled prior turn) | Small |
| Checkpoints (per turn) | T3 CheckpointReactor is provider-agnostic (git refs). ZCode also emits its own checkpoint.created events + refs/zcode/checkpoints/… | ARCH | Zero adapter code |
| Rollback / rewind | v4 applyFileRewind + v4/conversation/fileRewindPreview, session/fork, forkAssistant (CAS-gated) | DOC | DECIDE verify semantics first; else declare supportsConversationRollback:false (Antigravity precedent) |
| Text generation (titles/commits) | workspace/generateText + one-shot --prompt --output-format json. Note: zcode auto-generates session titles itself (session.titleUpdated, lite model) | DOC | Medium (GrokTextGeneration pattern) |
| Usage / context meter | turn.completed.usage (input/output/reasoning/cacheRead/cacheWrite + webFetch/webSearch counts), contextUsageBreakdown, usage/stats, projection contextUsed/contextWindow | LIVE | Small. DECIDE cost is always $0 (subscription) → show tokens, not dollars; skip UsageProviderKind initially |
| Subagents / background | Agent/SendMessage/TaskOutput/TaskStop tools; session/subagents; subagent.lifecycle events; cancelBackgroundWork | DOC + 1,536 subagent sessions in DB | Comes through event mapping |
| Skills | ~/.zcode/skills/*/SKILL.md (Claude-Code format), skills/referenceCatalog | DOC | Optional; ClaudeSkills pattern |
| Slash commands | /compact /fork /mcp /mode /model /new /resume /rewind /skill /goal /expert | DOC | Snapshot advertisement |
| Feedback (👍/👎) | v4 setAssistantFeedback | DOC | Small |
| Remote / mobile / relay | app-server is stdio on the T3 host — T3's existing remote stack carries it. ZCode even offers deliveryKind:"web-remote-replayable" (replay cursor via eventSeq) | LIVE | Zero — provider-agnostic in T3 |
| Onboarding / auth | zcode login (OAuth, --no-browser prints URL for remote); detection = credential files exist | DOC | Generic status UI covers it; no in-app OAuth needed |
| History import (T3 ← ZCode) | ~/.zcode/cli/db/db.sqlite (session/message/part/turn_usage) — everything T3 renders; ZCode's own Claude importer proves the pattern | FRESH §4.6 | Optional later phase |
Protocol verified, transcript captured, architecture mapped, file lists enumerated.
packages/contracts/src/settings.ts — ZcodeSettings: binaryPath override (optional), enabled default false, extra args passthrough. + zcode: in ServerSettings.providers + patch schemapackages/contracts/src/model.ts — display name "ZCode", default model builtin:zai/glm-5.1 (or latest from catalog), text-gen defaultpackages/contracts/src/providerRuntime.ts — add "zcode.jsonrpc" to RuntimeEventRawSourceapps/server/src/provider/Layers/ZcodeProvider.ts — binary resolution order: settings override → zcode on PATH → bundle candidates (/Applications/ZCode.app/Contents/Resources/glm/zcode.cjs, ~/.zcode/server/agents/glm/zcode.cjs) → snapshot installed:false with install hint. Version via --version; auth via credential files (never triggers login); models from ~/.zcode/v2/model-providers.json + static fallback. Gate: runtime ≥ 0.16 (protocol generation)apps/server/src/provider/Drivers/ZcodeDriver.ts + Services/ZcodeAdapter.ts + builtInDrivers.ts registration — adapter initially throws "not implemented"; snapshot already renders "ZCode" everywhereZcodeIcon, providerDriverMeta.ts, providerIconUtils.ts; mobile: ProviderIcon.tsx + modelOptions.ts label — §6 checklist itemspackages/effect-zcode-app-server/ (new first-party package) — NDJSON framing, request-id correlation, reverse-call dispatch (mandatory session/requestRuntimePreferences answered within 15 s with defaults), tolerant zod schemas (unknown event kinds ignored), error algebra. Model on effect-codex-app-server (client/protocol/errors, ~1–2k LOC)apps/server/src/provider/zcode/ZcodeSessionRuntime.ts — spawn (node <bundle> app-server --stdio, cwd = project), lifecycle, stderr bounding, protocol logging via EventNdjsonLogger. Model on CodexSessionRuntime.tsLayers/ZcodeAdapter.ts — startSession (create + subscribe desktop-continuous), sendTurn (send), event mapping: turn.started→turn.started, session.updated(content)→content.delta/item events, turn.completed→turn.completed (+usage), state.updated→status, session.titleUpdated→thread title, interaction/requestPermission→request.opened, stopSession/interruptTurn→session/stop, listSessions/hasSession/readThread→session/list,messagesscripts/zcode-mock-app-server.ts (env-flag-configurable fake speaking the verified transcript shapes — the acp-mock-agent pattern) + ZcodeAdapter.test.ts lifecycle + ZcodeProvider.test.ts probe/parse unitssession/send {attachments} → v4 chunked upload for large files)session/setModel/setMode/setThoughtLevel → in-session switching; dynamic model list into snapshot refreshModelssession/compact); resume cursor persistence ({sessionId, eventSeq})textGeneration/ZcodeTextGeneration.ts via one-shot --prompt --output-format json (or workspace/generateText)contextUsageBreakdown)docs/user/providers-zcode.md (install ZCode → zcode login → select in composer)setAssistantFeedback); rollback decision (verify applyFileRewind semantics or declare unsupported)db.sqlite → T3 threads), UsageProviderKind extension, welcome-wizard card| # | Risk | Severity | Mitigation |
|---|---|---|---|
| 1 | Undocumented protocol, no compat promise. It already broke once: 0.15→0.16 renamed methods, changed session/create params, removed steer/rewind, added mandatory reverse-calls. App auto-updates (3.11.2 is current) can break the adapter silently. | HIGH | Version gate on snapshot (≥0.16, re-checked on refresh TTL); tolerant schemas (ignore unknown methods/event kinds — verified the server tolerates unanswered unknown reverse-calls with an error reply); EventNdjsonLogger protocol capture for diagnosis; integration test against the real installed CLI in CI-nightly style; surface "ZCode updated — protocol mismatch" states. |
| 2 | Terms of Service. GLM Coding Plan is "strictly limited to use within officially supported tools and products"; the CLI surface is undocumented. Community precedent exists (Paseo ships ZCode via ACP bridge; Zed adapters; pi provider; ExMCP) and ZCode itself ships env overrides (ZCODE_AGENT_SERVER_COMMAND) that invite alternative hosts — but it is formally gray. | MAINTAINER DECISION | Explicit sign-off from maintainers; user-visible note in provider setup; keep the integration local-only (T3 spawns the user's own installed CLI — no redistribution of ZCode assets). |
| 3 | Shared session DB with the desktop app. T3 and ZCode desktop write the same ~/.zcode/cli/db/db.sqlite (WAL). Proven by probe. Desktop already multiplexes multiple CLI children this way, so it is a supported pattern — but sessions intermix in history and a desktop-side DB migration could collide. | MEDIUM | Treat as a feature (thread interop: start in T3, continue in desktop); never write the DB from T3 (read-only importer only, mode=ro); tolerate "session moved/missing" gracefully. If ZCode later honors a DB-path override, offer optional isolation. |
| 4 | Test isolation is subtle. ZCODE_STORAGE_DIR redirects only rollout//plugins/ in 0.16.5 — the DB still went to the live WAL (verified). Sub-agent isolation claims must be re-checked each CLI bump. | MEDIUM | Use HOME override for sandbox tests; copy-in credentials (copy, never link); assert where sessions landed before running suites. |
| 5 | Context weight. Every turn carries ~50k tokens of system+tool+MCP schemas (86 KB + 230 KB). Cache reads work (10,368 tokens cached on request 1), but prompt-caching behavior on z.ai may differ from Anthropic's. | LOW | Surface contextUsageBreakdown + cache stats in T3's context meter so users see it; no adapter fix possible. |
| 6 | Secrets hygiene (pre-existing, not introduced by an adapter): ~/.zcode/cli/config.json and ~/.zcode/v2/model-providers.json hold plaintext API keys; ~/.zcode/cli/rollout/model-io-*.jsonl embeds live Bearer tokens; credentials.json is AES-GCM with a deterministic fallback key. Action item: the prior artifact ~/ZCodeProject/artifacts/t3-codex-glm-setup.html embeds a full live Coding Plan token (9dc51c4f…) — rotate it. During this investigation, live secret values also transited two sub-agent transcripts (discovery probes, since redacted) — same rotation covers them. | ACTION | Rotate the z.ai key; scrub the artifact; T3 must never read/log these files beyond existence checks. |
| 7 | Unknowns to verify during implementation: permission reverse-call in a real tool-use turn (probe's build-mode turn made no tool calls); attachment schema exact shape; web-remote-replayable delivery semantics; rewind (applyFileRewind) conversation semantics; behavior when both T3 and desktop drive the same session concurrently. | SPIKE | Each has a designated verify step in Phase 2/3; transcript harness at /tmp/zcode-t3-verify/driver.mjs is the template. |
# Process observation (read-only)
ps aux | grep -i zcode; ps -ww -p <pid> -o command=; lsof -p <pid> → one zcode-cli per project cwd + zcode-host-local-1 broker
# Independent protocol verification (isolated workspace /tmp/zcode-t3-verify)
node /Applications/ZCode.app/Contents/Resources/glm/zcode.cjs --version # → 0.16.5
node …/zcode.cjs app-server --stdio # driven by /tmp/zcode-t3-verify/driver.mjs
# run2: proved no hello handshake; run3: deliveryKind enum via -32602 zod detail;
# run4: full transcript — create/subscribe/send/turn.completed("T3_ZCODE_OK", 50,816 tokens)/messages/list
sqlite3 "file:…db.sqlite?mode=ro&immutable=1" … # DB location forensics
strings ~/.zcode/cli/db/db.sqlite-wal | grep -c sess_74f13653 # → 72 (DB not redirected by ZCODE_STORAGE_DIR)
Probe disclosure: the verification wrote 3 trivial sessions into the shared live ZCode history (titles "Reply with exactly: T3_ZCODE_OK" / "Request for exact T3_ZCODE_OK reply", workspace /tmp/zcode-t3-verify/ws). They are visible in ZCode desktop and deletable there; the live DB was never opened read-write by this investigation.
| Artifact | What it proved |
|---|---|
/Applications/ZCode.app/Contents/Resources/glm/zcode.cjs (12.6 MB) | CLI runtime 0.16.5; method table (session/* workspace/* mcp/list plugins/* skills/* automation/* usage/stats interaction/* computer-use/*); modes enum; stream-json output; runs on plain Node |
app.asar → out/main + extracted host code | Exact spawn line (zcode.cjs app-server --stdio, ELECTRON_RUN_AS_NODE=1); ZCODE_AGENT_SERVER_COMMAND/GLM_BINARY_PATH overrides; zcode-server.cjs = remote-host packaging of the same host layer |
Host bundle zcode-server.cjs (remote-assets-cache, 3.11.2) | V4 method/command surface (26 command types, CAS envelope, conversation row kinds, delta event kinds); VSCode-style binary channel protocol (rejected alternative) |
~/.zcode/cli/db/db.sqlite (read-only) | 2,032 sessions / 85k messages / 315k parts; schema; 12,023 workspace checkpoints; subagent linkage; usage tables |
~/.zcode/v2/ (read-only) | credentials.json (AES-GCM enc:v1:), model-providers.json (anthropic+openai endpoints, Claude alias map), checkpoints (hidden git refs), tasks-index.sqlite |
/tmp/zcode-t3-verify/run4.log, driver.mjs | The verification transcript + harness (this session) |
| Lane | Key contribution |
|---|---|
| 1. CLI protocol forensics | Live handshake transcript, method table from bundle constant, auth paths, one-shot stream-json capture, Claude-Code-lineage verdict, isolation env vars |
| 2. Server + app architecture | 3-tier process tree from asar, V4 protocol surface, why zcode-server.cjs is rejected, model-provider catalog, DB schemas |
| 3. T3 provider architecture | Driver/Adapter SPIs, materialization pipeline, four protocol families, ordered server-side file list, test patterns |
| 4. T3 UI/contracts map | Open ProviderDriverKind, snapshot-driven pickers, exhaustive per-surface checklist (web/mobile/desktop/docs) |
| 5. Prior artifacts digest | July–Aug reports consolidated; 0.15.x vs 0.16 drift; existing GLM-in-T3 hacks (Claude-endpoint env, LiteLLM bridge); plaintext-token finding |
| 6. Web research | Official z.ai docs (endpoints, tiers, ToS), protocol break history, prior art (Paseo #1670, Zed, pi, ExMCP), version currency |
| 7. State/session formats | Three storage generations, db.sqlite schema + message/part JSON shapes, credential map, history-import feasibility: HIGH |
| 8. Adapter comparison | Six-adapter comparison table, reusable protocol layer inventory, Grok commit archaeology (+3,673 ln) vs Antigravity (+21,470), template verdict + per-feature effort |
Report generated by Claude Code (GLM-5.3 via T3 Code) · 8 parallel investigation sub-agents + independent live verification · 2026-09-09