The native-child rail: taking job dispatch off the shell

Disposition: read as live — no act has disposed this arc. FIND-1018 closed an office, not the work, and FIND-1009 is a declared living edge, not a hold. That is a reading from the record, weaker than a Human verdict or a named hold. Delivery: PATCH-086 through PATCH-091 verified; PATCH-092 abandoned as a dead claim over code that had already shipped. Evidence: strong. Fourteen live canaries against a single baseline, preserved byte-for-byte. Last observed: 2026-08-30T01:00:21-04:00. Opened: 2026-08-28. Controlling FIND: FIND-995. Canonical drawing: house/offices/code-troll/NATIVE_CHILD_RAIL_SKETCH.md.

No BANNER exists, so that FIND and that sketch are interim custody and hold weaker standing than a live goal object would. The membership below is declared from them, not inferred.

Read this before you touch tools/grok_job.py, tools/codex_job.py, tools/capability_bundles.py, or tools/grok_corpus_mcp.py. It tells you what was already proven on 2026-08-28, what the probes actually did, and which rows are still short of the story told about them.

The question this answers

Corpus needs a provider host to run a Hands or Eyes job. How does it start one?

Until 2026-08-28 the answer was: build a command line and launch it with Popen. This arc replaced that with each host’s own protocol, and proved every step live before changing production.

But that was the route, not the destination. Per FIND-1027, the want was to see the work: a job dispatched under ops job dispatch runs, and the Human cannot watch the subagent or see what it is doing. Popen was the thing in the way of a window. The transports are means.

Read through the store alone this arc looks nearly finished, with bookkeeping left. Read against what it was for, the missing piece is the point. Do not polish dispatch and call it delivered.

Why it had to change

FIND-993 named the disease: provider hosts were fitted to a Claude-shaped contract. Corpus learned bounded invocation, read-only Eyes, controlled children, and turn and spend rails on Claude, then treated those as the provider contract and required Codex and Grok to fit them. Host-native behaviour was translated into Claude-shaped knobs — a fan-out ceiling became --no-subagents on Grok and --disable multi_agent on Codex; a job budget became --max-turns.

The damage shows at the end of the translation. Turn every native child off, and what the host has left is a shell — Grok’s run_terminal_cmd, Codex’s Bash, usually under Windows cmd. That is not Eyes-after-Hands. The hop exists so Hands do not certify their own work, and the shell was quietly collapsing it.

Two Orchestrator findings the same day showed the cost in the field: FIND-988, where a budget ceiling of 1 became one --max-turns and could not implement anything, and FIND-992, where a Grok Eyes PASS printed to stdout did not fulfil REQ-367. FIND-995 opened the recut; FIND-996 reproduced it against current ground.

What the rows do not say

FIND-1026 preserves the Human’s account of the intake, recorded the following day. It is testimony, not a stored fact, and it supplies two things no row carries.

The origin was not a design goal — it was Popen, and being fed up with it. The Human worked with the Troll holders individually rather than routing a contract, because the work was going well.

And the seam extraction in PATCH-087 was not refactoring hygiene. Pulling Claude earlier into job start had produced one shared command in which each host reached for different variables — “claude uses this variable, grok this variable, codex this variable.” The split existed to end that, and the shape being aimed at was plugins into dispatch: each host owning its own functions and fitting a common job dispatch, rather than three forks of one template. FIND-993 named the disease from the floor. That is the cure it was pointed at.

How they worked: canary-first

The method is the most reusable thing here.

One probe, one claim, one FIND, and production stays untouched until the mouth is proven.

Every canary is a single-purpose Python script that drives one host’s native mouth against a real, pre-created Hands worktree, captures what the host actually returned into a JSON receipt beside it, and proves or refuses exactly one thing. They are not tests and not production code; none sits on an import path. Each FIND body opens by naming what it did not touch.

Every probe ran against the same baseline: parent on shared main at HEAD 79b0e53, disposable Hands worktrees via prepare_worktree under REQ-357. Host-level isolation — isolation=worktree, Grok --worktree — was deliberately not used, so any tree a child landed in had to be the one Corpus made.

What the canaries proved

FIND Probe Result
FIND-997 Grok Eyes root process Root is real; no native children observed. A subagent substring in stdout is not identity — no host-returned child id was bound.
FIND-999 Codex exec -C into an existing worktree Child Git toplevel was the exact Hands worktree, not main. Marker written.
FIND-1000 Codex thread/start into that worktree thread/start echoed the exact worktree cwd. Not a silent parent-cwd fallback.
FIND-1001 Codex model slug gpt-5.6 is a MODELS.json host_model, not a request slug — sending it 400s. Default is gpt-5.6-sol.
FIND-1002 model/list, explicit model and effort, live turn/interrupt Interrupt during fileChange landed on the exact thread and turn; archive after.
FIND-1003 Interrupt during a running commandExecution The turn goes interrupted; the command item itself carries no interrupted status.
FIND-1005 Grok -p --cwd into that worktree Git toplevel exact and session bound — but the write was cancelled. Headless -p is not the mouth that writes.
FIND-1006 Grok ACP full chain session/new cwd exact; write via fs/write_text_file; bind; cancel; close. This is the mouth that writes.
FIND-1007 Grok spawn_subagent lineage The child session id rides the subagent_spawned notification, not the tool-call title. FIND-1006 had missed it by compacting titles.
FIND-1010 Live ACP dispatch --no-subagents must precede agent in argv; Eyes writes over ACP. The first live dispatch failed as TRACE-806 before TRACE-807 succeeded.
FIND-1012 --leader attach The resident host owns the session; the stdio process is a client shim, and closing it must not be read as killing the host.
FIND-1014 Controller mouth Orchestrator invokes dispatch_job in process over MCP rather than through a shell.
FIND-1015 TUI visibility Corpus Hands and Eyes are leader roots; a sitting TUI drops their live stream.

FIND-1004 turned the Codex half into production: current Codex Hands and Eyes launch codex app-server --listen stdio:// and drive initializemodel/listthread/startturn/startturn/interruptthread/archive. Prompt, model, cwd, and sandbox became thread and turn parameters instead of argv. Legacy Codex v3 and v5 still use codex exec.

The code

Cut What it did Where it lives
PATCH-086 Replaced the fan-out meter with a versioned native_children policy tools/capability_bundles.py
PATCH-087 Split the Grok and Codex job seams out; Claude stayed behind tools/grok_job.py, tools/codex_job.py
PATCH-088 Grok Hands and Eyes dispatch runs ACP, not grok -p tools/grok_job.py run_native_job (PATCH-088 wrote it as run_acp_job; PATCH-101 renamed it)
PATCH-089 ACP argv ordering: --no-subagents precedes agent tools/grok_job.py
PATCH-090 Grok v5 omits --no-subagents; v4 retained disabled tools/capability_bundles.py
PATCH-091 Grok ACP attaches --leader; the resident host owns the session tools/grok_job.py
PATCH-092 Named the removal of the shell from production dispatch (controller mouth); abandoned, the cut having already shipped tools/grok_corpus_mcp.py

Landed across commits 80b9720, ce0697c, and ebb3420. The Codex receipt schema is codex-app-server-job/v1 in tools/codex_job.py; the controller mouth exposes corpus_job_start and corpus_job_dispatch in tools/grok_corpus_mcp.py.

PATCH-084 and PATCH-085 precede the arc and are part of why it happened: they unmapped two Claude collisions on Grok — budget is not turns, mode is not reasoning effort — and made the job mouths say so in --help.

The samples

The canaries and their captured receipts are the evidence every finding above was read from. They were written to .work/, which is .gitignore line 32 and has zero tracked members — so fourteen findings cited, by exact path, files that existed in one copy on one machine.

They are now preserved as archive custody, byte-for-byte with a sha256 manifest: archive/native-child-rail-canaries-2026-08-28/ — 27 files, 327,779 bytes, written between 18:12 and 22:45 on 2026-08-28.

Those are now the only copies. The .work/ originals were removed once the archive landed, rather than left to rot beside them: two copies of one body of evidence, with no rule about which is authoritative, is worse than one copy in a named place. Eleven finding bodies are immutable and still cite the old paths, so FIND-1028 records the move with the full manifest, and each of those eleven carries a points-to tie to it. A reader walking back from any of them reaches the sign.

Archive custody is not Library enrollment and not product approval. It means the bytes are recoverable, nothing more.

The nineteen .work/troll-find-*.md drafts were not preserved. Each is byte-identical to its minted FIND body; the store already holds them.

Where the records disagreed, and how it ended

For one day, two records said different things about the last cut.

PATCH-092 / REQ-370 / TRACE-819 FIND-1016, the close account
said named, proposed, still open “shipped this sit (do not redo)”
knew that no one advanced the row that CodeTroll_Cradle saw the mouth work

A row is a record of what someone recorded, and a finding is a record of what someone saw. A holder retiring at 22:45 who does not advance a patch status has not thereby made the code absent, and a close FIND saying “shipped” has not thereby verified anything. Both were testimony from the same night, and this entry was cut while neither settled it.

The owning office settled it the next evening, and not by running the missing verification. On 2026-08-29 at 21:09 CodeTroll_Riser abandoned PATCH-092 (TRACE-845): “the controller-mouth cut shipped at FIND-1014 and is live at HEAD; the claim on tools/corpus_ops.py is dead.” The row was not a pending cut. It was a claim on files the work no longer needed.

So the disagreement resolved toward FIND-1016. What is checkable still is: corpus_job_start and corpus_job_dispatch are in tools/grok_corpus_mcp.py, and TRACE-817 closed ok. The mouth exists and ran. REQ-369 reached fulfilled the same evening on an Eyes PASS that was written in the wrong shape and had to be recovered — FIND-1087 owns that trap, which is about the review ordering, not about this cut.

The seventh row never went through Goblin-writes/Imp-verifies, and now it never will. That is a real difference from the other six, and abandoning the row does not retroactively supply the hop. Read PATCH-092 as settled, not as verified: the code is live and the claim is closed, on the owning office’s judgment rather than on a verification receipt.

Span and movement

Opened by FIND-993 and FIND-995, on precursors FIND-987 through FIND-992. Worked through the night of 2026-08-28 by three Code Troll holders in succession — CodeTroll_Plate for FIND-996 to FIND-1008, CodeTroll_Brake for FIND-1010 to FIND-1013, CodeTroll_Cradle for FIND-1014 to FIND-1016 — each retiring and handing the edge on. Orchestrator_Windlass raised FIND-1009 mid-arc. Closed by Architect_Ampersand in FIND-1018.

Three turns changed the drawing. Without them the results table reads as a straight line, and it was not one.

The mouth was not the one they started with. FIND-1005 took the obvious route — headless grok -p --cwd into the worktree. The tree was right, the session bound, and the write came back cancelled. The probe that looked like a near-miss was the finding: -p is not a writing mouth on this host. That turned the Grok half toward ACP, and FIND-1006 proved it.

A probe’s own instrumentation hid the answer. FIND-1006 ran spawn_subagent and reported the child id as missed — but it had been compacting tool-call titles, and the id was never in a title. FIND-1007 read the host source and found it on the subagent_spawned notification all along. The correction was to the observer, not to the host.

Ownership inverted at the end. Through FIND-1010 the assumption was that Corpus launches the host. FIND-1011 and FIND-1012 turned it around: the resident host owns the session and Corpus attaches with --leader, so the stdio process is a client shim and closing it must not be read as killing the host. FIND-1014 then pushed the same inversion up a level, from worker custody to the controller mouth.

What survives, whatever happens to the rail

Separate from whether the dispatch cut is finished, these outlast it:

What did not survive: the numeric fan-out meter, which PATCH-086 replaced after FIND-996 found it was a switch dressed as a meter.

Next, and when to reopen

The living edge is FIND-1009 / MSG-1664 — the Eyes AMEND hop: same native session, new Hands execution, then independent Eyes. Not session enter, not spawn_subagent, and the thread must not be closed between the two.

TUI attach is not a loose end beside the arc. It is the arc’s purpose, unmet. FIND-1015 names the mechanism: Corpus Hands and Eyes are leader roots, and the sitting TUI drops their live stream. That is exactly the invisibility FIND-1027 says the whole recut was aimed at. Six verified patches moved the transports; the window is still not there.

And that edge did not run out of time — it was stopped. Per FIND-1026, a Troll was modifying grok-build source so the TUI could receive peer and root provider sessions that sat outside the resident TUI’s native parent and session tree. Compiling grok-build bluescreened the Human’s machine, and the arc ended there with the mirror left mid-edit. The Code Troll dead-roads note carries the surviving instruction — “source mid-write, grok.exe not rebuilt. Do not cargo grok-build from a sitting TUI” — without saying why. Anyone picking this edge up should treat the build, not the protocol, as the known hazard.

That also sharpens FIND-980, where editing a vendored mirror was mistaken for changing the host: library/sources/hosts/grok-build was not only being read as evidence, it was being edited and compiled. Whether that is a legitimate use of a tree the ignore rule calls “read-side reference” belongs to the open PROP-242.

Reopen this account when REQ-370 moves, when the AMEND hop lands, or when a host changes a native mouth this arc proved. Do not reopen it for ordinary work on the same files. PATCH-092 and REQ-369 both reached terminal status on 2026-08-29 and are read above, not left open here.

The findings, patches, and traces own these facts. This entry owns only the reading and the route back to them. FIND-1024 records the intake that produced it.