ultimate-pi 0.15.0 → 0.17.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 (90) 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 +54 -6
  26. package/.pi/extensions/harness-run-context.ts +108 -2
  27. package/.pi/extensions/harness-subagent-submit.ts +172 -0
  28. package/.pi/extensions/harness-telemetry.ts +29 -4
  29. package/.pi/extensions/lib/debate-bus-core.ts +49 -6
  30. package/.pi/extensions/lib/harness-subagent-auth.ts +104 -19
  31. package/.pi/extensions/lib/harness-subagent-policy.ts +59 -0
  32. package/.pi/extensions/lib/harness-subagent-submit-pipeline.ts +82 -0
  33. package/.pi/extensions/lib/harness-subagent-submit-registry.ts +172 -0
  34. package/.pi/extensions/lib/harness-subagents-bridge.ts +127 -0
  35. package/.pi/extensions/lib/plan-debate-eligibility.ts +61 -8
  36. package/.pi/extensions/lib/plan-debate-focus.ts +21 -9
  37. package/.pi/extensions/lib/plan-debate-gate.ts +92 -18
  38. package/.pi/extensions/lib/plan-debate-lane.ts +15 -0
  39. package/.pi/extensions/lib/plan-debate-lanes.ts +27 -3
  40. package/.pi/extensions/lib/plan-debate-round-status.ts +18 -7
  41. package/.pi/extensions/lib/plan-messenger.ts +4 -0
  42. package/.pi/extensions/lib/plan-review-gate.ts +51 -0
  43. package/.pi/extensions/trace-recorder.ts +1 -0
  44. package/.pi/harness/agents.manifest.json +22 -22
  45. package/.pi/harness/docs/adrs/0037-subagent-submit-tools.md +31 -0
  46. package/.pi/harness/docs/adrs/0038-budget-telemetry-only.md +23 -0
  47. package/.pi/harness/docs/adrs/README.md +2 -0
  48. package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med-fast/artifacts/implementation-research.yaml +28 -0
  49. package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med-fast/artifacts/review-round-consolidated.yaml +25 -0
  50. package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med-fast/plan-packet.yaml +196 -0
  51. package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med-fast/plan-review.md +14 -0
  52. package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med-fast/research-brief.yaml +62 -0
  53. package/.pi/harness/evals/smoke/smoke-harness-plan.mjs +40 -17
  54. package/.pi/harness/specs/harness-executor-handoff.schema.json +19 -0
  55. package/.pi/harness/specs/harness-human-required.schema.json +16 -0
  56. package/.pi/harness/specs/plan-review-round-draft.schema.json +1 -1
  57. package/.pi/harness/specs/plan-scout-findings.schema.json +19 -0
  58. package/.pi/lib/harness-agent-output.ts +45 -0
  59. package/.pi/lib/harness-budget-enforce.ts +18 -0
  60. package/.pi/lib/harness-schema-validate.ts +89 -0
  61. package/.pi/lib/harness-spawn-parse.ts +86 -0
  62. package/.pi/lib/harness-subagent-submit-path.ts +41 -0
  63. package/.pi/lib/harness-ui-state.ts +15 -2
  64. package/.pi/model-router.example.json +13 -4
  65. package/.pi/prompts/harness-auto.md +2 -2
  66. package/.pi/prompts/harness-plan.md +34 -14
  67. package/.pi/prompts/harness-run.md +2 -2
  68. package/.pi/prompts/harness-setup.md +4 -4
  69. package/.pi/scripts/harness-generate-model-router.mjs +118 -36
  70. package/.pi/scripts/harness-model-router-routing.test.mjs +97 -0
  71. package/.pi/scripts/harness-sync-model-router.mjs +15 -2
  72. package/.pi/scripts/harness-verify.mjs +31 -0
  73. package/.pi/scripts/harness_web/__pycache__/__init__.cpython-314.pyc +0 -0
  74. package/.pi/scripts/harness_web/__pycache__/config.cpython-314.pyc +0 -0
  75. package/.pi/scripts/harness_web/__pycache__/output.cpython-314.pyc +0 -0
  76. package/.pi/scripts/harness_web/__pycache__/scrape.cpython-314.pyc +0 -0
  77. package/.pi/scripts/harness_web/__pycache__/search.cpython-314.pyc +0 -0
  78. package/.pi/scripts/harness_web/__pycache__/search_ddg.cpython-314.pyc +0 -0
  79. package/.pi/scripts/harness_web/__pycache__/search_searxng.cpython-314.pyc +0 -0
  80. package/CHANGELOG.md +21 -0
  81. package/package.json +4 -2
  82. package/vendor/pi-model-router/UPSTREAM_PIN.md +3 -1
  83. package/vendor/pi-model-router/extensions/commands.ts +4 -4
  84. package/vendor/pi-model-router/extensions/index.ts +21 -0
  85. package/vendor/pi-model-router/extensions/provider.ts +130 -79
  86. package/vendor/pi-model-router/extensions/routing.ts +148 -0
  87. package/vendor/pi-model-router/extensions/state.ts +3 -0
  88. package/vendor/pi-model-router/extensions/types.ts +9 -0
  89. package/vendor/pi-model-router/extensions/ui.ts +16 -2
  90. package/vendor/pi-subagents/src/subagents.ts +29 -3
@@ -1,23 +1,32 @@
1
1
  /**
2
2
  * Resolve concrete LLM credentials for harness subagent subprocesses.
3
3
  *
4
- * Parent sessions often use `router/auto` (pi-model-router). Subagents run with
4
+ * Parent sessions often use `router/<profile>` (pi-model-router). Subagents run with
5
5
  * `--no-extensions`, so they cannot use the logical router provider — they need
6
6
  * a real provider/model plus that provider's API key.
7
+ *
8
+ * Session-locked routing: subprocess model is chosen once from agent system prompt
9
+ * complexity (same analysis as parent session lock), not from per-turn parent tier.
7
10
  */
8
11
 
9
12
  import { existsSync, readFileSync } from "node:fs";
10
13
  import { join } from "node:path";
14
+ import { resolveTierFromPrompt } from "../../../vendor/pi-model-router/extensions/routing.js";
15
+ import type {
16
+ RouterProfile,
17
+ RouterTier,
18
+ RoutingRule,
19
+ } from "../../../vendor/pi-model-router/extensions/types.js";
11
20
  import type { AgentConfig } from "../../../vendor/pi-subagents/src/agents.js";
12
21
 
13
22
  const ROUTER_SENTINEL_KEY = "pi-model-router";
14
23
  const SENTINEL_API_KEYS = new Set([ROUTER_SENTINEL_KEY, "<authenticated>"]);
15
24
 
16
- type RouterTier = "high" | "medium" | "low";
17
-
18
25
  interface ModelRouterJson {
19
26
  defaultProfile?: string;
20
- profiles?: Record<string, Partial<Record<RouterTier, { model?: string }>>>;
27
+ phaseBias?: number;
28
+ rules?: RoutingRule[];
29
+ profiles?: Record<string, RouterProfile>;
21
30
  }
22
31
 
23
32
  export function isUsableApiKey(key: string | undefined): key is string {
@@ -35,7 +44,33 @@ export function parseModelRef(
35
44
  return { provider, modelId };
36
45
  }
37
46
 
38
- export function thinkingToRouterTier(thinking?: string): RouterTier {
47
+ /** Planning subagents that should prefer low/medium router tier for latency. */
48
+ const ROUTINE_PLANNING_AGENT_PATHS = new Set([
49
+ "harness/planning/plan-evaluator",
50
+ "harness/planning/plan-adversary",
51
+ "harness/planning/review-integrator",
52
+ "harness/planning/hypothesis-validator",
53
+ "harness/planning/sprint-contract-auditor",
54
+ "harness/planning/scout-structure",
55
+ "harness/planning/scout-semantic",
56
+ "harness/planning/decompose",
57
+ "harness/planning/hypothesis",
58
+ "harness/planning/stack-research",
59
+ "harness/planning/plan-validator",
60
+ ]);
61
+
62
+ export function isRoutinePlanningAgent(agentName: string): boolean {
63
+ return ROUTINE_PLANNING_AGENT_PATHS.has(agentName);
64
+ }
65
+
66
+ export function thinkingToRouterTier(
67
+ thinking?: string,
68
+ agentName?: string,
69
+ ): RouterTier {
70
+ if (agentName && isRoutinePlanningAgent(agentName)) {
71
+ if (thinking === "high" || thinking === "xhigh") return "medium";
72
+ return "low";
73
+ }
39
74
  if (thinking === "high" || thinking === "xhigh") return "high";
40
75
  if (thinking === "off" || thinking === "minimal" || thinking === "low") {
41
76
  return "low";
@@ -43,6 +78,64 @@ export function thinkingToRouterTier(thinking?: string): RouterTier {
43
78
  return "medium";
44
79
  }
45
80
 
81
+ function loadModelRouterConfig(cwd: string): ModelRouterJson | undefined {
82
+ const path = join(cwd, ".pi", "model-router.json");
83
+ if (!existsSync(path)) return undefined;
84
+ try {
85
+ return JSON.parse(readFileSync(path, "utf8")) as ModelRouterJson;
86
+ } catch {
87
+ return undefined;
88
+ }
89
+ }
90
+
91
+ function resolveRouterProfileEntry(
92
+ config: ModelRouterJson,
93
+ profileId: string,
94
+ ): { profileId: string; profile: RouterProfile } | undefined {
95
+ const profiles = config.profiles;
96
+ if (!profiles) return undefined;
97
+ const candidates = [
98
+ profileId,
99
+ config.defaultProfile ?? "auto",
100
+ "auto",
101
+ "opencode-go",
102
+ ];
103
+ const seen = new Set<string>();
104
+ for (const id of candidates) {
105
+ if (!id || seen.has(id)) continue;
106
+ seen.add(id);
107
+ const profile = profiles[id];
108
+ if (profile?.high?.model && profile.medium?.model && profile.low?.model) {
109
+ return { profileId: id, profile };
110
+ }
111
+ }
112
+ return undefined;
113
+ }
114
+
115
+ /** Tier from agent system prompt (+ optional task line) for session model lock. */
116
+ export function resolveSubagentRouterTier(
117
+ cwd: string,
118
+ profileId: string,
119
+ agent: AgentConfig,
120
+ taskSnippet?: string,
121
+ ): RouterTier {
122
+ const config = loadModelRouterConfig(cwd);
123
+ if (config) {
124
+ const entry = resolveRouterProfileEntry(config, profileId);
125
+ if (entry) {
126
+ return resolveTierFromPrompt(
127
+ agent.systemPrompt ?? "",
128
+ taskSnippet?.trim() ?? "",
129
+ entry.profileId,
130
+ entry.profile,
131
+ config.rules,
132
+ config.phaseBias ?? 0.5,
133
+ );
134
+ }
135
+ }
136
+ return thinkingToRouterTier(agent.thinking, agent.name);
137
+ }
138
+
46
139
  /** Map router profile tier → concrete `provider/model` from `.pi/model-router.json`. */
47
140
  export function resolveRouterConcreteModelRef(
48
141
  cwd: string,
@@ -51,19 +144,10 @@ export function resolveRouterConcreteModelRef(
51
144
  ): string | undefined {
52
145
  const path = join(cwd, ".pi", "model-router.json");
53
146
  if (!existsSync(path)) return undefined;
54
- let raw: ModelRouterJson;
55
- try {
56
- raw = JSON.parse(readFileSync(path, "utf8")) as ModelRouterJson;
57
- } catch {
58
- return undefined;
59
- }
60
- const profiles = raw.profiles;
61
- if (!profiles) return undefined;
62
- const profile =
63
- profiles[profileId] ??
64
- profiles[raw.defaultProfile ?? "auto"] ??
65
- profiles.auto;
66
- const model = profile?.[tier]?.model;
147
+ const raw = loadModelRouterConfig(cwd);
148
+ if (!raw) return undefined;
149
+ const entry = resolveRouterProfileEntry(raw, profileId);
150
+ const model = entry?.profile[tier]?.model;
67
151
  return typeof model === "string" && model.includes("/") ? model : undefined;
68
152
  }
69
153
 
@@ -83,6 +167,7 @@ export function resolveConcreteSubagentModel(
83
167
  cwd: string,
84
168
  parentModel: { provider: string; id: string } | undefined,
85
169
  agent: AgentConfig,
170
+ taskSnippet?: string,
86
171
  ): ConcreteSubagentModel | undefined {
87
172
  if (agent.model && !agent.model.startsWith("router/")) {
88
173
  const parsed = parseModelRef(agent.model);
@@ -109,7 +194,7 @@ export function resolveConcreteSubagentModel(
109
194
  agentIsRouter && agent.model
110
195
  ? agent.model.slice("router/".length)
111
196
  : (parentModel?.id ?? "auto");
112
- const tier = thinkingToRouterTier(agent.thinking);
197
+ const tier = resolveSubagentRouterTier(cwd, profileId, agent, taskSnippet);
113
198
  const concrete = resolveRouterConcreteModelRef(cwd, profileId, tier);
114
199
  if (!concrete) return undefined;
115
200
  const parsed = parseModelRef(concrete);
@@ -2,6 +2,10 @@
2
2
  * Per-agent tool policy for harness/* subagents (defense in depth with frontmatter).
3
3
  */
4
4
 
5
+ import {
6
+ isSubmitToolName,
7
+ SUBMIT_TOOLS_BY_AGENT,
8
+ } from "./harness-subagent-submit-registry.js";
5
9
  import {
6
10
  evaluateSubagentToolCall,
7
11
  type ToolCallDecision,
@@ -20,6 +24,9 @@ export type HarnessAgentKind =
20
24
 
21
25
  const MUTATING_TOOLS = new Set(["write", "edit"]);
22
26
 
27
+ /** Planning agents must use submit_* → canonical artifacts/*.yaml, not JSON dumps. */
28
+ const PLANNING_ARTIFACT_JSON_WRITE = /artifacts\/[^\s'"`;]+\.json\b/i;
29
+
23
30
  const PLANNING_BASH_DENY_PATTERNS = [
24
31
  /\bgraphify\s+update\b/i,
25
32
  /\bgraphify\s+extract\b/i,
@@ -107,6 +114,45 @@ export function evaluateHarnessSubagentToolCall(
107
114
  }
108
115
 
109
116
  if (!isHarnessPackageAgent(agentType)) {
117
+ if (
118
+ isSubmitToolName(toolName) &&
119
+ process.env.PI_HARNESS_SUBPROCESS !== "1"
120
+ ) {
121
+ return {
122
+ action: "block",
123
+ reason:
124
+ "harness-subagent-policy: submit_* tools are subprocess-only; parent orchestrator must use harness_artifact_ready and write_harness_yaml for merges.",
125
+ };
126
+ }
127
+ return { action: "allow" };
128
+ }
129
+
130
+ if (isSubmitToolName(toolName)) {
131
+ if (process.env.PI_HARNESS_SUBPROCESS !== "1") {
132
+ return {
133
+ action: "block",
134
+ reason:
135
+ "harness-subagent-policy: submit_* tools are not available in the parent harness session.",
136
+ };
137
+ }
138
+ if (toolName === "submit_human_required") {
139
+ const kind = classifyHarnessAgent(agentType);
140
+ if (kind === "executor") {
141
+ return {
142
+ action: "block",
143
+ reason:
144
+ "submit_human_required is not available for harness/executor.",
145
+ };
146
+ }
147
+ return { action: "allow" };
148
+ }
149
+ const allowed = SUBMIT_TOOLS_BY_AGENT[agentType];
150
+ if (!allowed?.has(toolName)) {
151
+ return {
152
+ action: "block",
153
+ reason: `harness-subagent-policy: ${toolName} is not allowed for ${agentType}.`,
154
+ };
155
+ }
110
156
  return { action: "allow" };
111
157
  }
112
158
 
@@ -131,6 +177,17 @@ export function evaluateHarnessSubagentToolCall(
131
177
 
132
178
  if (toolName === "bash") {
133
179
  const command = String(input?.command ?? "");
180
+ if (
181
+ kind === "planner" &&
182
+ command &&
183
+ PLANNING_ARTIFACT_JSON_WRITE.test(command)
184
+ ) {
185
+ return {
186
+ action: "block",
187
+ reason:
188
+ "harness-subagent-policy: artifacts must be YAML only — use submit_* (e.g. submit_hypothesis_brief → artifacts/hypothesis.yaml), not bash writes to .json.",
189
+ };
190
+ }
134
191
  if (command && isMutatingBash(command)) {
135
192
  return {
136
193
  action: "block",
@@ -153,6 +210,8 @@ export function evaluateHarnessSubagentToolCall(
153
210
  return { action: "allow" };
154
211
  }
155
212
 
213
+ export { isSubmitToolName } from "./harness-subagent-submit-registry.js";
214
+
156
215
  export function harnessSubagentPhaseHint(agentType: string): string | null {
157
216
  if (isHarnessPlanningAgent(agentType)) {
158
217
  return "plan";
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Shared write pipeline for harness subagent submit tools.
3
+ */
4
+
5
+ import { mkdir } from "node:fs/promises";
6
+ import { dirname, join } from "node:path";
7
+ import { validateAgainstHarnessSchema } from "../../lib/harness-schema-validate.js";
8
+ import { resolveGuardedRunDir } from "../../lib/harness-subagent-submit-path.js";
9
+ import { writeYamlFile } from "../../lib/harness-yaml.js";
10
+ import {
11
+ resolveArtifactRelPath,
12
+ type SubmitToolSpec,
13
+ } from "./harness-subagent-submit-registry.js";
14
+ import {
15
+ type ApplyDebateLaneResult,
16
+ applyDebateLaneFromDoc,
17
+ } from "./plan-debate-lane.js";
18
+
19
+ export interface SubmitPipelineResult {
20
+ ok: boolean;
21
+ artifact_path?: string;
22
+ validation_errors?: string[];
23
+ lane_result?: ApplyDebateLaneResult;
24
+ human_required?: boolean;
25
+ }
26
+
27
+ export async function executeSubmitPipeline(opts: {
28
+ projectRoot: string;
29
+ specsDir: string;
30
+ spec: SubmitToolSpec;
31
+ agentId: string;
32
+ document: Record<string, unknown>;
33
+ runId: string;
34
+ runDirEnv?: string;
35
+ }): Promise<SubmitPipelineResult> {
36
+ const runResolved = await resolveGuardedRunDir({
37
+ projectRoot: opts.projectRoot,
38
+ runId: opts.runId,
39
+ runDirEnv: opts.runDirEnv,
40
+ });
41
+ if (!runResolved.ok) {
42
+ return { ok: false, validation_errors: [runResolved.error] };
43
+ }
44
+
45
+ const validation = await validateAgainstHarnessSchema(
46
+ opts.specsDir,
47
+ opts.spec.schemaFile,
48
+ opts.document,
49
+ );
50
+ if (!validation.ok) {
51
+ return { ok: false, validation_errors: validation.errors };
52
+ }
53
+
54
+ const relPath = resolveArtifactRelPath(opts.spec, opts.document);
55
+ const absPath = join(runResolved.runDir, relPath);
56
+ await mkdir(dirname(absPath), { recursive: true });
57
+ await writeYamlFile(absPath, opts.document);
58
+
59
+ let laneResult: ApplyDebateLaneResult | undefined;
60
+ if (opts.spec.debateLane) {
61
+ laneResult = await applyDebateLaneFromDoc({
62
+ runDir: runResolved.runDir,
63
+ lane: opts.spec.debateLane,
64
+ doc: opts.document,
65
+ });
66
+ if (!laneResult.ok) {
67
+ return {
68
+ ok: false,
69
+ artifact_path: relPath,
70
+ validation_errors: laneResult.errors,
71
+ lane_result: laneResult,
72
+ };
73
+ }
74
+ }
75
+
76
+ return {
77
+ ok: true,
78
+ artifact_path: relPath,
79
+ lane_result: laneResult,
80
+ human_required: opts.spec.humanRequired === true,
81
+ };
82
+ }
@@ -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
+ };