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.
- package/.agents/skills/harness-governor/SKILL.md +11 -0
- package/.agents/skills/harness-orchestration/SKILL.md +3 -1
- package/.agents/skills/harness-plan/SKILL.md +5 -5
- package/.pi/agents/harness/adversary.md +1 -1
- package/.pi/agents/harness/evaluator.md +1 -1
- package/.pi/agents/harness/executor.md +1 -1
- package/.pi/agents/harness/incident-recorder.md +1 -1
- package/.pi/agents/harness/meta-optimizer.md +1 -1
- package/.pi/agents/harness/planning/decompose.md +4 -33
- package/.pi/agents/harness/planning/execution-plan-author.md +3 -2
- package/.pi/agents/harness/planning/hypothesis-validator.md +3 -2
- package/.pi/agents/harness/planning/hypothesis.md +4 -27
- package/.pi/agents/harness/planning/implementation-researcher.md +3 -2
- package/.pi/agents/harness/planning/plan-adversary.md +2 -3
- package/.pi/agents/harness/planning/plan-evaluator.md +3 -2
- package/.pi/agents/harness/planning/review-integrator.md +2 -3
- package/.pi/agents/harness/planning/scout-graphify.md +3 -22
- package/.pi/agents/harness/planning/scout-semantic.md +3 -18
- package/.pi/agents/harness/planning/scout-structure.md +3 -18
- package/.pi/agents/harness/planning/sprint-contract-auditor.md +3 -2
- package/.pi/agents/harness/planning/stack-researcher.md +3 -2
- package/.pi/agents/harness/tie-breaker.md +1 -1
- package/.pi/agents/harness/trace-librarian.md +1 -1
- package/.pi/extensions/budget-guard.ts +33 -19
- package/.pi/extensions/harness-debate-tools.ts +54 -6
- package/.pi/extensions/harness-run-context.ts +108 -2
- package/.pi/extensions/harness-subagent-submit.ts +172 -0
- package/.pi/extensions/harness-telemetry.ts +29 -4
- package/.pi/extensions/lib/debate-bus-core.ts +49 -6
- package/.pi/extensions/lib/harness-subagent-auth.ts +104 -19
- package/.pi/extensions/lib/harness-subagent-policy.ts +59 -0
- package/.pi/extensions/lib/harness-subagent-submit-pipeline.ts +82 -0
- package/.pi/extensions/lib/harness-subagent-submit-registry.ts +172 -0
- package/.pi/extensions/lib/harness-subagents-bridge.ts +127 -0
- package/.pi/extensions/lib/plan-debate-eligibility.ts +61 -8
- package/.pi/extensions/lib/plan-debate-focus.ts +21 -9
- package/.pi/extensions/lib/plan-debate-gate.ts +92 -18
- package/.pi/extensions/lib/plan-debate-lane.ts +15 -0
- package/.pi/extensions/lib/plan-debate-lanes.ts +27 -3
- package/.pi/extensions/lib/plan-debate-round-status.ts +18 -7
- package/.pi/extensions/lib/plan-messenger.ts +4 -0
- package/.pi/extensions/lib/plan-review-gate.ts +51 -0
- package/.pi/extensions/trace-recorder.ts +1 -0
- package/.pi/harness/agents.manifest.json +22 -22
- package/.pi/harness/docs/adrs/0037-subagent-submit-tools.md +31 -0
- package/.pi/harness/docs/adrs/0038-budget-telemetry-only.md +23 -0
- package/.pi/harness/docs/adrs/README.md +2 -0
- package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med-fast/artifacts/implementation-research.yaml +28 -0
- package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med-fast/artifacts/review-round-consolidated.yaml +25 -0
- package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med-fast/plan-packet.yaml +196 -0
- package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med-fast/plan-review.md +14 -0
- package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med-fast/research-brief.yaml +62 -0
- package/.pi/harness/evals/smoke/smoke-harness-plan.mjs +40 -17
- package/.pi/harness/specs/harness-executor-handoff.schema.json +19 -0
- package/.pi/harness/specs/harness-human-required.schema.json +16 -0
- package/.pi/harness/specs/plan-review-round-draft.schema.json +1 -1
- package/.pi/harness/specs/plan-scout-findings.schema.json +19 -0
- package/.pi/lib/harness-agent-output.ts +45 -0
- package/.pi/lib/harness-budget-enforce.ts +18 -0
- package/.pi/lib/harness-schema-validate.ts +89 -0
- package/.pi/lib/harness-spawn-parse.ts +86 -0
- package/.pi/lib/harness-subagent-submit-path.ts +41 -0
- package/.pi/lib/harness-ui-state.ts +15 -2
- package/.pi/model-router.example.json +13 -4
- package/.pi/prompts/harness-auto.md +2 -2
- package/.pi/prompts/harness-plan.md +34 -14
- package/.pi/prompts/harness-run.md +2 -2
- package/.pi/prompts/harness-setup.md +4 -4
- package/.pi/scripts/harness-generate-model-router.mjs +118 -36
- package/.pi/scripts/harness-model-router-routing.test.mjs +97 -0
- package/.pi/scripts/harness-sync-model-router.mjs +15 -2
- package/.pi/scripts/harness-verify.mjs +31 -0
- package/.pi/scripts/harness_web/__pycache__/__init__.cpython-314.pyc +0 -0
- package/.pi/scripts/harness_web/__pycache__/config.cpython-314.pyc +0 -0
- package/.pi/scripts/harness_web/__pycache__/output.cpython-314.pyc +0 -0
- package/.pi/scripts/harness_web/__pycache__/scrape.cpython-314.pyc +0 -0
- package/.pi/scripts/harness_web/__pycache__/search.cpython-314.pyc +0 -0
- package/.pi/scripts/harness_web/__pycache__/search_ddg.cpython-314.pyc +0 -0
- package/.pi/scripts/harness_web/__pycache__/search_searxng.cpython-314.pyc +0 -0
- package/CHANGELOG.md +21 -0
- package/package.json +4 -2
- package/vendor/pi-model-router/UPSTREAM_PIN.md +3 -1
- package/vendor/pi-model-router/extensions/commands.ts +4 -4
- package/vendor/pi-model-router/extensions/index.ts +21 -0
- package/vendor/pi-model-router/extensions/provider.ts +130 -79
- package/vendor/pi-model-router/extensions/routing.ts +148 -0
- package/vendor/pi-model-router/extensions/state.ts +3 -0
- package/vendor/pi-model-router/extensions/types.ts +9 -0
- package/vendor/pi-model-router/extensions/ui.ts +16 -2
- 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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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 =
|
|
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
|
+
};
|