repo-harness 0.12.1 → 0.12.2
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/README.es.md +2 -2
- package/README.fr.md +2 -2
- package/README.ja.md +2 -2
- package/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/agents/fleet/gatekeeper.md +3 -3
- package/assets/reference-configs/external-tooling.md +55 -14
- package/assets/skill-version.json +6 -2
- package/assets/templates/helpers/check-agent-tooling.sh +65 -5
- package/assets/templates/helpers/ensure-task-workflow.sh +2 -2
- package/assets/templates/helpers/install-agent-fleet.sh +3 -1
- package/assets/templates/helpers/run-bounded-verifier-command.ts +18 -0
- package/assets/templates/helpers/verify-sprint.sh +13 -1
- package/package.json +1 -1
- package/scripts/check-agent-tooling.sh +65 -5
- package/scripts/ensure-task-workflow.sh +2 -2
- package/scripts/install-agent-fleet.sh +3 -1
- package/scripts/lib/project-init-lib.sh +2 -2
- package/scripts/run-bounded-verifier-command.ts +18 -0
- package/scripts/verify-sprint.sh +13 -1
- package/src/cli/hook/session-context.ts +29 -6
- package/src/cli/hook/subagent-handler.ts +10 -3
package/README.es.md
CHANGED
|
@@ -466,8 +466,8 @@ repositorio adopte la misma política.
|
|
|
466
466
|
|
|
467
467
|
## Versión actual
|
|
468
468
|
|
|
469
|
-
- Paquete npm: `repo-harness@0.12.
|
|
470
|
-
- Sello de workflow generado: `repo-harness@0.12.
|
|
469
|
+
- Paquete npm: `repo-harness@0.12.2`
|
|
470
|
+
- Sello de workflow generado: `repo-harness@0.12.2+template@0.12.2`
|
|
471
471
|
- Repositorio de GitHub: `Ancienttwo/repo-harness`
|
|
472
472
|
- Notas de versión e historial: [`docs/CHANGELOG.md`](docs/CHANGELOG.md)
|
|
473
473
|
|
package/README.fr.md
CHANGED
|
@@ -461,8 +461,8 @@ adopte la même policy.
|
|
|
461
461
|
|
|
462
462
|
## Release actuelle
|
|
463
463
|
|
|
464
|
-
- Package npm : `repo-harness@0.12.
|
|
465
|
-
- Generated workflow stamp : `repo-harness@0.12.
|
|
464
|
+
- Package npm : `repo-harness@0.12.2`
|
|
465
|
+
- Generated workflow stamp : `repo-harness@0.12.2+template@0.12.2`
|
|
466
466
|
- Dépôt GitHub : `Ancienttwo/repo-harness`
|
|
467
467
|
- Notes et historique de release : [`docs/CHANGELOG.md`](docs/CHANGELOG.md)
|
|
468
468
|
|
package/README.ja.md
CHANGED
|
@@ -469,8 +469,8 @@ commit script や hooks に組み込まないでください。
|
|
|
469
469
|
|
|
470
470
|
## 現在の Release
|
|
471
471
|
|
|
472
|
-
- npm package:`repo-harness@0.12.
|
|
473
|
-
- Generated workflow stamp:`repo-harness@0.12.
|
|
472
|
+
- npm package:`repo-harness@0.12.2`
|
|
473
|
+
- Generated workflow stamp:`repo-harness@0.12.2+template@0.12.2`
|
|
474
474
|
- GitHub repository:`Ancienttwo/repo-harness`
|
|
475
475
|
- Release notes and history:[`docs/CHANGELOG.md`](docs/CHANGELOG.md)
|
|
476
476
|
|
package/README.md
CHANGED
|
@@ -441,8 +441,8 @@ repo-harness commit scripts or hooks unless that repo adopts the same policy.
|
|
|
441
441
|
|
|
442
442
|
## Current Release
|
|
443
443
|
|
|
444
|
-
- npm package: `repo-harness@0.12.
|
|
445
|
-
- Generated workflow stamp: `repo-harness@0.12.
|
|
444
|
+
- npm package: `repo-harness@0.12.2`
|
|
445
|
+
- Generated workflow stamp: `repo-harness@0.12.2+template@0.12.2`
|
|
446
446
|
- GitHub repository: `Ancienttwo/repo-harness`
|
|
447
447
|
- Release notes and history: [`docs/CHANGELOG.md`](docs/CHANGELOG.md)
|
|
448
448
|
|
package/README.zh-CN.md
CHANGED
|
@@ -448,8 +448,8 @@ policy。
|
|
|
448
448
|
|
|
449
449
|
## 当前 Release
|
|
450
450
|
|
|
451
|
-
- npm package:`repo-harness@0.12.
|
|
452
|
-
- Generated workflow stamp:`repo-harness@0.12.
|
|
451
|
+
- npm package:`repo-harness@0.12.2`
|
|
452
|
+
- Generated workflow stamp:`repo-harness@0.12.2+template@0.12.2`
|
|
453
453
|
- GitHub repository:`Ancienttwo/repo-harness`
|
|
454
454
|
- Release notes 和 history:[`docs/CHANGELOG.md`](docs/CHANGELOG.md)
|
|
455
455
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gatekeeper
|
|
3
|
-
description: Read-only acceptance and ship gate on
|
|
3
|
+
description: Read-only acceptance and ship gate on Opus at high effort. Use after execution workers deliver work: it reviews the diff against the goal, runs the project's real verification, and returns PASS/FAIL/BLOCKED with evidence and a ship recommendation. It never edits, commits, pushes, opens or merges PRs, or decides to ship; fixes and terminal actions stay with the orchestrator.
|
|
4
4
|
tools: ["Read", "Grep", "Glob", "Bash"]
|
|
5
|
-
model:
|
|
6
|
-
effort:
|
|
5
|
+
model: opus
|
|
6
|
+
effort: high
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
You are the read-only acceptance and ship gate. Execution workers deliver work; you judge whether it is fit to ship and recommend. The orchestrator decides and owns every side effect. You never edit code, commit, push, open or merge PRs, or execute a release order.
|
|
@@ -443,19 +443,19 @@ mapping.
|
|
|
443
443
|
| `sonnet`, `haiku` | `gpt-5.6-luna` | `low`, `medium`, `high`, `xhigh`, `max` | same string, unchanged |
|
|
444
444
|
| `fable` | `gpt-5.6-sol` | `low`, `medium`, `high`, `xhigh`, `max` | same string, unchanged |
|
|
445
445
|
|
|
446
|
-
|
|
446
|
+
Three per-agent target overrides are applied after tuple validation, on top of
|
|
447
447
|
the family row above, and are the only effort remaps in the generator:
|
|
448
448
|
`fast-worker` (`opus`/`medium`) targets `gpt-5.6-luna` at `max` reasoning
|
|
449
449
|
instead of the opus family's `gpt-5.6-terra`/`medium`; `deep-worker`
|
|
450
|
-
(`opus`/`high`)
|
|
451
|
-
reasoning to `xhigh` instead of `high`. Every
|
|
452
|
-
effort follow the family row unchanged.
|
|
450
|
+
(`opus`/`high`) and `gatekeeper` (`opus`/`high`) keep the opus family's
|
|
451
|
+
`gpt-5.6-terra` model but bump reasoning to `xhigh` instead of `high`. Every
|
|
452
|
+
other agent's Codex model and effort follow the family row unchanged.
|
|
453
453
|
|
|
454
454
|
`fast-worker`, `deep-worker`, `root-cause-prover`, and `harness-evaluator`
|
|
455
455
|
receive `sandbox_mode = "workspace-write"`; every other role receives
|
|
456
456
|
`sandbox_mode = "read-only"`. Current assignments are explorer
|
|
457
457
|
(`sonnet/high`), deep-reasoner (`opus/xhigh`), fast-worker (`opus/medium`),
|
|
458
|
-
deep-worker (`opus/high`), gatekeeper (`
|
|
458
|
+
deep-worker (`opus/high`), gatekeeper (`opus/high`), root-cause-prover
|
|
459
459
|
(`opus/high`), and harness-evaluator (`opus/high`). Root-cause-prover's prompt further limits
|
|
460
460
|
writes to bugfix evidence inside the active contract's allowed paths;
|
|
461
461
|
harness-evaluator runs existing skill/adoption surfaces only when both repo and
|
|
@@ -467,7 +467,7 @@ profiles scrub inherited repo-harness source/helper overrides. The guard rejects
|
|
|
467
467
|
checkout and real HOME in either argument position; the role returns BLOCKED
|
|
468
468
|
when the guard fails and must not access the independent `evals/bdd2/**` authority. The opus
|
|
469
469
|
family projects to Terra by default with effort carried through unchanged; the only effort
|
|
470
|
-
remaps are the
|
|
470
|
+
remaps are the three explicit per-agent overrides above, and any unmapped model/effort
|
|
471
471
|
combination remains a hard error.
|
|
472
472
|
|
|
473
473
|
The Codex generator also rewrites the exact upstream provider label in the
|
|
@@ -475,10 +475,17 @@ description (for example, `Opus at max effort` or `Sonnet at high effort`) to th
|
|
|
475
475
|
mapped GPT-5.6 model and reasoning level. A missing label fails closed so the
|
|
476
476
|
installed metadata cannot claim a different model from the TOML settings.
|
|
477
477
|
|
|
478
|
-
These files define the desired installed role configuration
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
478
|
+
These files define the desired installed role configuration and feed both Codex
|
|
479
|
+
dispatch surfaces: native MultiAgentV2 `agent_type` selection, and Codex App
|
|
480
|
+
Thread dispatch, where `model` and `model_reasoning_effort` are read back out of
|
|
481
|
+
the installed TOML and passed explicitly to `codex_app__create_thread`. App
|
|
482
|
+
Thread dispatch is the default Codex path because the flat native spawn schema
|
|
483
|
+
carries only a task name, a message, and `fork_turns`: it cannot select a model
|
|
484
|
+
per role, so a `gpt-5.6-luna` role dispatched natively silently inherits the
|
|
485
|
+
parent model. These files do not by themselves prove that either surface honored
|
|
486
|
+
the configured model; keep runtime selection claims behind a real canary — a
|
|
487
|
+
SubagentStart observation on the native path, an official thread read on the
|
|
488
|
+
thread path.
|
|
482
489
|
|
|
483
490
|
### Local merge gate
|
|
484
491
|
|
|
@@ -540,6 +547,34 @@ readiness separate:
|
|
|
540
547
|
SubagentStart does not expose `model_reasoning_effort`, so repo-harness never
|
|
541
548
|
claims that per-role reasoning effort is verified from this gate.
|
|
542
549
|
|
|
550
|
+
`native_role_routing` evidence stays scoped to the native fallback path; it says
|
|
551
|
+
nothing about App Thread dispatch, and an absent native canary is not a failure
|
|
552
|
+
of the default thread path. Thread-path model evidence comes from official
|
|
553
|
+
thread reads instead: the requested `model`/`thinking` passed to
|
|
554
|
+
`codex_app__create_thread` and the model observed on the materialized thread are
|
|
555
|
+
recorded separately, and a thread whose runtime model was never read back stays
|
|
556
|
+
unverified. A post-merge live canary on 2026-08-02 requested
|
|
557
|
+
`gpt-5.6-luna`/`max`, materialized a worktree thread, and completed a bounded
|
|
558
|
+
read-only task at the shipped main SHA. The host-owned rollout `turn_context`
|
|
559
|
+
observed `model = gpt-5.6-luna` and `effort = max`, which proves the host-local
|
|
560
|
+
execution matched the request. It does not close the orchestrator contract: the
|
|
561
|
+
current `codex_app__read_thread` response omitted model and effort fields, so
|
|
562
|
+
portable runtime readiness remains unverified and must not be inferred from
|
|
563
|
+
create-call acceptance or private rollout parsing.
|
|
564
|
+
|
|
565
|
+
The readback contract is version-bound and fail-closed. On Codex CLI
|
|
566
|
+
`0.146.0-alpha.9.2`, `codex app-server generate-json-schema` shows that
|
|
567
|
+
`ThreadReadResponse` contains only `thread`, while its `Thread` object exposes
|
|
568
|
+
`modelProvider` but neither `model` nor `reasoningEffort`. The same generated
|
|
569
|
+
schema exposes `model` and nullable `reasoningEffort` on `ThreadStartResponse`
|
|
570
|
+
and `ThreadResumeResponse`, but the App Thread tools do not propagate those
|
|
571
|
+
fields across the pending-worktree materialization path. Runtime readiness is
|
|
572
|
+
closed only when a public App Thread response exposes the materialized thread's
|
|
573
|
+
observed `model` and reasoning effort separately from the requested tuple. A
|
|
574
|
+
missing or mismatched observed tuple keeps the role-routed result unverified and
|
|
575
|
+
selects the declared fallback; repo-harness must not scrape rollout JSONL or
|
|
576
|
+
SQLite as a compatibility path.
|
|
577
|
+
|
|
543
578
|
`developer_instructions` is the packaged `.md` body plus the canonical
|
|
544
579
|
EXECUTION_BOUNDARY anti-extras clause, kept byte-identical to the
|
|
545
580
|
`EXECUTION_BOUNDARY` constant in `scripts/contract-run.ts` so every generated
|
|
@@ -621,10 +656,16 @@ reports packaged files as `up-to-date`.
|
|
|
621
656
|
|
|
622
657
|
`repo-harness run check-agent-tooling --host both --strict-readiness` reports
|
|
623
658
|
`agent_fleet` alongside `codegraph`. With `--check-updates`, it compares each
|
|
624
|
-
installed Claude-side `.md` against the packaged source hash
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
the
|
|
659
|
+
installed Claude-side `.md` against the packaged source hash without network
|
|
660
|
+
access. When a file differs from that source, it consults the
|
|
661
|
+
`--accept-user-managed` receipt (`~/.repo-harness/agent-fleet-user-managed.json`):
|
|
662
|
+
an entry whose SHA-256 still matches the file's current installed content
|
|
663
|
+
reports `user-managed` on its own report line instead of drift, while a
|
|
664
|
+
missing or malformed receipt, an absent entry for that path, or a stale hash
|
|
665
|
+
still reports `drift`, fail-closed. The per-host rollup is `up-to-date` once
|
|
666
|
+
nothing is left in drift. The Codex `.toml` side is a generated artifact and
|
|
667
|
+
is checked for presence; installer golden tests prove the deterministic
|
|
668
|
+
generation.
|
|
628
669
|
|
|
629
670
|
### Uninstall
|
|
630
671
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.12.
|
|
3
|
-
"templateVersion": "0.12.
|
|
2
|
+
"version": "0.12.2",
|
|
3
|
+
"templateVersion": "0.12.2",
|
|
4
4
|
"skillName": "repo-harness",
|
|
5
5
|
"contractId": "tasks-first-harness-v1",
|
|
6
6
|
"compatibility": {
|
|
@@ -231,6 +231,10 @@
|
|
|
231
231
|
{
|
|
232
232
|
"version": "0.12.1",
|
|
233
233
|
"description": "Threads a single SessionStart-minted run identity through hook telemetry and evidence correlation, restructures the five-language README into a get-started-first layout, and refreshes the CodeGraph dev dependency line to latest"
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
"version": "0.12.2",
|
|
237
|
+
"description": "Makes agent-fleet drift checks receipt-aware, hardens verifier environment and provenance boundaries, and routes Codex delegation through model-pinned App Threads with fail-closed fallbacks"
|
|
234
238
|
}
|
|
235
239
|
],
|
|
236
240
|
"generatedProjectStamp": {
|
|
@@ -105,6 +105,7 @@ const AGENT_FLEET_SOURCE_DIR = process.env.REPO_HARNESS_AGENT_FLEET_SOURCE_DIR;
|
|
|
105
105
|
const AGENT_FLEET_SOURCE_LABEL = "package:agents/fleet";
|
|
106
106
|
const AGENT_FLEET_DEFAULT_MANAGED = ["explorer", "deep-reasoner", "fast-worker", "deep-worker", "gatekeeper", "root-cause-prover", "harness-evaluator"];
|
|
107
107
|
const AGENT_FLEET_INSTALL_COMMAND = "repo-harness run install-agent-fleet";
|
|
108
|
+
const AGENT_FLEET_USER_MANAGED_RECEIPT_PATH = path.join(HOME, ".repo-harness", "agent-fleet-user-managed.json");
|
|
108
109
|
const CODEGRAPH_PACKAGE = "@colbymchenry/codegraph";
|
|
109
110
|
const CODEGRAPH_GLOBAL_INSTALL_COMMAND = `bun add -g ${CODEGRAPH_PACKAGE} && repo-harness tools configure codegraph --target codex --location global`;
|
|
110
111
|
const CODEGRAPH_MCP_CONFIGURE_COMMAND = "repo-harness tools configure codegraph --target <codex|claude|both> --location global";
|
|
@@ -248,6 +249,10 @@ function sha1Buffer(buffer) {
|
|
|
248
249
|
return crypto.createHash("sha1").update(buffer).digest("hex");
|
|
249
250
|
}
|
|
250
251
|
|
|
252
|
+
function sha256Buffer(buffer) {
|
|
253
|
+
return `sha256:${crypto.createHash("sha256").update(buffer).digest("hex")}`;
|
|
254
|
+
}
|
|
255
|
+
|
|
251
256
|
function parseSkillVersion(text) {
|
|
252
257
|
const match = text.match(/^\s*version:\s*["']?([^"'\n]+)["']?/m);
|
|
253
258
|
return match ? match[1].trim() : null;
|
|
@@ -872,6 +877,37 @@ function readAgentFleetSource(agent) {
|
|
|
872
877
|
return { status: "read", path: sourcePath, hash: source.hash };
|
|
873
878
|
}
|
|
874
879
|
|
|
880
|
+
// Read-only mirror of install-agent-fleet.sh's loadUserManagedReceipt(): this
|
|
881
|
+
// checker never writes ~/.repo-harness/agent-fleet-user-managed.json, it only
|
|
882
|
+
// consults it so an operator-accepted customized file is not misreported as
|
|
883
|
+
// drift. Any malformation invalidates the whole receipt (fail-closed) rather
|
|
884
|
+
// than exempting individual entries.
|
|
885
|
+
function loadAgentFleetUserManagedReceipt() {
|
|
886
|
+
if (!fs.existsSync(AGENT_FLEET_USER_MANAGED_RECEIPT_PATH)) return { ok: true, hashes: new Map() };
|
|
887
|
+
try {
|
|
888
|
+
const parsed = JSON.parse(fs.readFileSync(AGENT_FLEET_USER_MANAGED_RECEIPT_PATH, "utf8"));
|
|
889
|
+
if (
|
|
890
|
+
parsed?.protocol !== 1
|
|
891
|
+
|| parsed?.authority !== "user-managed-agent-fleet"
|
|
892
|
+
|| !Array.isArray(parsed.files)
|
|
893
|
+
) return { ok: false, hashes: new Map() };
|
|
894
|
+
const hashes = new Map();
|
|
895
|
+
for (const entry of parsed.files) {
|
|
896
|
+
if (
|
|
897
|
+
!entry
|
|
898
|
+
|| typeof entry.path !== "string"
|
|
899
|
+
|| typeof entry.sha256 !== "string"
|
|
900
|
+
|| !/^sha256:[a-f0-9]{64}$/.test(entry.sha256)
|
|
901
|
+
|| hashes.has(entry.path)
|
|
902
|
+
) return { ok: false, hashes: new Map() };
|
|
903
|
+
hashes.set(entry.path, entry.sha256);
|
|
904
|
+
}
|
|
905
|
+
return { ok: true, hashes };
|
|
906
|
+
} catch (_error) {
|
|
907
|
+
return { ok: false, hashes: new Map() };
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
875
911
|
function detectAgentFleetHost(host, managedAgents) {
|
|
876
912
|
const meta = HOSTS[host];
|
|
877
913
|
const files = managedAgents.map((agent) => inspectAgentFleetFile(host, agent));
|
|
@@ -884,9 +920,11 @@ function detectAgentFleetHost(host, managedAgents) {
|
|
|
884
920
|
const driftAgents = [];
|
|
885
921
|
const syncedAgents = [];
|
|
886
922
|
const sourceMissingAgents = [];
|
|
923
|
+
const userManagedAgents = [];
|
|
887
924
|
|
|
888
925
|
if (checkUpdates) {
|
|
889
926
|
if (host === "claude") {
|
|
927
|
+
const receipt = loadAgentFleetUserManagedReceipt();
|
|
890
928
|
for (const entry of files) {
|
|
891
929
|
if (!entry.present) continue;
|
|
892
930
|
const source = readAgentFleetSource(entry.name);
|
|
@@ -896,9 +934,25 @@ function detectAgentFleetHost(host, managedAgents) {
|
|
|
896
934
|
}
|
|
897
935
|
if (source.hash === entry.hash) {
|
|
898
936
|
syncedAgents.push(entry.name);
|
|
899
|
-
|
|
900
|
-
driftAgents.push(entry.name);
|
|
937
|
+
continue;
|
|
901
938
|
}
|
|
939
|
+
// Differs from the packaged source: only a valid receipt entry whose
|
|
940
|
+
// sha256 matches the file's *current* installed content exempts it.
|
|
941
|
+
// A missing/invalid receipt, a path with no entry, or a hash mismatch
|
|
942
|
+
// (edited again after acceptance) all fall through to drift.
|
|
943
|
+
if (receipt.ok && receipt.hashes.get(entry.path) !== undefined) {
|
|
944
|
+
let installedHash = null;
|
|
945
|
+
try {
|
|
946
|
+
installedHash = sha256Buffer(fs.readFileSync(entry.path));
|
|
947
|
+
} catch (_error) {
|
|
948
|
+
installedHash = null;
|
|
949
|
+
}
|
|
950
|
+
if (installedHash === receipt.hashes.get(entry.path)) {
|
|
951
|
+
userManagedAgents.push(entry.name);
|
|
952
|
+
continue;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
driftAgents.push(entry.name);
|
|
902
956
|
}
|
|
903
957
|
|
|
904
958
|
if (driftAgents.length > 0) {
|
|
@@ -907,9 +961,11 @@ function detectAgentFleetHost(host, managedAgents) {
|
|
|
907
961
|
} else if (sourceMissingAgents.length > 0) {
|
|
908
962
|
updateStatus = "unknown";
|
|
909
963
|
updateReason = `Packaged repo-harness fleet source is missing files for: ${sourceMissingAgents.join(", ")}.`;
|
|
910
|
-
} else if (syncedAgents.length > 0) {
|
|
911
|
-
updateStatus = "
|
|
912
|
-
updateReason =
|
|
964
|
+
} else if (syncedAgents.length > 0 || userManagedAgents.length > 0) {
|
|
965
|
+
updateStatus = "up-to-date";
|
|
966
|
+
updateReason = userManagedAgents.length > 0
|
|
967
|
+
? `Installed Claude agent definitions match the packaged repo-harness fleet source, with user-managed exemptions accepted via receipt for: ${userManagedAgents.join(", ")}.`
|
|
968
|
+
: "Installed Claude agent definitions match the packaged repo-harness fleet source.";
|
|
913
969
|
} else {
|
|
914
970
|
updateStatus = "not-checked";
|
|
915
971
|
updateReason = "No installed Claude agent definitions to compare.";
|
|
@@ -931,6 +987,7 @@ function detectAgentFleetHost(host, managedAgents) {
|
|
|
931
987
|
drift_agents: driftAgents,
|
|
932
988
|
synced_agents: syncedAgents,
|
|
933
989
|
source_missing_agents: sourceMissingAgents,
|
|
990
|
+
user_managed_agents: userManagedAgents,
|
|
934
991
|
};
|
|
935
992
|
}
|
|
936
993
|
|
|
@@ -1631,6 +1688,9 @@ function printText(result) {
|
|
|
1631
1688
|
if (entry.update_status !== "not-checked") {
|
|
1632
1689
|
console.log(` updates: ${entry.update_status} (${entry.update_reason})`);
|
|
1633
1690
|
}
|
|
1691
|
+
if (entry.user_managed_agents.length) {
|
|
1692
|
+
console.log(` user-managed (receipt): ${entry.user_managed_agents.join(", ")}`);
|
|
1693
|
+
}
|
|
1634
1694
|
}
|
|
1635
1695
|
console.log(` - Install: ${agentFleet.install_command}`);
|
|
1636
1696
|
if (SELECTED_HOSTS.includes("codex")) {
|
|
@@ -1171,10 +1171,10 @@ ARCHITECTURE_INDEX_EOF
|
|
|
1171
1171
|
"allow_parallel_writers": false,
|
|
1172
1172
|
"stop_fallback": true,
|
|
1173
1173
|
"state_file": ".ai/harness/delegation/latest.json",
|
|
1174
|
-
"preferred_runners": ["subagent", "codex-subagent", "codex-exec", "main-thread"],
|
|
1174
|
+
"preferred_runners": ["subagent", "codex-app-thread", "codex-subagent", "codex-exec", "main-thread"],
|
|
1175
1175
|
"fallback_runner": "main-thread",
|
|
1176
1176
|
"brief_source": "tasks/contracts/<stem>.contract.md",
|
|
1177
|
-
"runner_rule": "the active task contract is the authoritative execution brief consumed by contract-run; native subagent
|
|
1177
|
+
"runner_rule": "the active task contract is the authoritative execution brief consumed by contract-run; the preferred parallelism accelerator is native subagent on the Claude host and Codex App Thread dispatch via codex_app__create_thread on the Codex host, each thread created with the role's exact model and reasoning effort read from the installed ~/.codex/agents/<role>.toml. Native codex-subagent (spawn_agent) is a declared fallback only when the App Thread tools are unavailable AND the live spawn schema accepts the role's exact model/effort combination; a role whose exact model/effort that schema does not accept MUST NOT fall back to native spawn, because native spawn silently inherits the parent model. Otherwise degrade to codex-exec, then sequential main-thread, on the SAME contract. Runner-availability fallback MUST be recorded in the contract-run manifest and MUST NOT silently succeed; it is a runner-availability fallback, not a product-semantics compatibility fallback.",
|
|
1178
1178
|
"rule": "UserPromptSubmit.delegation only injects bounded subagent context after explicit user authorization such as /delegate, /parallel, spawn subagents, or parallel investigation, regardless of delegation.mode. When delegation.mode resolves to \"auto\", the in-process session-context builder (src/cli/hook/session-context.ts) instead injects one standing bounded-delegation authorization block at SessionStart (Codex host only) for the whole session; UserPromptSubmit.delegation does not re-assert it on later prompts. The global ~/.repo-harness/config.json delegation.mode value, when exactly \"auto\" or \"explicit\", takes precedence over this repo policy value when resolving that SessionStart mode."
|
|
1179
1179
|
},
|
|
1180
1180
|
"sidecar_research": {
|
|
@@ -145,10 +145,12 @@ const MODEL_EFFORT_MAP = {
|
|
|
145
145
|
|
|
146
146
|
// Per-agent Codex target overrides — the only effort remaps in the fleet.
|
|
147
147
|
// fast-worker trades the opus/terra default down to Luna at max; deep-worker
|
|
148
|
-
//
|
|
148
|
+
// and gatekeeper bump terra effort to xhigh. Everything else follows the
|
|
149
|
+
// family default.
|
|
149
150
|
const AGENT_TARGET_OVERRIDES = {
|
|
150
151
|
"fast-worker": { model: "gpt-5.6-luna", effort: "max", targetDescription: "GPT-5.6 Luna at max reasoning" },
|
|
151
152
|
"deep-worker": { model: "gpt-5.6-terra", effort: "xhigh", targetDescription: "GPT-5.6 Terra at xhigh reasoning" },
|
|
153
|
+
gatekeeper: { model: "gpt-5.6-terra", effort: "xhigh", targetDescription: "GPT-5.6 Terra at xhigh reasoning" },
|
|
152
154
|
};
|
|
153
155
|
|
|
154
156
|
function readSource(agent) {
|
|
@@ -39,6 +39,23 @@ if (!Number.isFinite(deadlineMs)) usage();
|
|
|
39
39
|
// process group) outlive this wrapper undetected.
|
|
40
40
|
const FORCED_TERMINATION_CONFIRM_MS = 500;
|
|
41
41
|
|
|
42
|
+
// A verification command must observe the project's real behaviour in a clean
|
|
43
|
+
// environment. The package-dispatched helper mechanism injects `REPO_HARNESS_*`
|
|
44
|
+
// wiring (helper source path, target repo root, trusted tool binaries) into its
|
|
45
|
+
// child, and that set inherits all the way down verify-sprint -> verify-contract
|
|
46
|
+
// -> a nested test run, where it can silently override a fixture's own repo
|
|
47
|
+
// root or tool resolution. Strip the whole prefix -- not a curated subset --
|
|
48
|
+
// so harness-internal wiring never reaches the command under verification.
|
|
49
|
+
// This runner reads no `REPO_HARNESS_*` variable itself.
|
|
50
|
+
function scrubHarnessEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
|
51
|
+
const scrubbed: NodeJS.ProcessEnv = {};
|
|
52
|
+
for (const [key, value] of Object.entries(env)) {
|
|
53
|
+
if (key.startsWith('REPO_HARNESS_')) continue;
|
|
54
|
+
scrubbed[key] = value;
|
|
55
|
+
}
|
|
56
|
+
return scrubbed;
|
|
57
|
+
}
|
|
58
|
+
|
|
42
59
|
const startedAt = Date.now();
|
|
43
60
|
let timedOut = false;
|
|
44
61
|
let terminating = false;
|
|
@@ -49,6 +66,7 @@ const logFd = openSync(logPath, 'w');
|
|
|
49
66
|
const child = spawn(command, args, {
|
|
50
67
|
detached: process.platform !== 'win32',
|
|
51
68
|
stdio: ['ignore', logFd, logFd],
|
|
69
|
+
env: scrubHarnessEnv(process.env),
|
|
52
70
|
});
|
|
53
71
|
|
|
54
72
|
function terminate(signal: NodeJS.Signals): void {
|
|
@@ -545,13 +545,25 @@ finalize_prepared_acceptance() {
|
|
|
545
545
|
--contract "$contract_file" --verification "$checks_file" --review "$review_file" >/dev/null
|
|
546
546
|
|
|
547
547
|
finalized_checks="$(mktemp)"
|
|
548
|
+
# `$checks_file` is a materialized projection, not a run trace: the
|
|
549
|
+
# materializer (src/effects/evidence/checks-materializer.ts) appends its own
|
|
550
|
+
# `provenance` block to whatever run trace it projects. Re-emitting that
|
|
551
|
+
# block as payload would make the next materialization record
|
|
552
|
+
# `content_hash = sha256(run_trace including the previous provenance)` while
|
|
553
|
+
# publishing a file whose consumer-facing content excludes it, so the
|
|
554
|
+
# published projection could no longer be recomputed from its own bytes
|
|
555
|
+
# (tests/evidence-projection-drift.test.ts's live self-consistency check).
|
|
556
|
+
# `provenance` is materializer-owned derived metadata; strip it here so the
|
|
557
|
+
# emitted run trace is a run trace again and the next projection is
|
|
558
|
+
# self-consistent by construction -- no second hash implementation.
|
|
548
559
|
jq \
|
|
549
560
|
--arg reviewer "$acceptance_reviewer" \
|
|
550
561
|
--arg source "$acceptance_source" \
|
|
551
562
|
--arg disposition "$acceptance_disposition" \
|
|
552
563
|
--arg message "$acceptance_message" \
|
|
553
564
|
'
|
|
554
|
-
.
|
|
565
|
+
del(.provenance)
|
|
566
|
+
| .acceptance_receipt = {
|
|
555
567
|
status: "pass",
|
|
556
568
|
disposition: $disposition,
|
|
557
569
|
reviewer: $reviewer,
|
package/package.json
CHANGED
|
@@ -105,6 +105,7 @@ const AGENT_FLEET_SOURCE_DIR = process.env.REPO_HARNESS_AGENT_FLEET_SOURCE_DIR;
|
|
|
105
105
|
const AGENT_FLEET_SOURCE_LABEL = "package:agents/fleet";
|
|
106
106
|
const AGENT_FLEET_DEFAULT_MANAGED = ["explorer", "deep-reasoner", "fast-worker", "deep-worker", "gatekeeper", "root-cause-prover", "harness-evaluator"];
|
|
107
107
|
const AGENT_FLEET_INSTALL_COMMAND = "repo-harness run install-agent-fleet";
|
|
108
|
+
const AGENT_FLEET_USER_MANAGED_RECEIPT_PATH = path.join(HOME, ".repo-harness", "agent-fleet-user-managed.json");
|
|
108
109
|
const CODEGRAPH_PACKAGE = "@colbymchenry/codegraph";
|
|
109
110
|
const CODEGRAPH_GLOBAL_INSTALL_COMMAND = `bun add -g ${CODEGRAPH_PACKAGE} && repo-harness tools configure codegraph --target codex --location global`;
|
|
110
111
|
const CODEGRAPH_MCP_CONFIGURE_COMMAND = "repo-harness tools configure codegraph --target <codex|claude|both> --location global";
|
|
@@ -248,6 +249,10 @@ function sha1Buffer(buffer) {
|
|
|
248
249
|
return crypto.createHash("sha1").update(buffer).digest("hex");
|
|
249
250
|
}
|
|
250
251
|
|
|
252
|
+
function sha256Buffer(buffer) {
|
|
253
|
+
return `sha256:${crypto.createHash("sha256").update(buffer).digest("hex")}`;
|
|
254
|
+
}
|
|
255
|
+
|
|
251
256
|
function parseSkillVersion(text) {
|
|
252
257
|
const match = text.match(/^\s*version:\s*["']?([^"'\n]+)["']?/m);
|
|
253
258
|
return match ? match[1].trim() : null;
|
|
@@ -872,6 +877,37 @@ function readAgentFleetSource(agent) {
|
|
|
872
877
|
return { status: "read", path: sourcePath, hash: source.hash };
|
|
873
878
|
}
|
|
874
879
|
|
|
880
|
+
// Read-only mirror of install-agent-fleet.sh's loadUserManagedReceipt(): this
|
|
881
|
+
// checker never writes ~/.repo-harness/agent-fleet-user-managed.json, it only
|
|
882
|
+
// consults it so an operator-accepted customized file is not misreported as
|
|
883
|
+
// drift. Any malformation invalidates the whole receipt (fail-closed) rather
|
|
884
|
+
// than exempting individual entries.
|
|
885
|
+
function loadAgentFleetUserManagedReceipt() {
|
|
886
|
+
if (!fs.existsSync(AGENT_FLEET_USER_MANAGED_RECEIPT_PATH)) return { ok: true, hashes: new Map() };
|
|
887
|
+
try {
|
|
888
|
+
const parsed = JSON.parse(fs.readFileSync(AGENT_FLEET_USER_MANAGED_RECEIPT_PATH, "utf8"));
|
|
889
|
+
if (
|
|
890
|
+
parsed?.protocol !== 1
|
|
891
|
+
|| parsed?.authority !== "user-managed-agent-fleet"
|
|
892
|
+
|| !Array.isArray(parsed.files)
|
|
893
|
+
) return { ok: false, hashes: new Map() };
|
|
894
|
+
const hashes = new Map();
|
|
895
|
+
for (const entry of parsed.files) {
|
|
896
|
+
if (
|
|
897
|
+
!entry
|
|
898
|
+
|| typeof entry.path !== "string"
|
|
899
|
+
|| typeof entry.sha256 !== "string"
|
|
900
|
+
|| !/^sha256:[a-f0-9]{64}$/.test(entry.sha256)
|
|
901
|
+
|| hashes.has(entry.path)
|
|
902
|
+
) return { ok: false, hashes: new Map() };
|
|
903
|
+
hashes.set(entry.path, entry.sha256);
|
|
904
|
+
}
|
|
905
|
+
return { ok: true, hashes };
|
|
906
|
+
} catch (_error) {
|
|
907
|
+
return { ok: false, hashes: new Map() };
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
875
911
|
function detectAgentFleetHost(host, managedAgents) {
|
|
876
912
|
const meta = HOSTS[host];
|
|
877
913
|
const files = managedAgents.map((agent) => inspectAgentFleetFile(host, agent));
|
|
@@ -884,9 +920,11 @@ function detectAgentFleetHost(host, managedAgents) {
|
|
|
884
920
|
const driftAgents = [];
|
|
885
921
|
const syncedAgents = [];
|
|
886
922
|
const sourceMissingAgents = [];
|
|
923
|
+
const userManagedAgents = [];
|
|
887
924
|
|
|
888
925
|
if (checkUpdates) {
|
|
889
926
|
if (host === "claude") {
|
|
927
|
+
const receipt = loadAgentFleetUserManagedReceipt();
|
|
890
928
|
for (const entry of files) {
|
|
891
929
|
if (!entry.present) continue;
|
|
892
930
|
const source = readAgentFleetSource(entry.name);
|
|
@@ -896,9 +934,25 @@ function detectAgentFleetHost(host, managedAgents) {
|
|
|
896
934
|
}
|
|
897
935
|
if (source.hash === entry.hash) {
|
|
898
936
|
syncedAgents.push(entry.name);
|
|
899
|
-
|
|
900
|
-
driftAgents.push(entry.name);
|
|
937
|
+
continue;
|
|
901
938
|
}
|
|
939
|
+
// Differs from the packaged source: only a valid receipt entry whose
|
|
940
|
+
// sha256 matches the file's *current* installed content exempts it.
|
|
941
|
+
// A missing/invalid receipt, a path with no entry, or a hash mismatch
|
|
942
|
+
// (edited again after acceptance) all fall through to drift.
|
|
943
|
+
if (receipt.ok && receipt.hashes.get(entry.path) !== undefined) {
|
|
944
|
+
let installedHash = null;
|
|
945
|
+
try {
|
|
946
|
+
installedHash = sha256Buffer(fs.readFileSync(entry.path));
|
|
947
|
+
} catch (_error) {
|
|
948
|
+
installedHash = null;
|
|
949
|
+
}
|
|
950
|
+
if (installedHash === receipt.hashes.get(entry.path)) {
|
|
951
|
+
userManagedAgents.push(entry.name);
|
|
952
|
+
continue;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
driftAgents.push(entry.name);
|
|
902
956
|
}
|
|
903
957
|
|
|
904
958
|
if (driftAgents.length > 0) {
|
|
@@ -907,9 +961,11 @@ function detectAgentFleetHost(host, managedAgents) {
|
|
|
907
961
|
} else if (sourceMissingAgents.length > 0) {
|
|
908
962
|
updateStatus = "unknown";
|
|
909
963
|
updateReason = `Packaged repo-harness fleet source is missing files for: ${sourceMissingAgents.join(", ")}.`;
|
|
910
|
-
} else if (syncedAgents.length > 0) {
|
|
911
|
-
updateStatus = "
|
|
912
|
-
updateReason =
|
|
964
|
+
} else if (syncedAgents.length > 0 || userManagedAgents.length > 0) {
|
|
965
|
+
updateStatus = "up-to-date";
|
|
966
|
+
updateReason = userManagedAgents.length > 0
|
|
967
|
+
? `Installed Claude agent definitions match the packaged repo-harness fleet source, with user-managed exemptions accepted via receipt for: ${userManagedAgents.join(", ")}.`
|
|
968
|
+
: "Installed Claude agent definitions match the packaged repo-harness fleet source.";
|
|
913
969
|
} else {
|
|
914
970
|
updateStatus = "not-checked";
|
|
915
971
|
updateReason = "No installed Claude agent definitions to compare.";
|
|
@@ -931,6 +987,7 @@ function detectAgentFleetHost(host, managedAgents) {
|
|
|
931
987
|
drift_agents: driftAgents,
|
|
932
988
|
synced_agents: syncedAgents,
|
|
933
989
|
source_missing_agents: sourceMissingAgents,
|
|
990
|
+
user_managed_agents: userManagedAgents,
|
|
934
991
|
};
|
|
935
992
|
}
|
|
936
993
|
|
|
@@ -1631,6 +1688,9 @@ function printText(result) {
|
|
|
1631
1688
|
if (entry.update_status !== "not-checked") {
|
|
1632
1689
|
console.log(` updates: ${entry.update_status} (${entry.update_reason})`);
|
|
1633
1690
|
}
|
|
1691
|
+
if (entry.user_managed_agents.length) {
|
|
1692
|
+
console.log(` user-managed (receipt): ${entry.user_managed_agents.join(", ")}`);
|
|
1693
|
+
}
|
|
1634
1694
|
}
|
|
1635
1695
|
console.log(` - Install: ${agentFleet.install_command}`);
|
|
1636
1696
|
if (SELECTED_HOSTS.includes("codex")) {
|
|
@@ -1171,10 +1171,10 @@ ARCHITECTURE_INDEX_EOF
|
|
|
1171
1171
|
"allow_parallel_writers": false,
|
|
1172
1172
|
"stop_fallback": true,
|
|
1173
1173
|
"state_file": ".ai/harness/delegation/latest.json",
|
|
1174
|
-
"preferred_runners": ["subagent", "codex-subagent", "codex-exec", "main-thread"],
|
|
1174
|
+
"preferred_runners": ["subagent", "codex-app-thread", "codex-subagent", "codex-exec", "main-thread"],
|
|
1175
1175
|
"fallback_runner": "main-thread",
|
|
1176
1176
|
"brief_source": "tasks/contracts/<stem>.contract.md",
|
|
1177
|
-
"runner_rule": "the active task contract is the authoritative execution brief consumed by contract-run; native subagent
|
|
1177
|
+
"runner_rule": "the active task contract is the authoritative execution brief consumed by contract-run; the preferred parallelism accelerator is native subagent on the Claude host and Codex App Thread dispatch via codex_app__create_thread on the Codex host, each thread created with the role's exact model and reasoning effort read from the installed ~/.codex/agents/<role>.toml. Native codex-subagent (spawn_agent) is a declared fallback only when the App Thread tools are unavailable AND the live spawn schema accepts the role's exact model/effort combination; a role whose exact model/effort that schema does not accept MUST NOT fall back to native spawn, because native spawn silently inherits the parent model. Otherwise degrade to codex-exec, then sequential main-thread, on the SAME contract. Runner-availability fallback MUST be recorded in the contract-run manifest and MUST NOT silently succeed; it is a runner-availability fallback, not a product-semantics compatibility fallback.",
|
|
1178
1178
|
"rule": "UserPromptSubmit.delegation only injects bounded subagent context after explicit user authorization such as /delegate, /parallel, spawn subagents, or parallel investigation, regardless of delegation.mode. When delegation.mode resolves to \"auto\", the in-process session-context builder (src/cli/hook/session-context.ts) instead injects one standing bounded-delegation authorization block at SessionStart (Codex host only) for the whole session; UserPromptSubmit.delegation does not re-assert it on later prompts. The global ~/.repo-harness/config.json delegation.mode value, when exactly \"auto\" or \"explicit\", takes precedence over this repo policy value when resolving that SessionStart mode."
|
|
1179
1179
|
},
|
|
1180
1180
|
"sidecar_research": {
|
|
@@ -145,10 +145,12 @@ const MODEL_EFFORT_MAP = {
|
|
|
145
145
|
|
|
146
146
|
// Per-agent Codex target overrides — the only effort remaps in the fleet.
|
|
147
147
|
// fast-worker trades the opus/terra default down to Luna at max; deep-worker
|
|
148
|
-
//
|
|
148
|
+
// and gatekeeper bump terra effort to xhigh. Everything else follows the
|
|
149
|
+
// family default.
|
|
149
150
|
const AGENT_TARGET_OVERRIDES = {
|
|
150
151
|
"fast-worker": { model: "gpt-5.6-luna", effort: "max", targetDescription: "GPT-5.6 Luna at max reasoning" },
|
|
151
152
|
"deep-worker": { model: "gpt-5.6-terra", effort: "xhigh", targetDescription: "GPT-5.6 Terra at xhigh reasoning" },
|
|
153
|
+
gatekeeper: { model: "gpt-5.6-terra", effort: "xhigh", targetDescription: "GPT-5.6 Terra at xhigh reasoning" },
|
|
152
154
|
};
|
|
153
155
|
|
|
154
156
|
function readSource(agent) {
|
|
@@ -1859,10 +1859,10 @@ pi_write_harness_policy() {
|
|
|
1859
1859
|
"allow_parallel_writers": false,
|
|
1860
1860
|
"stop_fallback": true,
|
|
1861
1861
|
"state_file": ".ai/harness/delegation/latest.json",
|
|
1862
|
-
"preferred_runners": ["subagent", "codex-subagent", "codex-exec", "main-thread"],
|
|
1862
|
+
"preferred_runners": ["subagent", "codex-app-thread", "codex-subagent", "codex-exec", "main-thread"],
|
|
1863
1863
|
"fallback_runner": "main-thread",
|
|
1864
1864
|
"brief_source": "tasks/contracts/<stem>.contract.md",
|
|
1865
|
-
"runner_rule": "the active task contract is the authoritative execution brief consumed by contract-run; native subagent
|
|
1865
|
+
"runner_rule": "the active task contract is the authoritative execution brief consumed by contract-run; the preferred parallelism accelerator is native subagent on the Claude host and Codex App Thread dispatch via codex_app__create_thread on the Codex host, each thread created with the role's exact model and reasoning effort read from the installed ~/.codex/agents/<role>.toml. Native codex-subagent (spawn_agent) is a declared fallback only when the App Thread tools are unavailable AND the live spawn schema accepts the role's exact model/effort combination; a role whose exact model/effort that schema does not accept MUST NOT fall back to native spawn, because native spawn silently inherits the parent model. Otherwise degrade to codex-exec, then sequential main-thread, on the SAME contract. Runner-availability fallback MUST be recorded in the contract-run manifest and MUST NOT silently succeed; it is a runner-availability fallback, not a product-semantics compatibility fallback.",
|
|
1866
1866
|
"rule": "UserPromptSubmit.delegation only injects bounded subagent context after explicit user authorization such as /delegate, /parallel, spawn subagents, or parallel investigation, regardless of delegation.mode. When delegation.mode resolves to \"auto\", the in-process session-context builder (src/cli/hook/session-context.ts) instead injects one standing bounded-delegation authorization block at SessionStart (Codex host only) for the whole session; UserPromptSubmit.delegation does not re-assert it on later prompts. The global ~/.repo-harness/config.json delegation.mode value, when exactly \"auto\" or \"explicit\", takes precedence over this repo policy value when resolving that SessionStart mode."
|
|
1867
1867
|
},
|
|
1868
1868
|
"sidecar_research": {
|
|
@@ -39,6 +39,23 @@ if (!Number.isFinite(deadlineMs)) usage();
|
|
|
39
39
|
// process group) outlive this wrapper undetected.
|
|
40
40
|
const FORCED_TERMINATION_CONFIRM_MS = 500;
|
|
41
41
|
|
|
42
|
+
// A verification command must observe the project's real behaviour in a clean
|
|
43
|
+
// environment. The package-dispatched helper mechanism injects `REPO_HARNESS_*`
|
|
44
|
+
// wiring (helper source path, target repo root, trusted tool binaries) into its
|
|
45
|
+
// child, and that set inherits all the way down verify-sprint -> verify-contract
|
|
46
|
+
// -> a nested test run, where it can silently override a fixture's own repo
|
|
47
|
+
// root or tool resolution. Strip the whole prefix -- not a curated subset --
|
|
48
|
+
// so harness-internal wiring never reaches the command under verification.
|
|
49
|
+
// This runner reads no `REPO_HARNESS_*` variable itself.
|
|
50
|
+
function scrubHarnessEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
|
51
|
+
const scrubbed: NodeJS.ProcessEnv = {};
|
|
52
|
+
for (const [key, value] of Object.entries(env)) {
|
|
53
|
+
if (key.startsWith('REPO_HARNESS_')) continue;
|
|
54
|
+
scrubbed[key] = value;
|
|
55
|
+
}
|
|
56
|
+
return scrubbed;
|
|
57
|
+
}
|
|
58
|
+
|
|
42
59
|
const startedAt = Date.now();
|
|
43
60
|
let timedOut = false;
|
|
44
61
|
let terminating = false;
|
|
@@ -49,6 +66,7 @@ const logFd = openSync(logPath, 'w');
|
|
|
49
66
|
const child = spawn(command, args, {
|
|
50
67
|
detached: process.platform !== 'win32',
|
|
51
68
|
stdio: ['ignore', logFd, logFd],
|
|
69
|
+
env: scrubHarnessEnv(process.env),
|
|
52
70
|
});
|
|
53
71
|
|
|
54
72
|
function terminate(signal: NodeJS.Signals): void {
|
package/scripts/verify-sprint.sh
CHANGED
|
@@ -545,13 +545,25 @@ finalize_prepared_acceptance() {
|
|
|
545
545
|
--contract "$contract_file" --verification "$checks_file" --review "$review_file" >/dev/null
|
|
546
546
|
|
|
547
547
|
finalized_checks="$(mktemp)"
|
|
548
|
+
# `$checks_file` is a materialized projection, not a run trace: the
|
|
549
|
+
# materializer (src/effects/evidence/checks-materializer.ts) appends its own
|
|
550
|
+
# `provenance` block to whatever run trace it projects. Re-emitting that
|
|
551
|
+
# block as payload would make the next materialization record
|
|
552
|
+
# `content_hash = sha256(run_trace including the previous provenance)` while
|
|
553
|
+
# publishing a file whose consumer-facing content excludes it, so the
|
|
554
|
+
# published projection could no longer be recomputed from its own bytes
|
|
555
|
+
# (tests/evidence-projection-drift.test.ts's live self-consistency check).
|
|
556
|
+
# `provenance` is materializer-owned derived metadata; strip it here so the
|
|
557
|
+
# emitted run trace is a run trace again and the next projection is
|
|
558
|
+
# self-consistent by construction -- no second hash implementation.
|
|
548
559
|
jq \
|
|
549
560
|
--arg reviewer "$acceptance_reviewer" \
|
|
550
561
|
--arg source "$acceptance_source" \
|
|
551
562
|
--arg disposition "$acceptance_disposition" \
|
|
552
563
|
--arg message "$acceptance_message" \
|
|
553
564
|
'
|
|
554
|
-
.
|
|
565
|
+
del(.provenance)
|
|
566
|
+
| .acceptance_receipt = {
|
|
555
567
|
status: "pass",
|
|
556
568
|
disposition: $disposition,
|
|
557
569
|
reviewer: $reviewer,
|
|
@@ -1312,12 +1312,35 @@ function codexDelegationAutoContext(repoRoot: string, env: NodeJS.ProcessEnv): s
|
|
|
1312
1312
|
'# Delegation Standing Authorization',
|
|
1313
1313
|
'',
|
|
1314
1314
|
'delegation.mode=auto is standing user authorization for bounded native',
|
|
1315
|
-
'subagent delegation for this session.
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
'
|
|
1319
|
-
'
|
|
1320
|
-
'
|
|
1315
|
+
'subagent and Codex App Thread delegation for this session. Dispatch only',
|
|
1316
|
+
'when at least two independent, bounded workstreams exist;',
|
|
1317
|
+
`spawn no more than ${maxAgents} agents; never give concurrent writers`,
|
|
1318
|
+
'overlapping write ownership; do not spawn for a trivial or strictly',
|
|
1319
|
+
'sequential task.',
|
|
1320
|
+
'',
|
|
1321
|
+
'Prefer Codex App Thread dispatch: one codex_app__create_thread per',
|
|
1322
|
+
'workstream, created with the exact model and reasoning effort read from the',
|
|
1323
|
+
'installed ~/.codex/agents/<role>.toml. Use a unique task id per creation',
|
|
1324
|
+
'attempt; a pendingWorktreeId is not a thread id, so confirm materialization',
|
|
1325
|
+
'with an official thread read before counting the worker as running; record',
|
|
1326
|
+
'the requested model and effort separately from the observed runtime model;',
|
|
1327
|
+
'adopt a result only after reading the final turn of that thread; archive',
|
|
1328
|
+
'completed threads one at a time; thread workers must not create further',
|
|
1329
|
+
'threads, agents, or background tasks.',
|
|
1330
|
+
'',
|
|
1331
|
+
'If the live create_thread surface cannot carry that exact model and',
|
|
1332
|
+
'reasoning effort, the thread is inherited-model and must not be adopted as a',
|
|
1333
|
+
'role-routed worker; degrade to codex-exec, then the sequential main thread,',
|
|
1334
|
+
'on the SAME contract and record the degradation.',
|
|
1335
|
+
'',
|
|
1336
|
+
'Native spawn_agent is a declared fallback only when the App Thread tools are',
|
|
1337
|
+
'unavailable and the live spawn schema accepts the exact model and reasoning',
|
|
1338
|
+
'effort of that role; a role whose exact model/effort that schema does not',
|
|
1339
|
+
'accept must not fall back to native spawn, because native spawn silently',
|
|
1340
|
+
'inherits the parent model. Otherwise degrade to codex-exec, then the',
|
|
1341
|
+
'sequential main thread, on the SAME contract and record the degradation. On',
|
|
1342
|
+
'the native fallback pass fork_turns="none" on spawn_agent calls that select',
|
|
1343
|
+
'an agent_type and close finished agent threads.',
|
|
1321
1344
|
].join('\n');
|
|
1322
1345
|
}
|
|
1323
1346
|
|
|
@@ -322,12 +322,19 @@ function runDelegationAdvisor(repoRoot: string, input: JsonObject, env: NodeJS.P
|
|
|
322
322
|
'- Never give two agents overlapping write ownership.',
|
|
323
323
|
`- Keep max spawn depth at ${maxDepth}.`,
|
|
324
324
|
'- Give every agent a precise scope and required return format.',
|
|
325
|
-
'-
|
|
325
|
+
'- On the native spawn_agent fallback path, pass fork_turns="none" on every spawn_agent call that selects an agent_type: the default fork_turns="all" copies the full parent conversation into the child. A named-role child works from its self-contained packet and the contract brief, not inherited parent history.',
|
|
326
|
+
'- Use a unique task id for every thread creation attempt; never reuse one across retries.',
|
|
327
|
+
'- A pendingWorktreeId is not a thread id: confirm the thread materialized with an official thread read before counting the worker as running.',
|
|
328
|
+
'- Record the requested model and reasoning effort separately from the observed runtime model; an unread model claim stays unverified.',
|
|
329
|
+
'- Adopt a thread worker result only after reading the final turn of that thread.',
|
|
330
|
+
'- Archive completed threads one at a time.',
|
|
331
|
+
'- Thread workers must not create further threads, agents, or background tasks.',
|
|
326
332
|
'- Wait for all requested agents.',
|
|
327
333
|
'- Reconcile contradictory findings in the parent.',
|
|
328
334
|
'- Close completed agent threads.',
|
|
329
335
|
'- Do not spawn for a trivial or strictly sequential task.',
|
|
330
|
-
'- The role labels above describe responsibilities only; they do not prove that Codex selected a same-name custom-agent profile or its configured model.',
|
|
336
|
+
'- The role labels above describe responsibilities only; on both the thread path and the native path they do not prove that Codex selected a same-name custom-agent profile or its configured model.',
|
|
337
|
+
"- Treat a thread as inherited-model whenever the live create_thread surface cannot carry that role's exact model and reasoning effort; such a thread MUST NOT be adopted as a role-routed worker. Degrade to codex-exec, then sequential main-thread, on the SAME contract and record the degradation.",
|
|
331
338
|
'- Treat native children as inherited-model until the SubagentStart hook records matching non-default agent_type and model evidence. If it records unavailable or mismatch, report runner degradation and use the contract runner fallback instead of claiming role-specific routing.',
|
|
332
339
|
];
|
|
333
340
|
const permissionContext = [
|
|
@@ -348,7 +355,7 @@ function runDelegationAdvisor(repoRoot: string, input: JsonObject, env: NodeJS.P
|
|
|
348
355
|
'',
|
|
349
356
|
`The current user turn is the execution authority. The active task contract (${activeContract?.contract}) constrains the implementation scope authorized by the current turn, but does not by itself authorize resuming prior implementation or completing Exit Criteria.`,
|
|
350
357
|
'',
|
|
351
|
-
`Runner preference (policy delegation.preferred_runners): ${preferredRunners.join(', ')}.
|
|
358
|
+
`Runner preference (policy delegation.preferred_runners): ${preferredRunners.join(', ')}. Codex App Thread dispatch (codex_app__create_thread) is the preferred parallelism accelerator that consumes the contract brief: one thread per bounded workstream, created with the exact model and reasoning effort read from the installed ~/.codex/agents/<role>.toml, with that TOML developer_instructions plus the contract brief inlined as the first message of the thread. If the live create_thread schema cannot carry that exact model and reasoning effort, the resulting thread is inherited-model and MUST NOT be adopted as a role-routed worker. Native spawn_agent is a declared fallback only when the App Thread tools are unavailable AND the live spawn schema accepts the exact model and reasoning effort of that role; a role whose exact model/effort that schema does not accept MUST NOT fall back to native spawn, because native spawn silently inherits the parent model. Otherwise degrade to codex-exec, then sequential ${fallbackRunner || 'main-thread'}, on the SAME contract via contract-run. Runner-availability degradation MUST be recorded in the contract-run manifest and MUST NOT silently succeed; it is a runner-availability fallback, not a product-semantics change.`,
|
|
352
359
|
'',
|
|
353
360
|
'If this task contains at least two independent, bounded workstreams, dispatch per the contract before doing the corresponding work in the parent; otherwise run it sequentially.',
|
|
354
361
|
'',
|