agent-bios 0.3.0 → 0.5.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.
- package/DEPENDENCIES.md +1 -0
- package/README.md +2 -1
- package/claude/CLAUDE.md +7 -1
- package/claude/guides/cli-multi-model-workflow.md +6 -0
- package/claude/guides/learning-flow.md +106 -0
- package/claude/guides/{session-learning-workflow.md → session-distill-workflow.md} +13 -13
- package/claude/hooks/tooling-gotchas-hook.py +1 -1
- package/codex/AGENTS.md +7 -1
- package/codex/guides/cli-multi-model-workflow.md +6 -0
- package/codex/guides/learning-flow.md +106 -0
- package/codex/guides/{session-learning-workflow.md → session-distill-workflow.md} +13 -13
- package/config/agent-launch.toml +34 -8
- package/config/domains.json +150 -0
- package/config/learning.schema.json +104 -0
- package/package.json +5 -1
- package/scripts/agent-launch.py +248 -159
- package/scripts/check-learning.py +231 -0
- package/scripts/check-parity.sh +103 -14
- package/scripts/collect-learning.py +557 -0
- package/scripts/install.sh +116 -15
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Learning record gate + validator (collection loop, Phase 0).
|
|
3
|
+
|
|
4
|
+
config/learning.schema.json is the SSOT for the learning record
|
|
5
|
+
(design/collection-loop/DESIGN.md); the dashboard mirrors only minimal
|
|
6
|
+
validation. Full client-side validity = JSON Schema conformance PLUS domain
|
|
7
|
+
membership in config/domains.json domains ∪ 'unclassified' — membership is
|
|
8
|
+
checked here, not frozen in the schema, so vocabulary evolution never needs
|
|
9
|
+
a schema_version bump.
|
|
10
|
+
|
|
11
|
+
Modes:
|
|
12
|
+
(no args) gate: schema validates against its 2020-12 metaschema;
|
|
13
|
+
every fixtures/valid-*.json passes; every fixtures/broken-*.json
|
|
14
|
+
fails AND the failure names the field the fixture breaks
|
|
15
|
+
(a broken fixture failing for an unrelated reason is a FAIL);
|
|
16
|
+
non-vacuity: >=1 valid fixture, >=1 broken fixture,
|
|
17
|
+
>=1 registered domain.
|
|
18
|
+
<file.json>… validate the given record file(s); exit 0 iff all valid.
|
|
19
|
+
This is the client-side validation entry point for Phase 1.
|
|
20
|
+
--self-test negative controls: in-memory mutations of a valid record
|
|
21
|
+
(each required field dropped, plus type/pattern/membership
|
|
22
|
+
mutations) must ALL be rejected; the unmutated record must
|
|
23
|
+
pass. Proves the gate can fail.
|
|
24
|
+
|
|
25
|
+
Any violation exits 1 with all violations listed.
|
|
26
|
+
"""
|
|
27
|
+
import json
|
|
28
|
+
import pathlib
|
|
29
|
+
import sys
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
import jsonschema
|
|
33
|
+
except ImportError:
|
|
34
|
+
sys.exit("check-learning: the 'jsonschema' package is required "
|
|
35
|
+
"(pip install jsonschema; verified 4.26.0 in DEPENDENCIES.md)")
|
|
36
|
+
|
|
37
|
+
REPO = pathlib.Path(__file__).resolve().parent.parent
|
|
38
|
+
SCHEMA = REPO / "config" / "learning.schema.json"
|
|
39
|
+
DOMAINS = REPO / "config" / "domains.json"
|
|
40
|
+
FIXTURES = REPO / "design" / "collection-loop" / "fixtures"
|
|
41
|
+
|
|
42
|
+
# broken fixture -> field its single defect lives in; the reported errors
|
|
43
|
+
# must mention it, so a fixture failing for an accidental other reason fails
|
|
44
|
+
# the gate instead of silently passing as "broken as expected".
|
|
45
|
+
BROKEN_EXPECT = {
|
|
46
|
+
"broken-missing-lesson.json": "lesson",
|
|
47
|
+
"broken-unknown-domain.json": "domain",
|
|
48
|
+
"broken-schema-version.json": "schema_version",
|
|
49
|
+
"broken-extra-field.json": "email",
|
|
50
|
+
"broken-empty-sessions.json": "supporting_sessions",
|
|
51
|
+
"broken-bad-created.json": "created",
|
|
52
|
+
"broken-proposed-domain-conflict.json": "domain",
|
|
53
|
+
"broken-bad-classification.json": "classification",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def load_json(path):
|
|
58
|
+
with open(path, encoding="utf-8") as f:
|
|
59
|
+
return json.load(f)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def valid_domain_values(path=DOMAINS):
|
|
63
|
+
"""The set a record's `domain` may take, drawn from config/domains.json.
|
|
64
|
+
|
|
65
|
+
Ledger-compatible (design/session-distill/ledger.json): the ledger's
|
|
66
|
+
`domain` field uses BOTH domain keys (builder-base, …) for
|
|
67
|
+
domain-specific lessons AND tier names (core, infra, …) for cross-cutting
|
|
68
|
+
ones — 4 real entries carry core/infra. So the valid set is the union of
|
|
69
|
+
both registered vocabularies plus 'unclassified' (refinement B — the
|
|
70
|
+
not-yet-triaged escape; kept distinct from 'core', which means a genuinely
|
|
71
|
+
cross-cutting lesson). Reuses existing vocabulary; introduces none.
|
|
72
|
+
"""
|
|
73
|
+
manifest = load_json(path)
|
|
74
|
+
values = set(manifest["domains"]) | set(manifest["tiers"]) | {"unclassified"}
|
|
75
|
+
if len(values) <= 1:
|
|
76
|
+
sys.exit("FAIL: config/domains.json registers no domains/tiers (vacuous gate)")
|
|
77
|
+
return values
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def validate_record(record, validator, domain_values):
|
|
81
|
+
"""Full validity: schema conformance + domain membership. Returns error strings."""
|
|
82
|
+
errors = []
|
|
83
|
+
for e in validator.iter_errors(record):
|
|
84
|
+
where = "/".join(str(p) for p in e.path) or "<root>"
|
|
85
|
+
errors.append(f"{where}: {e.message}")
|
|
86
|
+
if isinstance(record, dict):
|
|
87
|
+
dom = record.get("domain")
|
|
88
|
+
if isinstance(dom, str) and dom not in domain_values:
|
|
89
|
+
errors.append(
|
|
90
|
+
f"domain: {dom!r} is not a registered domain key, tier name, "
|
|
91
|
+
f"or 'unclassified' (config/domains.json)")
|
|
92
|
+
return errors
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def build_validator():
|
|
96
|
+
schema = load_json(SCHEMA)
|
|
97
|
+
jsonschema.Draft202012Validator.check_schema(schema)
|
|
98
|
+
return jsonschema.Draft202012Validator(schema)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def gate():
|
|
102
|
+
validator = build_validator()
|
|
103
|
+
domain_values = valid_domain_values()
|
|
104
|
+
failures = []
|
|
105
|
+
|
|
106
|
+
valid_fx = sorted(FIXTURES.glob("valid-*.json"))
|
|
107
|
+
broken_fx = sorted(FIXTURES.glob("broken-*.json"))
|
|
108
|
+
if not valid_fx:
|
|
109
|
+
failures.append(f"no valid-*.json fixtures in {FIXTURES} (vacuous)")
|
|
110
|
+
if not broken_fx:
|
|
111
|
+
failures.append(f"no broken-*.json fixtures in {FIXTURES} (vacuous)")
|
|
112
|
+
|
|
113
|
+
for fx in valid_fx:
|
|
114
|
+
errors = validate_record(load_json(fx), validator, domain_values)
|
|
115
|
+
if errors:
|
|
116
|
+
failures.append(f"{fx.name} must validate but failed: {'; '.join(errors)}")
|
|
117
|
+
|
|
118
|
+
for fx in broken_fx:
|
|
119
|
+
errors = validate_record(load_json(fx), validator, domain_values)
|
|
120
|
+
expect = BROKEN_EXPECT.get(fx.name)
|
|
121
|
+
if expect is None:
|
|
122
|
+
failures.append(f"{fx.name} has no BROKEN_EXPECT entry (unmapped negative control)")
|
|
123
|
+
elif not errors:
|
|
124
|
+
failures.append(f"{fx.name} must FAIL but validated (negative control broken)")
|
|
125
|
+
elif not any(expect in err for err in errors):
|
|
126
|
+
failures.append(
|
|
127
|
+
f"{fx.name} failed for the wrong reason (expected mention of "
|
|
128
|
+
f"{expect!r}): {'; '.join(errors)}")
|
|
129
|
+
for name in BROKEN_EXPECT:
|
|
130
|
+
if not (FIXTURES / name).is_file():
|
|
131
|
+
failures.append(f"BROKEN_EXPECT names a missing fixture: {name}")
|
|
132
|
+
|
|
133
|
+
if failures:
|
|
134
|
+
print("check-learning: FAIL")
|
|
135
|
+
for f in failures:
|
|
136
|
+
print(f" - {f}")
|
|
137
|
+
return 1
|
|
138
|
+
print(f"check-learning: OK ({len(valid_fx)} valid, "
|
|
139
|
+
f"{len(broken_fx)} negative controls)")
|
|
140
|
+
return 0
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def self_test():
|
|
144
|
+
validator = build_validator()
|
|
145
|
+
domain_values = valid_domain_values()
|
|
146
|
+
base = load_json(FIXTURES / "valid-minimal.json")
|
|
147
|
+
schema = load_json(SCHEMA)
|
|
148
|
+
|
|
149
|
+
def drop(field):
|
|
150
|
+
r = dict(base)
|
|
151
|
+
del r[field]
|
|
152
|
+
return r
|
|
153
|
+
|
|
154
|
+
def swap(field, value):
|
|
155
|
+
return {**base, field: value}
|
|
156
|
+
|
|
157
|
+
mutations = [(f"drop required '{f}'", drop(f), f) for f in schema["required"]]
|
|
158
|
+
mutations += [
|
|
159
|
+
("schema_version as string", swap("schema_version", "1"), "schema_version"),
|
|
160
|
+
("uppercase learning_id", swap("learning_id", base["learning_id"].upper()), "learning_id"),
|
|
161
|
+
("lesson below minLength", swap("lesson", "short"), "lesson"),
|
|
162
|
+
("capitalized domain", swap("domain", "Builder-Base"), "domain"),
|
|
163
|
+
("session id without tool prefix", swap("supporting_sessions", ["517fbcea"]), "supporting_sessions"),
|
|
164
|
+
("created without T/zone", swap("created", "20260720T050000Z"), "created"),
|
|
165
|
+
("unknown extra key", {**base, "user_email": "x@y"}, "user_email"),
|
|
166
|
+
("classification type out of A-G enum", swap("classification", {"type": "Z"}), "classification"),
|
|
167
|
+
("classification unknown sub-key", swap("classification", {"mechanism": "x"}), "classification"),
|
|
168
|
+
("proposed_domain with a concrete domain", {**base, "proposed_domain": "data-pipeline", "domain": "builder-base"}, "domain"),
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
failures = []
|
|
172
|
+
if validate_record(base, validator, domain_values):
|
|
173
|
+
failures.append("positive control (valid-minimal) does not validate")
|
|
174
|
+
# positive control: a tier-name domain (real ledger precedent) must pass.
|
|
175
|
+
if validate_record({**base, "domain": "core"}, validator, domain_values):
|
|
176
|
+
failures.append("positive control (domain='core' tier name) does not validate")
|
|
177
|
+
# positive control: the optional slim classification block must pass.
|
|
178
|
+
if validate_record({**base, "classification": {"type": "B", "layer": "hook", "meets_bar": True}}, validator, domain_values):
|
|
179
|
+
failures.append("positive control (valid classification block) does not validate")
|
|
180
|
+
# positive control: proposed_domain rides only with an unclassified domain (base is unclassified).
|
|
181
|
+
if validate_record({**base, "proposed_domain": "data-pipeline"}, validator, domain_values):
|
|
182
|
+
failures.append("positive control (proposed_domain + unclassified) does not validate")
|
|
183
|
+
for name, record, expect in mutations:
|
|
184
|
+
errors = validate_record(record, validator, domain_values)
|
|
185
|
+
if not errors:
|
|
186
|
+
failures.append(f"mutation NOT caught: {name}")
|
|
187
|
+
elif not any(expect in err for err in errors):
|
|
188
|
+
failures.append(f"mutation caught for the wrong reason: {name} -> {errors}")
|
|
189
|
+
|
|
190
|
+
if failures:
|
|
191
|
+
print("check-learning --self-test: FAIL")
|
|
192
|
+
for f in failures:
|
|
193
|
+
print(f" - {f}")
|
|
194
|
+
return 1
|
|
195
|
+
print(f"check-learning --self-test: OK ({len(mutations)} mutations caught, "
|
|
196
|
+
f"4 positive controls)")
|
|
197
|
+
return 0
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def validate_files(paths):
|
|
201
|
+
validator = build_validator()
|
|
202
|
+
domain_values = valid_domain_values()
|
|
203
|
+
rc = 0
|
|
204
|
+
for p in paths:
|
|
205
|
+
# Client entry point (Phase 1): inputs are not hand-authored fixtures,
|
|
206
|
+
# so malformed JSON / unreadable files report cleanly, never a traceback.
|
|
207
|
+
try:
|
|
208
|
+
record = load_json(p)
|
|
209
|
+
except (json.JSONDecodeError, OSError) as e:
|
|
210
|
+
rc = 1
|
|
211
|
+
print(f"INVALID {p}")
|
|
212
|
+
print(f" - not readable JSON: {e}")
|
|
213
|
+
continue
|
|
214
|
+
errors = validate_record(record, validator, domain_values)
|
|
215
|
+
if errors:
|
|
216
|
+
rc = 1
|
|
217
|
+
print(f"INVALID {p}")
|
|
218
|
+
for e in errors:
|
|
219
|
+
print(f" - {e}")
|
|
220
|
+
else:
|
|
221
|
+
print(f"OK {p}")
|
|
222
|
+
return rc
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
if __name__ == "__main__":
|
|
226
|
+
args = sys.argv[1:]
|
|
227
|
+
if args == ["--self-test"]:
|
|
228
|
+
sys.exit(self_test())
|
|
229
|
+
if args:
|
|
230
|
+
sys.exit(validate_files(args))
|
|
231
|
+
sys.exit(gate())
|
package/scripts/check-parity.sh
CHANGED
|
@@ -113,8 +113,43 @@ real Microsoft Excel engine|claude/CLAUDE.md|claude/guides/coding-staged-workflo
|
|
|
113
113
|
severity contract|README.md|claude/guides/coding-staged-workflow.md
|
|
114
114
|
Ambient state|claude/CLAUDE.md|claude/guides/tooling-gotchas.md
|
|
115
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
|
|
116
117
|
ANCHORS
|
|
117
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
|
+
# collect-learning Phase 2 transport: the watermark upload-drain logic
|
|
139
|
+
# (status classification, watermark advance, transient-stop, poison-skip).
|
|
140
|
+
python3 scripts/collect-learning.py --self-test >/dev/null \
|
|
141
|
+
|| { echo "FAIL: collect-learning upload-drain self-test"; fail=1; }
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
# Lexicon gate: forbid deprecated terminology tokens in live files
|
|
145
|
+
# (LEXICON.md is the SSOT); --self-test proves the detector can fire.
|
|
146
|
+
if [ -f LEXICON.md ]; then
|
|
147
|
+
python3 scripts/check-lexicon.py >/dev/null \
|
|
148
|
+
|| { echo "FAIL: lexicon gate (run scripts/check-lexicon.py)"; fail=1; }
|
|
149
|
+
python3 scripts/check-lexicon.py --self-test >/dev/null \
|
|
150
|
+
|| { echo "FAIL: lexicon gate self-test failed"; fail=1; }
|
|
151
|
+
fi
|
|
152
|
+
|
|
118
153
|
# The launcher's Textual preflight UI tests need the managed venv (textual).
|
|
119
154
|
# Provision it if missing; every non-UI check above runs under system python.
|
|
120
155
|
VENV="${AGENT_LAUNCH_VENV:-$HOME/.local/share/agent-launch/venv}"
|
|
@@ -551,7 +586,9 @@ if launcher.is_file() and launch_profile:
|
|
|
551
586
|
def set_plan(self, plan):
|
|
552
587
|
self.plan = plan
|
|
553
588
|
|
|
554
|
-
def choose(self, title, options, default, allow_back, preview=None,
|
|
589
|
+
def choose(self, title, options, default, allow_back, preview=None, corpus_lines=None):
|
|
590
|
+
if title == "Mode":
|
|
591
|
+
return "builder"
|
|
555
592
|
if title == "Preset":
|
|
556
593
|
if preview is not None:
|
|
557
594
|
balanced = launcher_module.setup_summary_lines(preview("balanced"))
|
|
@@ -772,12 +809,17 @@ if launcher.is_file() and launch_profile:
|
|
|
772
809
|
os.close(master)
|
|
773
810
|
return bytes(transcript), picker_status
|
|
774
811
|
|
|
812
|
+
# Root menu is now a Mode picker (General user / Builder / Session
|
|
813
|
+
# distill); Builder is the default highlight and lists Custom. Reaching
|
|
814
|
+
# Custom means: Enter the root to open Builder's preset submenu, then
|
|
815
|
+
# step down through its fixed presets (Balanced default-highlighted).
|
|
775
816
|
open_custom_steps = (
|
|
817
|
+
(b"Esc cancel | q cancel", b"\r"),
|
|
776
818
|
(b"native multi-perspective review", b""),
|
|
777
|
-
(b"Esc
|
|
819
|
+
(b"Esc back | q cancel", b"\x1b[B"),
|
|
778
820
|
(b"hybrid onto", b"\x1b[B"),
|
|
779
821
|
(b"high-volume", b"\x1b[B"),
|
|
780
|
-
(b"
|
|
822
|
+
(b"the standing spawn policy is lifted", b"\x1b[B"),
|
|
781
823
|
(b"Open a settings hub", b"\r"),
|
|
782
824
|
)
|
|
783
825
|
down = b"\x1b[B"
|
|
@@ -798,7 +840,7 @@ if launcher.is_file() and launch_profile:
|
|
|
798
840
|
b"WORKHORSE",
|
|
799
841
|
b"SWEEP",
|
|
800
842
|
b"About highlighted option",
|
|
801
|
-
b"
|
|
843
|
+
b"Tune tiers, review routes, and permissions",
|
|
802
844
|
b"Options (",
|
|
803
845
|
)
|
|
804
846
|
positions = {token: transcript.find(token) for token in layout_tokens}
|
|
@@ -809,7 +851,7 @@ if launcher.is_file() and launch_profile:
|
|
|
809
851
|
positions[b"Current setup"]
|
|
810
852
|
< min(positions[token] for token in (b"FRONTIER", b"HELM", b"WORKHORSE", b"SWEEP"))
|
|
811
853
|
< positions[b"About highlighted option"]
|
|
812
|
-
< positions[b"
|
|
854
|
+
< positions[b"Tune tiers, review routes, and permissions"]
|
|
813
855
|
< positions[b"Options ("]
|
|
814
856
|
):
|
|
815
857
|
mark_fail("agent-launch layout is not setup then description then options")
|
|
@@ -821,12 +863,27 @@ if launcher.is_file() and launch_profile:
|
|
|
821
863
|
if picker_status != 130:
|
|
822
864
|
mark_fail(f"agent-launch root Esc returned {picker_status}, want 130")
|
|
823
865
|
|
|
866
|
+
# Root Mode picker navigation: Esc from the Builder preset submenu
|
|
867
|
+
# returns to a freshly re-rendered Mode picker (Builder's own
|
|
868
|
+
# description reappears) rather than cancelling the launcher outright.
|
|
869
|
+
_, picker_status = run_picker_scenario(
|
|
870
|
+
"mode picker navigation",
|
|
871
|
+
(
|
|
872
|
+
(b"Esc cancel | q cancel", b"\r"),
|
|
873
|
+
(b"native multi-perspective review", b"\x1b"),
|
|
874
|
+
(b"Tune tiers, review routes, and permissions", b"q"),
|
|
875
|
+
),
|
|
876
|
+
)
|
|
877
|
+
if picker_status != 130:
|
|
878
|
+
mark_fail(f"agent-launch mode picker Esc-back returned {picker_status}, want 130")
|
|
879
|
+
|
|
824
880
|
if argv_log.exists():
|
|
825
881
|
argv_log.unlink()
|
|
826
882
|
_, picker_status = run_picker_scenario(
|
|
827
883
|
"launch confirmation q cancellation",
|
|
828
884
|
(
|
|
829
885
|
(b"Esc cancel | q cancel", b"\r"),
|
|
886
|
+
(b"HELM default for everyday work", b"\r"),
|
|
830
887
|
(b"Launch? [Y/n/q]:", b"q\r"),
|
|
831
888
|
),
|
|
832
889
|
dry_run=False,
|
|
@@ -1128,10 +1185,19 @@ if launcher.is_file() and launch_profile:
|
|
|
1128
1185
|
# TERM=dumb routes to numbered prompts (textual renders on any usable
|
|
1129
1186
|
# terminal, so the numbered fallback is gated on TERM/non-TTY/textual
|
|
1130
1187
|
# availability, not a terminfo probe). 'b' is the numbered back command.
|
|
1131
|
-
|
|
1188
|
+
# The root Mode menu is fixed (General=1, Builder=2, Session distill=3)
|
|
1189
|
+
# regardless of preset count; only Custom's position within Builder's
|
|
1190
|
+
# own preset submenu depends on how many builder-mode presets exist.
|
|
1191
|
+
builder_preset_count = sum(
|
|
1192
|
+
1
|
|
1193
|
+
for data in fake_data["presets"].values()
|
|
1194
|
+
if data.get("mode", "builder") == "builder"
|
|
1195
|
+
)
|
|
1196
|
+
custom_number = str(builder_preset_count + 1).encode()
|
|
1132
1197
|
transcript, picker_status = run_picker_scenario(
|
|
1133
1198
|
"numbered fallback (TERM=dumb)",
|
|
1134
1199
|
(
|
|
1200
|
+
(b"Tune tiers, review routes, and permissions", b"2\n"),
|
|
1135
1201
|
(b"Open a settings hub", custom_number + b"\n"),
|
|
1136
1202
|
(b"Custom settings", b"b\n"),
|
|
1137
1203
|
(b"HELM default for everyday work", b"1\n"),
|
|
@@ -1145,27 +1211,27 @@ if launcher.is_file() and launch_profile:
|
|
|
1145
1211
|
):
|
|
1146
1212
|
mark_fail("agent-launch numbered fallback (TERM=dumb) did not preserve numbered back")
|
|
1147
1213
|
|
|
1148
|
-
# Session
|
|
1149
|
-
learning_number = str(len(fake_data["presets"]) + 2).encode()
|
|
1214
|
+
# Session Distill hub: enter, render status, back out, launch balanced.
|
|
1150
1215
|
transcript, picker_status = run_picker_scenario(
|
|
1151
|
-
"numbered
|
|
1216
|
+
"numbered distill hub (TERM=dumb)",
|
|
1152
1217
|
(
|
|
1153
|
-
(b"Session
|
|
1218
|
+
(b"Session distill", b"3\n"),
|
|
1154
1219
|
(b"Versions & rollback", b"4\n"),
|
|
1220
|
+
(b"Tune tiers, review routes, and permissions", b"2\n"),
|
|
1155
1221
|
(b"HELM default for everyday work", b"1\n"),
|
|
1156
1222
|
),
|
|
1157
1223
|
term="dumb",
|
|
1158
1224
|
)
|
|
1159
|
-
|
|
1225
|
+
corpus_panel_rendered = (
|
|
1160
1226
|
b"Applied version" in transcript or b"not projected yet" in transcript
|
|
1161
1227
|
)
|
|
1162
1228
|
if (
|
|
1163
1229
|
picker_status != 0
|
|
1164
1230
|
or b"Traceback" in transcript
|
|
1165
|
-
or not
|
|
1231
|
+
or not corpus_panel_rendered
|
|
1166
1232
|
or b"Preset Balanced" not in transcript
|
|
1167
1233
|
):
|
|
1168
|
-
mark_fail("agent-launch numbered
|
|
1234
|
+
mark_fail("agent-launch numbered distill hub did not render or return")
|
|
1169
1235
|
|
|
1170
1236
|
passed = invoke([
|
|
1171
1237
|
sys.executable, str(launcher), "--no-tui", "codex", "--", "exec", "--json", "probe"
|
|
@@ -1492,6 +1558,29 @@ if launcher.is_file() and launch_profile:
|
|
|
1492
1558
|
"agent-launch Claude expert overrides were not appended last"
|
|
1493
1559
|
)
|
|
1494
1560
|
|
|
1561
|
+
# Vanilla (mode=general): the raw backend with nothing applied — no
|
|
1562
|
+
# launch contract, no agents, no permission-bypass flag.
|
|
1563
|
+
vanilla_configured = invoke([
|
|
1564
|
+
sys.executable, str(launcher), "--preset", "vanilla", "--yes", "--dry-run",
|
|
1565
|
+
"claude",
|
|
1566
|
+
], env=env)
|
|
1567
|
+
if vanilla_configured.returncode != 0:
|
|
1568
|
+
mark_fail(
|
|
1569
|
+
f"agent-launch vanilla dry-run failed: {vanilla_configured.stderr.strip()}"
|
|
1570
|
+
)
|
|
1571
|
+
else:
|
|
1572
|
+
vanilla_argv = json.loads(vanilla_configured.stdout.splitlines()[-1])
|
|
1573
|
+
for forbidden in (
|
|
1574
|
+
"--append-system-prompt", "--agents", "--dangerously-skip-permissions",
|
|
1575
|
+
):
|
|
1576
|
+
if forbidden in vanilla_argv:
|
|
1577
|
+
mark_fail(f"agent-launch vanilla preset unexpectedly carried {forbidden}")
|
|
1578
|
+
if vanilla_argv != [str(backend)]:
|
|
1579
|
+
mark_fail(
|
|
1580
|
+
"agent-launch vanilla preset must project the bare backend with "
|
|
1581
|
+
f"nothing applied: {vanilla_argv!r}"
|
|
1582
|
+
)
|
|
1583
|
+
|
|
1495
1584
|
# Cross-family default: each main routes every review route to the OPPOSITE
|
|
1496
1585
|
# model family, with concrete tools/paths/bindings named in the contract.
|
|
1497
1586
|
# The cross CODEX_HOME needs the reviewer wrappers (bin) and the same-family
|
|
@@ -1766,5 +1855,5 @@ then
|
|
|
1766
1855
|
fail=1
|
|
1767
1856
|
fi
|
|
1768
1857
|
|
|
1769
|
-
[ "$fail" -eq 0 ] && echo "PARITY OK: mirrors, globals, guides, launch profile, bypass paths, role bindings, and wrapper defaults aligned"
|
|
1858
|
+
[ "$fail" -eq 0 ] && echo "PARITY OK: mirrors, globals, guides, domain manifest, assembler, launch profile, bypass paths, role bindings, and wrapper defaults aligned"
|
|
1770
1859
|
exit "$fail"
|