mission: planner codex lane — derivation gate, role-generic driver probe, default flip (m-planner-codex-lane) - #580
Merged
Conversation
…mplate field M-PLANNER-CODEX-LANE milestone 1. Zero runtime effect: nothing invokes derive-planner-lane.sh until M3 edits the mission-control skill. - tools/launchd/derive-planner-lane.sh — Bash 3.2 only, pure text, no network and no codex invocation. Six-step contract in strict order (env-pin, no-doc, Planner-Lane field present/valid, opus-required short-circuit, D2 allowlist cross-check, codex-ok). Exactly one line on stdout, always exit 0; the caller reads the line, not the rc. - 11 fixtures under tools/launchd/testdata/planner-lane/. - design-doc-creator: **Planner-Lane** template field + the one-path-per-bullet, path-first convention that the §0.4 extractor depends on. - m-planner-codex-lane.md: controller ruling encoded — the two ~/absolute sync-copy bullets move out of Files-to-Modify into a new Landing Checklist, and the internal/parser prose in the fixture bullet is de-backticked. The doc now self-qualifies (AC-D0). Acceptance re-run first-party by the controller OUTSIDE the codex sandbox — AC7b, AC8(a,b,c,f,g,h,i,j,k,l,m), AC8d, AC8-env, AC-D0 all as expected, /bin/bash 3.2.57. Known-positive controls (c) and (k) both return codex declared:codex-ok, so the negative sweep is a measurement rather than a claim. PLAN DEFECT FOUND — AC8(i) contradicts binding task 1. Task 1 step 4 specifies "section absent / found more than once -> opus fail-closed:no-files-section", while the AC8 list groups (i) with (f)(g)(h) under path-not-in-codex-allowlist. Both are fail-closed to opus so the SAFETY behaviour is identical; only the reason token differs. Resolved in favour of task 1: §0's corrections are binding, and a duplicate section means the parser cannot know which one to read, which is what no-files-section says. AC8(i)'s expected token is corrected accordingly. Sanity sweep over every planned doc carrying a Files heading: 41 docs, 40 planner-lane-field-missing, 1 codex-ok (this sprint's own doc). Zero hard stops — no doc touching internal/ derives codex. Co-Authored-By: codex <gpt-5.6-sol> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t uncovered Controller mutation probe on M1's derivation script. Four mutations, each PROVEN applied by cmp against the original before its result was read (a mutation that never applied and a mutation that does not discriminate are the same output): MUT1 allowlist arm neutered -> (a)(f)(g) flip to codex-ok KILLED MUT2 duplicate-section guard -ne 1 -> -lt 1 -> (i) flips KILLED MUT3 __UNPARSABLE_PATH_ENTRY__ sentinel neutered SURVIVED MUT4 step-0 env-pin removed -> AC8-env flips to codex-ok KILLED MUT3 survived because no committed fixture reaches that arm. Diagnosed by relabelling the two candidate arms separately: fixture (j)'s first backticked token EXISTS but is not path-shaped, so (j) is caught one arm later by the path-shape check — it never touches the sentinel. The sentinel covers a different input class: a Files bullet with no backticks at all. Both arms are correct and both emit unparsable-path-entry, which is exactly why the gap was invisible — the matrix read green while a whole arm went unexercised. Fixture (n) supplies that input. Re-running MUT3 with (n) in the matrix now yields codex declared:codex-ok from the mutant against opus fail-closed:unparsable-path-entry from the real script, so the mutation is killed. derive-planner-lane.sh is byte-identical to M1 (sha256 026553562c93…) — restored from the executor's .snap/M1/ snapshot after an earlier mutation harness of mine clobbered its own backup. Design doc fixture list corrected eight -> twelve; AC-D0 re-verified after the edit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ing in the driver
M-PLANNER-CODEX-LANE milestone 2, the sprint's point of no return: this edits
tools/launchd/mission-control.sh, the launchd driver that fires the mission loop
every 90 minutes. It has ZERO CI coverage — no shellcheck, no bash -n gate, no
test — so a green CI says nothing about this change and the acceptance criteria
below are the only real gate.
Two edits, nothing else:
- The executor-only codex probe `case` block becomes the design doc's D3
role-generic loop, adopted VERBATIM (Bash 3.2 form: ':'-delimited string sets
instead of `declare -A`, `tr` instead of `${role,,}` — both 4.0-isms crash the
driver's actual /bin/bash 3.2.57). One probe per DISTINCT model, so the
post-flip both-roles-codex default still costs ONE probe, not two.
- A two-line source of ~/.config/ailang/mission-${MISSION_NAME}.env after
MISSION_NAME resolution, so D6's documented one-env-var rollback has a delivery
mechanism for V1 (whose plist sets no MISSION_PROFILE). D6 previously claimed
this existed; it did not.
The planner default on line 286 is deliberately UNTOUCHED — the flip is M4.
VERIFIED BY THE CONTROLLER OUTSIDE THE CODEX SANDBOX (mandatory: the executor
correctly labelled AC1/AC2/AC9 UNINFORMATIVE UNDER SANDBOX, its nested codex
probes failing at app-server init rather than on the model, and ~/.config writes
being denied outright):
(a) /bin/bash -n rc=0
(b) acheck dry-run, zero probes eligible planner=opus rc=0
AC1 planner=codex:gpt-5.6-sol pinned planner=codex:gpt-5.6-sol, no fallback
AC2 planner=codex:no-such-model 400 "'no-such-model' model is not
supported when using Codex with a ChatGPT account" -> "codex planner lane ->
falling back to opus" -> planner=opus. The failure is caused BY --model.
#486 non-regression control: the same probe WITHOUT --model returns rc=0 "ok",
so a model-less probe would still false-green the lane. The guard is
load-bearing, measured rather than asserted.
AC7 /bin/bash 3.2.57 banner + roles line rc=0
AC9 three-sided: file pinning a SENTINEL -> planner=SENTINEL-FROM-FILE; file
deleted -> planner=opus; explicit command-line pin -> planner=opus.
AC9 AS WRITTEN IN THE PLAN IS VACUOUS. It pins `opus` via the env file and expects
planner=opus — but pre-flip `opus` is ALSO the built-in default, so it passes
identically whether the file is sourced or not. Only substituting a sentinel value
turns it into a measurement. Same vacuous-pass class this mission has closed three
times before. The sentinel form is what is recorded above.
Also recorded: an earlier sentinel run read as "the rollback plumbing is broken"
because this controller session's OWN shell exports MISSION_PLANNER_MODEL=opus
(the driver exports it into every tool shell), so the file's ${VAR:-...} correctly
deferred to it. The instrument was contaminated, not the plumbing. Every AC9 side
above is run under `env -u MISSION_PLANNER_MODEL`. M4's post-flip AC1 needs the
same precaution or it will read the session's stale pin as the new default.
The v1 namespace cannot be dry-run while the loop is live — it yields at the
overlap guard on this iteration's own pid. That is exactly why §0.1 mandates the
acheck namespace; the sourced path is namespace-generic.
~/.config/ailang/mission-v1.env created with the rollback line COMMENTED OUT and
proven inert; uncommenting it was exercised once (planner=ROLLBACK-PROVEN) and
reverted.
Co-Authored-By: codex <gpt-5.6-sol>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…codex recipe M-PLANNER-CODEX-LANE milestone 3. Runtime effect: the NEXT controller session of EVERY mission on this rig. - Roles table: Sprint-planner default becomes `codex:gpt-5.6-sol`, with the cell stating that the EFFECTIVE lane is derive-planner-lane.sh's output used verbatim, fail-closed to opus. Supersedes the stale "down-tier A/B = M3; keep Opus until evidence" note, which this sprint is the resolution of. - New Gate-3 step 1b: derivation is MANDATORY and runs before any planner probe or spawn. Output beginning `opus ` routes straight to the opus Agent path with NO codex probe for the planner role, and the reason token is copied verbatim into the Gate-4 evidence row. A missing script fails closed to opus, loudly. - New planner sub-bullet under PROVIDER=codex, PARAMETERIZING the executor recipe rather than forking it — the four deltas only (detached sibling-of-repo worktree from local HEAD; per-iteration directive file; GOCACHE/GOMODCACHE plus the in-sandbox-verdicts-are-not-evidence caveat; six post-run controller steps). Every shared guard is a REFERENCE, so a future guard fix still lands once. WHY THIS IS SAFE FOR THE SIBLING MISSION, stated because it is NOT optional: the edit is live for Ailang World the moment it reaches the main checkout, since ~/.claude/skills/mission-control is a SYMLINK to this repo's copy and World has no repo-local skills directory at all. Two independent guards make that degrade safely: step 0 of the script returns `opus fail-closed:env-pin` whenever MISSION_PLANNER_MODEL is not `codex:*` (World's driver still defaults to opus), and derive-planner-lane.sh does not exist in the World checkout, so the missing-script rule fails closed. Expect fail-closed tokens in World's evidence rows; that is the design working, not a break. CONTROLLER RULINGS APPLIED (the plan's open questions, answered): - Task 5 "sync to ~/.claude" is a NO-OP BY CONSTRUCTION, not a skipped step. The two paths are ONE FILE (readlink + same inode, measured). The plan's `diff -q` criterion is satisfied by construction once the edit reaches the main checkout — and `diff -q` reporting a difference from a WORKTREE is the expected reading, not a failure. - Task 6: .agents/skills/mission-control/SKILL.md stays OUT OF SCOPE, stale by design, tracked by #544. Not synced, and .agents/ is deliberately NOT added to the D2 allowlist. CONTROLLER CORRECTION to the executor's text: it hardcoded an absolute /Users/voightkampff/... planner-worktree path into a skill that three missions share. Replaced with a DERIVED sibling-of-repo path and the reason spelled out. The /tmp prohibition is kept — the plan's own M4 still says /tmp, which predates the iter-133 rule; deviation recorded. A SUSPICION I HAD, REPRODUCED AND REFUTED: post-run step (5), `ailang messages import-github --labels bug,feature,ailang-message`, reads as a non-sequitur in a planner recipe and I took it for an invented step. It is verbatim from the design doc's D4 step (5) (compensating for a possibly-skipped in-sandbox sync, L9/L18) and the subcommand exists in the CLI. The executor was right. Verified first-party outside the sandbox: AC8e fixture (a) derives `opus fail-closed:path-not-in-codex-allowlist`; the planner bullet mentions `exit 64` / `< /dev/null` / `run_in_background` only as references (control: the executor recipe carries 4 `exit 64` hits, so the grep discriminates); the stale note is gone (0 hits, control `Sprint-planner` = 1); no hardcoded absolute path remains in the planner bullet; only SKILL.md changed. Co-Authored-By: codex <gpt-5.6-sol> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…codex, doc landed M-PLANNER-CODEX-LANE milestone 4, the last landing. The sprint-planner default is now `codex:gpt-5.6-sol` (Mark quota-offload #1), so opus stays controller-only. THE FLIP WAS GATED, NOT ASSUMED. The plan permits it only after M1-M3 and AC3a are green. AC3a is a REAL codex planner run — full D4 recipe, detached worktree, sprint id REHEARSAL-ITER136 — and every criterion was re-checked by the controller in the worktree: `jq -e .` rc=0; 4 features; ZERO `MILESTONE_ID` / `auto-parse failed` (control: `sprint_id` = 1 hit, so the grep reads the file); all `estimated_loc` > 0; the plan names the input doc; the `-o` final message is a plan summary, NOT "What would you like me to work on?". The repo's OWN validator then passed it rc=0. The codex planner lane works end to end. Post-flip measurements, all under `env -u MISSION_PLANNER_MODEL`: AC1 post-flip, no env pin planner=codex:gpt-5.6-sol the same command WITHOUT `env -u` planner=opus <- the trap, shown for contrast D6 rollback file uncommented planner=opus <- rollback beats the flip AC5 directive absent rc=64, no codex spawn AC5b directive truncated to 50 B rc=64 ("suspected truncation") AC5 control: real 2951 B directive assertion passes, so 64 is not the only answer AC4 `git worktree list | grep planner-wt-` 0 (control: sprint worktree still listed) PROBE DEDUPE PROVEN WITH A DISCRIMINATING CONTROL, not asserted. Both roles pinned to the SAME bad model: ONE "codex model 'no-such-model' unusable" line and TWO "lane -> falling back" lines, both roles landing on opus. Both roles pinned to DIFFERENT bad models: TWO "unusable" lines. So the counter really does distinguish one probe from two, and the post-flip both-roles-codex default costs ONE probe per fire. WORLD-SYNC DECISION — DECLINED, and the reason is a CHANGED PREMISE, not laziness. The plan recommended syncing this driver into the World checkout "verifiable with zero live risk, World is currently kill-switched". World is armed and ran its iteration 45 the same night. Declining costs World nothing — its planner resolves to opus either way, via two independent guards (the script's step-0 env pin, and the script being absent from the World checkout so the skill's missing-script rule fails closed). Every layer now agrees on opus instead of relying on a pin. The cost is real and recorded: the World driver LAGS this one, and `diff -q` — which was the drift detector — is now tripped by design. Cross-mission message sent (msg_20260804_012921_899f0e6a); surfaced to Mark as a pending ops call. Landing checklist written into the design doc with what was ACTUALLY done, including the engagement-rate reality the doc must not oversell: 41 planned docs carry a Files heading, 40 derive `fail-closed:planner-lane-field-missing`, and exactly ONE declares `**Planner-Lane**` today — this sprint's own. The lane engages on newly authored infra docs only; the success metric matures over weeks, not on day one. Also recorded there: D6's "rollback = one env var" is true ONLY for M4, and only because M2 first built the delivery mechanism D6 already claimed existed. M2 and M3 need code reverts. Three-tier table in the doc. Design doc + sprint plan moved to design_docs/implemented/v1_0_0/ (plans travel with their doc). AC-D0 re-verified after the landing-checklist edit — the doc still self-qualifies `codex declared:codex-ok`. Co-Authored-By: codex <gpt-5.6-sol> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…cture Per .claude/rules/coding-standards.md — every change gets a CHANGELOG entry. Scoped as mission infrastructure and explicitly marked as no user-facing language or CLI change, since tools/launchd/ and .claude/skills/ ship to nobody. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
sunholo-voight-kampff
added a commit
that referenced
this pull request
Aug 3, 2026
…E and the default is flipped All four milestones of m-planner-codex-lane landed (PR #580 -> squash cd76499, 20 checks 0 failures SHA-addressed) and the sprint-planner default is now codex:gpt-5.6-sol, so opus is controller-only. Charter queue row -> LANDED; #498 Lane B becomes the queue head. The three findings worth the record, all from re-running the executor's work first-party outside its sandbox: - A MUTATION SURVIVED. Neutering the __UNPARSABLE_PATH_ENTRY__ sentinel changed nothing, because fixture (j) is caught one arm earlier by the path-shape check and never reaches the sentinel. Both arms are correct and emit the SAME token, which is exactly why a whole arm went unexercised while the matrix read green. Fixture (n) closes it. - THE PLAN'S OWN AC9 IS VACUOUS: it pins `opus` via the rollback file and expects planner=opus, but pre-flip `opus` is also the built-in default, so it passes identically whether the file is sourced or not. Only a sentinel value makes it a measurement. - I CONTAMINATED MY OWN INSTRUMENT: the sentinel first read as "the rollback plumbing is broken"; in fact this controller session exports MISSION_PLANNER_MODEL=opus, so the file's ${VAR:-...} correctly deferred to it. Everything now runs under `env -u`, with the contaminated form recorded beside the clean one because the two look identical. Two of my own suspicions were REFUTED by measurement and are recorded as such: the `ailang messages import-github` post-run step I took for a hallucination is verbatim from the design doc's D4 and the subcommand exists; and awk interval expressions {2,4} do work on this macOS awk, proven with controls in both directions. STATUS rotation scripted with the line-count assertion the skill demands: before=1732 after=1732 expected=1732, exactly one stamp archived, rotation invariant 3, and a known queue row re-grepped after the edit (the iter-127 damage was invisible in the STATUS block itself). git diff --stat read before any git add. FLAGGED, not buried: no evaluator ran this iteration. tools/launchd/ has zero CI coverage by design so the ACs were the only real gate and I ran all of them, but generator!=judge was not exercised. A sonnet judge is owed and is named as the first task of iter-137. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Executes the plan-ready
m-planner-codex-lanesprint (Mark's quota-offload #1,[NEXT #2]).Routes
MISSION_PLANNER_MODELthrough the hardened codex lane so the mission sprint-plannerrides the ChatGPT-subscription bucket and opus stays controller-only.
CI relevance is ZERO for the load-bearing half.
tools/launchd/has no workflow reference,no shellcheck, no
bash -ngate, no test — verified, with a positive control. A green CI heresays nothing about the driver. The acceptance criteria are the real gate, and every one was
re-run by the controller outside the codex sandbox.
Milestones
derive-planner-lane.sh(Bash 3.2 only, pure text, no network, no codex invocation) +12 fixtures + the
**Planner-Lane**design-doc template field. Zero runtime effect.casebecomes the role-genericD3 loop, plus the two-line
mission-<name>.envsource that gives D6's documented rollback anactual delivery mechanism (D6 previously claimed one existed; it did not).
that parameterizes the executor recipe rather than forking it.
then the default flip.
Three things worth reading
__UNPARSABLE_PATH_ENTRY__sentinel changed nothing:fixture (j) is caught one arm earlier by the path-shape check, so a whole arm had zero coverage
while the matrix read green. Fixture (n) closes it and now kills that mutation.
opusvia the rollback file and expectsplanner=opus— but pre-flip
opusis also the built-in default, so it passes identically whether the file issourced or not. Only a sentinel value makes it a measurement.
and TWO fallback lines; two different bad models → TWO "unusable" lines. The counter
discriminates.
Safety
Rollback is one commented line in
~/.config/ailang/mission-<name>.env, exercised end to end.The derivation fails closed to opus on every ambiguity, and today exactly one design doc
in the repo qualifies — the lane engages on newly authored infra docs only.
Sibling mission:
~/.claude/skills/mission-controlis a symlink to this repo's copy andAilang World has no repo-local skills directory, so M3 reaches World at its next fire. It fails
closed to opus there via two independent guards. World's driver was deliberately not synced
(the plan's recommendation assumed World was kill-switched; it is live) — cross-mission message
sent, and it is flagged to Mark as a pending ops call.
🤖 Generated with Claude Code