From d3e0a5b3c911ef9a8a377767ecc490570ef2f1c6 Mon Sep 17 00:00:00 2001 From: "Voight-Kampff (bot)" <151556158+sunholo-voight-kampff@users.noreply.github.com> Date: Tue, 4 Aug 2026 01:12:26 +0200 Subject: [PATCH 1/6] =?UTF-8?q?feat(mission):=20M1=20=E2=80=94=20derive-pl?= =?UTF-8?q?anner-lane.sh=20+=20fixtures=20+=20design-doc=20template=20fiel?= =?UTF-8?q?d?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Co-Authored-By: Claude Opus 4.8 --- .claude/skills/design-doc-creator/SKILL.md | 2 +- .../resources/design_doc_structure.md | 3 + .../planned/v1_0_0/m-planner-codex-lane.md | 11 ++- tools/launchd/derive-planner-lane.sh | 98 +++++++++++++++++++ .../planner-lane/a-unlisted-language-path.md | 9 ++ .../testdata/planner-lane/b-field-missing.md | 9 ++ .../testdata/planner-lane/c-clean-infra.md | 10 ++ .../planner-lane/f-future-internal-path.md | 9 ++ .../planner-lane/g-mixed-infra-language.md | 10 ++ .../planner-lane/h-malformed-files-section.md | 9 ++ .../i-duplicate-files-sections.md | 13 +++ .../planner-lane/j-prose-first-bullet.md | 9 ++ .../planner-lane/k-alternate-heading.md | 9 ++ .../testdata/planner-lane/l-field-invalid.md | 9 ++ .../testdata/planner-lane/m-opus-required.md | 9 ++ 15 files changed, 214 insertions(+), 5 deletions(-) create mode 100755 tools/launchd/derive-planner-lane.sh create mode 100644 tools/launchd/testdata/planner-lane/a-unlisted-language-path.md create mode 100644 tools/launchd/testdata/planner-lane/b-field-missing.md create mode 100644 tools/launchd/testdata/planner-lane/c-clean-infra.md create mode 100644 tools/launchd/testdata/planner-lane/f-future-internal-path.md create mode 100644 tools/launchd/testdata/planner-lane/g-mixed-infra-language.md create mode 100644 tools/launchd/testdata/planner-lane/h-malformed-files-section.md create mode 100644 tools/launchd/testdata/planner-lane/i-duplicate-files-sections.md create mode 100644 tools/launchd/testdata/planner-lane/j-prose-first-bullet.md create mode 100644 tools/launchd/testdata/planner-lane/k-alternate-heading.md create mode 100644 tools/launchd/testdata/planner-lane/l-field-invalid.md create mode 100644 tools/launchd/testdata/planner-lane/m-opus-required.md diff --git a/.claude/skills/design-doc-creator/SKILL.md b/.claude/skills/design-doc-creator/SKILL.md index 3e5952dba..b27028279 100644 --- a/.claude/skills/design-doc-creator/SKILL.md +++ b/.claude/skills/design-doc-creator/SKILL.md @@ -441,7 +441,7 @@ The script creates a comprehensive template. Fill in: - Overview: High-level approach - Architecture: Technical design - Implementation plan: Break into phases with tasks -- Files to modify: List new/changed files with LOC estimates +- Files to modify: List new/changed files with LOC estimates. Use one repo-relative path per top-level bullet and make it the first backticked token; continuation lines and later backticks are prose. **Examples:** - Show before/after code or workflows diff --git a/.claude/skills/design-doc-creator/resources/design_doc_structure.md b/.claude/skills/design-doc-creator/resources/design_doc_structure.md index 8c41e3fdb..350ee3c18 100644 --- a/.claude/skills/design-doc-creator/resources/design_doc_structure.md +++ b/.claude/skills/design-doc-creator/resources/design_doc_structure.md @@ -14,6 +14,7 @@ Complete reference for AILANG design documents. **Priority**: P0 (High) | P1 (Medium) | P2 (Low) **Estimated**: 3 days **Dependencies**: None | Feature X, Feature Y +**Planner-Lane**: codex-ok | opus-required ``` **Purpose**: Quick metadata for understanding scope and priority. @@ -297,6 +298,8 @@ Create a central builtin registry where all builtins are registered once with: ```markdown ### Files to Modify/Create +Use one repo-relative path per top-level bullet, with that path as the bullet's first backticked token; put explanatory prose and any later backticks after it. + **New files:** - `internal/builtins/spec.go` (~300 LOC) - BuiltinSpec struct and registry - `internal/builtins/validator.go` (~150 LOC) - Validation logic diff --git a/design_docs/planned/v1_0_0/m-planner-codex-lane.md b/design_docs/planned/v1_0_0/m-planner-codex-lane.md index b92d8c789..84a2e9abf 100644 --- a/design_docs/planned/v1_0_0/m-planner-codex-lane.md +++ b/design_docs/planned/v1_0_0/m-planner-codex-lane.md @@ -368,8 +368,6 @@ of forking them, so a future guard fix lands once. **Modified:** - `tools/launchd/mission-control.sh` — role-generic probe loop (Bash 3.2 form) + gated default flip (~35 LOC net) - `.claude/skills/mission-control/SKILL.md` — roles-table cell + derivation step + planner sub-bullet (~55 lines) -- `~/.claude/skills/mission-control/SKILL.md` — sync copy (L15) -- `~/dev/sunholo-data/ailang-world/tools/launchd/mission-control.sh` — sync copy (L15; see D6) - `.claude/skills/design-doc-creator/SKILL.md` — one-line template addition: the `**Planner-Lane**: codex-ok | opus-required` header field (D2 field supply) @@ -378,13 +376,18 @@ of forking them, so a future guard fix lands once. pure text — contains no codex invocation, provable by AC-D2's grep) - `tools/launchd/testdata/planner-lane/` — eight tiny fixture docs for AC-D2 (rev 3, per gpt5-6-sol's R2 fixture list): (a) unlisted language path (declares `codex-ok`, - Files-to-Modify touches `internal/parser/…` — the lying-declaration case), (b) field-missing, - (c) clean infra `codex-ok`, (f) unknown FUTURE `internal/...` path (a dir that does not exist + Files-to-Modify touches internal/parser/… — the lying-declaration case), (b) field-missing, + (c) clean infra `codex-ok`, (f) unknown FUTURE internal/... path (a dir that does not exist today), (g) mixed infra + language paths, (h) malformed Files-to-Modify section, (i) duplicate Files-to-Modify sections **No Go code. No AILANG code.** +### Landing Checklist + +- Sync `.claude/skills/mission-control/SKILL.md` through its `~/.claude/skills/mission-control/SKILL.md` symlink (a no-op when the verified symlink remains intact). +- Deploy `tools/launchd/mission-control.sh` to `~/dev/sunholo-data/ailang-world/tools/launchd/mission-control.sh` when the World rollout is authorized. + --- ## Acceptance Criteria (D5 — non-vacuous by construction) diff --git a/tools/launchd/derive-planner-lane.sh b/tools/launchd/derive-planner-lane.sh new file mode 100755 index 000000000..0e4d87b4e --- /dev/null +++ b/tools/launchd/derive-planner-lane.sh @@ -0,0 +1,98 @@ +#!/bin/bash +# Derive the mission planner lane from a design document. Pure text only. + +emit() { + printf '%s\n' "$1" + exit 0 +} + +# Step 0: an explicit non-codex planner pin always wins. +case "${MISSION_PLANNER_MODEL:-}" in + codex:*) ;; + *) emit "opus fail-closed:env-pin" ;; +esac + +# Step 1: require one readable document argument. +doc=${1:-} +if [ -z "$doc" ] || [ ! -r "$doc" ]; then + printf '%s\n' "derive-planner-lane: design document is missing or unreadable" >&2 + emit "opus fail-closed:no-doc" +fi + +# Step 2: read and validate the declaration. +planner_line=$(grep -m1 -E '^\*\*Planner-Lane\*\*:' "$doc" 2>/dev/null) +if [ -z "$planner_line" ]; then + emit "opus fail-closed:planner-lane-field-missing" +fi +planner_value=${planner_line#\*\*Planner-Lane\*\*:} +planner_value=$(printf '%s\n' "$planner_value" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') +case "$planner_value" in + codex-ok|opus-required) ;; + *) emit "opus fail-closed:planner-lane-field-invalid" ;; +esac + +# Step 3: an opus declaration needs no path analysis. +if [ "$planner_value" = "opus-required" ]; then + emit "opus declared:opus-required" +fi + +# Step 4: locate exactly one Files section and extract the first backticked +# token from every top-level bullet in it. Continuations and later tokens are +# prose. The awk matcher is the portable ERE equivalent of Files\b. +section_count=$(awk ' + BEGIN { count = 0 } + { + line = tolower($0) + if (line ~ /^#{2,4}[[:space:]]+files([^[:alnum:]_]|$)/) count++ + } + END { print count } +' "$doc") +if [ "$section_count" -ne 1 ]; then + emit "opus fail-closed:no-files-section" +fi + +paths=$(awk ' + BEGIN { in_files = 0 } + { + line = tolower($0) + if (!in_files && line ~ /^#{2,4}[[:space:]]+files([^[:alnum:]_]|$)/) { + in_files = 1 + next + } + if (in_files && ($0 ~ /^#{1,4}[[:space:]]/ || $0 ~ /^---$/)) exit + if (in_files && $0 ~ /^- /) { + if (match($0, /`[^`]+`/)) print substr($0, RSTART + 1, RLENGTH - 2) + else print "__UNPARSABLE_PATH_ENTRY__" + } + } +' "$doc") + +if [ -z "$paths" ]; then + printf '%s\n' "derive-planner-lane: Files section has no path bullets" >&2 + emit "opus fail-closed:unparsable-path-entry" +fi + +old_ifs=$IFS +IFS=' +' +for path in $paths; do + if [ -z "$path" ] || [ "$path" = "__UNPARSABLE_PATH_ENTRY__" ]; then + IFS=$old_ifs + emit "opus fail-closed:unparsable-path-entry" + fi + case "$path" in + */*|*.md|*.sh|*.go|*.yml) ;; + *) IFS=$old_ifs; emit "opus fail-closed:unparsable-path-entry" ;; + esac + case "$path" in + /*|~*|*..*) IFS=$old_ifs; emit "opus fail-closed:path-not-in-codex-allowlist" ;; + esac + case "$path" in + tools/launchd/*|.claude/skills/mission-control/SKILL.md|.claude/skills/design-doc-creator/*) ;; + *) IFS=$old_ifs; emit "opus fail-closed:path-not-in-codex-allowlist" ;; + esac +done +IFS=$old_ifs + +# Step 5: every declared path is approved infrastructure. +emit "codex declared:codex-ok" diff --git a/tools/launchd/testdata/planner-lane/a-unlisted-language-path.md b/tools/launchd/testdata/planner-lane/a-unlisted-language-path.md new file mode 100644 index 000000000..ce1dc7df1 --- /dev/null +++ b/tools/launchd/testdata/planner-lane/a-unlisted-language-path.md @@ -0,0 +1,9 @@ +# Unlisted Language Path + +**Planner-Lane**: codex-ok + +### Files to Modify/Create + +- `internal/parser/parser.go` — language implementation + +--- diff --git a/tools/launchd/testdata/planner-lane/b-field-missing.md b/tools/launchd/testdata/planner-lane/b-field-missing.md new file mode 100644 index 000000000..f4c8bca74 --- /dev/null +++ b/tools/launchd/testdata/planner-lane/b-field-missing.md @@ -0,0 +1,9 @@ +# Missing Field + +**Status**: Planned + +### Files to Modify/Create + +- `tools/launchd/example.sh` — infrastructure + +--- diff --git a/tools/launchd/testdata/planner-lane/c-clean-infra.md b/tools/launchd/testdata/planner-lane/c-clean-infra.md new file mode 100644 index 000000000..1fe04670e --- /dev/null +++ b/tools/launchd/testdata/planner-lane/c-clean-infra.md @@ -0,0 +1,10 @@ +# Clean Infrastructure + +**Planner-Lane**: codex-ok + +### Files to Modify/Create + +- `tools/launchd/example.sh` — infrastructure +- `.claude/skills/design-doc-creator/SKILL.md` — documentation + +--- diff --git a/tools/launchd/testdata/planner-lane/f-future-internal-path.md b/tools/launchd/testdata/planner-lane/f-future-internal-path.md new file mode 100644 index 000000000..72465ebcc --- /dev/null +++ b/tools/launchd/testdata/planner-lane/f-future-internal-path.md @@ -0,0 +1,9 @@ +# Future Internal Path + +**Planner-Lane**: codex-ok + +### Files to Modify/Create + +- `internal/future/component.go` — not present today + +--- diff --git a/tools/launchd/testdata/planner-lane/g-mixed-infra-language.md b/tools/launchd/testdata/planner-lane/g-mixed-infra-language.md new file mode 100644 index 000000000..259252fcd --- /dev/null +++ b/tools/launchd/testdata/planner-lane/g-mixed-infra-language.md @@ -0,0 +1,10 @@ +# Mixed Paths + +**Planner-Lane**: codex-ok + +### Files to Modify/Create + +- `tools/launchd/example.sh` — infrastructure +- `internal/parser/parser.go` — language implementation + +--- diff --git a/tools/launchd/testdata/planner-lane/h-malformed-files-section.md b/tools/launchd/testdata/planner-lane/h-malformed-files-section.md new file mode 100644 index 000000000..4fbf041b8 --- /dev/null +++ b/tools/launchd/testdata/planner-lane/h-malformed-files-section.md @@ -0,0 +1,9 @@ +# Malformed Files Section + +**Planner-Lane**: codex-ok + +### Files to Modify/Create + +- `/tmp/generated.sh` — absolute paths are forbidden + +--- diff --git a/tools/launchd/testdata/planner-lane/i-duplicate-files-sections.md b/tools/launchd/testdata/planner-lane/i-duplicate-files-sections.md new file mode 100644 index 000000000..ea5f6ff04 --- /dev/null +++ b/tools/launchd/testdata/planner-lane/i-duplicate-files-sections.md @@ -0,0 +1,13 @@ +# Duplicate Files Sections + +**Planner-Lane**: codex-ok + +### Files to Modify/Create + +- `tools/launchd/one.sh` — first section + +### Files to Create + +- `tools/launchd/two.sh` — second section + +--- diff --git a/tools/launchd/testdata/planner-lane/j-prose-first-bullet.md b/tools/launchd/testdata/planner-lane/j-prose-first-bullet.md new file mode 100644 index 000000000..927ed77cd --- /dev/null +++ b/tools/launchd/testdata/planner-lane/j-prose-first-bullet.md @@ -0,0 +1,9 @@ +# Prose First Bullet + +**Planner-Lane**: codex-ok + +### Files to Modify/Create + +- `codex-ok` is prose; `tools/launchd/example.sh` follows later + +--- diff --git a/tools/launchd/testdata/planner-lane/k-alternate-heading.md b/tools/launchd/testdata/planner-lane/k-alternate-heading.md new file mode 100644 index 000000000..f76fd586e --- /dev/null +++ b/tools/launchd/testdata/planner-lane/k-alternate-heading.md @@ -0,0 +1,9 @@ +# Alternate Heading + +**Planner-Lane**: codex-ok + +## Files to Modify + +- `tools/launchd/example.sh` — infrastructure + +--- diff --git a/tools/launchd/testdata/planner-lane/l-field-invalid.md b/tools/launchd/testdata/planner-lane/l-field-invalid.md new file mode 100644 index 000000000..714a1fefc --- /dev/null +++ b/tools/launchd/testdata/planner-lane/l-field-invalid.md @@ -0,0 +1,9 @@ +# Invalid Field + +**Planner-Lane**: maybe + +### Files to Modify/Create + +- `tools/launchd/example.sh` — infrastructure + +--- diff --git a/tools/launchd/testdata/planner-lane/m-opus-required.md b/tools/launchd/testdata/planner-lane/m-opus-required.md new file mode 100644 index 000000000..bf4d222bc --- /dev/null +++ b/tools/launchd/testdata/planner-lane/m-opus-required.md @@ -0,0 +1,9 @@ +# Opus Required + +**Planner-Lane**: opus-required + +### Files to Modify/Create + +- `internal/parser/parser.go` — declaration short-circuits path checks + +--- From 909e3b832d542f0a3022dfca473a85f9d3c16349 Mon Sep 17 00:00:00 2001 From: "Voight-Kampff (bot)" <151556158+sunholo-voight-kampff@users.noreply.github.com> Date: Tue, 4 Aug 2026 01:12:46 +0200 Subject: [PATCH 2/6] test(mission): fixture (n) closes the no-backtick sentinel gap M1 left uncovered MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- design_docs/planned/v1_0_0/m-planner-codex-lane.md | 6 ++++-- .../testdata/planner-lane/n-no-backtick-bullet.md | 12 ++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 tools/launchd/testdata/planner-lane/n-no-backtick-bullet.md diff --git a/design_docs/planned/v1_0_0/m-planner-codex-lane.md b/design_docs/planned/v1_0_0/m-planner-codex-lane.md index 84a2e9abf..d01791cdb 100644 --- a/design_docs/planned/v1_0_0/m-planner-codex-lane.md +++ b/design_docs/planned/v1_0_0/m-planner-codex-lane.md @@ -374,12 +374,14 @@ of forking them, so a future guard fix lands once. **New:** - `tools/launchd/derive-planner-lane.sh` — D2 stage-2 derivation (~60 LOC, Bash 3.2-compatible, pure text — contains no codex invocation, provable by AC-D2's grep) -- `tools/launchd/testdata/planner-lane/` — eight tiny fixture docs for AC-D2 (rev 3, per +- `tools/launchd/testdata/planner-lane/` — twelve tiny fixture docs for AC-D2 (rev 3, per gpt5-6-sol's R2 fixture list): (a) unlisted language path (declares `codex-ok`, Files-to-Modify touches internal/parser/… — the lying-declaration case), (b) field-missing, (c) clean infra `codex-ok`, (f) unknown FUTURE internal/... path (a dir that does not exist today), (g) mixed infra + language paths, (h) malformed Files-to-Modify section, (i) duplicate - Files-to-Modify sections + Files-to-Modify sections, (j) prose-first bullet, (k) alternate `## Files to Modify` + heading, (l) invalid Planner-Lane value, (m) `opus-required`, (n) a Files bullet carrying no + backticked token at all — the sentinel arm (j) does not reach **No Go code. No AILANG code.** diff --git a/tools/launchd/testdata/planner-lane/n-no-backtick-bullet.md b/tools/launchd/testdata/planner-lane/n-no-backtick-bullet.md new file mode 100644 index 000000000..0d81da1c0 --- /dev/null +++ b/tools/launchd/testdata/planner-lane/n-no-backtick-bullet.md @@ -0,0 +1,12 @@ +# Fixture (n) — a Files bullet carrying NO backticked token at all + +Covers the `__UNPARSABLE_PATH_ENTRY__` sentinel arm of `derive-planner-lane.sh`, which fixture +(j) does NOT reach: (j)'s first backticked token exists but is not path-shaped, so (j) is caught +one arm later by the path-shape check. Without this fixture, neutering the sentinel is a +mutation that survives the whole matrix. + +**Planner-Lane**: codex-ok + +### Files to Modify/Create + +- tools/launchd/derive-planner-lane.sh — path written as bare prose, no backticks From f724a7210d17a572a18813178012a799a5f9f9e3 Mon Sep 17 00:00:00 2001 From: "Voight-Kampff (bot)" <151556158+sunholo-voight-kampff@users.noreply.github.com> Date: Tue, 4 Aug 2026 01:19:52 +0200 Subject: [PATCH 3/6] =?UTF-8?q?feat(mission):=20M2=20=E2=80=94=20role-gene?= =?UTF-8?q?ric=20codex=20probe=20loop=20+=20D6=20rollback=20plumbing=20in?= =?UTF-8?q?=20the=20driver?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Co-Authored-By: Claude Opus 4.8 --- tools/launchd/mission-control.sh | 54 ++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/tools/launchd/mission-control.sh b/tools/launchd/mission-control.sh index 2e553bd3e..d445aebe1 100755 --- a/tools/launchd/mission-control.sh +++ b/tools/launchd/mission-control.sh @@ -50,6 +50,12 @@ MISSION_NAME="${MISSION_NAME:-v1}" MISSION_REPO="${MISSION_REPO:-sunholo-data/ailang}" MISSION_DOC="${MISSION_DOC:-design_docs/v1-mission.md}" export MISSION_NAME MISSION_REPO MISSION_DOC +# D6 rollback pin: always sourced for the RESOLVED mission name, so the documented +# `MISSION_PLANNER_MODEL=opus` rollback works for V1 (whose plist sets no MISSION_PROFILE). +# Convention: entries use ${VAR:-value} so a command-line env pin still wins (the AC fixtures +# depend on that). Double-source for World is idempotent. +[ -f "$HOME/.config/ailang/mission-${MISSION_NAME}.env" ] \ + && . "$HOME/.config/ailang/mission-${MISSION_NAME}.env" STATE_DIR="$HOME/.ailang/state" if [ "$MISSION_NAME" = "v1" ]; then # LEGACY paths — bit-for-bit compat with the live V1 loop (no migration). @@ -279,8 +285,11 @@ export MISSION_DESIGNER_MODEL="${MISSION_DESIGNER_MODEL:-claude:claude-fable-5}" export MISSION_METERED_BUDGET_USD="${MISSION_METERED_BUDGET_USD:-5}" export MISSION_PLANNER_MODEL="${MISSION_PLANNER_MODEL:-opus}" export MISSION_EXECUTOR_MODEL="${MISSION_EXECUTOR_MODEL:-codex:gpt-5.6-sol}" -# Codex-lane pre-flight (2026-07-27): subscription quota is invisible until it errors, so probe -# once per fire. Any probe failure → fall back to opus THIS fire only (logged, never wedged). +# Codex-lane pre-flight, ROLE-GENERIC (m-planner-codex-lane): probe once per DISTINCT +# codex model, fall back per-role on ANY non-zero rc (#486: probe MUST carry --model; +# an unusable pin is exactly as fatal as spent quota). Export AFTER fallback so the +# EXPORTED env — what the routing-evidence row reports — stays honest. +# BASH 3.2 (L19): ':'-delimited string sets, NOT associative arrays; no ${var,,}. # # The probe MUST carry --model (#486, 2026-07-27): without it codex exercises its DEFAULT model, # so a pinned-but-unreachable model false-greens the lane. Live evidence that day: codex-cli @@ -292,20 +301,33 @@ export MISSION_EXECUTOR_MODEL="${MISSION_EXECUTOR_MODEL:-codex:gpt-5.6-sol}" # fatal to the lane as a spent quota, and the old quota-only gate is what let #486 through. The # skill's Gate-3 recipe re-probes and would fall back anyway; doing it here keeps the EXPORTED # env honest, which is what the routing-evidence row reports. -case "$MISSION_EXECUTOR_MODEL" in codex:*) - cx_model="${MISSION_EXECUTOR_MODEL#codex:}" - _mc_bounded "$PROBE_TIMEOUT" codex exec --skip-git-repo-check --model "$cx_model" 'reply with exactly: ok' - cx_rc=$?; cx_out="$MC_BOUNDED_OUT" - if [ $cx_rc -ne 0 ]; then - if [ $cx_rc -eq 124 ]; then cx_why="probe timed out after ${PROBE_TIMEOUT}s" - elif printf '%s' "$cx_out" | grep -qiE "$QUOTA_SIG"; then cx_why="quota-limited" - else cx_why="probe failed (rc=$cx_rc)"; fi - log "codex executor lane $cx_why for model '$cx_model' -> falling back to opus for this fire" - log "codex probe output: $(printf '%s' "$cx_out" | tail -3 | tr '\n' ' ')" - MISSION_EXECUTOR_MODEL="opus"; export MISSION_EXECUTOR_MODEL - fi - ;; -esac +_cx_probed=":" # models probed this fire (dedupe: planner+executor share the default model) +_cx_failed=":" # models whose probe failed +for role in PLANNER EXECUTOR; do + var="MISSION_${role}_MODEL"; val="${!var}" + case "$val" in codex:*) + cx_model="${val#codex:}" + case "$_cx_probed" in *":${cx_model}:"*) : ;; *) # not yet probed + _cx_probed="${_cx_probed}${cx_model}:" + _mc_bounded "$PROBE_TIMEOUT" codex exec --skip-git-repo-check --model "$cx_model" 'reply with exactly: ok' + cx_rc=$?; cx_out="$MC_BOUNDED_OUT" + if [ "$cx_rc" -ne 0 ]; then + _cx_failed="${_cx_failed}${cx_model}:" + # why-classification happens ONCE, at probe time (timeout / quota-sig / other) + if [ "$cx_rc" -eq 124 ]; then cx_why="probe timed out after ${PROBE_TIMEOUT}s" + elif printf '%s' "$cx_out" | grep -qiE "$QUOTA_SIG"; then cx_why="quota-limited" + else cx_why="probe failed (rc=$cx_rc)"; fi + log "codex model '$cx_model' unusable: $cx_why" + log "codex probe output: $(printf '%s' "$cx_out" | tail -3 | tr '\n' ' ')" + fi + ;; esac + case "$_cx_failed" in *":${cx_model}:"*) + role_lc=$(printf '%s' "$role" | tr 'A-Z' 'a-z') # ${role,,} is bash-4.0-only (L21) + log "codex ${role_lc} lane -> falling back to opus for this fire (model '$cx_model')" + printf -v "$var" 'opus'; export "$var" + ;; esac + ;; esac +done # evaluator default = sonnet (2026-07-16, Mark directive on #399: "default can be gemini (if able # to git clone the codebase etc)? otherwise sonnet-5"). gemini managed_agents is NOT viable as the # evaluator today — VERIFIED iteration 38: (1) architecturally the request body carries only From 580f2df0e57b7750b6771a7b3031ef2c4bd3f36a Mon Sep 17 00:00:00 2001 From: "Voight-Kampff (bot)" <151556158+sunholo-voight-kampff@users.noreply.github.com> Date: Tue, 4 Aug 2026 01:24:13 +0200 Subject: [PATCH 4/6] =?UTF-8?q?feat(mission):=20M3=20=E2=80=94=20mandatory?= =?UTF-8?q?=20planner-lane=20derivation=20step=20+=20planner=20codex=20rec?= =?UTF-8?q?ipe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Co-Authored-By: Claude Opus 4.8 --- .claude/skills/mission-control/SKILL.md | 41 ++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/.claude/skills/mission-control/SKILL.md b/.claude/skills/mission-control/SKILL.md index e1443633d..694f56f55 100644 --- a/.claude/skills/mission-control/SKILL.md +++ b/.claude/skills/mission-control/SKILL.md @@ -564,7 +564,7 @@ model from the driver-exported env (defaults track the charter table): |---|---|---| | Controller (this session: triage/pick/record/retro) | `$MODEL` (session) | **Opus** (opus-first since 2026-07-16, Mark: the long orchestration session is mechanical work — it must NOT ride Fable) | | Design-doc-creator | **ROTATION** (Mark 2026-07-17; `$MISSION_DESIGNER_MODEL` is the rotation SEED, not a fixed pin) | Rotate per new-doc iteration: `claude:claude-fable-5` → `codex:gpt-5.6-sol` → (gemini after G4) → repeat. State: `~/.ailang/state/mission-designer-rotation` holds the LAST-USED value; pick the next list entry (missing file = start at claude), write back after the designer run. Every design passes the quorum regardless of author — record `(designer, quorum outcome)` in the evidence row. A probe-failed designer falls to the NEXT in rotation (not to `$MODEL`), FLAGGED | -| Sprint-planner | `$MISSION_PLANNER_MODEL` | Opus (down-tier A/B = M3; keep Opus until evidence) | +| Sprint-planner | `$MISSION_PLANNER_MODEL` | `codex:gpt-5.6-sol` configured default; effective lane = `derive-planner-lane.sh` output, used VERBATIM; fail-closed to opus | | Sprint-executor | `$MISSION_EXECUTOR_MODEL` | Opus | | Sprint-evaluator | `$MISSION_EVALUATOR_MODEL` | **Sonnet** (default changed fable→sonnet 2026-07-16 iter 38, Mark directive #399: "default … gemini (if able to git clone the codebase etc)? otherwise sonnet-5"; gemini-managed_agents VERIFIED not-viable-today — server-side sandbox sees no worktree + backend timed out; sonnet ≠ opus executor → generator≠judge, and it's Agent-tool-PINNABLE unlike fable) | @@ -586,6 +586,17 @@ session is NOT Fable, a fable pin is unenforceable — apply the generator≠jud silently inherit. `provider:model` values (e.g. `codex:gpt-5.6-sol`) instead signal cross-provider routing via `provider_executor` (fleet Phase C), not the Agent tool. +**Step 1b — derive the effective planner lane (MANDATORY; before ANY planner probe or spawn).** +Run `tools/launchd/derive-planner-lane.sh ` with the driver-exported +environment intact. Its output is exactly one line, ` `; use that line +VERBATIM. If it begins `opus `, spawn the opus Agent path directly and do **not** perform a codex +probe or spawn for the planner role; copy the reason token VERBATIM into the Gate-4 +routing-evidence row. Only `codex declared:codex-ok` enters the codex planner recipe below. If the +script is missing on disk, fail closed to opus **LOUDLY** and record the missing-script reason in +the same evidence row. This rule is mission-independent and live wherever this shared skill is +resolved: the step-0 environment pin protects missions configured for opus, and the missing-script +rule protects missions whose checkout has no derivation script (including Ailang World). + **Cross-provider spawn recipe (`provider:model`, M1b — currently `codex` only).** When a role's env value matches `^([a-z_]+):(.+)$`, DO NOT use the Agent tool. Split it (`PROVIDER=${VAL%%:*}`, `MODEL=${VAL#*:}`) and route: @@ -691,6 +702,34 @@ value matches `^([a-z_]+):(.+)$`, DO NOT use the Agent tool. Split it (`PROVIDER (d) proves the reconstruction faithful by sha256-manifesting the executor's final tree BEFORE starting and `shasum -c` after the last commit — byte-identity or the reconstruction is wrong. Two milestones that touch the SAME file are exactly why snapshots beat file-lists here. + 2a. **Planner role — parameterize this executor recipe; do not fork it.** Apply every shared + probe, bounded-background-run, directive-delivery, stdin, sandbox, output-capture, hygiene, + timeout, and fallback guard above by reference (including the executor recipe's `exit 64`, + `< /dev/null`, and `run_in_background` guards). There are exactly four planner deltas: + - **Working directory:** first assert + `git status --porcelain -- ` is empty. From local `HEAD`, create an ephemeral + detached worktree with `git worktree add --detach`, then pass its path with `-C`. The path + MUST be a SIBLING OF THIS MISSION'S REPO — DERIVE it, never hardcode it + (`"$(cd "$REPO/.." && pwd)/.planner-wt-iter"`): this skill is shared by every mission on + the rig, so an absolute path baked in for one of them is wrong for the others. Worktrees + under `/tmp` are forbidden — CWD-relative path tests then fail for the LOCATION rather than + the code, and CI never reproduces that red. + Never use `-b` or base it on `origin/dev`: a committed-but-unpushed design doc must be + visible to the planner. + - **Directive:** use the per-iteration file + `/tmp/codex_planner_directive_iter.txt`, carrying the executor recipe's identical + ≥200-byte delivery assertion and closed-stdin behavior on both probe and run by reference. + - **Sandbox directories and evidence:** keep `--add-dir "$GOCACHE" --add-dir "$GOMODCACHE"`. + **In-sandbox gate verdicts are NOT evidence**: socket-touching checks + are `UNINFORMATIVE UNDER SANDBOX`, and the controller re-verifies load-bearing premises + outside the sandbox before handing the plan to the executor. + - **Post-run controller steps:** (1) assert both artifacts exist in the worktree and are + well-formed (`jq -e . sprint_.json`; plan non-empty and names the design doc); (2) reject + placeholder vacuous-passes (`MILESTONE_ID` or `auto-parse failed`); (3) copy both artifacts + to their main-checkout paths, refusing to overwrite unexpected existing files; (4) remove + the planner worktree; (5) run + `ailang messages import-github --labels bug,feature,ailang-message` outside the sandbox; + (6) commit with `Co-Authored-By: codex `. 3. **generator≠judge guard (HARD, constraint #3):** before spawning the evaluator, assert the evaluator's PROVIDER ≠ the executor's PROVIDER. If the executor ran on codex, the evaluator MUST NOT be a codex `provider:model` — if `$MISSION_EVALUATOR_MODEL` collides, re-route the evaluator From caf2cda864895fa6baee49be83e956907180eeda Mon Sep 17 00:00:00 2001 From: "Voight-Kampff (bot)" <151556158+sunholo-voight-kampff@users.noreply.github.com> Date: Tue, 4 Aug 2026 01:29:45 +0200 Subject: [PATCH 5/6] =?UTF-8?q?feat(mission):=20M4=20=E2=80=94=20AC3a=20re?= =?UTF-8?q?hearsal=20green,=20planner=20default=20FLIPPED=20to=20codex,=20?= =?UTF-8?q?doc=20landed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Co-Authored-By: Claude Opus 4.8 --- .../m-planner-codex-lane-sprint-plan.md | 0 .../v1_0_0/m-planner-codex-lane.md | 41 ++++++++++++++++++- tools/launchd/mission-control.sh | 9 +++- 3 files changed, 47 insertions(+), 3 deletions(-) rename design_docs/{planned => implemented}/v1_0_0/m-planner-codex-lane-sprint-plan.md (100%) rename design_docs/{planned => implemented}/v1_0_0/m-planner-codex-lane.md (94%) diff --git a/design_docs/planned/v1_0_0/m-planner-codex-lane-sprint-plan.md b/design_docs/implemented/v1_0_0/m-planner-codex-lane-sprint-plan.md similarity index 100% rename from design_docs/planned/v1_0_0/m-planner-codex-lane-sprint-plan.md rename to design_docs/implemented/v1_0_0/m-planner-codex-lane-sprint-plan.md diff --git a/design_docs/planned/v1_0_0/m-planner-codex-lane.md b/design_docs/implemented/v1_0_0/m-planner-codex-lane.md similarity index 94% rename from design_docs/planned/v1_0_0/m-planner-codex-lane.md rename to design_docs/implemented/v1_0_0/m-planner-codex-lane.md index d01791cdb..d6ccdabe5 100644 --- a/design_docs/planned/v1_0_0/m-planner-codex-lane.md +++ b/design_docs/implemented/v1_0_0/m-planner-codex-lane.md @@ -387,8 +387,45 @@ of forking them, so a future guard fix lands once. ### Landing Checklist -- Sync `.claude/skills/mission-control/SKILL.md` through its `~/.claude/skills/mission-control/SKILL.md` symlink (a no-op when the verified symlink remains intact). -- Deploy `tools/launchd/mission-control.sh` to `~/dev/sunholo-data/ailang-world/tools/launchd/mission-control.sh` when the World rollout is authorized. +Completed at mission iteration 136 (2026-08-04). Each row records what was actually done. + +- **`~/.claude` skill copy — NO-OP BY CONSTRUCTION, not skipped.** `~/.claude/skills/mission-control` + is a SYMLINK to this repo's `.claude/skills/mission-control` (`readlink` + same inode, measured). + The two "copies" are ONE FILE, so the plan's `diff -q` criterion is satisfied the moment the edit + reaches the MAIN checkout. It follows that a skill edit committed only from a worktree never + reaches the running skill — this sprint landed via PR and then fast-forwarded the main checkout. +- **World driver sync — DECLINED, deliberately, message sent.** The plan recommended syncing + `tools/launchd/mission-control.sh` into `~/dev/sunholo-data/ailang-world/` and pinning World's + planner to opus in `mission-world.env`, on the stated premise that World was kill-switched and so + "verifiable with zero live risk". THAT PREMISE NO LONGER HOLDS — World is armed and ran its + iteration 45 the same night. Declining is strictly lower risk and costs World nothing: its planner + resolves to opus either way, and now every layer agrees on opus rather than relying on a pin. + Cost: the World driver now LAGS this one (they were byte-identical; `diff -q` was the drift + detector and it is now tripped by design). Surfaced to Mark as a pending ops call. +- **`.agents/skills/mission-control/SKILL.md` — OUT OF SCOPE, stale by design**, tracked by `#544` + (44,067 B vs 92,077 B). Not synced; `.agents/` deliberately NOT added to the D2 allowlist. +- **`mission-v1.env` rollback line present and COMMENTED.** Created at + `~/.config/ailang/mission-v1.env`; proven inert while commented, and uncommenting was exercised + once (`planner=ROLLBACK-PROVEN`) then reverted. +- **`git worktree list | grep planner-wt-` — empty.** The ephemeral AC3a worktree was removed + (control: the sprint worktree is still listed, so the grep sees worktrees). + +### Engagement-rate reality (§0.5, measured at landing — do NOT oversell this lane) + +Of the planned design docs, **41 carry a `Files` heading** and **40 of them derive +`opus fail-closed:planner-lane-field-missing`**. Exactly **ONE** doc declares `**Planner-Lane**` +today: this sprint's own. So the codex planner lane engages only on NEWLY AUTHORED infra docs, and +the doc's success metric ("`planner=codex…` appears in the evidence row on infra iterations") +matures over weeks, not on day one. That is the fail-closed design working as intended. + +### The revert is three-tier — D6's "one env var" is true only for M4 + +| Landed | Revert | Covered by D6's env var? | +|---|---|---| +| M1 script/fixtures/template | `rm` the script + revert 2 markdown files | n/a — zero runtime effect | +| **M2 driver probe loop + rollback plumbing** | `git checkout -- tools/launchd/mission-control.sh` — a CODE revert. An env var cannot un-break a driver that fails to parse. | **NO** | +| M3 skill | revert the skill (one file; the `~/.claude` copy follows by symlink) | **NO** (mitigated: derivation fails closed to opus if the script is gone) | +| M4 default flip | uncomment `MISSION_PLANNER_MODEL` in `~/.config/ailang/mission-.env` | **YES** — and only because M2 first built the delivery mechanism D6 already claimed existed | --- diff --git a/tools/launchd/mission-control.sh b/tools/launchd/mission-control.sh index d445aebe1..ec326282a 100755 --- a/tools/launchd/mission-control.sh +++ b/tools/launchd/mission-control.sh @@ -283,7 +283,14 @@ export MISSION_DESIGNER_MODEL="${MISSION_DESIGNER_MODEL:-claude:claude-fable-5}" # this. Enforced by the skill's Gate-3 metered ledger; quota-bucket (subscription) spend is # NOT counted — this caps dollars, not tokens. export MISSION_METERED_BUDGET_USD="${MISSION_METERED_BUDGET_USD:-5}" -export MISSION_PLANNER_MODEL="${MISSION_PLANNER_MODEL:-opus}" +# THE FLIP (m-planner-codex-lane M4, mission iteration 136): the sprint-planner default +# moves to the ChatGPT-subscription codex bucket so opus stays controller-only (Mark +# quota-offload #1). The CONFIGURED default is not the EFFECTIVE lane: the skill's Gate-3 +# step 1b runs tools/launchd/derive-planner-lane.sh on the picked design doc and fails +# CLOSED to opus unless that doc declares **Planner-Lane**: codex-ok AND every path it +# declares is inside the D2 infra allowlist. Rollback = uncomment MISSION_PLANNER_MODEL +# in ~/.config/ailang/mission-.env (delivery mechanism added by M2 above). +export MISSION_PLANNER_MODEL="${MISSION_PLANNER_MODEL:-codex:gpt-5.6-sol}" export MISSION_EXECUTOR_MODEL="${MISSION_EXECUTOR_MODEL:-codex:gpt-5.6-sol}" # Codex-lane pre-flight, ROLE-GENERIC (m-planner-codex-lane): probe once per DISTINCT # codex model, fall back per-role on ANY non-zero rc (#486: probe MUST carry --model; From 623b937009e7e0d54762d220e3065c296edc501e Mon Sep 17 00:00:00 2001 From: "Voight-Kampff (bot)" <151556158+sunholo-voight-kampff@users.noreply.github.com> Date: Tue, 4 Aug 2026 01:29:59 +0200 Subject: [PATCH 6/6] docs(changelog): m-planner-codex-lane under v0.32.0 mission infrastructure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c608c9e20..a5e1d8627 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,18 @@ For the latest version, see [changelogs/v0.18-current.md](changelogs/v0.18-curre unencodable chunk. See [#546](https://github.com/sunholo-data/ailang/issues/546) and [arniwesth/ailang#2](https://github.com/arniwesth/ailang/pull/2). +### Mission infrastructure (no user-facing language or CLI change) + +- The mission loop's sprint-planner now defaults to the ChatGPT-subscription codex + lane instead of opus, so opus stays controller-only. The configured default is not + the effective lane: a new `tools/launchd/derive-planner-lane.sh` reads the picked + design doc and fails **closed** to opus unless the doc declares + `**Planner-Lane**: codex-ok` and every path in its Files section is inside a narrow + infrastructure allowlist. Rollback is one commented line in + `~/.config/ailang/mission-.env`. Design docs gain an optional + `**Planner-Lane**` header field. See + `design_docs/implemented/v1_0_0/m-planner-codex-lane.md`. + ## Changelog Archives The full changelog has been split into themed files for searchability and readability: