ultimate-pi 0.15.0 → 0.16.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.
Files changed (61) hide show
  1. package/.agents/skills/harness-governor/SKILL.md +11 -0
  2. package/.agents/skills/harness-orchestration/SKILL.md +3 -1
  3. package/.agents/skills/harness-plan/SKILL.md +5 -5
  4. package/.pi/agents/harness/adversary.md +1 -1
  5. package/.pi/agents/harness/evaluator.md +1 -1
  6. package/.pi/agents/harness/executor.md +1 -1
  7. package/.pi/agents/harness/incident-recorder.md +1 -1
  8. package/.pi/agents/harness/meta-optimizer.md +1 -1
  9. package/.pi/agents/harness/planning/decompose.md +4 -33
  10. package/.pi/agents/harness/planning/execution-plan-author.md +3 -2
  11. package/.pi/agents/harness/planning/hypothesis-validator.md +3 -2
  12. package/.pi/agents/harness/planning/hypothesis.md +4 -27
  13. package/.pi/agents/harness/planning/implementation-researcher.md +3 -2
  14. package/.pi/agents/harness/planning/plan-adversary.md +2 -3
  15. package/.pi/agents/harness/planning/plan-evaluator.md +3 -2
  16. package/.pi/agents/harness/planning/review-integrator.md +2 -3
  17. package/.pi/agents/harness/planning/scout-graphify.md +3 -22
  18. package/.pi/agents/harness/planning/scout-semantic.md +3 -18
  19. package/.pi/agents/harness/planning/scout-structure.md +3 -18
  20. package/.pi/agents/harness/planning/sprint-contract-auditor.md +3 -2
  21. package/.pi/agents/harness/planning/stack-researcher.md +3 -2
  22. package/.pi/agents/harness/tie-breaker.md +1 -1
  23. package/.pi/agents/harness/trace-librarian.md +1 -1
  24. package/.pi/extensions/budget-guard.ts +33 -19
  25. package/.pi/extensions/harness-debate-tools.ts +42 -3
  26. package/.pi/extensions/harness-run-context.ts +96 -2
  27. package/.pi/extensions/harness-subagent-submit.ts +195 -0
  28. package/.pi/extensions/lib/debate-bus-core.ts +42 -5
  29. package/.pi/extensions/lib/harness-subagent-policy.ts +45 -0
  30. package/.pi/extensions/lib/harness-subagent-submit-pipeline.ts +82 -0
  31. package/.pi/extensions/lib/harness-subagent-submit-registry.ts +172 -0
  32. package/.pi/extensions/lib/harness-subagents-bridge.ts +42 -0
  33. package/.pi/extensions/lib/plan-debate-gate.ts +12 -1
  34. package/.pi/extensions/lib/plan-debate-lane.ts +15 -0
  35. package/.pi/harness/agents.manifest.json +22 -22
  36. package/.pi/harness/docs/adrs/0037-subagent-submit-tools.md +31 -0
  37. package/.pi/harness/docs/adrs/0038-budget-telemetry-only.md +23 -0
  38. package/.pi/harness/docs/adrs/README.md +2 -0
  39. package/.pi/harness/specs/harness-executor-handoff.schema.json +19 -0
  40. package/.pi/harness/specs/harness-human-required.schema.json +16 -0
  41. package/.pi/harness/specs/plan-scout-findings.schema.json +19 -0
  42. package/.pi/lib/harness-agent-output.ts +45 -0
  43. package/.pi/lib/harness-budget-enforce.ts +18 -0
  44. package/.pi/lib/harness-schema-validate.ts +89 -0
  45. package/.pi/lib/harness-spawn-parse.ts +86 -0
  46. package/.pi/lib/harness-subagent-submit-path.ts +41 -0
  47. package/.pi/lib/harness-ui-state.ts +15 -2
  48. package/.pi/prompts/harness-auto.md +2 -2
  49. package/.pi/prompts/harness-plan.md +9 -7
  50. package/.pi/prompts/harness-run.md +2 -2
  51. package/.pi/scripts/harness-verify.mjs +2 -0
  52. package/.pi/scripts/harness_web/__pycache__/__init__.cpython-314.pyc +0 -0
  53. package/.pi/scripts/harness_web/__pycache__/config.cpython-314.pyc +0 -0
  54. package/.pi/scripts/harness_web/__pycache__/output.cpython-314.pyc +0 -0
  55. package/.pi/scripts/harness_web/__pycache__/scrape.cpython-314.pyc +0 -0
  56. package/.pi/scripts/harness_web/__pycache__/search.cpython-314.pyc +0 -0
  57. package/.pi/scripts/harness_web/__pycache__/search_ddg.cpython-314.pyc +0 -0
  58. package/.pi/scripts/harness_web/__pycache__/search_searxng.cpython-314.pyc +0 -0
  59. package/CHANGELOG.md +10 -0
  60. package/package.json +4 -2
  61. package/vendor/pi-subagents/src/subagents.ts +29 -3
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Registry: submit tool name → agent allowlist, schema, artifact path.
3
+ */
4
+
5
+ import type { DebateLaneKind } from "./plan-debate-lane.js";
6
+
7
+ export interface SubmitToolSpec {
8
+ toolName: string;
9
+ agents: readonly string[];
10
+ schemaFile: string;
11
+ artifactPath: string | ((doc: Record<string, unknown>) => string);
12
+ debateLane?: DebateLaneKind;
13
+ humanRequired?: boolean;
14
+ }
15
+
16
+ function roundPath(prefix: string, doc: Record<string, unknown>): string {
17
+ const r =
18
+ typeof doc.round_index === "number"
19
+ ? doc.round_index
20
+ : Number(doc.round_index ?? 1);
21
+ return `artifacts/${prefix}-r${r}.yaml`;
22
+ }
23
+
24
+ export const SUBMIT_TOOL_SPECS: readonly SubmitToolSpec[] = [
25
+ {
26
+ toolName: "submit_scout_findings",
27
+ agents: [
28
+ "harness/planning/scout-graphify",
29
+ "harness/planning/scout-structure",
30
+ "harness/planning/scout-semantic",
31
+ ],
32
+ schemaFile: "plan-scout-findings.schema.json",
33
+ artifactPath: (doc) => {
34
+ const lane =
35
+ typeof doc.lane === "string"
36
+ ? doc.lane
37
+ : typeof doc.scout_lane === "string"
38
+ ? doc.scout_lane
39
+ : "graphify";
40
+ return `artifacts/scout-${lane}.yaml`;
41
+ },
42
+ },
43
+ {
44
+ toolName: "submit_decomposition_brief",
45
+ agents: ["harness/planning/decompose"],
46
+ schemaFile: "plan-decomposition-brief.schema.json",
47
+ artifactPath: "artifacts/decomposition.yaml",
48
+ },
49
+ {
50
+ toolName: "submit_hypothesis_brief",
51
+ agents: ["harness/planning/hypothesis"],
52
+ schemaFile: "plan-hypothesis-brief.schema.json",
53
+ artifactPath: "artifacts/hypothesis.yaml",
54
+ },
55
+ {
56
+ toolName: "submit_implementation_research",
57
+ agents: ["harness/planning/implementation-researcher"],
58
+ schemaFile: "plan-implementation-research-brief.schema.json",
59
+ artifactPath: "artifacts/implementation-research.yaml",
60
+ },
61
+ {
62
+ toolName: "submit_stack_brief",
63
+ agents: ["harness/planning/stack-researcher"],
64
+ schemaFile: "plan-stack-brief.schema.json",
65
+ artifactPath: "artifacts/stack.yaml",
66
+ },
67
+ {
68
+ toolName: "submit_execution_plan_brief",
69
+ agents: ["harness/planning/execution-plan-author"],
70
+ schemaFile: "plan-execution-plan-brief.schema.json",
71
+ artifactPath: "artifacts/execution-plan-draft.yaml",
72
+ },
73
+ {
74
+ toolName: "submit_hypothesis_validation",
75
+ agents: ["harness/planning/hypothesis-validator"],
76
+ schemaFile: "plan-hypothesis-eval.schema.json",
77
+ artifactPath: (doc) => roundPath("hypothesis-validation", doc),
78
+ debateLane: "hypothesis-validation",
79
+ },
80
+ {
81
+ toolName: "submit_validation_turn",
82
+ agents: ["harness/planning/plan-evaluator"],
83
+ schemaFile: "plan-validation-turn.schema.json",
84
+ artifactPath: (doc) => roundPath("validation-turn", doc),
85
+ debateLane: "validation-turn",
86
+ },
87
+ {
88
+ toolName: "submit_adversary_brief",
89
+ agents: ["harness/planning/plan-adversary"],
90
+ schemaFile: "plan-adversary-brief.schema.json",
91
+ artifactPath: (doc) => roundPath("adversary-brief", doc),
92
+ debateLane: "adversary-brief",
93
+ },
94
+ {
95
+ toolName: "submit_sprint_audit",
96
+ agents: ["harness/planning/sprint-contract-auditor"],
97
+ schemaFile: "plan-sprint-audit-turn.schema.json",
98
+ artifactPath: (doc) => roundPath("sprint-audit", doc),
99
+ debateLane: "sprint-audit",
100
+ },
101
+ {
102
+ toolName: "submit_review_round_draft",
103
+ agents: ["harness/planning/review-integrator"],
104
+ schemaFile: "plan-review-round-draft.schema.json",
105
+ artifactPath: (doc) => roundPath("review-round-draft", doc),
106
+ },
107
+ {
108
+ toolName: "submit_executor_handoff",
109
+ agents: ["harness/executor"],
110
+ schemaFile: "harness-executor-handoff.schema.json",
111
+ artifactPath: "handoff/executor-summary.yaml",
112
+ },
113
+ {
114
+ toolName: "submit_eval_verdict",
115
+ agents: ["harness/evaluator"],
116
+ schemaFile: "eval-verdict.schema.json",
117
+ artifactPath: "artifacts/eval-verdict.yaml",
118
+ },
119
+ {
120
+ toolName: "submit_adversary_report",
121
+ agents: ["harness/adversary"],
122
+ schemaFile: "adversary-report.schema.json",
123
+ artifactPath: "artifacts/adversary-report.yaml",
124
+ },
125
+ {
126
+ toolName: "submit_human_required",
127
+ agents: ["harness/planning/decompose", "harness/planning/hypothesis"],
128
+ schemaFile: "harness-human-required.schema.json",
129
+ artifactPath: "artifacts/human-required.yaml",
130
+ humanRequired: true,
131
+ },
132
+ ] as const;
133
+
134
+ export const SUBMIT_TOOLS_BY_AGENT: Readonly<
135
+ Record<string, ReadonlySet<string>>
136
+ > = (() => {
137
+ const map = new Map<string, Set<string>>();
138
+ for (const spec of SUBMIT_TOOL_SPECS) {
139
+ for (const agent of spec.agents) {
140
+ if (!map.has(agent)) map.set(agent, new Set());
141
+ map.get(agent)?.add(spec.toolName);
142
+ }
143
+ }
144
+ return Object.fromEntries(map.entries());
145
+ })();
146
+
147
+ export function specForSubmitTool(
148
+ toolName: string,
149
+ ): SubmitToolSpec | undefined {
150
+ return SUBMIT_TOOL_SPECS.find((s) => s.toolName === toolName);
151
+ }
152
+
153
+ export function resolveArtifactRelPath(
154
+ spec: SubmitToolSpec,
155
+ doc: Record<string, unknown>,
156
+ ): string {
157
+ if (typeof spec.artifactPath === "function") {
158
+ return spec.artifactPath(doc);
159
+ }
160
+ return spec.artifactPath;
161
+ }
162
+
163
+ export function isSubmitToolName(toolName: string): boolean {
164
+ return toolName.startsWith("submit_");
165
+ }
166
+
167
+ export const DEBATE_AGENT_SUBMIT_TOOL: Readonly<Record<string, string>> = {
168
+ "harness/planning/hypothesis-validator": "submit_hypothesis_validation",
169
+ "harness/planning/plan-evaluator": "submit_validation_turn",
170
+ "harness/planning/plan-adversary": "submit_adversary_brief",
171
+ "harness/planning/sprint-contract-auditor": "submit_sprint_audit",
172
+ };
@@ -2,6 +2,7 @@
2
2
  * ultimate-pi harness wrapper around vendored pi-subagents.
3
3
  */
4
4
 
5
+ import { join } from "node:path";
5
6
  import type {
6
7
  ExtensionAPI,
7
8
  ExtensionContext,
@@ -12,6 +13,8 @@ import {
12
13
  type HarnessSubagentsOptions,
13
14
  type SpawnAuthForward,
14
15
  } from "../../../vendor/pi-subagents/src/subagents.js";
16
+ import { parseSpawnContextFromTask } from "../../lib/harness-spawn-parse.js";
17
+ import { harnessSubagentSubmitExtensionPath } from "../harness-subagent-submit.js";
15
18
  import { refreshHarnessCocoindexIndex } from "./harness-cocoindex-refresh.js";
16
19
  import { captureHarnessEvent } from "./harness-posthog.js";
17
20
  import {
@@ -58,8 +61,47 @@ async function resolveHarnessSpawnAuth(
58
61
  export function createHarnessSubagentsExtension(
59
62
  packageRoot: string,
60
63
  ): (pi: ExtensionAPI) => void {
64
+ const submitExtPath = harnessSubagentSubmitExtensionPath(packageRoot);
61
65
  const options: HarnessSubagentsOptions = {
62
66
  packageRoot,
67
+ harnessSubprocessExtensionPath: submitExtPath,
68
+ resolveSubprocessEnv: (task, agent) => {
69
+ if (!agent.name.startsWith("harness/")) return undefined;
70
+ const ctx = parseSpawnContextFromTask(task);
71
+ // #region agent log
72
+ fetch(
73
+ "http://127.0.0.1:7928/ingest/a5d40896-34cb-4f12-97db-df7ada0b22f0",
74
+ {
75
+ method: "POST",
76
+ headers: {
77
+ "Content-Type": "application/json",
78
+ "X-Debug-Session-Id": "2ca12b",
79
+ },
80
+ body: JSON.stringify({
81
+ sessionId: "2ca12b",
82
+ hypothesisId: "H1",
83
+ location: "harness-subagents-bridge.ts:resolveSubprocessEnv",
84
+ message: "parsed spawn context for subprocess env",
85
+ data: {
86
+ agent: agent.name,
87
+ hasCtx: Boolean(ctx?.run_id),
88
+ run_id: ctx?.run_id ?? null,
89
+ run_dir: ctx?.run_dir ?? null,
90
+ taskPrefix: task.slice(0, 160),
91
+ },
92
+ timestamp: Date.now(),
93
+ }),
94
+ },
95
+ ).catch(() => {});
96
+ // #endregion
97
+ if (!ctx?.run_id) return undefined;
98
+ return {
99
+ HARNESS_RUN_ID: ctx.run_id,
100
+ HARNESS_RUN_DIR:
101
+ ctx.run_dir ??
102
+ join(packageRoot, ".pi", "harness", "runs", ctx.run_id),
103
+ };
104
+ },
63
105
  defaultAgentScope: "both",
64
106
  defaultConfirmProjectAgents: false,
65
107
  truncateDetails: true,
@@ -5,6 +5,7 @@
5
5
  import { constants } from "node:fs";
6
6
  import { access, readFile } from "node:fs/promises";
7
7
  import { join } from "node:path";
8
+ import { isHarnessBudgetEnforceOn } from "../../lib/harness-budget-enforce.js";
8
9
  import { capsForDebate } from "./debate-bus-core.js";
9
10
  import {
10
11
  getPlanFocusCoverage,
@@ -117,10 +118,20 @@ export async function validatePlanDebateGate(
117
118
  }
118
119
  }
119
120
 
120
- if (coverage.last_round_index > caps.max_rounds) {
121
+ if (
122
+ isHarnessBudgetEnforceOn() &&
123
+ coverage.last_round_index > caps.max_rounds
124
+ ) {
121
125
  errors.push(
122
126
  `round_count ${coverage.last_round_index} exceeds max_rounds ${caps.max_rounds}`,
123
127
  );
128
+ } else if (
129
+ !isHarnessBudgetEnforceOn() &&
130
+ coverage.last_round_index > caps.max_rounds
131
+ ) {
132
+ warnings.push(
133
+ `round_count ${coverage.last_round_index} exceeds advisory max_rounds ${caps.max_rounds} (budget enforce off)`,
134
+ );
124
135
  }
125
136
 
126
137
  if (!messenger) {
@@ -45,6 +45,21 @@ export function laneArtifactPath(
45
45
  }
46
46
  }
47
47
 
48
+ /** Apply messenger side effects when artifact YAML was already written via submit tool. */
49
+ export async function applyDebateLaneFromDoc(opts: {
50
+ runDir: string;
51
+ lane: DebateLaneKind;
52
+ doc: Record<string, unknown>;
53
+ roundIndex?: number;
54
+ }): Promise<ApplyDebateLaneResult> {
55
+ return applyDebateLane({
56
+ runDir: opts.runDir,
57
+ lane: opts.lane,
58
+ content: JSON.stringify(opts.doc),
59
+ roundIndex: opts.roundIndex,
60
+ });
61
+ }
62
+
48
63
  export function extractClaimIds(doc: Record<string, unknown>): string[] {
49
64
  const explicit = doc.messenger_claim_ids;
50
65
  if (Array.isArray(explicit)) {
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schema_version": "1.0.0",
3
3
  "package": "ultimate-pi",
4
- "package_version": "0.14.0",
5
- "generated_at": "2026-05-19T10:53:28.359Z",
4
+ "package_version": "0.15.0",
5
+ "generated_at": "2026-05-19T12:56:13.369Z",
6
6
  "agents": {
7
7
  "pi-pi/agent-expert": {
8
8
  "path": ".pi/agents/pi-pi/agent-expert.md",
@@ -46,23 +46,23 @@
46
46
  },
47
47
  "harness/adversary": {
48
48
  "path": ".pi/agents/harness/adversary.md",
49
- "sha256": "dd2ef87139cb175d795f4d7bde78dca1a181d2e42c3c3bd0d48832cf5069aa29"
49
+ "sha256": "560c7571ab91478bde1271e9ae6c3a112c3e1d28e1a261c5450fd1d00f9f89af"
50
50
  },
51
51
  "harness/evaluator": {
52
52
  "path": ".pi/agents/harness/evaluator.md",
53
- "sha256": "2b8039fd79f9177fdafd5319a53a96812719d4f1f68e2de70632030142649cfe"
53
+ "sha256": "a4667d3efb305ba2fe79118e3d7d2b0de5e0369637af040d1238161d75cd28ac"
54
54
  },
55
55
  "harness/executor": {
56
56
  "path": ".pi/agents/harness/executor.md",
57
- "sha256": "b549e9fc802ba23857a1bc6b2ff36f3c169e708fe5ec13857b3bcfe841384f1f"
57
+ "sha256": "6baffcc3d89954494ce3ae439175686a39928b6a543a0a451da27475094b1712"
58
58
  },
59
59
  "harness/incident-recorder": {
60
60
  "path": ".pi/agents/harness/incident-recorder.md",
61
- "sha256": "d7577c911a9e6c9607eb64f76337aab85c4eb9a92e7cd917eb8d989ef3cd1de5"
61
+ "sha256": "d42fa45de1a2fe3842d075c6f319315266588942e314f1b650caabac39bdc29a"
62
62
  },
63
63
  "harness/meta-optimizer": {
64
64
  "path": ".pi/agents/harness/meta-optimizer.md",
65
- "sha256": "a4eed88084c7cfb5ace3edc72b72d7ead4134b3eae0d444b391decfe2640a632"
65
+ "sha256": "cbaab35367126796b7136389a02ab41b4fd1fe7098cf83be562d7b7493ccc297"
66
66
  },
67
67
  "harness/sentrux-bootstrap": {
68
68
  "path": ".pi/agents/harness/sentrux-bootstrap.md",
@@ -70,63 +70,63 @@
70
70
  },
71
71
  "harness/tie-breaker": {
72
72
  "path": ".pi/agents/harness/tie-breaker.md",
73
- "sha256": "68f02b86e95927f06d7f963e1f61f193159bbef1ba4558d90c84d5457d62b3f7"
73
+ "sha256": "1c54c1c3274291dea1ea8826563a7ad4fe1d9c4302984e907bfcd22cfc4f5eba"
74
74
  },
75
75
  "harness/trace-librarian": {
76
76
  "path": ".pi/agents/harness/trace-librarian.md",
77
- "sha256": "03b499a948b8467f1cfe2b4e63190feb7b8b9d96461055638e774253b9b6b2d4"
77
+ "sha256": "336b3f3f6141cef8750ab18d29bbe454caf26973830a86afe099d9e4ad8b0abe"
78
78
  },
79
79
  "harness/planning/decompose": {
80
80
  "path": ".pi/agents/harness/planning/decompose.md",
81
- "sha256": "5c3b983772d013741d50f39945bc77f178aa338aecab56b93c09216d72192c69"
81
+ "sha256": "0919dafa1d1cd008d513c28524c1e7218867586a138982dccf01db5270c42c73"
82
82
  },
83
83
  "harness/planning/execution-plan-author": {
84
84
  "path": ".pi/agents/harness/planning/execution-plan-author.md",
85
- "sha256": "16f8800c50bcaf1b82ed9138889c8a0e538ee6a139aeae129ccd20cec2ec25f7"
85
+ "sha256": "55ece0f1ee14abd17fe7b3e478b548240f637eacbfc2a34758e98d3878dc82fd"
86
86
  },
87
87
  "harness/planning/hypothesis-validator": {
88
88
  "path": ".pi/agents/harness/planning/hypothesis-validator.md",
89
- "sha256": "9e68ec5d6aef96a3666c30227c3cbddf1aaed1182fdc94dbbd21ad3d48315ff2"
89
+ "sha256": "36f0baa7796229f21bd02faf5e70402c7bf054289eab557a25bfbe3cb7781de7"
90
90
  },
91
91
  "harness/planning/hypothesis": {
92
92
  "path": ".pi/agents/harness/planning/hypothesis.md",
93
- "sha256": "b20c527d15c2243cd5d3a8f16cea6d44bdfd16e01915d42f3b830bf9938e5f8b"
93
+ "sha256": "e83d5c4faaee8d32af4a5f22c9917b70a173f3e22d7c0f182b361706f2309171"
94
94
  },
95
95
  "harness/planning/implementation-researcher": {
96
96
  "path": ".pi/agents/harness/planning/implementation-researcher.md",
97
- "sha256": "dbd1c4fc74d538b110d406febfd4603eebea77d82e8b367df4596ac7ff6e54cc"
97
+ "sha256": "653f320b5d51bb331774246687f24a75347b406bba4e6dfd2968d6e5d4cc8bb3"
98
98
  },
99
99
  "harness/planning/plan-adversary": {
100
100
  "path": ".pi/agents/harness/planning/plan-adversary.md",
101
- "sha256": "7c14eaab65f356003ee2ff380f5d4e620170b5126daa67c3d226b12342f47bd2"
101
+ "sha256": "3241d7ec939dc29e0af64690b99e9f74b209f40b0daa4a2a1f9ff86f99f94a8d"
102
102
  },
103
103
  "harness/planning/plan-evaluator": {
104
104
  "path": ".pi/agents/harness/planning/plan-evaluator.md",
105
- "sha256": "846575abe9df3e7e5be812c0c474989c1a9de8074a7884d77b9d3dd423643480"
105
+ "sha256": "71660ab58bfcfdfae56c873140d4ea5946ae30cd5719c96afeabfd02b1d1f81d"
106
106
  },
107
107
  "harness/planning/review-integrator": {
108
108
  "path": ".pi/agents/harness/planning/review-integrator.md",
109
- "sha256": "bed43f3f049c279ac50a24bcffac1bbe46a8605d89c9cc6d0c3c6a87d488b1b8"
109
+ "sha256": "cf3f0dbe81274ec9ef0ff2e0c170e8dc929b20be65492d0ee9a80d985acf6d71"
110
110
  },
111
111
  "harness/planning/scout-graphify": {
112
112
  "path": ".pi/agents/harness/planning/scout-graphify.md",
113
- "sha256": "7f385d5bda2fe04b9da52cb4cb9247324efd345579b483d3ad55a6abefad50d5"
113
+ "sha256": "6e2bda8ad38311810c9916d9dab311873bc776e4b8832bb0e574136e45e1255e"
114
114
  },
115
115
  "harness/planning/scout-semantic": {
116
116
  "path": ".pi/agents/harness/planning/scout-semantic.md",
117
- "sha256": "36bd424ebd422bda82bd447b22f591f99f32ec897ea43f385586119da5c26caa"
117
+ "sha256": "416e518d8204a55b26dc53da1f750865c6f09ee2c7f343b41e7c08da3230c089"
118
118
  },
119
119
  "harness/planning/scout-structure": {
120
120
  "path": ".pi/agents/harness/planning/scout-structure.md",
121
- "sha256": "e67b7cd75519e5ae36e1bb5f49ca158888c28d365465863aee50a9b2e8e5b7d7"
121
+ "sha256": "76c42a15cc74cf1de2cf861cb0146c865c205f69cce7b9605d41893b19600029"
122
122
  },
123
123
  "harness/planning/sprint-contract-auditor": {
124
124
  "path": ".pi/agents/harness/planning/sprint-contract-auditor.md",
125
- "sha256": "d915274dc9b5addae5499bc2390b348eddeb8f133b526a816e23d0d19a2618bf"
125
+ "sha256": "12cb5e6b53dcc19ace62e8e4c152d96440717df53a182e76216dd2327410df4d"
126
126
  },
127
127
  "harness/planning/stack-researcher": {
128
128
  "path": ".pi/agents/harness/planning/stack-researcher.md",
129
- "sha256": "fa228920abe2b66d4d8921c4a5d85593e3019a24bbe9ae512ed9149f235e3536"
129
+ "sha256": "ce546ef3aca19da7f334f07cef8f510b79068bffeb7f276c428f3e6236bbe96b"
130
130
  }
131
131
  }
132
132
  }
@@ -0,0 +1,31 @@
1
+ # ADR 0037: Subagent submit tools (replace JSON prose contracts)
2
+
3
+ **Status:** Accepted
4
+ **Date:** 2026-05-19
5
+
6
+ ## Context
7
+
8
+ Harness plan/execute agents used fenced JSON in `finalOutput`, requiring the parent orchestrator to parse prose and call `write_harness_yaml`. This was fragile (truncated parallel summaries, invalid JSON, double-hop writes).
9
+
10
+ Planning agents set `extensions: false` and subprocess spawn used `--no-extensions`, so harness tools were unavailable in children.
11
+
12
+ ## Decision
13
+
14
+ 1. **Option A — subprocess-only extension bundle:** vendored spawn passes `--no-extensions -e .pi/extensions/harness-subagent-submit.ts` for `harness/*` agents with `extensions: false`.
15
+ 2. **Scoped `submit_*` tools** per agent, validated against `.pi/harness/specs/*.schema.json` (Ajv) and written deterministically under `HARNESS_RUN_DIR`.
16
+ 3. **Parent gates** via `harness_artifact_ready` (file existence) instead of parsing subprocess JSON.
17
+ 4. **Debate lanes:** `tool_result` hook prefers last `submit_*` in `details.results[].messages`; skips `finalOutput` auto-apply when submit present (`HARNESS_SUBMIT_TOOLS` default on).
18
+ 5. **Parent** blocks all `submit_*`; keeps `write_harness_yaml` for merges and debate round submission only.
19
+
20
+ ## Consequences
21
+
22
+ - Agent frontmatter lists one terminal `submit_*` tool per role.
23
+ - `HarnessSpawnContext` must include `run_id` / `run_dir`; bridge sets `HARNESS_RUN_ID`, `HARNESS_RUN_DIR`, `HARNESS_AGENT_ID` on spawn.
24
+ - `parseHarnessAgentJson` retained for migration/tests; hot path is tool args.
25
+ - See ADR 0038 for budget telemetry-only default.
26
+
27
+ ## References
28
+
29
+ - `.pi/extensions/harness-subagent-submit.ts`
30
+ - `.pi/extensions/lib/harness-subagent-submit-registry.ts`
31
+ - `.pi/harness/specs/plan-scout-findings.schema.json`
@@ -0,0 +1,23 @@
1
+ # ADR 0038: Budget enforcement telemetry-only (default)
2
+
3
+ **Status:** Accepted
4
+ **Date:** 2026-05-19
5
+
6
+ ## Context
7
+
8
+ Token and debate caps emitted `harness-budget-exhausted`, which set `budgetExhausted` in the live widget and blocked flows even when `HARNESS_BUDGET_HARD_STOP` was false. `max_rounds` and messenger exchange limits in `validatePlanDebateGate` also hard-failed approval.
9
+
10
+ ## Decision
11
+
12
+ - **`HARNESS_BUDGET_ENFORCE` default `off`:** phase/debate caps log `harness-budget-soft-limit` and `harness-budget-telemetry` only; `harness-budget-exhausted` is emitted only when enforce is on **and** hard-stop flags are set.
13
+ - **UI:** `budgetExhausted` / blocked substate only when blocking exhaustion events qualify.
14
+ - **Debate:** `capsForDebate` uses sentinel caps when enforce is off; `max_rounds` gate errors become warnings.
15
+ - **CLI:** `--budget` on harness prompts is reserved/no-op until a real budget story ships.
16
+
17
+ Re-enable: `HARNESS_BUDGET_ENFORCE=1` plus `HARNESS_BUDGET_HARD_STOP` / `HARNESS_DEBATE_HARD_STOP` as needed.
18
+
19
+ ## Consequences
20
+
21
+ - Long debates and large plans are not blocked by soft token telemetry.
22
+ - Quality gates (`min_focus_rounds`, required focuses, `review_gate_ready`) remain enforced.
23
+ - PostHog should prefer `harness_budget_telemetry` over exhausted for dashboards until enforce returns.
@@ -22,6 +22,8 @@ Team-shared ADRs for the ultimate-pi harness live under `.pi/harness/docs/adrs/`
22
22
  | [0034](0034-darwin-plan-research-pipeline.md) | Darwin plan research pipeline | Accepted |
23
23
  | [0035](0035-plan-phase-review-gate.md) | Plan-phase Review Gate | Accepted |
24
24
  | [0036](0036-implementation-research-and-selective-debate.md) | Implementation research and selective debate | Accepted |
25
+ | [0037](0037-subagent-submit-tools.md) | Subagent submit tools (subprocess extension) | Accepted |
26
+ | [0038](0038-budget-telemetry-only.md) | Budget caps telemetry-only by default | Accepted |
25
27
 
26
28
  ## Template
27
29
 
@@ -0,0 +1,19 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://ultimate-pi.local/.pi/harness/specs/harness-executor-handoff.schema.json",
4
+ "title": "HarnessExecutorHandoff",
5
+ "type": "object",
6
+ "additionalProperties": true,
7
+ "required": ["schema_version", "execution_status"],
8
+ "properties": {
9
+ "schema_version": { "type": "string", "const": "1.0.0" },
10
+ "execution_status": {
11
+ "type": "string",
12
+ "enum": ["completed", "blocked", "scope_drift"]
13
+ },
14
+ "files_changed": { "type": "array" },
15
+ "validation_summary": { "type": "string" },
16
+ "rollback_refs": { "type": "object" },
17
+ "handoff_ready": { "type": "object" }
18
+ }
19
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://ultimate-pi.local/.pi/harness/specs/harness-human-required.schema.json",
4
+ "title": "HarnessHumanRequired",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schema_version", "reason"],
8
+ "properties": {
9
+ "schema_version": { "type": "string", "const": "1.0.0" },
10
+ "reason": { "type": "string", "minLength": 1 },
11
+ "questions": {
12
+ "type": "array",
13
+ "items": { "type": "string" }
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://ultimate-pi.local/.pi/harness/specs/plan-scout-findings.schema.json",
4
+ "title": "PlanScoutFindings",
5
+ "type": "object",
6
+ "additionalProperties": true,
7
+ "required": ["schema_version", "lane", "summary"],
8
+ "properties": {
9
+ "schema_version": { "type": "string", "const": "1.0.0" },
10
+ "lane": {
11
+ "type": "string",
12
+ "enum": ["graphify", "structure", "semantic"]
13
+ },
14
+ "scout_lane": { "type": "string" },
15
+ "summary": { "type": "string", "minLength": 1 },
16
+ "key_paths": { "type": "array", "items": { "type": "string" } },
17
+ "findings": { "type": "array" }
18
+ }
19
+ }
@@ -21,6 +21,51 @@ export function extractJsonBlock(text: string): string | null {
21
21
  return null;
22
22
  }
23
23
 
24
+ export interface ToolCallPartLike {
25
+ type?: string;
26
+ name?: string;
27
+ arguments?: Record<string, unknown>;
28
+ }
29
+
30
+ export interface MessageLike {
31
+ role?: string;
32
+ content?: ToolCallPartLike[] | unknown;
33
+ }
34
+
35
+ /** Last matching submit_* tool call in subprocess messages (chain-safe). */
36
+ export function extractLastSubmitCall(
37
+ messages: MessageLike[],
38
+ toolNames: string | string[],
39
+ ): { toolName: string; document: Record<string, unknown> } | null {
40
+ const allowed = new Set(
41
+ (Array.isArray(toolNames) ? toolNames : [toolNames]).map((n) => n.trim()),
42
+ );
43
+ let last: { toolName: string; document: Record<string, unknown> } | null =
44
+ null;
45
+ for (const msg of messages) {
46
+ if (msg.role !== "assistant" || !Array.isArray(msg.content)) continue;
47
+ for (const part of msg.content) {
48
+ if (part.type !== "toolCall" || !part.name) continue;
49
+ if (!allowed.has(part.name)) continue;
50
+ const doc = part.arguments?.document;
51
+ if (doc && typeof doc === "object" && !Array.isArray(doc)) {
52
+ last = {
53
+ toolName: part.name,
54
+ document: doc as Record<string, unknown>,
55
+ };
56
+ }
57
+ }
58
+ }
59
+ return last;
60
+ }
61
+
62
+ export function extractLastSubmitCallForAgent(
63
+ messages: MessageLike[],
64
+ agentToolNames: readonly string[],
65
+ ): { toolName: string; document: Record<string, unknown> } | null {
66
+ return extractLastSubmitCall(messages, [...agentToolNames]);
67
+ }
68
+
24
69
  export function parseHarnessAgentJson<T extends Record<string, unknown>>(
25
70
  text: string,
26
71
  ): { ok: true; value: T } | { ok: false; error: string } {
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Central switch for harness token/debate budget enforcement.
3
+ * Default: telemetry-only (HARNESS_BUDGET_ENFORCE off).
4
+ */
5
+
6
+ export function isHarnessBudgetEnforceOn(): boolean {
7
+ const raw = (process.env.HARNESS_BUDGET_ENFORCE ?? "off").toLowerCase();
8
+ return raw === "1" || raw === "true" || raw === "on";
9
+ }
10
+
11
+ /** When false, soft-limit and debate telemetry must not block UI or gates. */
12
+ export function shouldEmitBlockingBudgetExhausted(): boolean {
13
+ if (!isHarnessBudgetEnforceOn()) return false;
14
+ return (
15
+ process.env.HARNESS_BUDGET_HARD_STOP === "true" ||
16
+ process.env.HARNESS_DEBATE_HARD_STOP === "true"
17
+ );
18
+ }