agent-relay 2.3.11 → 2.3.13
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/install.sh +32 -0
- package/package.json +21 -21
- package/packages/acp-bridge/package.json +2 -2
- package/packages/bridge/package.json +7 -7
- package/packages/broker-sdk/README.md +32 -0
- package/packages/broker-sdk/dist/__tests__/unit.test.js +70 -2
- package/packages/broker-sdk/dist/__tests__/unit.test.js.map +1 -1
- package/packages/broker-sdk/dist/client.d.ts +2 -0
- package/packages/broker-sdk/dist/client.d.ts.map +1 -1
- package/packages/broker-sdk/dist/client.js +10 -0
- package/packages/broker-sdk/dist/client.js.map +1 -1
- package/packages/broker-sdk/dist/protocol.d.ts +4 -0
- package/packages/broker-sdk/dist/protocol.d.ts.map +1 -1
- package/packages/broker-sdk/dist/relay.d.ts +10 -0
- package/packages/broker-sdk/dist/relay.d.ts.map +1 -1
- package/packages/broker-sdk/dist/relay.js +53 -0
- package/packages/broker-sdk/dist/relay.js.map +1 -1
- package/packages/broker-sdk/dist/relaycast.d.ts +10 -0
- package/packages/broker-sdk/dist/relaycast.d.ts.map +1 -1
- package/packages/broker-sdk/dist/relaycast.js +40 -0
- package/packages/broker-sdk/dist/relaycast.js.map +1 -1
- package/packages/broker-sdk/dist/workflows/coordinator.d.ts +1 -0
- package/packages/broker-sdk/dist/workflows/coordinator.d.ts.map +1 -1
- package/packages/broker-sdk/dist/workflows/coordinator.js +239 -7
- package/packages/broker-sdk/dist/workflows/coordinator.js.map +1 -1
- package/packages/broker-sdk/dist/workflows/index.d.ts +1 -0
- package/packages/broker-sdk/dist/workflows/index.d.ts.map +1 -1
- package/packages/broker-sdk/dist/workflows/index.js +1 -0
- package/packages/broker-sdk/dist/workflows/index.js.map +1 -1
- package/packages/broker-sdk/dist/workflows/run.d.ts +3 -1
- package/packages/broker-sdk/dist/workflows/run.d.ts.map +1 -1
- package/packages/broker-sdk/dist/workflows/run.js +4 -0
- package/packages/broker-sdk/dist/workflows/run.js.map +1 -1
- package/packages/broker-sdk/dist/workflows/runner.d.ts +9 -0
- package/packages/broker-sdk/dist/workflows/runner.d.ts.map +1 -1
- package/packages/broker-sdk/dist/workflows/runner.js +203 -14
- package/packages/broker-sdk/dist/workflows/runner.js.map +1 -1
- package/packages/broker-sdk/dist/workflows/trajectory.d.ts +80 -0
- package/packages/broker-sdk/dist/workflows/trajectory.d.ts.map +1 -0
- package/packages/broker-sdk/dist/workflows/trajectory.js +362 -0
- package/packages/broker-sdk/dist/workflows/trajectory.js.map +1 -0
- package/packages/broker-sdk/dist/workflows/types.d.ts +15 -1
- package/packages/broker-sdk/dist/workflows/types.d.ts.map +1 -1
- package/packages/broker-sdk/package.json +2 -2
- package/packages/broker-sdk/src/__tests__/swarm-coordinator.test.ts +356 -0
- package/packages/broker-sdk/src/__tests__/unit.test.ts +92 -1
- package/packages/broker-sdk/src/__tests__/workflow-trajectory.test.ts +408 -0
- package/packages/broker-sdk/src/client.ts +15 -0
- package/packages/broker-sdk/src/protocol.ts +5 -0
- package/packages/broker-sdk/src/relay.ts +59 -0
- package/packages/broker-sdk/src/relaycast.ts +42 -0
- package/packages/broker-sdk/src/workflows/README.md +64 -0
- package/packages/broker-sdk/src/workflows/coordinator.ts +246 -8
- package/packages/broker-sdk/src/workflows/index.ts +1 -0
- package/packages/broker-sdk/src/workflows/run.ts +9 -1
- package/packages/broker-sdk/src/workflows/runner.ts +249 -14
- package/packages/broker-sdk/src/workflows/schema.json +13 -1
- package/packages/broker-sdk/src/workflows/trajectory.ts +507 -0
- package/packages/broker-sdk/src/workflows/types.ts +31 -1
- package/packages/broker-sdk/tsconfig.json +1 -0
- package/packages/broker-sdk/vitest.config.ts +9 -0
- package/packages/config/package.json +2 -2
- package/packages/continuity/package.json +2 -2
- package/packages/daemon/package.json +12 -12
- package/packages/hooks/package.json +4 -4
- package/packages/mcp/package.json +5 -5
- package/packages/memory/package.json +2 -2
- package/packages/policy/package.json +2 -2
- package/packages/protocol/package.json +1 -1
- package/packages/resiliency/package.json +1 -1
- package/packages/sdk/package.json +3 -3
- package/packages/sdk-py/src/agent_relay/builder.py +4 -0
- package/packages/sdk-py/src/agent_relay/types.py +15 -0
- package/packages/spawner/package.json +1 -1
- package/packages/state/package.json +1 -1
- package/packages/storage/package.json +2 -2
- package/packages/telemetry/package.json +1 -1
- package/packages/trajectory/package.json +2 -2
- package/packages/user-directory/package.json +2 -2
- package/packages/utils/package.json +3 -3
- package/packages/wrapper/package.json +5 -5
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WorkflowTrajectory — records a structured trajectory for each workflow run.
|
|
3
|
+
*
|
|
4
|
+
* Writes trajectory JSON files directly to `.trajectories/active/` in a format
|
|
5
|
+
* compatible with `trail show`. No external CLI or package dependency required.
|
|
6
|
+
*
|
|
7
|
+
* Design principles:
|
|
8
|
+
* 1. One trajectory per workflow run
|
|
9
|
+
* 2. Chapters map to workflow phases, not individual steps
|
|
10
|
+
* 3. Non-blocking — trajectory recording never fails the workflow
|
|
11
|
+
* 4. Opt-in but default-on
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { randomBytes } from 'node:crypto';
|
|
15
|
+
import { existsSync } from 'node:fs';
|
|
16
|
+
import { mkdir, writeFile, rename } from 'node:fs/promises';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
|
|
19
|
+
import type { TrajectoryConfig, WorkflowStep } from './types.js';
|
|
20
|
+
|
|
21
|
+
// ── Trajectory file format (compatible with trail CLI) ───────────────────────
|
|
22
|
+
|
|
23
|
+
interface TrajectoryEvent {
|
|
24
|
+
ts: number;
|
|
25
|
+
type: string;
|
|
26
|
+
content: string;
|
|
27
|
+
raw?: Record<string, unknown>;
|
|
28
|
+
significance?: 'low' | 'medium' | 'high';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface TrajectoryChapter {
|
|
32
|
+
id: string;
|
|
33
|
+
title: string;
|
|
34
|
+
agentName: string;
|
|
35
|
+
startedAt: string;
|
|
36
|
+
endedAt?: string;
|
|
37
|
+
events: TrajectoryEvent[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface TrajectoryAgent {
|
|
41
|
+
name: string;
|
|
42
|
+
role: string;
|
|
43
|
+
joinedAt: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface TrajectoryFile {
|
|
47
|
+
id: string;
|
|
48
|
+
version: number;
|
|
49
|
+
task: { title: string; source?: { system: string; id: string } };
|
|
50
|
+
status: 'active' | 'completed' | 'abandoned';
|
|
51
|
+
startedAt: string;
|
|
52
|
+
completedAt?: string;
|
|
53
|
+
agents: TrajectoryAgent[];
|
|
54
|
+
chapters: TrajectoryChapter[];
|
|
55
|
+
retrospective?: {
|
|
56
|
+
summary: string;
|
|
57
|
+
approach: string;
|
|
58
|
+
confidence: number;
|
|
59
|
+
learnings?: string[];
|
|
60
|
+
challenges?: string[];
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ── Step state for synthesis ─────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
export interface StepOutcome {
|
|
67
|
+
name: string;
|
|
68
|
+
agent: string;
|
|
69
|
+
status: 'completed' | 'failed' | 'skipped';
|
|
70
|
+
attempts: number;
|
|
71
|
+
output?: string;
|
|
72
|
+
error?: string;
|
|
73
|
+
verificationPassed?: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ── WorkflowTrajectory ──────────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
export class WorkflowTrajectory {
|
|
79
|
+
private trajectory: TrajectoryFile | null = null;
|
|
80
|
+
private currentChapterId: string | null = null;
|
|
81
|
+
private readonly enabled: boolean;
|
|
82
|
+
private readonly reflectOnBarriers: boolean;
|
|
83
|
+
private readonly reflectOnConverge: boolean;
|
|
84
|
+
private readonly autoDecisions: boolean;
|
|
85
|
+
private readonly dataDir: string;
|
|
86
|
+
private readonly runId: string;
|
|
87
|
+
private startTime: number = 0;
|
|
88
|
+
|
|
89
|
+
constructor(
|
|
90
|
+
config: TrajectoryConfig | false | undefined,
|
|
91
|
+
runId: string,
|
|
92
|
+
cwd: string,
|
|
93
|
+
) {
|
|
94
|
+
const cfg = config === false ? { enabled: false } : (config ?? {});
|
|
95
|
+
this.enabled = cfg.enabled !== false;
|
|
96
|
+
this.reflectOnBarriers = cfg.reflectOnBarriers !== false;
|
|
97
|
+
this.reflectOnConverge = cfg.reflectOnConverge !== false;
|
|
98
|
+
this.autoDecisions = cfg.autoDecisions !== false;
|
|
99
|
+
|
|
100
|
+
this.runId = runId;
|
|
101
|
+
this.dataDir = process.env.TRAJECTORIES_DATA_DIR
|
|
102
|
+
?? path.join(cwd, '.trajectories');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
/** Start the trajectory (called at run:started). */
|
|
108
|
+
async start(workflowName: string, stepCount: number, trackInfo?: string): Promise<void> {
|
|
109
|
+
if (!this.enabled) return;
|
|
110
|
+
|
|
111
|
+
this.startTime = Date.now();
|
|
112
|
+
const id = `traj_${Date.now()}_${randomBytes(4).toString('hex')}`;
|
|
113
|
+
|
|
114
|
+
this.trajectory = {
|
|
115
|
+
id,
|
|
116
|
+
version: 1,
|
|
117
|
+
task: {
|
|
118
|
+
title: `${workflowName} run #${this.runId.slice(0, 8)}`,
|
|
119
|
+
source: { system: 'workflow-runner', id: this.runId },
|
|
120
|
+
},
|
|
121
|
+
status: 'active',
|
|
122
|
+
startedAt: new Date().toISOString(),
|
|
123
|
+
agents: [{ name: 'orchestrator', role: 'workflow-runner', joinedAt: new Date().toISOString() }],
|
|
124
|
+
chapters: [],
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// Open Planning chapter
|
|
128
|
+
this.openChapter('Planning', 'orchestrator');
|
|
129
|
+
this.addEvent('note', `Workflow "${workflowName}" started with ${stepCount} steps`);
|
|
130
|
+
if (trackInfo) {
|
|
131
|
+
this.addEvent('note', trackInfo);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
await this.flush();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ── Chapters ───────────────────────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
/** Begin a new parallel track chapter. */
|
|
140
|
+
async beginTrack(trackName: string): Promise<void> {
|
|
141
|
+
if (!this.enabled || !this.trajectory) return;
|
|
142
|
+
|
|
143
|
+
this.closeCurrentChapter();
|
|
144
|
+
this.openChapter(`Execution: ${trackName}`, 'orchestrator');
|
|
145
|
+
await this.flush();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Begin a convergence chapter (after barrier/parallel completion). */
|
|
149
|
+
async beginConvergence(label: string): Promise<void> {
|
|
150
|
+
if (!this.enabled || !this.trajectory) return;
|
|
151
|
+
|
|
152
|
+
this.closeCurrentChapter();
|
|
153
|
+
this.openChapter(`Convergence: ${label}`, 'orchestrator');
|
|
154
|
+
await this.flush();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Begin the retrospective chapter. */
|
|
158
|
+
private openRetrospective(): void {
|
|
159
|
+
if (!this.trajectory) return;
|
|
160
|
+
this.closeCurrentChapter();
|
|
161
|
+
this.openChapter('Retrospective', 'orchestrator');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ── Step events ────────────────────────────────────────────────────────────
|
|
165
|
+
|
|
166
|
+
/** Record step started. */
|
|
167
|
+
async stepStarted(step: WorkflowStep, agent: string): Promise<void> {
|
|
168
|
+
if (!this.enabled || !this.trajectory) return;
|
|
169
|
+
|
|
170
|
+
// Register agent if not seen
|
|
171
|
+
if (!this.trajectory.agents.some((a) => a.name === agent)) {
|
|
172
|
+
this.trajectory.agents.push({
|
|
173
|
+
name: agent,
|
|
174
|
+
role: step.agent,
|
|
175
|
+
joinedAt: new Date().toISOString(),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
this.addEvent('note', `Step "${step.name}" assigned to agent "${agent}"`);
|
|
180
|
+
await this.flush();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Record step completed with output summary. */
|
|
184
|
+
async stepCompleted(step: WorkflowStep, output: string, attempt: number): Promise<void> {
|
|
185
|
+
if (!this.enabled || !this.trajectory) return;
|
|
186
|
+
|
|
187
|
+
const suffix = attempt > 1 ? ` (attempt ${attempt})` : '';
|
|
188
|
+
const summary = output.length > 200 ? output.slice(0, 200) + '...' : output;
|
|
189
|
+
this.addEvent('finding', `Step "${step.name}" completed${suffix}: ${summary}`, 'medium');
|
|
190
|
+
await this.flush();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Record step failed. */
|
|
194
|
+
async stepFailed(step: WorkflowStep, error: string, attempt: number, maxRetries: number): Promise<void> {
|
|
195
|
+
if (!this.enabled || !this.trajectory) return;
|
|
196
|
+
|
|
197
|
+
this.addEvent(
|
|
198
|
+
'error',
|
|
199
|
+
`Step "${step.name}" failed (attempt ${attempt}/${maxRetries + 1}): ${error}`,
|
|
200
|
+
'high',
|
|
201
|
+
);
|
|
202
|
+
await this.flush();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Record step skipped. */
|
|
206
|
+
async stepSkipped(step: WorkflowStep, reason: string): Promise<void> {
|
|
207
|
+
if (!this.enabled || !this.trajectory) return;
|
|
208
|
+
|
|
209
|
+
this.addEvent('note', `Skipped step "${step.name}": ${reason}`);
|
|
210
|
+
await this.flush();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Record step retrying. */
|
|
214
|
+
async stepRetrying(step: WorkflowStep, attempt: number, maxRetries: number): Promise<void> {
|
|
215
|
+
if (!this.enabled || !this.trajectory) return;
|
|
216
|
+
|
|
217
|
+
this.addEvent('note', `Retrying step "${step.name}" (attempt ${attempt}/${maxRetries + 1})`);
|
|
218
|
+
await this.flush();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ── Reflections ────────────────────────────────────────────────────────────
|
|
222
|
+
|
|
223
|
+
/** Record a reflection at a convergence point. */
|
|
224
|
+
async reflect(synthesis: string, confidence: number, focalPoints?: string[]): Promise<void> {
|
|
225
|
+
if (!this.enabled || !this.trajectory) return;
|
|
226
|
+
|
|
227
|
+
const raw: Record<string, unknown> = { confidence };
|
|
228
|
+
if (focalPoints?.length) {
|
|
229
|
+
raw.focalPoints = focalPoints;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
this.addEvent('reflection', synthesis, 'high', raw);
|
|
233
|
+
await this.flush();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Synthesize and reflect after a set of steps complete (barrier or parallel convergence). */
|
|
237
|
+
async synthesizeAndReflect(
|
|
238
|
+
label: string,
|
|
239
|
+
outcomes: StepOutcome[],
|
|
240
|
+
unblocks?: string[],
|
|
241
|
+
): Promise<void> {
|
|
242
|
+
if (!this.enabled || !this.trajectory) return;
|
|
243
|
+
|
|
244
|
+
const synthesis = this.buildSynthesis(label, outcomes, unblocks);
|
|
245
|
+
const confidence = this.computeConfidence(outcomes);
|
|
246
|
+
const focalPoints = outcomes.map((o) => `${o.name}: ${o.status}`);
|
|
247
|
+
|
|
248
|
+
await this.beginConvergence(label);
|
|
249
|
+
await this.reflect(synthesis, confidence, focalPoints);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ── Decisions ──────────────────────────────────────────────────────────────
|
|
253
|
+
|
|
254
|
+
/** Record an orchestrator decision. */
|
|
255
|
+
async decide(question: string, chosen: string, reasoning: string): Promise<void> {
|
|
256
|
+
if (!this.enabled || !this.trajectory || !this.autoDecisions) return;
|
|
257
|
+
|
|
258
|
+
this.addEvent('decision', `${question} → ${chosen}: ${reasoning}`, 'medium', {
|
|
259
|
+
question,
|
|
260
|
+
chosen,
|
|
261
|
+
reasoning,
|
|
262
|
+
});
|
|
263
|
+
await this.flush();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ── Completion ─────────────────────────────────────────────────────────────
|
|
267
|
+
|
|
268
|
+
/** Complete the trajectory with a summary. */
|
|
269
|
+
async complete(
|
|
270
|
+
summary: string,
|
|
271
|
+
confidence: number,
|
|
272
|
+
meta?: { learnings?: string[]; challenges?: string[] },
|
|
273
|
+
): Promise<void> {
|
|
274
|
+
if (!this.enabled || !this.trajectory) return;
|
|
275
|
+
|
|
276
|
+
this.openRetrospective();
|
|
277
|
+
|
|
278
|
+
const elapsed = Date.now() - this.startTime;
|
|
279
|
+
const elapsedStr = elapsed > 60_000
|
|
280
|
+
? `${Math.round(elapsed / 60_000)} minutes`
|
|
281
|
+
: `${Math.round(elapsed / 1_000)} seconds`;
|
|
282
|
+
|
|
283
|
+
this.addEvent('reflection', `${summary} (completed in ${elapsedStr})`, 'high');
|
|
284
|
+
|
|
285
|
+
this.trajectory.status = 'completed';
|
|
286
|
+
this.trajectory.completedAt = new Date().toISOString();
|
|
287
|
+
this.trajectory.retrospective = {
|
|
288
|
+
summary,
|
|
289
|
+
approach: 'workflow-runner DAG execution',
|
|
290
|
+
confidence,
|
|
291
|
+
learnings: meta?.learnings,
|
|
292
|
+
challenges: meta?.challenges,
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
this.closeCurrentChapter();
|
|
296
|
+
await this.flush();
|
|
297
|
+
await this.moveToCompleted();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** Abandon the trajectory. */
|
|
301
|
+
async abandon(reason: string): Promise<void> {
|
|
302
|
+
if (!this.enabled || !this.trajectory) return;
|
|
303
|
+
|
|
304
|
+
this.addEvent('error', `Workflow abandoned: ${reason}`, 'high');
|
|
305
|
+
this.trajectory.status = 'abandoned';
|
|
306
|
+
this.trajectory.completedAt = new Date().toISOString();
|
|
307
|
+
|
|
308
|
+
this.closeCurrentChapter();
|
|
309
|
+
await this.flush();
|
|
310
|
+
await this.moveToCompleted();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// ── Getters ────────────────────────────────────────────────────────────────
|
|
314
|
+
|
|
315
|
+
isEnabled(): boolean {
|
|
316
|
+
return this.enabled;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
shouldReflectOnConverge(): boolean {
|
|
320
|
+
return this.enabled && this.reflectOnConverge;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
shouldReflectOnBarriers(): boolean {
|
|
324
|
+
return this.enabled && this.reflectOnBarriers;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
getTrajectoryId(): string | null {
|
|
328
|
+
return this.trajectory?.id ?? null;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// ── Synthesis helpers ──────────────────────────────────────────────────────
|
|
332
|
+
|
|
333
|
+
buildSynthesis(label: string, outcomes: StepOutcome[], unblocks?: string[]): string {
|
|
334
|
+
const completed = outcomes.filter((o) => o.status === 'completed');
|
|
335
|
+
const failed = outcomes.filter((o) => o.status === 'failed');
|
|
336
|
+
const retried = outcomes.filter((o) => o.attempts > 1 && o.status !== 'failed');
|
|
337
|
+
|
|
338
|
+
const parts: string[] = [
|
|
339
|
+
`${label} resolved.`,
|
|
340
|
+
`${completed.length}/${outcomes.length} steps completed.`,
|
|
341
|
+
];
|
|
342
|
+
|
|
343
|
+
if (failed.length > 0) {
|
|
344
|
+
parts.push(`${failed.length} step(s) failed: ${failed.map((s) => s.name).join(', ')}.`);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (retried.length > 0) {
|
|
348
|
+
parts.push(
|
|
349
|
+
`${retried.length} step(s) required retries: ${retried.map((s) => s.name).join(', ')}.`,
|
|
350
|
+
);
|
|
351
|
+
} else if (failed.length === 0) {
|
|
352
|
+
parts.push('All steps completed on first attempt.');
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (unblocks?.length) {
|
|
356
|
+
parts.push(`Unblocking: ${unblocks.join(', ')}.`);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return parts.join(' ');
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
computeConfidence(outcomes: StepOutcome[]): number {
|
|
363
|
+
if (outcomes.length === 0) return 0.7;
|
|
364
|
+
|
|
365
|
+
const total = outcomes.length;
|
|
366
|
+
const completed = outcomes.filter((o) => o.status === 'completed').length;
|
|
367
|
+
const firstAttempt = outcomes.filter((o) => o.attempts === 1 && o.status === 'completed').length;
|
|
368
|
+
const verified = outcomes.filter((o) => o.verificationPassed).length;
|
|
369
|
+
|
|
370
|
+
// Base: 0.5 scaled by completion rate, +0.25 for first-attempt, +0.25 for verified
|
|
371
|
+
const completionRate = completed / total;
|
|
372
|
+
return Math.min(
|
|
373
|
+
1.0,
|
|
374
|
+
0.5 * completionRate +
|
|
375
|
+
(firstAttempt / total) * 0.25 +
|
|
376
|
+
(verified / total) * 0.25,
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
buildRunSummary(outcomes: StepOutcome[]): string {
|
|
381
|
+
const completed = outcomes.filter((o) => o.status === 'completed').length;
|
|
382
|
+
const failed = outcomes.filter((o) => o.status === 'failed').length;
|
|
383
|
+
const skipped = outcomes.filter((o) => o.status === 'skipped').length;
|
|
384
|
+
const totalRetries = outcomes.reduce((sum, o) => sum + Math.max(0, o.attempts - 1), 0);
|
|
385
|
+
|
|
386
|
+
const elapsed = Date.now() - this.startTime;
|
|
387
|
+
const elapsedStr = elapsed > 60_000
|
|
388
|
+
? `${Math.round(elapsed / 60_000)} minutes`
|
|
389
|
+
: `${Math.round(elapsed / 1_000)} seconds`;
|
|
390
|
+
|
|
391
|
+
const parts = [`Workflow completed in ${elapsedStr}.`];
|
|
392
|
+
parts.push(`${completed}/${outcomes.length} steps passed.`);
|
|
393
|
+
|
|
394
|
+
if (failed > 0) parts.push(`${failed} failed.`);
|
|
395
|
+
if (skipped > 0) parts.push(`${skipped} skipped.`);
|
|
396
|
+
if (totalRetries > 0) parts.push(`${totalRetries} total retries.`);
|
|
397
|
+
|
|
398
|
+
return parts.join(' ');
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
extractLearnings(outcomes: StepOutcome[]): string[] {
|
|
402
|
+
const learnings: string[] = [];
|
|
403
|
+
const retried = outcomes.filter((o) => o.attempts > 1 && o.status === 'completed');
|
|
404
|
+
if (retried.length > 0) {
|
|
405
|
+
learnings.push(
|
|
406
|
+
`Steps requiring retries: ${retried.map((o) => `${o.name} (${o.attempts} attempts)`).join(', ')}`,
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
return learnings;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
extractChallenges(outcomes: StepOutcome[]): string[] {
|
|
413
|
+
const challenges: string[] = [];
|
|
414
|
+
const failed = outcomes.filter((o) => o.status === 'failed');
|
|
415
|
+
for (const step of failed) {
|
|
416
|
+
challenges.push(`${step.name}: ${step.error ?? 'unknown error'}`);
|
|
417
|
+
}
|
|
418
|
+
return challenges;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// ── Internal helpers ───────────────────────────────────────────────────────
|
|
422
|
+
|
|
423
|
+
private openChapter(title: string, agentName: string): void {
|
|
424
|
+
if (!this.trajectory) return;
|
|
425
|
+
|
|
426
|
+
const chapter: TrajectoryChapter = {
|
|
427
|
+
id: `ch_${randomBytes(4).toString('hex')}`,
|
|
428
|
+
title,
|
|
429
|
+
agentName,
|
|
430
|
+
startedAt: new Date().toISOString(),
|
|
431
|
+
events: [],
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
this.trajectory.chapters.push(chapter);
|
|
435
|
+
this.currentChapterId = chapter.id;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
private closeCurrentChapter(): void {
|
|
439
|
+
if (!this.trajectory || !this.currentChapterId) return;
|
|
440
|
+
|
|
441
|
+
const chapter = this.trajectory.chapters.find((c) => c.id === this.currentChapterId);
|
|
442
|
+
if (chapter && !chapter.endedAt) {
|
|
443
|
+
chapter.endedAt = new Date().toISOString();
|
|
444
|
+
}
|
|
445
|
+
this.currentChapterId = null;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
private addEvent(
|
|
449
|
+
type: string,
|
|
450
|
+
content: string,
|
|
451
|
+
significance?: 'low' | 'medium' | 'high',
|
|
452
|
+
raw?: Record<string, unknown>,
|
|
453
|
+
): void {
|
|
454
|
+
if (!this.trajectory) return;
|
|
455
|
+
|
|
456
|
+
// Find current chapter or create a default one
|
|
457
|
+
let chapter = this.trajectory.chapters.find((c) => c.id === this.currentChapterId);
|
|
458
|
+
if (!chapter) {
|
|
459
|
+
this.openChapter('Execution', 'orchestrator');
|
|
460
|
+
chapter = this.trajectory.chapters[this.trajectory.chapters.length - 1];
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const event: TrajectoryEvent = {
|
|
464
|
+
ts: Date.now(),
|
|
465
|
+
type,
|
|
466
|
+
content,
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
if (significance) event.significance = significance;
|
|
470
|
+
if (raw) event.raw = raw;
|
|
471
|
+
|
|
472
|
+
chapter.events.push(event);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
private async flush(): Promise<void> {
|
|
476
|
+
if (!this.trajectory) return;
|
|
477
|
+
|
|
478
|
+
try {
|
|
479
|
+
const activeDir = path.join(this.dataDir, 'active');
|
|
480
|
+
await mkdir(activeDir, { recursive: true });
|
|
481
|
+
|
|
482
|
+
const filePath = path.join(activeDir, `${this.trajectory.id}.json`);
|
|
483
|
+
await writeFile(filePath, JSON.stringify(this.trajectory, null, 2), 'utf-8');
|
|
484
|
+
} catch {
|
|
485
|
+
// Non-blocking: trajectory recording failure should never break the workflow
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
private async moveToCompleted(): Promise<void> {
|
|
490
|
+
if (!this.trajectory) return;
|
|
491
|
+
|
|
492
|
+
try {
|
|
493
|
+
const activeDir = path.join(this.dataDir, 'active');
|
|
494
|
+
const completedDir = path.join(this.dataDir, 'completed');
|
|
495
|
+
await mkdir(completedDir, { recursive: true });
|
|
496
|
+
|
|
497
|
+
const activePath = path.join(activeDir, `${this.trajectory.id}.json`);
|
|
498
|
+
const completedPath = path.join(completedDir, `${this.trajectory.id}.json`);
|
|
499
|
+
|
|
500
|
+
if (existsSync(activePath)) {
|
|
501
|
+
await rename(activePath, completedPath);
|
|
502
|
+
}
|
|
503
|
+
} catch {
|
|
504
|
+
// Non-blocking
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
@@ -18,6 +18,21 @@ export interface RelayYamlConfig {
|
|
|
18
18
|
coordination?: CoordinationConfig;
|
|
19
19
|
state?: StateConfig;
|
|
20
20
|
errorHandling?: ErrorHandlingConfig;
|
|
21
|
+
trajectories?: TrajectoryConfig | false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ── Trajectory configuration ─────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
/** Configuration for workflow trajectory recording. */
|
|
27
|
+
export interface TrajectoryConfig {
|
|
28
|
+
/** Enable trajectory recording (default: true). */
|
|
29
|
+
enabled?: boolean;
|
|
30
|
+
/** Auto-reflect when barriers resolve (default: true). */
|
|
31
|
+
reflectOnBarriers?: boolean;
|
|
32
|
+
/** Auto-reflect when parallel tracks converge (default: true). */
|
|
33
|
+
reflectOnConverge?: boolean;
|
|
34
|
+
/** Record retry/skip/fail decisions automatically (default: true). */
|
|
35
|
+
autoDecisions?: boolean;
|
|
21
36
|
}
|
|
22
37
|
|
|
23
38
|
// ── Swarm configuration ─────────────────────────────────────────────────────
|
|
@@ -40,7 +55,20 @@ export type SwarmPattern =
|
|
|
40
55
|
| "cascade"
|
|
41
56
|
| "dag"
|
|
42
57
|
| "debate"
|
|
43
|
-
| "hierarchical"
|
|
58
|
+
| "hierarchical"
|
|
59
|
+
// Additional patterns
|
|
60
|
+
| "map-reduce"
|
|
61
|
+
| "scatter-gather"
|
|
62
|
+
| "supervisor"
|
|
63
|
+
| "reflection"
|
|
64
|
+
| "red-team"
|
|
65
|
+
| "verifier"
|
|
66
|
+
| "auction"
|
|
67
|
+
| "escalation"
|
|
68
|
+
| "saga"
|
|
69
|
+
| "circuit-breaker"
|
|
70
|
+
| "blackboard"
|
|
71
|
+
| "swarm";
|
|
44
72
|
|
|
45
73
|
// ── Agent definitions ───────────────────────────────────────────────────────
|
|
46
74
|
|
|
@@ -62,6 +90,8 @@ export interface AgentConstraints {
|
|
|
62
90
|
timeoutMs?: number;
|
|
63
91
|
retries?: number;
|
|
64
92
|
model?: string;
|
|
93
|
+
/** Silence duration in seconds before the agent is considered idle (0 = disabled, default: 30). */
|
|
94
|
+
idleThresholdSecs?: number;
|
|
65
95
|
}
|
|
66
96
|
|
|
67
97
|
// ── Workflow definitions ────────────────────────────────────────────────────
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-relay/config",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.13",
|
|
4
4
|
"description": "Shared configuration schemas and loaders for Agent Relay",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"test:watch": "vitest"
|
|
84
84
|
},
|
|
85
85
|
"dependencies": {
|
|
86
|
-
"@agent-relay/protocol": "2.3.
|
|
86
|
+
"@agent-relay/protocol": "2.3.13",
|
|
87
87
|
"zod": "^3.23.8",
|
|
88
88
|
"zod-to-json-schema": "^3.23.1"
|
|
89
89
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-relay/continuity",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.13",
|
|
4
4
|
"description": "Session continuity manager for Relay (ledgers, handoffs, resume)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"test:watch": "vitest"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@agent-relay/memory": "2.3.
|
|
25
|
+
"@agent-relay/memory": "2.3.13"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^22.19.3",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-relay/daemon",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.13",
|
|
4
4
|
"description": "Relay daemon server - agent coordination and message routing",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -22,17 +22,17 @@
|
|
|
22
22
|
"test:watch": "vitest"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@agent-relay/protocol": "2.3.
|
|
26
|
-
"@agent-relay/config": "2.3.
|
|
27
|
-
"@agent-relay/storage": "2.3.
|
|
28
|
-
"@agent-relay/bridge": "2.3.
|
|
29
|
-
"@agent-relay/utils": "2.3.
|
|
30
|
-
"@agent-relay/policy": "2.3.
|
|
31
|
-
"@agent-relay/memory": "2.3.
|
|
32
|
-
"@agent-relay/resiliency": "2.3.
|
|
33
|
-
"@agent-relay/user-directory": "2.3.
|
|
34
|
-
"@agent-relay/wrapper": "2.3.
|
|
35
|
-
"@agent-relay/telemetry": "2.3.
|
|
25
|
+
"@agent-relay/protocol": "2.3.13",
|
|
26
|
+
"@agent-relay/config": "2.3.13",
|
|
27
|
+
"@agent-relay/storage": "2.3.13",
|
|
28
|
+
"@agent-relay/bridge": "2.3.13",
|
|
29
|
+
"@agent-relay/utils": "2.3.13",
|
|
30
|
+
"@agent-relay/policy": "2.3.13",
|
|
31
|
+
"@agent-relay/memory": "2.3.13",
|
|
32
|
+
"@agent-relay/resiliency": "2.3.13",
|
|
33
|
+
"@agent-relay/user-directory": "2.3.13",
|
|
34
|
+
"@agent-relay/wrapper": "2.3.13",
|
|
35
|
+
"@agent-relay/telemetry": "2.3.13",
|
|
36
36
|
"ws": "^8.18.3",
|
|
37
37
|
"pg": "^8.16.3",
|
|
38
38
|
"uuid": "^10.0.0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-relay/hooks",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.13",
|
|
4
4
|
"description": "Hook emitter, registry, and trajectory hooks for Agent Relay",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
"test:watch": "vitest"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@agent-relay/protocol": "2.3.
|
|
41
|
-
"@agent-relay/config": "2.3.
|
|
42
|
-
"@agent-relay/trajectory": "2.3.
|
|
40
|
+
"@agent-relay/protocol": "2.3.13",
|
|
41
|
+
"@agent-relay/config": "2.3.13",
|
|
42
|
+
"@agent-relay/trajectory": "2.3.13"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/node": "^22.19.3",
|