FROM feat/746-firstmate-executor TO development - #748
Conversation
Four-file contract per SPEC v0.7 §tasks/: - prd.md: 10 user stories - prd.json: schemaVersion 1, branchName feat/746-firstmate-executor - prompt.md: Ralph iteration instructions - progress.txt: empty header - critique.md: 2-critic review (3 rounds, 11 highs mitigated at AC level) Tracks #746. PRD generated by /prd from the Captain-approved plan; reviewed by 2 critics (implementer + user lens) across three adversarial rounds incl. live herdr 0.7.4 verification; converted by /ralph. Submitted-by: Claude
Sourceable session-runner library so any executor launches a long-lived agent session through one herdr -> tmux -> foreground ladder: runner_detect / runner_launch / runner_verify_cwd / runner_alive / runner_teardown, plus resolve_timeout_ms, runner_watch and runner_abort. herdr eligibility is a zeroth nesting guard (HERDR_ENV / HERDR_PANE_ID — the detection path must never itself nest a pane) plus three conjuncts: `command -v herdr`, a health predicate pinned to the two literal `herdr status` fields (`status: running` + `compatible: yes`), and the execution-context gate — a short-lived probe pane emits an environment fingerprint (hostname, /.dockerenv, worktree resolution) which is compared against the caller's own, gathered by the same snippet. Any mismatch makes herdr ineligible, degrades to tmux and logs which field differed; the gate closes its probe pane on both verdicts. An explicit --runner/OH_RUNNER that cannot be honoured is a hard error naming the mismatch, never a silent host-side run. resolve_timeout_ms is the only source of the session budget (default 14400000ms = 4h) and validates FIRSTMATE_TIMEOUT_MS: 0, negative, non-numeric, empty and out-of-range values are rejected with the default applied and the rejection logged, so a non-positive value can never reach `herdr wait output --timeout` nor unbound the poll loops. Every non-success exit runs one path: runner_teardown -> lock removal -> FIRSTMATE-INCOMPLETE. Teardown is `herdr pane close <pane_id>` (0.7.4 has no agent stop/kill verb) using the one pane id runner_launch parses out of the agent_started payload and exposes via runner_pane_id — the same id verify_cwd and the watch use. No `set -euo pipefail` at file scope: the caller owns shell options, and `set -u` is avoided even function-scoped because an unbound expansion under it would terminate the caller's shell. 46 vitest cases in .oh/scripts/__tests__/session-runner.test.ts (not under .oh/skills/, which vitest.config.ts excludes) cover four degrade cases (herdr absent, binary-up/server-down, tmux absent, fingerprint mismatch), the nesting guard proving no probe pane is launched, budget validation, the watch-timeout exit path, and option-neutral sourcing from strict and non-strict callers. shellcheck clean at default and warning severity. Refs #746. Submitted-by: Claude
Flip US-001 to passes: true (commit ad9b5d4) and seed progress.txt with the Codebase Patterns section future iterations need: vitest-not-bats for shell, the STUB_*/PATH fixture-bin pattern, `npx vitest run` instead of `pnpm run test`, running shellcheck via docker over STDIN, and the fact that CI's shellcheck glob does not cover .oh/scripts/lib/. Submitted-by: Claude
Skill-owned prompt template for the firstmate executor at .oh/skills/firstmate/templates/session-prompt.md — a derivative of .oh/prompts/advisor/implement.yml and pr.yml step order that drives one long-lived First-Mate session over the whole prd.json task graph.
The body encodes: load userStories[] ordered by priority into the session's native task list; /compact at every story boundary; the per-story cycle in exact order (implement -> quality checks -> commit with a Submitted-by: trailer -> validate against acceptanceCriteria -> flip passes: true -> append the progress entry); the First Mate certifies and delegates never self-certify; a max-3 bounded AUDIT-FAIL re-brief that marks a story BLOCKED; the dual-channel STATUS: COMPLETE terminal contract; resume semantics after FIRSTMATE-INCOMPLETE; and the runner policy forbidding a nested herdr launch, stated as prompt-level policy rather than a server-enforced guarantee.
Two machine-checkable contracts live in the file's own header comment. PLACEHOLDER CONTRACT: the closed set is exactly <slug>, <branch>, <issue>, with {curly braces} declared as runtime-fill notation the renderer must not substitute — verified, the only angle-bracket tokens in the file are those three; the renderer itself is US-003's. ORDERED ANCHOR LIST: eight anchors recorded verbatim with their pack line provenance, plus the excluded plan-side steps and the additions beyond the pack. Equivalence means only that the anchors appear in the BODY in the same relative order by first occurrence; the ordering scope excludes the header so the US-009 probe cannot pass vacuously on the list itself.
.oh/prompts/ is zero-diff. No SKILL.md created or modified (US-004 owns it).
Submitted-by: Claude
Flip US-002 passes: true (commit 86b1928) and append the iteration entry. Two new Codebase Patterns for later iterations: angle-vs-curly placeholder notation is what makes a closed placeholder set greppable at all (ship-spec's own template mixes render-time and runtime-fill tokens under one notation), and a contract recorded verbatim in a file's header makes an ordering probe vacuous unless the probe skips past the header marker. The entry also hands US-009 the exact 8-anchor list with pack line provenance, records why /audit pr is deliberately excluded from it, and notes that the three tail anchors (evidence.md, /retro, Ready PR) appear only as a handoff sentence because /ship-spec Stages 11-13 stay unmodified per US-005. Submitted-by: Claude
Adds the opt-in firstmate executor entrypoint: slug + four-file validation via the new shared .oh/scripts/lib/task-contract.sh helper (ralph.sh's wording, attributed), the STATUS: COMPLETE sentinel short-circuit, an atomic mkdir /tmp/firstmate-<slug>.lock launch claim with a runner_alive cross-check and stale-lock reclaim, the cross-executor ralph guard, render_session_prompt over the US-002 template, and the --kill escape hatch. Launch, watch and every exit path go through the US-001 session-runner ladder. ralph.sh is zero-diff. Submitted-by: Claude
Submitted-by: Claude
New .oh/skills/firstmate/SKILL.md documents the opt-in third build executor: the herdr -> tmux -> foreground runner ladder (nesting guard + three eligibility conjuncts, and why the execution-context gate refuses herdr in this deployment), the naming contract, the FIRSTMATE_TIMEOUT_MS session budget, the watch matrix (progress.txt is the authority in every mode), the recovery matrix, and the per-mode manual kill procedure — herdr's being 'herdr agent list' then 'herdr pane close <pane_id>', since 0.7.4 has no agent stop/kill verb, with the server never stopped or restarted. It opens with the name-disambiguation note separating 'firstmate the build executor' from 'First Mate the supervisory role charter' (.oh/context/rules/first-mate.md), and states that concurrent firstmate sessions across different slugs are unsupported. protected-paths.txt gains the bare skill name 'firstmate' plus .oh/scripts/firstmate.sh, .oh/scripts/lib/session-runner.sh, and .oh/scripts/lib/task-contract.sh — the last beyond the AC's two named paths, on the same 'load-bearing shared infra' rationale and that file's own same-PR rule. AGENTS.md gains the /firstmate Skills row (AGENTS.md edited only; CLAUDE.md -> AGENTS.md symlink verified intact, diff empty). Zero .pi/ edits needed: Pi, Claude, and Codex all inherit through their existing blanket skills symlinks. Submitted-by: Claude
Submitted-by: Claude
Stage 1 gains the additive '*--executor=firstmate*) SHIP_SPEC_EXECUTOR=firstmate' case arm and 'firstmate' joins the 'case "$SHIP_SPEC_EXECUTOR" in ralph|delegate-advisor)' validation list, so --executor=firstmate resolves instead of failing the invalid-executor guard. The default line SHIP_SPEC_EXECUTOR="\${SHIP_SPEC_EXECUTOR:-ralph}" is byte-identical (grep -Fx verified) and ralph stays the default; the probe-pinned delegate-advisor arm is untouched.
Stage 10 gains an 'Opt-in (firstmate)' subsection carrying the launch + watch path: the step-(2) substitution for the Advisor /goal prompt, the .oh/scripts/firstmate.sh invocations, the per-mode session-handle/log/watch-command table, progress.txt as the authority in every mode, the FIRSTMATE_TIMEOUT_MS wall-clock bound, and the FIRSTMATE-INCOMPLETE -> stays-draft recovery that mirrors RALPH-INCOMPLETE. Mechanics defer to /firstmate; this subsection is the ship-spec seam only. The Stage 10 intro and the frontmatter argument-hint now name the third arm so the flag is discoverable where operators read (ship-spec's own hint is not probe-pinned, unlike autopilot's).
Stages 11-13 byte-identical (136-line tail diffed against HEAD); ralph.sh and .oh/prompts/ zero-diff; CLAUDE.md -> AGENTS.md symlink intact. 14 probes green including autopilot-executor-toggle and ship-spec-ready-finalization; 564/564 vitest across 41 files; typecheck clean.
Submitted-by: Claude
Submitted-by: Claude
Add the firstmate arm to /autopilot as pure deferral: the CLI case arm, the AUTOPILOT_EXECUTOR validation list (the two-part edit — the arm alone is inert while the list rejects firstmate), a §5 subsection mirroring delegate-advisor, and the argument-hint bracket. The bracket is pinned by autopilot-executor-toggle.sh:32, so the probe's pinned literal is updated in this same commit (the one intentional exception to "every existing pinned string intact"), plus a new assertion for the validation list so the two halves can never land apart again. Session budget: the deferral inherits FIRSTMATE_TIMEOUT_MS (default 14400000 = 4h) and sets it nowhere — enforcement stays in US-001's resolve_timeout_ms. Submitted-by: Claude
Submitted-by: Claude
Adds exactly one sentence to .oh/skills/spec/references/execute.md §1 naming `--executor=firstmate` as the third build executor, stating it launches ONE long-lived First-Mate session over the whole task graph and reaches the same `STATUS: COMPLETE` terminal interface, and deferring its mechanics to /ship-spec Stage 10's "Opt-in (`firstmate`)" subsection. Pure insertion — the diff has zero modified/deleted lines, and the §2 AUDIT-FAIL loop-back text is byte-identical. Submitted-by: Claude
Submitted-by: Claude
…entry Submitted-by: Claude
Submitted-by: Claude
Submitted-by: Claude
Add .oh/evals/probes/firstmate-executor-contract.sh and .oh/evals/probes/session-runner-ladder.sh, pinning the additive firstmate executor and the shared herdr -> tmux -> foreground runner ladder. Both probes were verified by REJECTION, not by passing: 19 mutations of the pinned sources were each shown to turn the relevant probe red. That pass found three assertions a comment could satisfy after the code was deleted (the HERDR_ENV nesting guard, the `herdr pane close` teardown verb, and the `| tee` launch pipe), plus a health predicate satisfiable by its own error message; all four now filter to executable lines via a shared code_only helper. /eval reports both probes PASS. RESULTS.md gains four PASS rows and no new SKIPPED rows (the three SKIPPED entries are pre-existing and carried forward). Submitted-by: Claude
Also records two reusable codebase patterns: grep-based probes must exclude comments and log lines (well-documented code otherwise passes after deletion), and probes are verified by rejection in a scratch root, not by passing. Submitted-by: Claude
Ran both runner-mode arms on two throwaway slugs in a disposable worktree on branch smoke/firstmate-746-throwaway (never merged; removed after capture). tmux arm (unconditional, PASSED): with herdr masked off PATH via a subshell-scoped override, runner_detect logged "herdr is not installed" and degraded to tmux; one First-Mate session walked the 2-story graph to the whole line STATUS: COMPLETE in 2m51s, which the watch loop observed before teardown and lock removal. gate1 prints "task-graph: 2/2 stories pass". herdr arm: OBSERVED GATE REFUSAL. Against a healthy 0.7.4 server the probe pane reported host=legion-laptop docker=no worktree=no vs the caller's host=34263ba23a57 docker=yes worktree=yes, and agent start ignored --cwd to land in the host home /home/ryaneggz — so herdr is ineligible here and an explicit --runner herdr is a hard error (exit 3), never a silent host-side run. Also records an observed defect: the session appends the sentinel before its final bookkeeping commit, so the 5s-poll teardown can cut that commit off. Promoted all three PROVISIONAL PENDING US-010 wiki claims against what the runs actually showed (grep count now 0; body 818/900 words). Herdr binary and server verified untouched in a fresh shell after every arm. Submitted-by: Claude
All ten stories now pass; appends the STATUS: COMPLETE terminal marker that ends the ralph loop for this task graph. Submitted-by: Claude
The binding AC (US-006, mirrored in prd.json) extends the argument-hint bracket with the probe literal updated in lockstep; the §6 table row and FR-18 still carried the earlier byte-identical wording. Align both to the built, critic-approved contract and record the deviation from the plan's lockstep-matrix line (the guarded invariant — the ralph default — remains pinned and green). Submitted-by: Claude
99 probes: 96 PASS, 3 SKIPPED, 0 REGRESSION. Both new probes (firstmate-executor-contract, session-runner-ladder) green. vs committed baseline: cc-safety-net-wiring SKIPPED->PASS and next-dev-prod REGRESSION->SKIPPED, both environmental (this branch touches neither probe); no green->red transitions. Submitted-by: Claude
…vocabulary `/audit skills` scores Dimension D against `## Guidelines|## Important Notes| ## Reference|## See Also`. The section was titled `## Related`, which is the same section under a name the deterministic rubric does not recognize, so the new skill scored STALE on a naming mismatch rather than a real gap. Renamed to the sibling convention (`/spec`, `/git`). No content change. Submitted-by: Claude
Captain amendment at the PR gate: Fable is too expensive for this workload. Fable 5 lists at $10/$50 per MTok against Opus 5's $5/$25, so the five Fable rows in the §3 second block (FIRSTMATE, ADVISOR, CRITIQUE, AUDIT, RETRO) now route to Opus at their original effort tiers. Opus 5 supports the full low/medium/high/xhigh/max ladder, so no tier needed re-mapping. Planning artifact only — the Luna/Sol target block is unchanged, and no story, AC, FR, or probe pins this table, so no code or test changes. Submitted-by: Claude
Doc/comment only. PR #748 stays at the human merge gate; no code, test, probe, or acceptance-criterion changes. Three provenance gaps closed against the #733 execution-boundary RFC: 1. session-runner.sh carried no RFC citation, though rfc-brain-hands-boundary.md opens with an AUTHORITY CLAUSE. The header now cites section 2 (the iteration loop is BRAIN -- "it invokes hands; it is not hands", so this ladder reaches a session HOST and must not grow into an execution target) and section 5 (hostRoot === targetRoot is the only legal Phase-0 mapping; the gate REFUSES a mismatched runner instead of translating paths across the seam). 2. The herdr note said panes "may be HOST processes ... over a mounted socket". The mechanism is now confirmed: the operator's config directory is bind-mounted read-write into the container, so the container's herdr CLI reads the HOST operator's herdr config and drives the HOST's herdr server. Recorded with a migration trigger -- when #756 closes, the gate should begin admitting herdr on its own, which is the verification signal and not a reason to delete the gate. 3. prd.md section 2 decision 6 asserted "herdr panes ARE host processes" as a property of herdr. It is now stated as a property of this deployment, and the gate is re-characterised as a build-correctness guard that outlives #756. Decision 5 gains a supersession note: an earlier revision locked "defer herdr; ship tmux -> foreground", and the shipped ladder keeps the herdr rung, so the reversal is recorded rather than silently dropped. Verified: 41 test files / 564 tests pass; bash -n clean; the .sh diff contains no non-comment lines. Submitted-by: Claude Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The gate's premise now has a mechanism — and it changes how to read §2 decision 6Pushed What changed. The " That reframes the amendment in two ways worth having before you rule on it:
The bind is one of two host-root escape paths out of the sandbox (the other is Also closed in the same commit — three provenance gaps against the #733
Two things for the reviewer, neither touched here:
|
Correction: the merge state above is stale — this PR now conflicts
All four CI checks still pass at I did not rebase. Whichever PR merges first re-conflicts the other two, so The So the rule stands: whichever of #748 and #740 merges second must re-run |
Catch-up merge so #748 stops reading CONFLICTING at the gate. `development` advanced from 72a8e7e to b40f3fc while this branch was open (#751 /ste, #754 remove /caveman, #757 protected-paths repair). Per /git SKILL.md line 170, an open feature branch that falls behind merges the target branch in rather than rebasing: it preserves published history, avoids force-push churn, and the final squash keeps `development` free of this catch-up commit. One conflict, resolved by keeping both entries: CHANGELOG.md — both branches appended to the same `### Added` list. #750's /ste entry and this branch's #746 firstmate entry are independent additions, so both survive; they are ordered newest-issue-first (750, 746) to match the descending order of the surrounding entries. No entry text was edited. Everything else auto-merged (.claude/protected-paths.txt, AGENTS.md). No source file in this branch changed as part of the merge, and `.oh/docs/rfcs/` stays zero-diff against the merge base. Submitted-by: Claude Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`/eval` re-run after the catch-up merge, per the rule this branch's own PR comment states: the benchmark must describe the base it will land on, and hand-resolving it produces a scoreboard describing neither branch. 101 probes ran, 101 PASS, 0 FAIL, 0 SKIPPED. Two rows are new-pass and come from `development`, not from this branch: `protected-paths-resolve` (#757) and `ste-checker-contract` (#751). This branch's own two probes — `firstmate-executor-contract` and `session-runner-ladder` — stay PASS across the merge, so nothing `development` added disturbs the firstmate contract. Submitted-by: Claude Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The header cites `.oh/docs/rfcs/rfc-brain-hands-boundary.md`, which is not in this tree. That file arrives with #733 (PR #736), open alongside this branch. Checked, not assumed: `development` carries `.oh/docs/rfcs/` with four files and no brain/hands RFC, and its `rfc-runtime-support.md` has zero Sysbox mentions — the Sysbox ranking and the DinD "ANSWERED" clause are #736-side too. So the citation resolves only after #736 merges. Rather than drop it or let a reader hit a dangling path, the header now states the dependency and the merge order that fixes it. The quoted clauses stay, so the header reads correctly on its own in either order. Comment-only: `git diff -U0` reports zero changed lines that are not comments, and `bash -n` is clean. `session-runner-ladder` and `firstmate-executor-contract` both still pass, so `.oh/evals/RESULTS.md` is left as regenerated at 72961c1 — no probe status moved and rewriting it would be timestamp churn. Submitted-by: Claude Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Resolved — this PR is mergeable againSuperseding my previous comment, which reported this PR as Two commits, neither of which touches a source file:
The full local gate was run before pushing: 41 test files / 564 tests pass, typecheck and build clean, pnpm pin has no drift. The PR body was stale and is corrected. It still opened with
This gives merge order a hard reason, not just a convention: #736 → #748 → #740. The merge-order hazard with #740 is unchanged and still invisible at this gate. GitHub compares each branch only against Both sandbox escapes remain open, by design. Verified from inside the container after all of the above: 12 host-facing mount entries and |
…arget contract (#736) Closes #733. First slice of EPIC #731; the EPIC stays open. Ratifies the Phase-0 brain/hands boundary in a durable RFC — `.oh/docs/rfcs/rfc-brain-hands-boundary.md`, plus a factual amendment to `rfc-runtime-support.md` — and lands the provider-neutral `ExecutionTarget` contract at `.oh/cli/src/lib/execution/target.ts` with a `DockerComposeExecutionTarget` adapter that delegates to the existing `.oh/scripts/docker-compose.sh` machinery. `oh sandbox` and `oh shell` route through the contract; `attach()` stays synchronous in contractVersion 1 so the `lifecycle.test.ts` assertions pass unchanged. Merged first of two by design: #748's `session-runner.sh` header cites `rfc-brain-hands-boundary.md`, so landing this PR first makes that citation resolve rather than dangle. Submitted-by: Claude Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…eference `development` moved to 6fce602 when #736 merged, which is what this branch was waiting for: `.oh/docs/rfcs/rfc-brain-hands-boundary.md` is now in the tree, so the `session-runner.sh` citation resolves instead of dangling. The forward-reference paragraph that named the dependency is replaced by a plain statement of when the RFC landed; the quoted clauses stay, so the header still reads without opening the RFC. Comment-only: `git diff -U0` reports zero changed lines that are not comments, and `bash -n` is clean. Three conflicts, each resolved on its own terms rather than by picking a side: CHANGELOG.md — both branches appended to `### Added`. Both entries kept, no text edited, ordered newest-issue-first (750, 746, 733) to match the surrounding rows. .oh/skills/wiki/corpus/README.md — both branches added an index row. All rows kept in the file's Updated-descending order. `wiki-readme-index` confirms the index matches the git-tracked corpus frontmatter. .oh/evals/RESULTS.md — regenerated by `/eval` against the merged base rather than hand-resolved, which is the rule this PR states for a benchmark that two branches both rewrite. 102 probes, 99 PASS, 3 pre-existing SKIPPED, 0 FAIL and 0 REGRESSION. The 102nd probe arrives with #736. Local gate before pushing: 42 test files / 585 tests pass, typecheck clean, and `session-runner-ladder`, `firstmate-executor-contract`, and `wiki-readme-index` all pass individually. Submitted-by: Claude Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes #761. Refs #746, #748, #756. The firstmate execution-context gate could never admit herdr, in any environment. herdr destroys a pane the instant its command returns and answers `pane_not_found` on a read against a destroyed pane, so a probe that printed one fingerprint line and exited lost the race with its own reader. The gate reported "no probe fingerprint obtained" and degraded to tmux — correct-looking output for the wrong reason, and a permanent refusal wearing the shape of a proof obligation. A keep-alive suffix now rides the pane invocation only. It is never folded into `RUNNER_PROBE_SCRIPT`, because that snippet also runs in-process for the caller side, and "the same snippet runs in the probe pane and locally" is what makes the comparison true by construction rather than by convention. The budget derives from the single `RUNNER_PROBE_TIMEOUT_MS` source and is an upper bound rather than a cost: the gate closes the pane as soon as the read completes, on either verdict. Two reasons the defect survived the original build, both now closed: * The live path failed earlier and correctly. While the operator config bind (#756) was open, the probe pane was a HOST shell that stayed alive long enough to read, and the gate refused herdr on a genuine fingerprint mismatch. The race is only reachable once the environment matches, so closing #756 is what exposed it. * The unit-test stub was more forgiving than herdr — it replayed pane output regardless of pane lifetime, which is why 46 tests passed against a gate that could not work live. The stub now models real pane lifetime, so removing the keep-alive fails five tests, three of which predate this change. `session-runner-ladder.sh` gains five assertions covering the composition, the derived budget, and a keep-alive leaking into the shared snippet. Each was verified by rejection against a deliberately broken copy rather than only by passing against the good one. The gate is unchanged in intent and stays: it is a build-correctness guard whose job is to prove environment identity rather than assume it. The header's MIGRATION TRIGGER is discharged and replaced with the observed result — its prediction that the gate would stop rejecting herdr by itself was wrong, and the note records why: a gate failing for one reason can hide a second reason it would fail anyway. 588 tests pass; 102 probes run with 99 PASS and 3 pre-existing SKIPPED. Submitted-by: Claude Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes #746.
Status: READY. All 10 stories are implemented,
STATUS: COMPLETEis recorded,/audit prcleared the promotable gate, and/evalhas run against the merged base. The branch is caught up todevelopmentatb40f3fceand merges clean. The remaining gate is the human merge.Summary
Adds the opt-in
firstmatebuild executor: a single long-lived First-Mate agent session that holds the prd.json task graph natively, launched through a new herdr → tmux → foreground session-runner ladder, alongside the unchangedralphdefault. Includes an execution-context gate discovered during critique: herdr 0.7.4 panes execute as HOST processes in this deployment (live-verified), sorunner_detectproves same-environment execution before ever selecting herdr and otherwise degrades to tmux — the Captain's pre-approved fallback.Stories
.oh/scripts/lib/session-runner.sh— the runner ladder.oh/scripts/firstmate.sh— the executor entrypoint/firstmateSKILL.md + protected-paths registration/ship-specStage 10 opt-in arm/autopilotpass-through flag/spec executenames the third executorThe execution-context gate is a scope change, surfaced here rather than absorbed silently. It adds a third conjunct to herdr eligibility: same-environment execution must be proven by a probe-pane fingerprint, not assumed.
Observed consequence in this deployment: herdr mode is REFUSED, so the ladder always resolves to tmux. US-010's live run measured it against a healthy 0.7.4 server (
status: running,compatible: yes):/.dockerenv34263ba23a57legion-laptopherdr agent startalso ignored--cwd <container path>and landed in the host home/home/ryaneggz, a path that does not exist in this container. An explicit--runner herdrin that state is a hard error (exit 3), never a silent host-side run.The top rung of the shipped ladder is therefore unreachable here until a herdr server runs inside the container. The gate is what keeps AGENTS.md's "all building and testing happens INSIDE the sandbox" true — without it
--executor=firstmatewould have quietly run builds on the host. This is the Captain's call, not this PR's.The premise now has a mechanism (added 2026-08-12, commit
1be646a9)The probe above was right, but it measured a symptom. The cause is identified: the operator's config directory is bind-mounted read-write into this container, so the container's herdr CLI reads the host operator's herdr config — socket path and server address included — and drives the host's herdr server. That is why the probe pane reports
legion-laptopand lands in a path this container does not have.Two consequences for the ruling:
session-runner.shhedged "herdr panes may be HOST processes" whileprd.mdasserted they are.prd.mdnow matches the code.session-runner.shcarries this as an explicit migration trigger.The bind is one of two host-root escape paths out of the sandbox; the other is the mounted
/run/docker.sock. Both are filed as #756 under EPIC #731. Remediation is entirely host-side and is not part of this PR.The same commit closes three provenance gaps against the #733 execution-boundary RFC, which opens with an AUTHORITY CLAUSE this PR previously never cited:
rfc-brain-hands-boundary.mdsession-runner.shheader now cites §2 (the loop is brain — "it invokes hands; it is not hands") and §5 (hostRoot === targetRoot; the gate refuses a mismatched runner rather than translating paths across the seam).oh/docs/rfcs/is zero-diff on this branch — the RFC is cited, never restated.The citation was a forward reference; it now resolves.
rfc-brain-hands-boundary.md, the §8 Sysbox ranking, and the §9 DinD "ANSWERED" clause all arrive with #733 — none of them existed ondevelopmentwhile that PR was open. #736 merged first, deliberately, so this branch's citation points at a file that is in the tree. The clauses stay quoted in the header so it reads without opening the RFC.Live smoke evidence (US-010)
📄
.oh/tasks/firstmate-executor/evidence.mdBoth arms ran on two throwaway slugs in a disposable worktree on branch
smoke/firstmate-746-throwaway— never merged, removed after capture. The PR branch carries no smoke-run story commits;evidence.mdis the only smoke artifact on it.PATHby a subshell-scoped override socommand -v herdrgenuinely fails,runner_detectloggedherdr is not installed (command -v herdr failed); degrading to tmux. One session walked a 2-story graph to the whole lineSTATUS: COMPLETEin 2 m 51 s, which the watch loop observed before teardown and lock removal (exit 0).implementation-gates.sh gate1printstask-graph: 2/2 stories pass.chmod'd and the server was never stopped or restarted. Verified in a fresh shell after every arm —command -v herdr→/usr/local/bin/herdr,status: running,compatible: yes.FIRSTMATE_TIMEOUT_MS=1800000(30 min) so a wedged throwaway would self-terminate. The shipped default is unchanged at14400000(4 h); every timing above is flagged PROVISIONAL inevidence.mdand is explicitly not a basis for lowering it.Observed defect (recorded, not blocking)
The session appends the terminal sentinel before its final bookkeeping commit, so the 5-second-poll teardown cut that commit off and truncated the
--printlog.progress.txtis the authority and was correct, so the terminal contract holds — but a real build wants the commit before the marker, or a short drain window after the match. Detailed inevidence.mdas a follow-up.Critique
Reviewer notes
.oh/skills/wiki/corpus/build-executor-ladder.mdis 818 / 900 words (body; frontmatter and H1 excluded). All threePROVISIONAL PENDING US-010claims were promoted or corrected against what the runs actually showed —grep -cnow returns 0..oh/evals/RESULTS.mdcarries 3 pre-existing SKIPPED rows, not the 2 US-009's AC names —autopilot-preflight-gate,debugmcp-availability, andnext-dev-prod. All three predate this branch; zero new SKIPPED rows were introduced. (An earlier revision of this line namedcc-safety-net-wiringas the third. That was wrong at the time of writing and is wrong now: it reads PASS.).oh/scripts/ralph.shand.oh/prompts/are byte-identical;.oh/skills/t3/references/sandbox-processes.mdtook a pure insertion (zero deleted lines).Merge state
Drive the task graph to— done; all 10 stories pass.STATUS: COMPLETERun— done, and re-run against the merged base: 101 probes, 101 PASS, 0 FAIL, 3 pre-existing SKIPPED. The two new-pass rows (/evalprotected-paths-resolve,ste-checker-contract) come fromdevelopment, not from this branch. This branch's own probes —firstmate-executor-contractandsession-runner-ladder— stay PASS across the merge.— done; the PR is marked ready./audit pr, then undraftCaught up to
developmentatb40f3fce. FROM skill/750-ste-controlled-language TO development #751 (/ste), FROM task/752-remove-caveman TO development #754 (remove/caveman), and FROM fix/753-protected-paths-resolve TO development #757 (protected-paths repair) landed while this branch was open, which flipped it toCONFLICTING. Per/gitSKILL.md, an open feature branch that falls behind merges the target branch in rather than rebasing — no force-push, published history intact, and the final squash keepsdevelopmentfree of the catch-up commit. One conflict:CHANGELOG.md, where both branches appended to the same### Addedlist. Resolved by keeping both entries with no text edited, ordered newest-issue-first to match the surrounding rows. Everything else auto-merged.Second catch-up merge, after FROM feat/733-execution-target-contract TO development #736 landed (
c05d0f16).developmentmoved to6fce6023. Three conflicts, each resolved on its own terms:CHANGELOG.md(both entries kept, newest-issue-first);.oh/skills/wiki/corpus/README.md(all index rows kept in Updated-descending order, confirmed bywiki-readme-index);.oh/evals/RESULTS.md(regenerated by/evalagainst the merged base rather than hand-resolved — 102 probes, 99 PASS, 3 pre-existing SKIPPED, 0 FAIL). The same commit replaces the forward-reference note with a plain statement of when the RFC landed. Comment-only insession-runner.sh; 42 test files / 585 tests pass.No automated step is left. The remaining gate is the human merge.
Merge order: #736 (merged) → this PR → #740. #736 went first because it carries the RFC this branch's header cites. #740 stays parked; see the hazard below.
Merge-order hazard with #740, unchanged and still invisible here.
git merge-tree --write-tree --messages upstream/feat/739-slack-compact upstream/feat/746-firstmate-executorreportsCONFLICT (content)in both.oh/evals/RESULTS.mdandCHANGELOG.md. GitHub compares each branch only againstdevelopment, never against the other, so both can read mergeable while conflicting with each other. Whichever of the two merges second must re-run/evaland regenerateRESULTS.mdagainst the merged base — hand-resolving that file produces a benchmark describing neither branch.🤖 Generated with Claude Code via /ship-spec