agent-bios 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -111,8 +111,41 @@ current-state dashboard|claude/CLAUDE.md|claude/guides/implementation-map.md
111
111
  Verification Menus|claude/CLAUDE.md|claude/guides/coding-staged-workflow.md
112
112
  real Microsoft Excel engine|claude/CLAUDE.md|claude/guides/coding-staged-workflow.md
113
113
  severity contract|README.md|claude/guides/coding-staged-workflow.md
114
+ Ambient state|claude/CLAUDE.md|claude/guides/tooling-gotchas.md
115
+ the full lifecycle of what you create|claude/CLAUDE.md|claude/guides/tooling-gotchas.md
116
+ dual-provider frontier design drafts|claude/CLAUDE.md|claude/guides/cli-multi-model-workflow.md
114
117
  ANCHORS
115
118
 
119
+ # Domain-manifest gate: bullet<->anchor bijection, file coverage, router
120
+ # co-packaging (config/domains.json vs the monolith); its --self-test proves
121
+ # every negative control still fails, so a green gate is falsifiable.
122
+ if [ -f config/domains.json ]; then
123
+ python3 scripts/check-domains.py >/dev/null \
124
+ || { echo "FAIL: domains manifest gate (run scripts/check-domains.py)"; fail=1; }
125
+ python3 scripts/check-domains.py --self-test >/dev/null \
126
+ || { echo "FAIL: domains gate self-test missed a negative control"; fail=1; }
127
+ bash scripts/test-assemble.sh >/dev/null \
128
+ || { echo "FAIL: assembler scenario suite (run scripts/test-assemble.sh)"; fail=1; }
129
+ fi
130
+
131
+ # Learning record gate: config/learning.schema.json (collection-loop
132
+ # SSOT) vs its fixtures; --self-test proves every mutation is still caught.
133
+ if [ -f config/learning.schema.json ]; then
134
+ python3 scripts/check-learning.py >/dev/null \
135
+ || { echo "FAIL: learning record gate (run scripts/check-learning.py)"; fail=1; }
136
+ python3 scripts/check-learning.py --self-test >/dev/null \
137
+ || { echo "FAIL: learning gate self-test missed a negative control"; fail=1; }
138
+ fi
139
+
140
+ # Lexicon gate: forbid deprecated terminology tokens in live files
141
+ # (LEXICON.md is the SSOT); --self-test proves the detector can fire.
142
+ if [ -f LEXICON.md ]; then
143
+ python3 scripts/check-lexicon.py >/dev/null \
144
+ || { echo "FAIL: lexicon gate (run scripts/check-lexicon.py)"; fail=1; }
145
+ python3 scripts/check-lexicon.py --self-test >/dev/null \
146
+ || { echo "FAIL: lexicon gate self-test failed"; fail=1; }
147
+ fi
148
+
116
149
  # The launcher's Textual preflight UI tests need the managed venv (textual).
117
150
  # Provision it if missing; every non-UI check above runs under system python.
118
151
  VENV="${AGENT_LAUNCH_VENV:-$HOME/.local/share/agent-launch/venv}"
@@ -264,6 +297,33 @@ for filename, (expected_name, expected_model, expected_effort) in expected_agent
264
297
  f"want {expected_effort!r}"
265
298
  )
266
299
 
300
+ # codex/config-additions.toml is the additive fragment install merges into the
301
+ # live ~/.codex/config.toml; its agent entries are projections of the canonical
302
+ # codex/agents/*.toml templates and must not drift from them.
303
+ fragment_path = pathlib.Path("codex/config-additions.toml")
304
+ if not fragment_path.is_file():
305
+ mark_fail("required file missing: codex/config-additions.toml")
306
+ else:
307
+ fragment = tomllib.loads(fragment_path.read_text())
308
+ if fragment.get("features", {}).get("multi_agent") is not True:
309
+ mark_fail("config-additions must set features.multi_agent = true")
310
+ fragment_agents = fragment.get("agents", {})
311
+ if set(fragment_agents) != {"frontier", "workhorse", "sweep"}:
312
+ mark_fail(
313
+ "config-additions agents must be exactly the spawnable tiers "
314
+ f"(no helm): {sorted(fragment_agents)}"
315
+ )
316
+ for tier, spec in fragment_agents.items():
317
+ template_path = agent_dir / f"{tier}.toml"
318
+ template = tomllib.loads(template_path.read_text()) if template_path.is_file() else {}
319
+ if spec.get("description") != template.get("description"):
320
+ mark_fail(f"config-additions {tier} description drifted from {template_path}")
321
+ if spec.get("config_file") != f"${{CODEX_HOME}}/agents/{tier}.toml":
322
+ mark_fail(
323
+ f"config-additions {tier} config_file must be "
324
+ f"${{CODEX_HOME}}/agents/{tier}.toml"
325
+ )
326
+
267
327
 
268
328
  def table_row(path, slot):
269
329
  for raw_line in path.read_text().splitlines():
@@ -522,7 +582,9 @@ if launcher.is_file() and launch_profile:
522
582
  def set_plan(self, plan):
523
583
  self.plan = plan
524
584
 
525
- def choose(self, title, options, default, allow_back, preview=None):
585
+ def choose(self, title, options, default, allow_back, preview=None, corpus_lines=None):
586
+ if title == "Mode":
587
+ return "builder"
526
588
  if title == "Preset":
527
589
  if preview is not None:
528
590
  balanced = launcher_module.setup_summary_lines(preview("balanced"))
@@ -743,11 +805,17 @@ if launcher.is_file() and launch_profile:
743
805
  os.close(master)
744
806
  return bytes(transcript), picker_status
745
807
 
808
+ # Root menu is now a Mode picker (General user / Builder / Session
809
+ # distill); Builder is the default highlight and lists Custom. Reaching
810
+ # Custom means: Enter the root to open Builder's preset submenu, then
811
+ # step down through its fixed presets (Balanced default-highlighted).
746
812
  open_custom_steps = (
813
+ (b"Esc cancel | q cancel", b"\r"),
747
814
  (b"native multi-perspective review", b""),
748
- (b"Esc cancel | q cancel", b"\x1b[B"),
815
+ (b"Esc back | q cancel", b"\x1b[B"),
749
816
  (b"hybrid onto", b"\x1b[B"),
750
817
  (b"high-volume", b"\x1b[B"),
818
+ (b"the standing spawn policy is lifted", b"\x1b[B"),
751
819
  (b"Open a settings hub", b"\r"),
752
820
  )
753
821
  down = b"\x1b[B"
@@ -768,7 +836,7 @@ if launcher.is_file() and launch_profile:
768
836
  b"WORKHORSE",
769
837
  b"SWEEP",
770
838
  b"About highlighted option",
771
- b"HELM default for everyday work",
839
+ b"Tune tiers, review routes, and permissions",
772
840
  b"Options (",
773
841
  )
774
842
  positions = {token: transcript.find(token) for token in layout_tokens}
@@ -779,7 +847,7 @@ if launcher.is_file() and launch_profile:
779
847
  positions[b"Current setup"]
780
848
  < min(positions[token] for token in (b"FRONTIER", b"HELM", b"WORKHORSE", b"SWEEP"))
781
849
  < positions[b"About highlighted option"]
782
- < positions[b"HELM default for everyday work"]
850
+ < positions[b"Tune tiers, review routes, and permissions"]
783
851
  < positions[b"Options ("]
784
852
  ):
785
853
  mark_fail("agent-launch layout is not setup then description then options")
@@ -791,12 +859,27 @@ if launcher.is_file() and launch_profile:
791
859
  if picker_status != 130:
792
860
  mark_fail(f"agent-launch root Esc returned {picker_status}, want 130")
793
861
 
862
+ # Root Mode picker navigation: Esc from the Builder preset submenu
863
+ # returns to a freshly re-rendered Mode picker (Builder's own
864
+ # description reappears) rather than cancelling the launcher outright.
865
+ _, picker_status = run_picker_scenario(
866
+ "mode picker navigation",
867
+ (
868
+ (b"Esc cancel | q cancel", b"\r"),
869
+ (b"native multi-perspective review", b"\x1b"),
870
+ (b"Tune tiers, review routes, and permissions", b"q"),
871
+ ),
872
+ )
873
+ if picker_status != 130:
874
+ mark_fail(f"agent-launch mode picker Esc-back returned {picker_status}, want 130")
875
+
794
876
  if argv_log.exists():
795
877
  argv_log.unlink()
796
878
  _, picker_status = run_picker_scenario(
797
879
  "launch confirmation q cancellation",
798
880
  (
799
881
  (b"Esc cancel | q cancel", b"\r"),
882
+ (b"HELM default for everyday work", b"\r"),
800
883
  (b"Launch? [Y/n/q]:", b"q\r"),
801
884
  ),
802
885
  dry_run=False,
@@ -1098,10 +1181,20 @@ if launcher.is_file() and launch_profile:
1098
1181
  # TERM=dumb routes to numbered prompts (textual renders on any usable
1099
1182
  # terminal, so the numbered fallback is gated on TERM/non-TTY/textual
1100
1183
  # availability, not a terminfo probe). 'b' is the numbered back command.
1184
+ # The root Mode menu is fixed (General=1, Builder=2, Session distill=3)
1185
+ # regardless of preset count; only Custom's position within Builder's
1186
+ # own preset submenu depends on how many builder-mode presets exist.
1187
+ builder_preset_count = sum(
1188
+ 1
1189
+ for data in fake_data["presets"].values()
1190
+ if data.get("mode", "builder") == "builder"
1191
+ )
1192
+ custom_number = str(builder_preset_count + 1).encode()
1101
1193
  transcript, picker_status = run_picker_scenario(
1102
1194
  "numbered fallback (TERM=dumb)",
1103
1195
  (
1104
- (b"Open a settings hub", b"4\n"),
1196
+ (b"Tune tiers, review routes, and permissions", b"2\n"),
1197
+ (b"Open a settings hub", custom_number + b"\n"),
1105
1198
  (b"Custom settings", b"b\n"),
1106
1199
  (b"HELM default for everyday work", b"1\n"),
1107
1200
  ),
@@ -1114,6 +1207,28 @@ if launcher.is_file() and launch_profile:
1114
1207
  ):
1115
1208
  mark_fail("agent-launch numbered fallback (TERM=dumb) did not preserve numbered back")
1116
1209
 
1210
+ # Session Distill hub: enter, render status, back out, launch balanced.
1211
+ transcript, picker_status = run_picker_scenario(
1212
+ "numbered distill hub (TERM=dumb)",
1213
+ (
1214
+ (b"Session distill", b"3\n"),
1215
+ (b"Versions & rollback", b"4\n"),
1216
+ (b"Tune tiers, review routes, and permissions", b"2\n"),
1217
+ (b"HELM default for everyday work", b"1\n"),
1218
+ ),
1219
+ term="dumb",
1220
+ )
1221
+ corpus_panel_rendered = (
1222
+ b"Applied version" in transcript or b"not projected yet" in transcript
1223
+ )
1224
+ if (
1225
+ picker_status != 0
1226
+ or b"Traceback" in transcript
1227
+ or not corpus_panel_rendered
1228
+ or b"Preset Balanced" not in transcript
1229
+ ):
1230
+ mark_fail("agent-launch numbered distill hub did not render or return")
1231
+
1117
1232
  passed = invoke([
1118
1233
  sys.executable, str(launcher), "--no-tui", "codex", "--", "exec", "--json", "probe"
1119
1234
  ], env=env)
@@ -1421,7 +1536,16 @@ if launcher.is_file() and launch_profile:
1421
1536
  except (ValueError, IndexError, json.JSONDecodeError) as exc:
1422
1537
  mark_fail(f"agent-launch Claude --agents projection is not valid JSON: {exc}")
1423
1538
  else:
1424
- for tier, (model, effort) in expected_claude_tiers.items():
1539
+ expected_spawnable = {
1540
+ tier: expected_claude_tiers[tier]
1541
+ for tier in ("frontier", "workhorse", "sweep")
1542
+ }
1543
+ if set(agents_arg) != set(expected_spawnable):
1544
+ mark_fail(
1545
+ "Claude --agents must project exactly the spawnable tiers "
1546
+ f"(HELM is the main, never a subagent): {sorted(agents_arg)}"
1547
+ )
1548
+ for tier, (model, effort) in expected_spawnable.items():
1425
1549
  role = agents_arg.get(tier, {})
1426
1550
  if role.get("model") != model or role.get("effort") != effort:
1427
1551
  mark_fail(f"agent-launch Claude {tier} role projection drifted: {role!r}")
@@ -1430,6 +1554,29 @@ if launcher.is_file() and launch_profile:
1430
1554
  "agent-launch Claude expert overrides were not appended last"
1431
1555
  )
1432
1556
 
1557
+ # Vanilla (mode=general): the raw backend with nothing applied — no
1558
+ # launch contract, no agents, no permission-bypass flag.
1559
+ vanilla_configured = invoke([
1560
+ sys.executable, str(launcher), "--preset", "vanilla", "--yes", "--dry-run",
1561
+ "claude",
1562
+ ], env=env)
1563
+ if vanilla_configured.returncode != 0:
1564
+ mark_fail(
1565
+ f"agent-launch vanilla dry-run failed: {vanilla_configured.stderr.strip()}"
1566
+ )
1567
+ else:
1568
+ vanilla_argv = json.loads(vanilla_configured.stdout.splitlines()[-1])
1569
+ for forbidden in (
1570
+ "--append-system-prompt", "--agents", "--dangerously-skip-permissions",
1571
+ ):
1572
+ if forbidden in vanilla_argv:
1573
+ mark_fail(f"agent-launch vanilla preset unexpectedly carried {forbidden}")
1574
+ if vanilla_argv != [str(backend)]:
1575
+ mark_fail(
1576
+ "agent-launch vanilla preset must project the bare backend with "
1577
+ f"nothing applied: {vanilla_argv!r}"
1578
+ )
1579
+
1433
1580
  # Cross-family default: each main routes every review route to the OPPOSITE
1434
1581
  # model family, with concrete tools/paths/bindings named in the contract.
1435
1582
  # The cross CODEX_HOME needs the reviewer wrappers (bin) and the same-family
@@ -1704,5 +1851,5 @@ then
1704
1851
  fail=1
1705
1852
  fi
1706
1853
 
1707
- [ "$fail" -eq 0 ] && echo "PARITY OK: mirrors, globals, guides, launch profile, bypass paths, role bindings, and wrapper defaults aligned"
1854
+ [ "$fail" -eq 0 ] && echo "PARITY OK: mirrors, globals, guides, domain manifest, assembler, launch profile, bypass paths, role bindings, and wrapper defaults aligned"
1708
1855
  exit "$fail"
@@ -268,9 +268,27 @@ EOF
268
268
  EOF
269
269
  }
270
270
 
271
+ # Review packets must carry the whole subject: range-based diffs silently omit
272
+ # staged-but-uncommitted changes and untracked files, so the dispatcher itself
273
+ # appends the subject tree's actual state to the packet.
274
+ scope_note=""
275
+ if [ "$mode" = "review" ] && { [ -n "$cd_dir" ] || [ "$reach" != "hermetic" ]; }; then
276
+ scope_root="${cd_dir:-$PWD}"
277
+ if git -C "$scope_root" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
278
+ scope_status="$(git -C "$scope_root" status --porcelain 2>/dev/null || true)"
279
+ if [ -n "$scope_status" ]; then
280
+ scope_note="Review scope manifest (dispatcher-generated \`git status --porcelain\` of the subject tree; uncommitted/untracked entries are part of the review subject unless the task says otherwise):
281
+ $scope_status"
282
+ echo "codex-helm: subject tree has uncommitted/untracked entries; scope manifest appended to the packet" >&2
283
+ fi
284
+ fi
285
+ fi
286
+
271
287
  final_prompt="$(build_preamble)
272
288
 
273
- User task:
289
+ ${scope_note:+$scope_note
290
+
291
+ }User task:
274
292
  $user_prompt"
275
293
 
276
294
  cleanup_dirs=()
@@ -167,6 +167,25 @@ if [ "${#extra_c[@]}" -gt 0 ]; then
167
167
  for kv in "${extra_c[@]}"; do args+=(-c "$kv"); done
168
168
  fi
169
169
 
170
+ # Dispatch audit: verifier diversity is only as real as the pinned backing
171
+ # model — an unpinned dispatch inherits config defaults and can silently
172
+ # collapse two "different" reviewers onto one backend. The audit line goes to
173
+ # the log file only; stdout/stderr stay reserved for the codex channels.
174
+ sandbox_label="$sandbox"
175
+ if [ "$bypass_sandbox" -eq 1 ]; then sandbox_label="bypass"; fi
176
+ cmodel=""
177
+ if [ "${#extra_c[@]}" -gt 0 ]; then
178
+ for kv in "${extra_c[@]}"; do
179
+ case "$kv" in model=*) cmodel="${kv#model=}" ;; esac
180
+ done
181
+ fi
182
+ dispatch_note="dispatch profile=$profile model=${model:-INHERITED_DEFAULT}${cmodel:+ c-model-override=$cmodel} effort=${effort:-config-default} sandbox=$sandbox_label"
183
+ if [ -z "$model" ] && [ -z "$cmodel" ]; then
184
+ echo "codex-run: WARNING: no --model pin; the backing model inherits the active config default" >&2
185
+ fi
186
+ mkdir -p "$real_home/log" 2>/dev/null || true
187
+ printf '%s %s\n' "$(date +%Y-%m-%dT%H:%M:%S%z)" "$dispatch_note" >> "$real_home/log/codex-run-dispatch.log" 2>/dev/null || true
188
+
170
189
  # stdin, stdout, and stderr already match this adapter's channel contract.
171
190
  set +e
172
191
  CODEX_HOME="$run_home" codex "${args[@]}"