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
initialize → model/list → thread/start → turn/start → turn/interrupt → thread/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:
- The canary method. One probe, one claim, one FIND, production untouched until the mouth is proven. Reusable against any host, and the reason this night produced findings instead of a rewrite.
- FIND-1001’s catch, which is not about dispatch at all.
gpt-5.6is aMODELS.jsonhost_model, not a request slug; sending it 400s. That is a standing lesson about reading a catalog before trusting a familiar name, andhouse/MODELS.jsonnow carries it as a note for anyone dispatching Codex. - The lineage seam.
subagent_spawnedcarrieschild_session_id,parent_session_id, andsubagent_idon the parent channel before the child prompt. That is a host fact, true whether or not Corpus ever mints a child execution — and Corpus still does not. - A refuted claim, kept refuted. Headless
-pwas tried twice, including with a private leader socket, and cancelled both times. Anyone reaching for it again should read FIND-1005 first rather than rediscover it. - Two extracted seams.
tools/grok_job.pyandtools/codex_job.pyexist as host-owned modules regardless of what happens to the controller mouth; Claude deliberately stayed intools/capability_bundles.py.
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.