Zcode CLI Runtime

July 17, 2026 ยท View on GitHub

Run Ouroboros workflows and completion-backed authoring through Z.ai's locally installed ZCode coding agent. The runtime supports either the macOS app-bundle zcode.cjs entry script or a zcode executable available on PATH.

The vendor does not currently publish a stable CLI or JSON-output contract. This adapter is pinned to behavior measured from Zcode CLI 0.15.0 and 0.15.2 and to the captured fixtures under tests/fixtures/zcode/. Treat ZCode application upgrades as compatibility events and rerun the Zcode runtime tests after an upgrade.

Prerequisites

RequirementWhy
ZCode desktop app or zcode executableProvides the coding-agent runtime
A configured Z.ai provider/modelZcode reads provider and model selection from its own config
Compatible Node.js for a standalone .cjs pathOfficial app bundles use their bundled Electron/Node runtime; standalone scripts use the system Node
Ouroboros base packageNo provider-specific Python extra is required

Quick start

ouroboros setup --runtime zcode
ouroboros run workflow seed.yaml --runtime zcode

If setup cannot find ZCode automatically, configure one of:

export OUROBOROS_ZCODE_CLI_PATH=/Applications/ZCode.app/Contents/Resources/glm/zcode.cjs
orchestrator:
  runtime_backend: zcode
  zcode_cli_path: /Applications/ZCode.app/Contents/Resources/glm/zcode.cjs

CLI path resolution

The runtime resolves the CLI in this order:

  1. Constructor argument cli_path=...
  2. OUROBOROS_ZCODE_CLI_PATH
  3. orchestrator.zcode_cli_path in ~/.ouroboros/config.yaml
  4. /Applications/ZCode.app/Contents/Resources/glm/zcode.cjs
  5. zcode on PATH

Official app bundles include .node-bundle-meta.json with runtime: electron-node. Ouroboros reads that metadata, launches ZCode.app/Contents/MacOS/ZCode, and sets ELECTRON_RUN_AS_NODE=1, matching ZCode's own launcher. A configured script inside a .app bundle fails closed when the metadata, plist, or bundled executable is missing or invalid; it never falls back to an unrelated system Node. Standalone .cjs, .js, or .mjs paths use the system Node.js. Other paths are treated as executable wrappers or binaries and are invoked directly. NODE_OPTIONS is removed from both Node launch shapes so a project or parent process cannot preload JavaScript into the vendor CLI.

Runtime and LLM backend

Zcode can drive both agentic execution and Ouroboros completion roles. Set the runtime independently with ouroboros setup --runtime zcode, or select Zcode for interview, Seed generation, evaluation, and QA through:

export OUROBOROS_LLM_BACKEND=zcode

Commands that expose an explicit backend flag also accept --llm-backend zcode. The completion adapter uses the same measured --prompt --json summary contract as the runtime, returns the top-level response, and validates requested json_object or json_schema output before accepting a result.

Zcode owns model selection in its configuration and exposes no --model flag. Requested Ouroboros model names are therefore not reported as observed effective identity unless a trusted Zcode output or configuration source confirms them.

Headless contract

For an official macOS app-bundle script, each task uses this command shape:

ELECTRON_RUN_AS_NODE=1 <ZCode.app/Contents/MacOS/ZCode> <zcode.cjs> \
  --json \
  --prompt <PROMPT> \
  --mode <edit|yolo> \
  [--cwd <PATH>] \
  [--resume <SESSION_ID>]

The measured --json behavior is one pretty-printed summary object emitted at the end of the turn, not an NDJSON event stream. The runtime reassembles stdout before parsing it and maps the top-level response to one terminal assistant message. The top-level sessionId becomes the resume handle.

Live compatibility evidence

The official notarized macOS ARM packages were exercised end to end with a local OpenAI-compatible model endpoint, so the vendor process, streaming model adapter, JSON summary, persisted session, and --resume path were all real:

ZCode appCLIResult
3.2.50.15.0Headless JSON response and same-session resume passed
3.3.50.15.2Headless JSON response and same-session resume passed

Both bundles declare runtime: electron-node and include Electron 41 / Node 24. Running their zcode.cjs directly with Node 20 fails on the vendor's node:sqlite import; the app-bundle launcher selection above prevents that environment-dependent failure.

The adapter does not emit --non-interactive, --approval-mode, or --model. Those are not accepted Zcode 0.15.0 or 0.15.2 flags. Model selection remains in Zcode's own configuration, including ~/.zcode/cli/config.json model.main.

Permission mapping

Ouroboros modeZcode --modeBehavior
acceptEditseditDefault non-interactive edit mode
bypassPermissionsyoloExplicit full bypass
defaulteditNormalized to the safe non-interactive default

Buffered-output timeout behavior

Zcode stays silent until its final JSON summary is ready. The inherited 60-second first-output watchdog is therefore disabled by default because it would otherwise become a 60-second total-task limit. Callers that require a no-output deadline can pass startup_output_timeout_seconds explicitly.

This is an operational tradeoff: without an explicit outer deadline, a vendor process that never emits its summary can remain pending. Production callers should retain their workflow-level deadline or configure a suitable startup output timeout for their expected task duration.

Capabilities and limits

CapabilityStatus
Headless executionYes, via --prompt --json
Structured final outputYes, one summary object per turn
Intermediate tool eventsNo, not present in measured stdout
Targeted session resumeYes, via --resume <sessionId>
Per-call model overrideNo, model selection is Zcode-owned
LLM-completion backendYes, via the buffered JSON summary adapter
Setup-owned instruction artifactNo, capability guide rendering is the fallback

Troubleshooting

Zcode is not detected. Set OUROBOROS_ZCODE_CLI_PATH, configure orchestrator.zcode_cli_path, or put a zcode executable on PATH.

A standalone script reports a missing Node built-in such as node:sqlite. Use the intact ZCode app-bundle path so Ouroboros can select the bundled Electron/Node runtime, install a Node version compatible with that Zcode build, or configure a directly executable zcode wrapper.

A model override has no effect. Zcode has no per-invocation --model flag. Select the model in ZCode's own provider/model configuration.

Parsing breaks after a ZCode update. Run tests/unit/orchestrator/test_zcode_cli_runtime.py and recapture the vendor summary fixture before changing the parser contract.