@chllming/wave-orchestration 0.8.3 → 0.8.4
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/CHANGELOG.md +19 -0
- package/README.md +47 -11
- package/docs/README.md +6 -2
- package/docs/concepts/what-is-a-wave.md +1 -1
- package/docs/plans/architecture-hardening-migration.md +8 -1
- package/docs/plans/current-state.md +15 -7
- package/docs/plans/end-state-architecture.md +82 -69
- package/docs/plans/examples/wave-example-live-proof.md +1 -1
- package/docs/plans/migration.md +235 -62
- package/docs/plans/wave-orchestrator.md +37 -11
- package/docs/reference/cli-reference.md +34 -14
- package/docs/reference/coordination-and-closure.md +19 -6
- package/docs/reference/npmjs-trusted-publishing.md +5 -4
- package/docs/reference/sample-waves.md +4 -4
- package/package.json +1 -1
- package/releases/manifest.json +20 -0
- package/scripts/wave-orchestrator/agent-state.mjs +0 -491
- package/scripts/wave-orchestrator/autonomous.mjs +10 -6
- package/scripts/wave-orchestrator/{launcher-closure.mjs → closure-engine.mjs} +190 -74
- package/scripts/wave-orchestrator/{launcher-derived-state.mjs → derived-state-engine.mjs} +34 -146
- package/scripts/wave-orchestrator/{launcher-gates.mjs → gate-engine.mjs} +395 -139
- package/scripts/wave-orchestrator/human-input-resolution.mjs +14 -10
- package/scripts/wave-orchestrator/human-input-workflow.mjs +104 -0
- package/scripts/wave-orchestrator/implementation-engine.mjs +120 -0
- package/scripts/wave-orchestrator/launcher-runtime.mjs +5 -6
- package/scripts/wave-orchestrator/launcher.mjs +271 -724
- package/scripts/wave-orchestrator/projection-writer.mjs +256 -0
- package/scripts/wave-orchestrator/reconcile-format.mjs +32 -0
- package/scripts/wave-orchestrator/reducer-snapshot.mjs +297 -0
- package/scripts/wave-orchestrator/replay.mjs +3 -1
- package/scripts/wave-orchestrator/result-envelope.mjs +589 -0
- package/scripts/wave-orchestrator/retry-control.mjs +5 -0
- package/scripts/wave-orchestrator/{launcher-retry.mjs → retry-engine.mjs} +267 -18
- package/scripts/wave-orchestrator/role-helpers.mjs +51 -0
- package/scripts/wave-orchestrator/{launcher-supervisor.mjs → session-supervisor.mjs} +178 -103
- package/scripts/wave-orchestrator/shared.mjs +1 -0
- package/scripts/wave-orchestrator/traces.mjs +10 -1
- package/scripts/wave-orchestrator/wave-files.mjs +11 -9
- package/scripts/wave-orchestrator/wave-state-reducer.mjs +52 -5
|
@@ -15,14 +15,15 @@ import {
|
|
|
15
15
|
waveControlPlaneLogPath,
|
|
16
16
|
} from "./control-plane.mjs";
|
|
17
17
|
import { readWaveLedger } from "./ledger.mjs";
|
|
18
|
-
import {
|
|
19
|
-
import { buildResumePlan, clearWaveRelaunchPlan } from "./
|
|
18
|
+
import { readRunResultEnvelope } from "./gate-engine.mjs";
|
|
19
|
+
import { buildResumePlan, clearWaveRelaunchPlan } from "./retry-engine.mjs";
|
|
20
20
|
import { readWaveProofRegistry } from "./proof-registry.mjs";
|
|
21
21
|
import { buildDependencySnapshot, buildRequestAssignments, syncAssignmentRecords } from "./routing-state.mjs";
|
|
22
22
|
import { buildLanePaths } from "./shared.mjs";
|
|
23
23
|
import { reduceWaveState } from "./wave-state-reducer.mjs";
|
|
24
24
|
import { parseWaveFiles } from "./wave-files.mjs";
|
|
25
25
|
import { writeWaveRetryOverride } from "./retry-control.mjs";
|
|
26
|
+
import { resolveWaveRoleBindings } from "./role-helpers.mjs";
|
|
26
27
|
|
|
27
28
|
function coordinationLogPath(lanePaths, waveNumber) {
|
|
28
29
|
return path.join(lanePaths.coordinationDir, `wave-${waveNumber}.jsonl`);
|
|
@@ -67,6 +68,9 @@ function taskRunInfoForAgent(lanePaths, wave, agent, proofRegistry) {
|
|
|
67
68
|
const safeName = `wave-${wave.wave}-${agent.slug}`;
|
|
68
69
|
return {
|
|
69
70
|
agent,
|
|
71
|
+
lane: lanePaths.lane,
|
|
72
|
+
wave: wave.wave,
|
|
73
|
+
resultsDir: lanePaths.resultsDir,
|
|
70
74
|
logPath: path.join(lanePaths.logsDir, `${safeName}.log`),
|
|
71
75
|
statusPath: path.join(lanePaths.statusDir, `${safeName}.status`),
|
|
72
76
|
proofRegistry,
|
|
@@ -207,10 +211,13 @@ export function buildResumePlanFromDisk({ lanePaths, wave }) {
|
|
|
207
211
|
const agentRuns = wave.agents.map((agent) =>
|
|
208
212
|
taskRunInfoForAgent(lanePaths, wave, agent, proofRegistry),
|
|
209
213
|
);
|
|
210
|
-
const
|
|
214
|
+
const agentEnvelopes = Object.fromEntries(
|
|
211
215
|
agentRuns
|
|
212
|
-
.map((runInfo) =>
|
|
213
|
-
|
|
216
|
+
.map((runInfo) => {
|
|
217
|
+
const envelopeResult = readRunResultEnvelope(runInfo, wave, { mode: "compat" });
|
|
218
|
+
return [runInfo.agent.agentId, envelopeResult?.valid ? envelopeResult.envelope : null];
|
|
219
|
+
})
|
|
220
|
+
.filter(([, envelope]) => Boolean(envelope)),
|
|
214
221
|
);
|
|
215
222
|
const coordinationState = readMaterializedCoordinationState(coordinationLogPath(lanePaths, wave.wave));
|
|
216
223
|
const feedbackRequests = readWaveHumanFeedbackRequests({
|
|
@@ -232,16 +239,13 @@ export function buildResumePlanFromDisk({ lanePaths, wave }) {
|
|
|
232
239
|
const reducerState = reduceWaveState({
|
|
233
240
|
controlPlaneEvents: readControlPlaneEvents(waveControlPlaneLogPath(lanePaths, wave.wave)),
|
|
234
241
|
coordinationRecords: coordinationState.latestRecords || [],
|
|
235
|
-
|
|
242
|
+
agentEnvelopes,
|
|
236
243
|
waveDefinition: wave,
|
|
237
244
|
dependencyTickets: dependencySnapshot,
|
|
238
245
|
feedbackRequests,
|
|
239
246
|
laneConfig: {
|
|
240
247
|
lane: lanePaths.lane,
|
|
241
|
-
|
|
242
|
-
contEvalAgentId: lanePaths.contEvalAgentId || "E0",
|
|
243
|
-
integrationAgentId: lanePaths.integrationAgentId || "A8",
|
|
244
|
-
documentationAgentId: lanePaths.documentationAgentId || "A9",
|
|
248
|
+
...resolveWaveRoleBindings(wave, lanePaths, wave.agents),
|
|
245
249
|
validationMode: "live",
|
|
246
250
|
evalTargets: wave.evalTargets,
|
|
247
251
|
benchmarkCatalogPath: lanePaths.laneProfile?.paths?.benchmarkCatalogPath,
|
|
@@ -287,3 +287,107 @@ export function computeHumanInputMetrics(requests) {
|
|
|
287
287
|
: null,
|
|
288
288
|
};
|
|
289
289
|
}
|
|
290
|
+
|
|
291
|
+
export function buildHumanFeedbackWorkflowUpdate({
|
|
292
|
+
request,
|
|
293
|
+
lane,
|
|
294
|
+
waveNumber,
|
|
295
|
+
existingEscalation = null,
|
|
296
|
+
}) {
|
|
297
|
+
const question = request?.question || "n/a";
|
|
298
|
+
const context = request?.context ? `; context=${request.context}` : "";
|
|
299
|
+
const agentId = request?.agentId || "human";
|
|
300
|
+
const responseOperator = request?.responseOperator || "human-operator";
|
|
301
|
+
const responseText = request?.responseText || "(empty response)";
|
|
302
|
+
if (request?.status === "pending") {
|
|
303
|
+
return {
|
|
304
|
+
combinedEvent: {
|
|
305
|
+
level: "warn",
|
|
306
|
+
agentId: request.agentId,
|
|
307
|
+
message: `Human feedback requested (${request.id}): ${question}`,
|
|
308
|
+
},
|
|
309
|
+
coordinationNotice: {
|
|
310
|
+
event: "human_feedback_requested",
|
|
311
|
+
waves: [waveNumber],
|
|
312
|
+
status: "waiting-human",
|
|
313
|
+
details: `request_id=${request.id}; agent=${request.agentId}; question=${question}${context}`,
|
|
314
|
+
actionRequested:
|
|
315
|
+
`Launcher operator should ask or answer in the parent session, then run: pnpm exec wave control task act answer --lane ${lane} --wave ${waveNumber} --id ${request.id} --response "<answer>" --operator "<name>"`,
|
|
316
|
+
},
|
|
317
|
+
consoleLines: [
|
|
318
|
+
`[human-feedback] wave=${waveNumber} agent=${request.agentId} request=${request.id} pending: ${question}`,
|
|
319
|
+
`[human-feedback] respond with: pnpm exec wave control task act answer --lane ${lane} --wave ${waveNumber} --id ${request.id} --response "<answer>" --operator "<name>"`,
|
|
320
|
+
],
|
|
321
|
+
coordinationUpdates: [
|
|
322
|
+
{
|
|
323
|
+
id: request.id,
|
|
324
|
+
lane,
|
|
325
|
+
wave: waveNumber,
|
|
326
|
+
agentId,
|
|
327
|
+
kind: "human-feedback",
|
|
328
|
+
targets: request.agentId ? [`agent:${request.agentId}`] : [],
|
|
329
|
+
priority: "high",
|
|
330
|
+
summary: question,
|
|
331
|
+
detail: request.context || "",
|
|
332
|
+
status: "open",
|
|
333
|
+
source: "feedback",
|
|
334
|
+
},
|
|
335
|
+
],
|
|
336
|
+
triageUpdates: [],
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
if (request?.status === "answered") {
|
|
340
|
+
const escalationId = `escalation-${request.id}`;
|
|
341
|
+
const escalationRecord = {
|
|
342
|
+
id: escalationId,
|
|
343
|
+
lane,
|
|
344
|
+
wave: waveNumber,
|
|
345
|
+
agentId,
|
|
346
|
+
kind: "human-escalation",
|
|
347
|
+
targets:
|
|
348
|
+
existingEscalation?.targets ||
|
|
349
|
+
(request.agentId ? [`agent:${request.agentId}`] : []),
|
|
350
|
+
dependsOn: existingEscalation?.dependsOn || [],
|
|
351
|
+
closureCondition: existingEscalation?.closureCondition || "",
|
|
352
|
+
priority: "high",
|
|
353
|
+
summary: question,
|
|
354
|
+
detail: responseText,
|
|
355
|
+
artifactRefs: [request.id],
|
|
356
|
+
status: "resolved",
|
|
357
|
+
source: "feedback",
|
|
358
|
+
};
|
|
359
|
+
return {
|
|
360
|
+
combinedEvent: {
|
|
361
|
+
level: "info",
|
|
362
|
+
agentId: request.agentId,
|
|
363
|
+
message: `Human feedback answered (${request.id}) by ${responseOperator}: ${responseText}`,
|
|
364
|
+
},
|
|
365
|
+
coordinationNotice: {
|
|
366
|
+
event: "human_feedback_answered",
|
|
367
|
+
waves: [waveNumber],
|
|
368
|
+
status: "resolved",
|
|
369
|
+
details: `request_id=${request.id}; agent=${request.agentId}; operator=${responseOperator}; response=${responseText}`,
|
|
370
|
+
actionRequested: "None",
|
|
371
|
+
},
|
|
372
|
+
consoleLines: [],
|
|
373
|
+
coordinationUpdates: [
|
|
374
|
+
escalationRecord,
|
|
375
|
+
{
|
|
376
|
+
id: request.id,
|
|
377
|
+
lane,
|
|
378
|
+
wave: waveNumber,
|
|
379
|
+
agentId,
|
|
380
|
+
kind: "human-feedback",
|
|
381
|
+
targets: request.agentId ? [`agent:${request.agentId}`] : [],
|
|
382
|
+
priority: "high",
|
|
383
|
+
summary: question,
|
|
384
|
+
detail: responseText,
|
|
385
|
+
status: "resolved",
|
|
386
|
+
source: "feedback",
|
|
387
|
+
},
|
|
388
|
+
],
|
|
389
|
+
triageUpdates: [escalationRecord],
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { readStatusCodeIfPresent } from "./dashboard-state.mjs";
|
|
2
|
+
import { resolveRetryOverrideRuns } from "./retry-control.mjs";
|
|
3
|
+
import {
|
|
4
|
+
applyPersistedRelaunchPlan,
|
|
5
|
+
persistedRelaunchPlanMatchesCurrentState,
|
|
6
|
+
resolveRelaunchRuns,
|
|
7
|
+
selectInitialWaveRuns,
|
|
8
|
+
selectReusablePreCompletedAgentIds,
|
|
9
|
+
} from "./retry-engine.mjs";
|
|
10
|
+
|
|
11
|
+
export function planInitialWaveAttempt({
|
|
12
|
+
agentRuns,
|
|
13
|
+
lanePaths,
|
|
14
|
+
wave,
|
|
15
|
+
derivedState,
|
|
16
|
+
proofRegistry,
|
|
17
|
+
retryOverride,
|
|
18
|
+
persistedRelaunchPlan,
|
|
19
|
+
}) {
|
|
20
|
+
const preCompletedAgentIds = selectReusablePreCompletedAgentIds(agentRuns, lanePaths, {
|
|
21
|
+
retryOverride,
|
|
22
|
+
wave,
|
|
23
|
+
derivedState,
|
|
24
|
+
proofRegistry,
|
|
25
|
+
});
|
|
26
|
+
const staleCompletedAgentIds = agentRuns
|
|
27
|
+
.filter(
|
|
28
|
+
(run) =>
|
|
29
|
+
!preCompletedAgentIds.has(run.agent.agentId) &&
|
|
30
|
+
readStatusCodeIfPresent(run.statusPath) === 0,
|
|
31
|
+
)
|
|
32
|
+
.map((run) => run.agent.agentId);
|
|
33
|
+
|
|
34
|
+
const persistedPlanIsCurrent =
|
|
35
|
+
!persistedRelaunchPlan ||
|
|
36
|
+
persistedRelaunchPlanMatchesCurrentState(
|
|
37
|
+
agentRuns,
|
|
38
|
+
persistedRelaunchPlan,
|
|
39
|
+
lanePaths,
|
|
40
|
+
wave,
|
|
41
|
+
);
|
|
42
|
+
const effectivePersistedPlan = persistedPlanIsCurrent ? persistedRelaunchPlan : null;
|
|
43
|
+
const availableRuns = agentRuns.filter((run) => !preCompletedAgentIds.has(run.agent.agentId));
|
|
44
|
+
const persistedRuns = applyPersistedRelaunchPlan(
|
|
45
|
+
availableRuns,
|
|
46
|
+
effectivePersistedPlan,
|
|
47
|
+
lanePaths,
|
|
48
|
+
wave,
|
|
49
|
+
);
|
|
50
|
+
const overrideResolution = resolveRetryOverrideRuns(availableRuns, retryOverride, lanePaths, wave);
|
|
51
|
+
|
|
52
|
+
let selectedRuns = [];
|
|
53
|
+
let source = "initial";
|
|
54
|
+
if (overrideResolution.unknownAgentIds.length === 0 && overrideResolution.runs.length > 0) {
|
|
55
|
+
selectedRuns = overrideResolution.runs;
|
|
56
|
+
source = "override";
|
|
57
|
+
} else if (persistedRuns.length > 0) {
|
|
58
|
+
selectedRuns = persistedRuns;
|
|
59
|
+
source = "persisted-relaunch";
|
|
60
|
+
} else {
|
|
61
|
+
selectedRuns = selectInitialWaveRuns(availableRuns, lanePaths, wave);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
preCompletedAgentIds,
|
|
66
|
+
staleCompletedAgentIds,
|
|
67
|
+
availableRuns,
|
|
68
|
+
selectedRuns,
|
|
69
|
+
source,
|
|
70
|
+
overrideResolution,
|
|
71
|
+
persistedPlanIsCurrent,
|
|
72
|
+
shouldClearPersistedRelaunchPlan: Boolean(
|
|
73
|
+
persistedRelaunchPlan && !persistedPlanIsCurrent,
|
|
74
|
+
),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function planRetryWaveAttempt({
|
|
79
|
+
agentRuns,
|
|
80
|
+
failures,
|
|
81
|
+
derivedState,
|
|
82
|
+
lanePaths,
|
|
83
|
+
wave,
|
|
84
|
+
retryOverride,
|
|
85
|
+
waveState = null,
|
|
86
|
+
}) {
|
|
87
|
+
const relaunchResolution = resolveRelaunchRuns(
|
|
88
|
+
agentRuns,
|
|
89
|
+
failures,
|
|
90
|
+
derivedState,
|
|
91
|
+
lanePaths,
|
|
92
|
+
wave,
|
|
93
|
+
waveState ? { waveState } : {},
|
|
94
|
+
);
|
|
95
|
+
const overrideResolution = resolveRetryOverrideRuns(
|
|
96
|
+
agentRuns,
|
|
97
|
+
retryOverride,
|
|
98
|
+
lanePaths,
|
|
99
|
+
wave,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
let selectedRuns = [];
|
|
103
|
+
let source = "retry";
|
|
104
|
+
let barrier = relaunchResolution.barrier || null;
|
|
105
|
+
if (overrideResolution.unknownAgentIds.length === 0 && overrideResolution.runs.length > 0) {
|
|
106
|
+
selectedRuns = overrideResolution.runs;
|
|
107
|
+
barrier = null;
|
|
108
|
+
source = "override";
|
|
109
|
+
} else if (!barrier) {
|
|
110
|
+
selectedRuns = relaunchResolution.runs;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
selectedRuns,
|
|
115
|
+
source,
|
|
116
|
+
barrier,
|
|
117
|
+
relaunchResolution,
|
|
118
|
+
overrideResolution,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
@@ -14,6 +14,7 @@ import { readStatusCodeIfPresent } from "./dashboard-state.mjs";
|
|
|
14
14
|
import { buildExecutorLaunchSpec } from "./executors.mjs";
|
|
15
15
|
import { hashAgentPromptFingerprint, prefetchContext7ForSelection } from "./context7.mjs";
|
|
16
16
|
import { killTmuxSessionIfExists } from "./terminals.mjs";
|
|
17
|
+
import { resolveWaveRoleBindings } from "./role-helpers.mjs";
|
|
17
18
|
import {
|
|
18
19
|
resolveAgentSkills,
|
|
19
20
|
summarizeResolvedSkills,
|
|
@@ -71,6 +72,7 @@ export async function launchAgentSession(lanePaths, params, { runTmuxFn }) {
|
|
|
71
72
|
inboxText,
|
|
72
73
|
promptOverride = "",
|
|
73
74
|
orchestratorId,
|
|
75
|
+
attempt = 1,
|
|
74
76
|
agentRateLimitRetries,
|
|
75
77
|
agentRateLimitBaseDelaySeconds,
|
|
76
78
|
agentRateLimitMaxDelaySeconds,
|
|
@@ -105,6 +107,7 @@ export async function launchAgentSession(lanePaths, params, { runTmuxFn }) {
|
|
|
105
107
|
const prompt =
|
|
106
108
|
String(promptOverride || "").trim() ||
|
|
107
109
|
buildExecutionPrompt({
|
|
110
|
+
...resolveWaveRoleBindings(resolvedWaveDefinition, lanePaths),
|
|
108
111
|
lane: lanePaths.lane,
|
|
109
112
|
wave,
|
|
110
113
|
agent,
|
|
@@ -120,10 +123,6 @@ export async function launchAgentSession(lanePaths, params, { runTmuxFn }) {
|
|
|
120
123
|
evalTargets: resolvedWaveDefinition.evalTargets,
|
|
121
124
|
benchmarkCatalogPath: lanePaths.laneProfile?.paths?.benchmarkCatalogPath,
|
|
122
125
|
sharedPlanDocs: lanePaths.sharedPlanDocs,
|
|
123
|
-
contQaAgentId: lanePaths.contQaAgentId,
|
|
124
|
-
contEvalAgentId: lanePaths.contEvalAgentId,
|
|
125
|
-
integrationAgentId: lanePaths.integrationAgentId,
|
|
126
|
-
documentationAgentId: lanePaths.documentationAgentId,
|
|
127
126
|
});
|
|
128
127
|
const promptHash = hashAgentPromptFingerprint(agent);
|
|
129
128
|
fs.writeFileSync(promptPath, `${prompt}\n`, "utf8");
|
|
@@ -216,8 +215,8 @@ export async function launchAgentSession(lanePaths, params, { runTmuxFn }) {
|
|
|
216
215
|
`export WAVE_EXECUTOR_MODE=${shellQuote(resolvedExecutorMode)}`,
|
|
217
216
|
...executionLines,
|
|
218
217
|
`node -e ${shellQuote(
|
|
219
|
-
"const fs=require('node:fs'); const statusPath=process.argv[1]; const payload={code:Number(process.argv[2]),promptHash:process.argv[3]||null,orchestratorId:process.argv[4]||null,completedAt:new Date().toISOString()}; fs.writeFileSync(statusPath, JSON.stringify(payload, null, 2)+'\\n', 'utf8');",
|
|
220
|
-
)} ${shellQuote(statusPath)} "$status" ${shellQuote(promptHash)} ${shellQuote(orchestratorId || "")}`,
|
|
218
|
+
"const fs=require('node:fs'); const statusPath=process.argv[1]; const payload={code:Number(process.argv[2]),promptHash:process.argv[3]||null,orchestratorId:process.argv[4]||null,attempt:Number(process.argv[5])||1,completedAt:new Date().toISOString()}; fs.writeFileSync(statusPath, JSON.stringify(payload, null, 2)+'\\n', 'utf8');",
|
|
219
|
+
)} ${shellQuote(statusPath)} "$status" ${shellQuote(promptHash)} ${shellQuote(orchestratorId || "")} ${shellQuote(String(attempt || 1))}`,
|
|
221
220
|
`echo "[${lanePaths.lane}-wave-launcher] ${sessionName} finished with code $status"`,
|
|
222
221
|
"exec bash -l",
|
|
223
222
|
].join("\n");
|