[FEATURE] Workflow tool: byte-exact data channel between workflow scripts and the host (model-retyped transport corrupts commands and payloads)
Summary
Workflow scripts (the Workflow tool, CLAUDE_CODE_WORKFLOWS=1) have no byte-exact way to move text between the script and the host. The sandbox bans filesystem, process, and clock access for replay determinism, so the only effector a script has is agent(). Every shell command a script wants to run must travel inside a subagent's prompt, and the subagent re-types it into its Bash tool call. Every result travels back as the subagent's final text. A language model therefore sits in the middle of every byte that crosses the script boundary, in both directions, and a model is not a reliable copier of long or quote-bearing strings.
We run a long-lived multi-agent orchestrator as a Workflow script (60+ subagent spawns per run, durable state checkpointed between runs). Over five consecutive runs, five distinct production failures traced to this single restriction. We believe the failure class is structural and worth a first-class fix in the Workflow API.
Observed failure modes (all reproduced in production runs)
- Multi-KB argument mis-escaped into invalid shell. A ~4 KB JSON document was passed as one argv element in the runner subagent's prompt ("pass each element as one argument verbatim"). The Bash-only subagent composed a double-quoted shell string, hand-escaped the inner quotes, and bash failed with exit 2 (
unexpected EOF while looking for matching quote) before the target program ran. The workflow crashed.
- Silent single-token corruption on a write that exited 0. A ~100-entry key list crossed the prompt boundary with exactly one key altered. The command succeeded, the corrupted value persisted into durable state, and a later resume crashed on it. Nothing at write time could detect this without an application-level checksum.
- Mixed escape interpretation across subagent instances. The same prompt rendering (a
JSON.stringify'd shell line) was decoded by one subagent instance (typed plain quotes) and copied verbatim by another (typed the escaped bytes\"literally). The same prompt produced different bytes on the wire depending on which sample of the model handled it.
- Behavioral deviation instead of copy error. A subagent ran the requested command correctly, then ran an additional command of its own and returned that second command's output as the requested command's stdout. A retry re-rolled the identical deviation 3 out of 3 times — retrying an LLM-backed step is not independent sampling when the prompt context is identical.
- Error text loss in the harness itself. When a Workflow script dies on an uncaught exception, the failed-run record serializes it as a bare
Errorwith no message, so the crash cause has to be reconstructed from other evidence. This is the same theme at the harness layer: text crossing a process boundary loses fidelity.
Workarounds we had to build
Each of these works, and each is machinery no application author should have to write:
- A chunked base64 stdin courier (
teewith exact-byte input) for every file write, because inline content cannot survive the prompt hop. - FNV checksums embedded in durable state, verified with bounded re-reads on every read, because reads come back model-typed.
- Redirect-to-file plus a separate
catre-read for command outputs, because inline stdout can be transcribed wrong. - Composition-time guards that reject double quotes, backslashes, and non-ASCII bytes from any composed command line, because some bytes reliably break the copy.
- A rule that any payload over roughly 1 KB must ride a file, never a prompt.
The five failures above each cost a full diagnose / fix / review / relaunch cycle. The checksum, courier, and guard code now make up a meaningful fraction of our orchestrator.
Feature request
A deterministic, byte-exact channel between a Workflow script and the host. Any one of these would eliminate the class; they are listed in order of how completely they solve it:
- A harness-executed
exec(argv, {input})primitive in the Workflow API. The harness spawns the process directly (no model in the path) and journals the result exactly the wayagent()results are journaled today, so resume/replay determinism is fully preserved.
- Sandbox-scoped byte-exact file primitives (
readFile/writeFileunder a per-run directory), journaled the same way.
- Verbatim tool-call binding for a subagent's first action: something like
agent(prompt, {initialToolCall: {name: 'Bash', input: {...}}}), where the harness executes the given tool call with the given bytes and the model only interprets the result. The model adds judgment where judgment is wanted and is removed from the copy path where it is not.
- Artifact passing on
agent(): accept and return file references rather than inline text, so large payloads never enter a prompt.
The sandbox restrictions exist for replay determinism, and we are not asking to weaken them. agent() already proves the pattern: a non-deterministic effect whose result is journaled is replay-safe. A process spawn is far more deterministic than a model call, so the same journaling mechanism covers it.
Environment
- Claude Code 2.1.172
- Windows 11 (host), subagent shell is Git Bash (POSIX)
- Workflow tool enabled via
CLAUDE_CODE_WORKFLOWS=1 - Orchestrator pattern: one Workflow script, Bash-only haiku command-runner subagents, sonnet/opus worker subagents
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗