# Zcode CLI Runtime 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 | Requirement | Why | | --- | --- | | ZCode desktop app or `zcode` executable | Provides the coding-agent runtime | | A configured Z.ai provider/model | Zcode reads provider and model selection from its own config | | `~/.zcode/cli/config.json` with an effective `model.main` | The headless CLI exits with `Model config is missing` without it; see [Model provider configuration](#model-provider-configuration) | | Compatible Node.js for a standalone `.cjs` path | Official app bundles use their bundled Electron/Node runtime; standalone scripts use the system Node | | Ouroboros base package | No provider-specific Python extra is required | ## Quick start ```bash ouroboros setup --runtime zcode ouroboros run workflow seed.yaml --runtime zcode ``` Setup warns, and `ouroboros status health` reports a warning, when `~/.zcode/cli/config.json` has no effective `model.main` — the CLI would otherwise fail only after Ouroboros has already spawned it. If setup cannot find ZCode automatically, configure one of: ```bash export OUROBOROS_ZCODE_CLI_PATH=/Applications/ZCode.app/Contents/Resources/glm/zcode.cjs ``` ```yaml 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. ## Model provider configuration Besides the CLI path, the Zcode CLI resolves its model provider from `~/.zcode/cli/config.json` on every run — including the headless `--prompt --json` invocations Ouroboros spawns. Without an effective `model.main` the CLI exits immediately with: ```text Error: Model config is missing. Create /Users//.zcode/cli/config.json with an explicit model provider before running ZCode. ``` `model.main` must be the `"provider-id/model-id"` **string**; an object entry is silently dropped by the CLI's config parser, which then reports the same missing-config error. The referenced provider id needs an entry in the same file's `provider` registry carrying `kind`, `options.baseURL`, and `options.apiKey`: ```json { "model": { "main": "builtin:zai-coding-plan/GLM-5.3" }, "provider": { "builtin:zai-coding-plan": { "name": "Z.ai - Coding Plan", "kind": "anthropic", "options": { "baseURL": "https://api.z.ai/api/anthropic", "apiKey": "", "apiKeyRequired": true }, "enabled": true } } } ``` The desktop app's `~/.zcode/v2/config.json` uses the same `provider` registry shape, so copying the active provider entry from there is the quickest way to satisfy the CLI. OAuth credentials in `~/.zcode/v2/credentials.json` are shared between the desktop app and the CLI, but the model reference itself is not: the desktop app's model selection does not create `~/.zcode/cli/config.json`. `ouroboros setup --runtime zcode` warns when this file has no effective `model.main`, and the `ouroboros status health` rows for the zcode runtime and LLM backend surface the same probe. ## 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: ```bash 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: ```text ELECTRON_RUN_AS_NODE=1 \ --json \ --prompt \ --mode \ [--cwd ] \ [--resume ] ``` 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 app | CLI | Result | | --- | --- | --- | | 3.2.5 | 0.15.0 | Headless JSON response and same-session resume passed | | 3.3.5 | 0.15.2 | Headless 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 mode | Zcode `--mode` | Behavior | | --- | --- | --- | | `acceptEdits` | `edit` | Default non-interactive edit mode | | `bypassPermissions` | `yolo` | Explicit full bypass | | `default` | `edit` | Normalized 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 | Capability | Status | | --- | --- | | Headless execution | Yes, via `--prompt --json` | | Structured final output | Yes, one summary object per turn | | Intermediate tool events | No, not present in measured stdout | | Targeted session resume | Yes, via `--resume ` | | Per-call model override | No, model selection is Zcode-owned | | LLM-completion backend | Yes, via the buffered JSON summary adapter | | Setup-owned instruction artifact | No, 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`. **Headless runs exit with `Model config is missing`.** The Zcode CLI has no `model.main` it can resolve in `~/.zcode/cli/config.json`. See [Model provider configuration](#model-provider-configuration); the two usual causes are a missing file and an object-form `model.main`, which the CLI silently drops — the reference must be the `"provider-id/model-id"` string. **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.