Skip to content

FROM feat/746-firstmate-executor TO development - #748

Merged
ryaneggz merged 31 commits into
developmentfrom
feat/746-firstmate-executor
Aug 13, 2026
Merged

FROM feat/746-firstmate-executor TO development#748
ryaneggz merged 31 commits into
developmentfrom
feat/746-firstmate-executor

Conversation

@ryaneggz

@ryaneggz ryaneggz commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Closes #746.

Status: READY. All 10 stories are implemented, STATUS: COMPLETE is recorded, /audit pr cleared the promotable gate, and /eval has run against the merged base. The branch is caught up to development at b40f3fce and merges clean. The remaining gate is the human merge.

Summary

Adds the opt-in firstmate build 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 unchanged ralph default. Includes an execution-context gate discovered during critique: herdr 0.7.4 panes execute as HOST processes in this deployment (live-verified), so runner_detect proves same-environment execution before ever selecting herdr and otherwise degrades to tmux — the Captain's pre-approved fallback.

Stories

  1. US-001 — .oh/scripts/lib/session-runner.sh — the runner ladder
  2. US-002 — session-prompt template + renderer contract
  3. US-003 — .oh/scripts/firstmate.sh — the executor entrypoint
  4. US-004 — /firstmate SKILL.md + protected-paths registration
  5. US-005 — /ship-spec Stage 10 opt-in arm
  6. US-006 — /autopilot pass-through flag
  7. US-007 — /spec execute names the third executor
  8. US-008 — sandbox-processes.md runner ladder + AGENTS.md + wiki entry
  9. US-009 — two new probes, zero new SKIPPED rows
  10. US-010 — live per-mode smoke (two throwaway slugs) → committed evidence.md

⚠️ PENDING CAPTAIN REVIEW — §2 decision 6 (orchestrator amendment)

The 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):

hostname /.dockerenv worktree resolves
caller (this container) 34263ba23a57 yes yes
herdr probe pane legion-laptop no no

herdr agent start also ignored --cwd <container path> and landed in the host home /home/ryaneggz, a path that does not exist in this container. An explicit --runner herdr in 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=firstmate would 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-laptop and lands in a path this container does not have.

Two consequences for the ruling:

  1. This is a property of the deployment, not of herdr. The code was already the more honest of the two artifacts — session-runner.sh hedged "herdr panes may be HOST processes" while prd.md asserted they are. prd.md now matches the code.
  2. The gate should outlive the fix. It is a build-correctness guard: its job is to prove same-environment execution before a build runs, not to encode one deployment's topology. When the bind is removed the gate begins admitting herdr on its own — that is the verification signal, not a cue to delete it. session-runner.sh carries 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:

Gap Fix
C1 An earlier revision locked decision 5 as "defer herdr; ship tmux → foreground"; the shipped ladder keeps the herdr rung Supersession note on decision 5, explaining why gating the rung and deferring it produce the same runtime behavior here
C2 Decision 6 asserted panes "are" host processes Restated as a deployment property, with the mechanism and the #756 link
C3 Nothing in this PR cited rfc-brain-hands-boundary.md session-runner.sh header 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 on development while 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.md

Both 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.md is the only smoke artifact on it.

  • tmux arm (unconditional, PASSED) — with herdr masked off PATH by a subshell-scoped override so command -v herdr genuinely fails, runner_detect logged herdr is not installed (command -v herdr failed); degrading to tmux. One session walked a 2-story graph to the whole line STATUS: COMPLETE in 2 m 51 s, which the watch loop observed before teardown and lock removal (exit 0). implementation-gates.sh gate1 prints task-graph: 2/2 stories pass.
  • herdr arm — observed gate refusal (no in-environment herdr server), per the table above.
  • Non-destructive: the herdr binary was never moved, renamed, or 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.
  • Session budget: tmux arm ran with FIRSTMATE_TIMEOUT_MS=1800000 (30 min) so a wedged throwaway would self-terminate. The shipped default is unchanged at 14400000 (4 h); every timing above is flagged PROVISIONAL in evidence.md and 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 --print log. progress.txt is 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 in evidence.md as a follow-up.

Critique

  • High-severity findings: 11 across 3 adversarial rounds — all mitigated at AC level (final two re-verified by their finding critics)
  • Medium-severity findings: 12 — folded into ACs or deferred via Non-Goals
  • Recommendation: PROCEED

Reviewer notes

  • Wiki: .oh/skills/wiki/corpus/build-executor-ladder.md is 818 / 900 words (body; frontmatter and H1 excluded). All three PROVISIONAL PENDING US-010 claims were promoted or corrected against what the runs actually showed — grep -c now returns 0.
  • Probe baseline: .oh/evals/RESULTS.md carries 3 pre-existing SKIPPED rows, not the 2 US-009's AC names — autopilot-preflight-gate, debugmcp-availability, and next-dev-prod. All three predate this branch; zero new SKIPPED rows were introduced. (An earlier revision of this line named cc-safety-net-wiring as the third. That was wrong at the time of writing and is wrong now: it reads PASS.)
  • Zero-diff guarantees: .oh/scripts/ralph.sh and .oh/prompts/ are byte-identical; .oh/skills/t3/references/sandbox-processes.md took a pure insertion (zero deleted lines).

Merge state

  1. Drive the task graph to STATUS: COMPLETE — done; all 10 stories pass.

  2. Run /eval — done, and re-run against the merged base: 101 probes, 101 PASS, 0 FAIL, 3 pre-existing SKIPPED. The two new-pass rows (protected-paths-resolve, ste-checker-contract) come from development, not from this branch. This branch's own probes — firstmate-executor-contract and session-runner-ladder — stay PASS across the merge.

  3. /audit pr, then undraft — done; the PR is marked ready.

  4. Caught up to development at b40f3fce. 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 to CONFLICTING. Per /git SKILL.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 keeps development free of the catch-up commit. One conflict: CHANGELOG.md, where both branches appended to the same ### Added list. Resolved by keeping both entries with no text edited, ordered newest-issue-first to match the surrounding rows. Everything else auto-merged.

  5. Second catch-up merge, after FROM feat/733-execution-target-contract TO development #736 landed (c05d0f16). development moved to 6fce6023. 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 by wiki-readme-index); .oh/evals/RESULTS.md (regenerated by /eval against 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 in session-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-executor reports CONFLICT (content) in both .oh/evals/RESULTS.md and CHANGELOG.md. GitHub compares each branch only against development, never against the other, so both can read mergeable while conflicting with each other. Whichever of the two merges second must re-run /eval and regenerate RESULTS.md against the merged base — hand-resolving that file produces a benchmark describing neither branch.

🤖 Generated with Claude Code via /ship-spec

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
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
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
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
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
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
@ryaneggz
ryaneggz marked this pull request as ready for review August 12, 2026 22:02
…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>
@ryaneggz

Copy link
Copy Markdown
Collaborator Author

The gate's premise now has a mechanism — and it changes how to read §2 decision 6

Pushed 1be646a9 (doc/comment only — no code, tests, probes, or ACs; all four
checks green).

What changed. The "⚠️ PENDING CAPTAIN REVIEW" amendment above rested on a
live probe showing herdr panes running as host processes. The probe was right,
but it measured a symptom. The cause is now 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 reframes the amendment in two ways worth having before you rule on it:

  1. This is a property of the deployment, not of herdr. The code was already
    the more honest of the two artifacts — session-runner.sh hedged "herdr panes
    may be HOST processes" while prd.md asserted they are. prd.md now
    matches the code.
  2. The gate should outlive the fix. It is a build-correctness guard: its job
    is to prove same-environment execution before a build runs, not to encode
    one deployment's topology. When the bind is removed the gate should begin
    admitting herdr on its own — that is the verification signal, not a cue to
    delete it. session-runner.sh now carries 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.

Also closed in the same commit — three provenance gaps against the #733
execution-boundary RFC:

Gap Fix
C1 An earlier revision locked decision 5 as "defer herdr; ship tmux → foreground"; the shipped ladder keeps the herdr rung Supersession note on decision 5
C2 Decision 6 asserted panes "are" host processes Restated as a deployment property + mechanism + #756 link
C3 Nothing in this PR cited rfc-brain-hands-boundary.md, which opens with an AUTHORITY CLAUSE session-runner.sh header 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)

Two things for the reviewer, neither touched here:

  • The body still opens with Status: DRAFT. The PR is marked ready and all four
    checks pass; that line is stale. Left for you rather than edited unilaterally.
  • Merge-order hazard with FROM feat/739-slack-compact TO development #740. git merge-tree --write-tree --messages upstream/feat/739-slack-compact upstream/feat/746-firstmate-executor reports
    CONFLICT (content) in both .oh/evals/RESULTS.md and CHANGELOG.md. Both
    PRs read MERGEABLE/CLEAN independently because GitHub only ever compares
    each branch to development, never to the other. Whichever merges second
    must re-run /eval and regenerate RESULTS.md against the merged base

    hand-resolving it produces a benchmark describing neither branch.

@ryaneggz

Copy link
Copy Markdown
Collaborator Author

Correction: the merge state above is stale — this PR now conflicts

development advanced after my previous comment (#751 and #754 merged,
72a8e7e2cdc03e14). Both added ## [Unreleased] entries, so all three
open PRs — #736, #748, #740 — now read CONFLICTING / DIRTY
, each on
CHANGELOG.md and nothing else. My earlier claim that both PRs read
MERGEABLE / CLEAN no longer holds.

All four CI checks still pass at 1be646a9. This is a merge-base problem, not
a build failure, and no code changed.

I did not rebase. Whichever PR merges first re-conflicts the other two, so
resolving all three now is work that gets discarded. Resolve at merge time, in
merge order — the CHANGELOG.md resolution is mechanical (keep both entries).

The RESULTS.md hazard is unchanged and still invisible here. It appears
only in the branch-to-branch comparison, never against development:

git merge-tree --write-tree --messages \
  upstream/feat/739-slack-compact upstream/feat/746-firstmate-executor
CONFLICT (content): Merge conflict in .oh/evals/RESULTS.md
CONFLICT (content): Merge conflict in CHANGELOG.md

So the rule stands: whichever of #748 and #740 merges second must re-run
/eval and regenerate .oh/evals/RESULTS.md against the merged base
rather
than hand-resolving it.

ryaneggz and others added 3 commits August 12, 2026 20:31
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>
@ryaneggz

Copy link
Copy Markdown
Collaborator Author

Resolved — this PR is mergeable again

Superseding my previous comment, which reported this PR as CONFLICTING / DIRTY. It is now MERGEABLE / CLEAN at 72961c17, with all four checks green.

Two commits, neither of which touches a source file:

d50018db — catch-up merge of development. The branch had fallen behind b40f3fce (#751 /ste, #754 remove /caveman, #757 protected-paths repair). Per /git SKILL.md, an open feature branch that falls behind merges the target branch in rather than rebasing — published history intact, no force-push, and the final squash keeps development free of the catch-up commit. One conflict: CHANGELOG.md, where both branches appended to the same ### Added list. Both entries kept, no text edited, ordered newest-issue-first to match the surrounding rows. Everything else auto-merged.

72961c17/eval regenerated against the merged base, which is what I said the second-to-merge PR owes. 101 probes, 101 PASS, 0 FAIL, 3 pre-existing SKIPPED. The two new-pass rows come from development (protected-paths-resolve, ste-checker-contract), not from this branch. 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.

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 Status: DRAFT — /eval and the /audit pr promotable gate are still pending, which had not been true for some time. It now states the real state and carries the mechanism behind decision 6, the three RFC provenance gaps, and the merge state. One factual correction in the reviewer notes: the third SKIPPED probe is next-dev-prod, not cc-safety-net-wiring, which reads PASS.

c374930c — the boundary-RFC citation is a forward reference, and the header now says so. Checking the citation rather than assuming it turned up something worth stating: rfc-brain-hands-boundary.md is not in this tree. development carries .oh/docs/rfcs/ with four files and no brain/hands RFC, and its rfc-runtime-support.md has zero Sysbox mentions — the §8 Sysbox ranking and the §9 DinD "ANSWERED" clause are #736-side too. So the path this header cites resolves only after #736 lands. The header now states the dependency and inlines the quoted clauses, so it reads correctly whichever order the two merge in. Comment-only: git diff -U0 reports zero changed non-comment lines, bash -n is clean, and both firstmate probes still pass.

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.

git merge-tree --write-tree --messages \
  upstream/feat/739-slack-compact upstream/feat/746-firstmate-executor
CONFLICT (content): Merge conflict in .oh/evals/RESULTS.md
CONFLICT (content): Merge conflict in CHANGELOG.md

GitHub compares each branch only against development, never against the other, so both can read mergeable while conflicting with each other. Whichever merges second must re-run /eval and regenerate RESULTS.md against the merged base rather than hand-resolving it.

Both sandbox escapes remain open, by design. Verified from inside the container after all of the above: 12 host-facing mount entries and /var/run/docker.sock still present. #756's remediation is host-side and is deliberately not in this PR.

ryaneggz added a commit that referenced this pull request Aug 13, 2026
…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>
@ryaneggz
ryaneggz merged commit 454d706 into development Aug 13, 2026
4 checks passed
ryaneggz added a commit that referenced this pull request Aug 13, 2026
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>
@ryaneggz
ryaneggz deleted the feat/746-firstmate-executor branch August 13, 2026 05:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant