How the harness drives the CLI

The Claude Code harness turns a chat round into one spawn of the claude CLI. This page is the record of how that spawn is shaped, why it is shaped that way, and what was measured on 2026-09-11 against Claude Code 2.1.269 before the shape was chosen (Systemorph/MeshWeaver.Plugins PR feat/claude-code-stream-json).

Why there is no SDK in between

Until this change the harness drove the CLI through the ClaudeAgentSdk NuGet package — a community C# port; Anthropic ships the Agent SDK for TypeScript and Python only. The port threw on every event type it did not know (rate_limit_event, system/status, …), which took the whole chat down, so the client already carried a workaround that ended the stream on the first unknown event. Two further gaps were structural rather than bugs:

The documented, language-agnostic way to embed Claude Code is the headless protocol:

claude --print --verbose \
       --input-format stream-json --output-format stream-json --include-partial-messages \
       --session-id <uuid> | --resume <uuid> \
       --append-system-prompt "<agent instructions>" \
       --allowedTools mcp__meshweaver \
       --mcp-config '{"mcpServers":{"meshweaver":{"type":"http","url":…,"headers":{"Authorization":"Bearer …"}}}}' \
       --setting-sources user,project

One user message goes in on stdin as {"type":"user","message":{"role":"user","content":[{"type":"text","text":…}]}}; the CLI answers with one JSON object per line and exits when the turn is complete.

Remote Control is not an alternative. claude --remote-control runs a session on the machine that started it, and its only clients are claude.ai/code and the Claude mobile app — there is no protocol a third-party UI can speak, and it needs the full interactive login (a claude setup-token token, which is what the Connect flow stores, is documented as not supporting it). It is a personal workflow — run claude locally with the mesh MCP server configured, drive it from the phone — not a harness.

The events the reader understands

Recorded from the real CLI; the reader (StreamJsonRound) takes exactly these and ignores the rest:

Event What the harness does with it
system / init Session id and model, for the log and the status bar.
stream_event / message_start Remembers the message id.
stream_event / content_block_delta with text_delta The streamed text — yielded token by token, and the message is marked as streamed.
assistant with a text block Yielded only when no deltas were streamed for that message (an older CLI, or a run without partial messages) — otherwise the text would appear twice.
assistant with a tool_use block A FunctionCallContent (id, name, arguments), so the thread renders the tool call.
user with a tool_result block A FunctionResultContent keyed by the call id; is_error becomes the fault.
result The verdict: subtype, is_error, result text, cost, duration, turns.
anything else Nothing. rate_limit_event, system/status, thinking_tokens, thinking deltas, a non-JSON line, an event a newer CLI adds — none may fault the round.

Two verdict shapes measured rather than assumed:

One CLI session per thread

The session id is a name-based UUID (version 5) of the thread path, so it is the same on every portal replica and never collides between threads. Each round the harness spawns the CLI with --resume <id>; the CLI holds the thread's earlier turns, its tool calls and their results itself, and only the new user message is sent. When the resume finds no conversation — the first round of a thread, a thread that switched to this harness mid-way, a session the CLI pruned, a recreated volume — the harness spawns once more with --session-id <id> and hands the CLI the thread's earlier user and assistant turns as a bounded transcript, so nothing starts from zero.

The CLI stores conversations per working directory, under CLAUDE_CONFIG_DIR/projects/<cwd>/. The harness therefore always runs in the same directory (the shared skills workspace when one is configured), and the config dir is the user's own — which is also what keeps two users' sessions apart on one replica.

--append-system-prompt keeps the CLI's own system prompt (its tool and MCP guidance) and appends the agent's instructions; the CLI records the prompt with the session and reuses it on resume until the conversation is compacted.

Permissions and the idle bound

Where it lives and how it is tested

Reconnecting…
The server was updated. Reloading the page to pick up the latest version.