ZCode → T3 Code: Native Provider Integration

Evidence-based feasibility report & implementation plan · Generated 2026-09-09 · T3 Code repo @ 3e6f856f23 · ZCode app 3.11.2 installed at /Applications/ZCode.app
BUNDLE FORENSICS LIVE PROCESS OBSERVATION PRIOR RESEARCH DIGEST T3 SOURCE MAP 8-SUBAGENT INVESTIGATION
Contents
  1. Executive summary & verdict
  2. What ZCode is (evidence)
  3. The integration surface: zcode app-server
  4. Prior research digest (what was already known)
  5. T3 Code integration map (server side)
  6. T3 Code integration map (UI / contracts / all surfaces)
  7. Per-T3-feature support plan
  8. Actionable build plan (ordered)
  9. Risks, unknowns & security notes
  10. Evidence log

1. Executive summary

Verdict: 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.

2. What ZCode is

PropertyValueEvidence class
ProductZ.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 versionApp 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
ModelsGLM-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
AuthGLM 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
CLINo 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 lineageOne 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

2.1 Protocol version history (community-documented, breaks are real)

App versionCLI runtimeProtocol changes
3.1.40.14.80.15→0.16 boundary broke the wire protocol: envelopes omit the jsonrpc field; session/create param cwdworkspace:{workspacePath, workspaceKey}; session/subscribe requires deliveryKind; new mandatory reverse-call session/requestRuntimePreferences; removed steer, rewind*, prompt/enhance*.
3.2.0–3.3.x0.15.0–0.15.2
3.10.20.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.

2.2 Prior art: GUIs already wrapping ZCode (proof the approach works)

ProjectHowNote
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 #1670Full arc documented: "no official interface" research → community adapter → shipped provider
Zed / JetBrainsACP 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 moduleDocuments the protocol surface in hexdocs
william0wang/zcode-acpACP server + remote hub daemon serving ZCode sessions to phone/browser over WebSocket, multi-client broadcastA working mini-T3 for ZCode — validates the remote/multi-surface requirement
Community-documented event vocabulary (0.16.x) — to be verified against the local 3.11.2 bundle in §3: 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.

3. The integration surface: spawn zcode.cjs app-server --stdio

3.1 Process architecture (verified live + from asar/host code) FRESH

ZCode (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.

3.2 The V4 protocol (host ↔ agent, NDJSON over stdio) FROM HOST BUNDLE

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.

Note on protocol drift. The 0.15.x community-documented surface (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).

3.3 Why not the alternatives

OptionVerdictWhy
1. Spawn zcode.cjs app-server --stdio, speak V4RECOMMENDEDExactly 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.cjsREJECTEDSame 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 bundlesREJECTEDMinified 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 ZCODEAlready 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.

3.4 Live verification — VERIFIED END-TO-END 2026-09-09

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}, …]}}

Verification findings (beyond the transcript)

4. Prior research digest (July–Aug 2026 artifacts in ~/ZCodeProject/artifacts/)

Provenance. Seven prior reports were digested (written 2026-07-16 → 2026-08-20 against ZCode app 3.3.6–3.4.2, CLI 0.15.2). They are code-grounded (de-minified bundle + live probes) but two major app versions old — current install is 3.11.2 (bundle 12.6 MB, Sep 4). Every claim below is re-verified against 3.11.2 in §3 where possible; items still unverified are tagged STALE-3.4.

4.1 Two headless surfaces exist (prior verification, v0.15.2)

ModeInvocationShapeFit 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 varPurpose
ZCODE_API_KEY / ZCODE_BASE_URLModel auth/endpoint override (alternative to zcode login OAuth)
ZCODE_PROJECT_DIR= --cwd
ZCODE_DATA_BASE_DIR / ZCODE_STORAGE_DIRSession-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_DIRDiagnostics

4.2 One-shot mode facts (prior-verified, v0.15.2)

4.3 Session storage & history (prior-verified)

4.4 GLM inside T3 Code today (without native integration)

Security note (action item). The prior artifact ~/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).

4.5 Other prior findings relevant to a provider adapter

4.6 On-disk state, session store & credentials — FRESH 2026-09-09

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).

Storage generations on this machine

GenerationLocationWhat 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 00010018, runtime versions 0.2.00.16.5, UA self-reports ZCode/3.11.2.
Version pinned: the installed app 3.11.2 runs CLI runtime 0.16.5 — i.e. the NEW protocol generation (post-break). Any adapter must target the 0.16.x surface from §2.1, not the 0.15.x surface in the July reports.

The canonical session store: ~/.zcode/cli/db/db.sqlite

Lineage verdict (fresh)

LayerVerdictEvidence
Model wire protocolAnthropic Messagesproviders 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 surfaceClaude-Code-likeTool 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 storageCustom SQLite (neither Claude *.jsonl nor Codex rollout)Normalized relational schema as above
Why ~/.zcode/codex/ existsZCode 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

Checkpoints — same mechanism as T3 VERIFIED

~/.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.

Credential reuse for T3 (paths only — values never reproduced here)

FileContentsFormat
~/.zcode/v2/credentials.json.oauth:zai:access_token, .zcodejwttoken, per-MCP OAuth, relay pass-hashflat JSON KV, chmod 600, live-written
~/.zcode/cli/config.jsonprovider.builtin:zai.options.apiKey + baseURL + model catalog; 12+ MCP serversplaintext JSON
~/.zcode/agent/config.jsonapiKey for …/api/coding/paas/v4plaintext JSON
~/.zcode/v2/model-providers.jsonprovider 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.

History import feasibility: HIGH

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.

5. T3 Code integration map (server side)

5.1 Where the provider layer lives

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

5.2 The two SPIs every provider implements

SPIDefinitionMembers
AdapterServices/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?.
DriverProviderDriver.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): ServerSettingsderiveProviderInstanceConfigMap (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.

5.3 The four existing protocol families

FamilyProvidersTransportAdapter sizeShared runtime
ACPGrok, Cursor, AntigravityJSON-RPC over stdio, ACP initialize handshake, session/new|resume, session/update notifications, permission requestsGrok: ~2,900 ln total (thinnest)acp/AcpSessionRuntime.ts (spawn/env/cwd, handshake, cancel behavior, stderr) + AcpRuntimeModel + AcpCoreRuntimeEvents + packages/effect-acp
app-server JSON-RPCCodexspawns codex app-server; JSON-RPC via first-party effect-codex-app-server client2,739 lnLayers/CodexSessionRuntime.ts
SDK stream-jsonClaude@anthropic-ai/claude-agent-sdk query(); SDK spawns CLI; canUseTool → approval/user-input requests blocked on Deferred5,149 ln (thickest)Agent SDK + EventNdjsonLogger
HTTP/SSEOpenCodeT3-managed OpenCode server via @opencode-ai/sdk/v2 with idle shutdown, or external serverUrl3,863 lnOpenCodeServerOwner.ts + shared opencodeRuntime.ts
ZCode's 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).

5.4 What a new provider gets for free (event-driven, provider-agnostic)

5.5 Exact ordered file list for zcode (server side)

1. packages/contracts/src/settings.tsZcodeSettings via makeProviderSettingsSchema (:516) + zcode: key in ServerSettings.providers (:1040) — required for settings UI + auto-hydration of the default instance
2. apps/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.ts
3. apps/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)
4. apps/server/src/provider/Layers/ZcodeAdapter.ts (new)makeZcodeAdapter implementing ProviderAdapterShape (model on Layers/GrokAdapter.ts structure, Codex transport)
5. 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:330
6. apps/server/src/provider/Services/ZcodeAdapter.ts (new, ~16 ln)shape anchor (copy Services/GrokAdapter.ts)
7. apps/server/src/provider/Drivers/ZcodeDriver.ts (new)the ProviderDriver value (copy Drivers/GrokDriver.ts)
8. apps/server/src/textGeneration/ZcodeTextGeneration.ts (new)text-gen closure (copy GrokTextGeneration.ts)
9. apps/server/src/provider/builtInDrivers.ts:36-56add to BuiltInDriversEnv + BUILT_IN_DRIVERS — the single registration point
10. packages/contracts/src/model.tsdisplay name + default models (+ §6 UI files)
Optional: skills discovery (~/.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 membership

server.ts needs no edit unless the driver introduces a new shared service beyond BuiltInDriversEnv.

5.6 Test patterns to imitate

5.7 Adapter comparison & template recommendation GIT-VERIFIED

CodexClaudeCursorGrokOpenCodeAntigravity
Process modelapp-server child/threadCLI child via SDKACP child/sessionACP child/sessionT3-managed HTTP server (or external URL)ACP child, T3-installed
Wire protocolcustom JSON-RPC (effect-codex-app-server)stream-json (via agent SDK)ACPACP + xAI ext.HTTP + event streamACP + normalization
Interruptnativehard kill + resumeacp.cancelacp.cancel+locksession.abortacp.cancel wait-for-prompt
Modelsdynamic model/liststatic manifestdynamic ext.dynamic CLI parsedynamic HTTPdynamic
Compactionnativeslashslashslashnativeslash
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.

Git archaeology — the ground-truth "add a provider" checklists

ProviderCommitScope
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 #9348169 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.

Template verdict. ZCode 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.

Additional contract point discovered

packages/contracts/src/providerRuntime.ts:19-28 — add a RuntimeEventRawSource literal (e.g. "zcode.jsonrpc") so native raw events round-trip through protocol logging.

6. T3 Code integration map (UI / contracts / all surfaces)

Architecture finding (drives everything): there is no closed provider enum. 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.

6.1 How provider selection flows today

SurfaceWhereData-driven?
Web composer pickerProviderModelPicker.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 → ProvidersProviderSettingsPanel.tsx:127 builds from DRIVER_OPTIONS (providerDriverMeta.ts:46-85); instances ProviderInstanceCard.tsx; add-instance dialog AddProviderInstanceDialog.tsx⚠️ needs ProviderClientDefinition entry
Command paletteCommandPalette.tsx:161,645-650 via deriveProviderInstanceEntries✅ no change
Keybindingsno provider-switch commands exist (packages/contracts/src/keybindings.ts)✅ nothing to do
Onboarding wizardWelcomeWizard.tsx:609 hardcodes PRIMARY_AGENT_DRIVERS=["claudeAgent","codex"]; install/login commands providerReadiness.logic.ts:79-129⚠️ decide membership + login command
Mobile thread sheetThreadSettingsSheet.tsx, catalog from config.providers grouped by modelOptions.ts:237; PRIMARY_PROVIDER_DRIVERS at :78⚠️ label + icon + expand-set
DesktopElectron shell is provider-agnostic (verified across apps/desktop/src — no bundled CLIs, no per-provider PATH, no tray items)✅ nothing to do

6.2 Icons & detection UI

6.3 Exhaustive file checklist for provider zcode

A. Contracts (packages/contracts)

src/settings.tsadd 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.tsadd 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:35extend 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

No changes — 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.tsxadd ZcodeIcon (reads at 16–20px, light+dark)
src/components/settings/providerDriverMeta.ts:46-85add ProviderClientDefinition (value:"zcode", label, icon, settingsSchema: ZcodeSettings) — one entry feeds Settings→Providers, add-instance dialog, and the generic settings form
src/components/chat/providerIconUtils.ts:12-19map driver → ZcodeIcon (composer trigger, picker rail, model rows, update notifications)
src/onboarding/providerReadiness.logic.ts:79-129install/login commands (or rely on generic driver-slug fallback)
src/components/onboarding/WelcomeWizard.tsx:609decide PRIMARY_AGENT_DRIVERS membership (+ sources typing :1483)
src/modelSelection.ts:61,101-104,311-319dynamic-model behavior decisions (defaults pass through if untouched)
src/components/settings/ProviderModelsSection.tsx:26-31 + customModelEditor.logic.ts:54-95custom-model placeholder/descriptors if custom models supported
src/components/usage/usageProviders.ts:16-33, UsageLimits.tsx:49-50only if UsageProviderKind extended (exhaustive maps)
Capability branches (no-op if answer "no"): 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-94optional COMING_SOON_DRIVER_OPTIONS teaser pre-launch

D. Mobile (apps/mobile)

src/components/ProviderIcon.tsx:13-85add provider === "zcode" branch (else wrong fallback glyph)
src/lib/modelOptions.ts:31-40providerDisplayLabel branch ("Zcode") so sheet header doesn't show raw instanceId
src/features/threads/ThreadSettingsSheet.tsx:78-82decide PRIMARY_PROVIDER_DRIVERS membership
Usage (only if extended): features/usage/usageProviders.ts:8-27, UsageLimitsPooled.tsx:28,239, UsageLimitsSection.tsx:37, ComposerUsageLimits.tsx:8
src/state/use-thread-composer-state.ts:346only for codex-style feedback-command parsing

E. Desktop — nothing (provider-agnostic shell). F. Server — see §5. G. Docsdocs/user/providers-zcode.md only if setup has user-facing quirks (pattern: providers-{claude,codex,opencode,antigravity}.md).

7. Per-T3-feature support plan

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 featureZCode surfaceEvidenceAdapter effort
Selectable provider (composer, settings, palette, mobile)Driver + ServerProvider snapshot (installed/version/auth/models)ARCH §5/§6Wiring only — pickers are snapshot-driven
Threads/sessionssession/create (workspace-keyed), session/list, session/resume, session/messages, session/readLIVESmall
Turns + streamingsession/sendsession/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 eventsLIVEMedium — event mapping to ProviderRuntimeEvent
Interruptsession/stop, session/cancelBackgroundTask, v4 stop {expectedForegroundExecutionId}DOCSmall
Approvals / permissions UIReverse-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)DOCSmall — direct map to T3 approval cards; mode maps to RuntimeMode
User-input questionsinteraction/requestUserInput reverse-callDOCSmall
Images in / outsession/send {attachments}; v4 attachment/begin|chunk|commit (≤512 KiB); models advertise supportsImages (glm-5v-turbo true); image gen via workspace toolsDOC + model flags LIVESmall–medium
Model picker (dynamic)settings.model.available from session/create / workspace/readState: label, ref, supportsImages, reasoning levels, contextWindowLIVESmall
In-session model switchsession/setModel, session/setThoughtLevel, session/setMode; v4 switchModelConfig (CAS)DOCSmall → sessionModelSwitch: "in-session"
Compactionsession/compactnative (vs Grok's slash-command)DOCTrivial: compaction: {type:"native"}
Resume/continuesession/resume, --resume sess_…, -cLIVE (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/…ARCHZero adapter code
Rollback / rewindv4 applyFileRewind + v4/conversation/fileRewindPreview, session/fork, forkAssistant (CAS-gated)DOCDECIDE 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)DOCMedium (GrokTextGeneration pattern)
Usage / context meterturn.completed.usage (input/output/reasoning/cacheRead/cacheWrite + webFetch/webSearch counts), contextUsageBreakdown, usage/stats, projection contextUsed/contextWindowLIVESmall. DECIDE cost is always $0 (subscription) → show tokens, not dollars; skip UsageProviderKind initially
Subagents / backgroundAgent/SendMessage/TaskOutput/TaskStop tools; session/subagents; subagent.lifecycle events; cancelBackgroundWorkDOC + 1,536 subagent sessions in DBComes through event mapping
Skills~/.zcode/skills/*/SKILL.md (Claude-Code format), skills/referenceCatalogDOCOptional; ClaudeSkills pattern
Slash commands/compact /fork /mcp /mode /model /new /resume /rewind /skill /goal /expertDOCSnapshot advertisement
Feedback (👍/👎)v4 setAssistantFeedbackDOCSmall
Remote / mobile / relayapp-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)LIVEZero — provider-agnostic in T3
Onboarding / authzcode login (OAuth, --no-browser prints URL for remote); detection = credential files existDOCGeneric 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 patternFRESH §4.6Optional later phase

8. Actionable build plan

Phase 0 — spike ✅ (this report)

Protocol verified, transcript captured, architecture mapped, file lists enumerated.

Phase 1 — contracts + skeleton (provider appears in UI)

1. packages/contracts/src/settings.tsZcodeSettings: binaryPath override (optional), enabled default false, extra args passthrough. + zcode: in ServerSettings.providers + patch schema
2. packages/contracts/src/model.tsdisplay name "ZCode", default model builtin:zai/glm-5.1 (or latest from catalog), text-gen default
3. packages/contracts/src/providerRuntime.tsadd "zcode.jsonrpc" to RuntimeEventRawSource
4. apps/server/src/provider/Layers/ZcodeProvider.tsbinary 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)
5. apps/server/src/provider/Drivers/ZcodeDriver.ts + Services/ZcodeAdapter.ts + builtInDrivers.ts registration — adapter initially throws "not implemented"; snapshot already renders "ZCode" everywhere
6. Web: ZcodeIcon, providerDriverMeta.ts, providerIconUtils.ts; mobile: ProviderIcon.tsx + modelOptions.ts label — §6 checklist items

Phase 2 — protocol client + session runtime (core loop)

7. packages/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)
8. apps/server/src/provider/zcode/ZcodeSessionRuntime.tsspawn (node <bundle> app-server --stdio, cwd = project), lifecycle, stderr bounding, protocol logging via EventNdjsonLogger. Model on CodexSessionRuntime.ts
9. Layers/ZcodeAdapter.tsstartSession (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,messages
10. Tests — scripts/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 units

Phase 3 — full feature parity

11. Approvals + user input wiring; RuntimeMode ↔ build/edit/plan/yolo mapping
12. Attachments/images (session/send {attachments} → v4 chunked upload for large files)
13. session/setModel/setMode/setThoughtLevel → in-session switching; dynamic model list into snapshot refreshModels
14. Native compaction (session/compact); resume cursor persistence ({sessionId, eventSeq})
15. textGeneration/ZcodeTextGeneration.ts via one-shot --prompt --output-format json (or workspace/generateText)
16. Usage mapping (tokens incl. cache; context meter from projection + contextUsageBreakdown)

Phase 4 — polish + docs

17. Onboarding entry (login command hint), Settings→Providers card, docs/user/providers-zcode.md (install ZCode → zcode login → select in composer)
18. Feedback (v4 setAssistantFeedback); rollback decision (verify applyFileRewind semantics or declare unsupported)
19. Optional: history importer (read-only db.sqlite → T3 threads), UsageProviderKind extension, welcome-wizard card
Definition of done for v1 (mirrors "select Zcode like any other provider, supporting all T3 features"): new thread → pick ZCode → stream a turn with tool calls and approval prompts in chat → interrupt → resume after T3 restart → checkpoint/diff/restore works → model picker shows live GLM catalog → mobile + remote relay functional (T3-side) → protocol logged and version-gated.

9. Risks, unknowns & security notes

#RiskSeverityMitigation
1Undocumented 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.HIGHVersion 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.
2Terms 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 DECISIONExplicit 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).
3Shared 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.MEDIUMTreat 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.
4Test 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.MEDIUMUse HOME override for sandbox tests; copy-in credentials (copy, never link); assert where sessions landed before running suites.
5Context 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.LOWSurface contextUsageBreakdown + cache stats in T3's context meter so users see it; no adapter fix possible.
6Secrets 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.ACTIONRotate the z.ai key; scrub the artifact; T3 must never read/log these files beyond existence checks.
7Unknowns 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.SPIKEEach has a designated verify step in Phase 2/3; transcript harness at /tmp/zcode-t3-verify/driver.mjs is the template.

10. Evidence log

10.1 Live commands executed (this session)

# 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.

10.2 Primary sources (local)

ArtifactWhat 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.asarout/main + extracted host codeExact 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.mjsThe verification transcript + harness (this session)

10.3 Investigation lanes (8 parallel sub-agents)

LaneKey contribution
1. CLI protocol forensicsLive handshake transcript, method table from bundle constant, auth paths, one-shot stream-json capture, Claude-Code-lineage verdict, isolation env vars
2. Server + app architecture3-tier process tree from asar, V4 protocol surface, why zcode-server.cjs is rejected, model-provider catalog, DB schemas
3. T3 provider architectureDriver/Adapter SPIs, materialization pipeline, four protocol families, ordered server-side file list, test patterns
4. T3 UI/contracts mapOpen ProviderDriverKind, snapshot-driven pickers, exhaustive per-surface checklist (web/mobile/desktop/docs)
5. Prior artifacts digestJuly–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 researchOfficial z.ai docs (endpoints, tiers, ToS), protocol break history, prior art (Paseo #1670, Zed, pi, ExMCP), version currency
7. State/session formatsThree storage generations, db.sqlite schema + message/part JSON shapes, credential map, history-import feasibility: HIGH
8. Adapter comparisonSix-adapter comparison table, reusable protocol layer inventory, Grok commit archaeology (+3,673 ln) vs Antigravity (+21,470), template verdict + per-feature effort

10.4 Key external references

Report generated by Claude Code (GLM-5.3 via T3 Code) · 8 parallel investigation sub-agents + independent live verification · 2026-09-09