agents-relay 1.0.96 → 1.0.98
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.
|
@@ -165,13 +165,14 @@ export class ReconcilerRuntimeBase {
|
|
|
165
165
|
this.detach(this.reconcile(job.id), job.id, task.id, 'late worker-start reconciliation');
|
|
166
166
|
}
|
|
167
167
|
isRecoverableWatchdogFailure(task) {
|
|
168
|
-
if (['worker_start_timeout', 'terminal_event_timeout', 'worker_exit_before_start', 'worker_exit_without_terminal_event'].includes(task.failureReason ?? ''))
|
|
168
|
+
if (['worker_start_timeout', 'terminal_event_timeout', 'worker_exit_before_start', 'worker_exit_without_terminal_event', 'worker_delivery_after_start'].includes(task.failureReason ?? ''))
|
|
169
169
|
return true;
|
|
170
170
|
const error = task.error ?? '';
|
|
171
171
|
return error.includes('worker_start_timeout')
|
|
172
172
|
|| error.includes('timed out waiting for the mandatory terminal worker event')
|
|
173
173
|
|| error.includes('exited before publishing task.started')
|
|
174
|
-
|| error.includes('exited after task.started without publishing a terminal task event')
|
|
174
|
+
|| error.includes('exited after task.started without publishing a terminal task event')
|
|
175
|
+
|| error.includes('Worker delivery failed after task.started');
|
|
175
176
|
}
|
|
176
177
|
isCorrelatedWorkerEvent(task, event) {
|
|
177
178
|
return task.executionId !== null && eventSourceId(event) === executionEventSource(event.job_id, task.id, task.executionId);
|
|
@@ -187,19 +188,18 @@ export class ReconcilerRuntimeBase {
|
|
|
187
188
|
const task = job.tasks.find(item => item.id === taskId);
|
|
188
189
|
if (!task || task.executionId !== executionId || !['QUEUED', 'RUNNING'].includes(task.state))
|
|
189
190
|
return;
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
task.error = event.message;
|
|
194
|
-
task.failureReason = phase === 'start-ack' ? 'worker_start_timeout' : undefined;
|
|
195
|
-
task.leaseOwner = null;
|
|
196
|
-
task.leaseExpiresAt = null;
|
|
197
|
-
await transitionTaskEvent(task, 'fail');
|
|
198
|
-
await this.store.saveTask(task);
|
|
199
|
-
}
|
|
200
|
-
else {
|
|
201
|
-
await this.applyTerminalEvent(event, true);
|
|
191
|
+
if (task.state === 'RUNNING') {
|
|
192
|
+
await this.emit(eventFor(jobId, taskId, task.parentTaskId, 'task.process.delivery_failed', 'failed', `Worker ${phase} reported after task.started: ${message}`, 'orchestrator', { phase, authoritative: false, executionId }, { agent: 'relay', component: 'agents-relay' }));
|
|
193
|
+
return;
|
|
202
194
|
}
|
|
195
|
+
const event = eventFor(jobId, taskId, task.parentTaskId, 'task.failed', 'failed', `Worker ${phase} failed: ${message}`, 'user', { phase, reason: phase === 'start-ack' ? 'worker_start_timeout' : undefined }, { agent: 'relay', component: 'agents-relay', watchdog: true });
|
|
196
|
+
this.clearExecution(task.id, executionId);
|
|
197
|
+
task.error = event.message;
|
|
198
|
+
task.failureReason = phase === 'start-ack' ? 'worker_start_timeout' : undefined;
|
|
199
|
+
task.leaseOwner = null;
|
|
200
|
+
task.leaseExpiresAt = null;
|
|
201
|
+
await transitionTaskEvent(task, 'fail');
|
|
202
|
+
await this.store.saveTask(task);
|
|
203
203
|
await this.emit(event);
|
|
204
204
|
}
|
|
205
205
|
async runtimeSettled(jobId, taskId, executionId, result, error) {
|
package/dist/reconciler.js
CHANGED
|
@@ -203,7 +203,7 @@ export class Reconciler extends ReconcilerReviewBase {
|
|
|
203
203
|
for (const task of job.tasks.filter(item => !['SUCCEEDED', 'FAILED', 'CANCELLED'].includes(item.state))) {
|
|
204
204
|
this.live.get(task.id)?.execution.cancel();
|
|
205
205
|
this.plannerLive.get(task.id)?.controller.abort();
|
|
206
|
-
await transitionTaskEvent(task, 'cancel');
|
|
206
|
+
await transitionTaskEvent(task, task.state === 'RUNNING' ? 'task.cancelled' : 'cancel');
|
|
207
207
|
task.error = reason;
|
|
208
208
|
task.leaseOwner = null;
|
|
209
209
|
task.leaseExpiresAt = null;
|
package/dist/troubleshooter.js
CHANGED
|
@@ -30,6 +30,18 @@ function belongsToCurrentRun(job, task) {
|
|
|
30
30
|
const slot = job.schedule?.currentRunSlot;
|
|
31
31
|
return !slot || task.createdAt >= slot;
|
|
32
32
|
}
|
|
33
|
+
const ACTIVE_RECOVERY_STATES = new Set(['DRAFT', 'QUEUED', 'RUNNING', 'BLOCKED']);
|
|
34
|
+
function recoveryTargetsTask(recovery, original) {
|
|
35
|
+
if (recovery.agentName !== 'troubleshooter')
|
|
36
|
+
return false;
|
|
37
|
+
if (recovery.parentTaskId)
|
|
38
|
+
return recovery.parentTaskId === original.id;
|
|
39
|
+
return recovery.id.startsWith(`troubleshoot-${slug(original.id)}-a`);
|
|
40
|
+
}
|
|
41
|
+
function hasRecoveryBarrier(job, original) {
|
|
42
|
+
return job.tasks.some(task => recoveryTargetsTask(task, original)
|
|
43
|
+
&& (ACTIVE_RECOVERY_STATES.has(task.state) || ['FAILED', 'CANCELLED'].includes(task.state)));
|
|
44
|
+
}
|
|
33
45
|
export function pendingTroubleshooterTasks(job) {
|
|
34
46
|
const existing = new Set(job.tasks.filter(task => task.agentName === 'troubleshooter').map(task => task.id));
|
|
35
47
|
const recoveries = [];
|
|
@@ -37,7 +49,7 @@ export function pendingTroubleshooterTasks(job) {
|
|
|
37
49
|
if (!['FAILED', 'BLOCKED'].includes(task.state) || task.kind === 'planner' || task.agentName === 'troubleshooter' || !belongsToCurrentRun(job, task))
|
|
38
50
|
continue;
|
|
39
51
|
const id = troubleshooterTaskId(task);
|
|
40
|
-
if (existing.has(id))
|
|
52
|
+
if (existing.has(id) || hasRecoveryBarrier(job, task))
|
|
41
53
|
continue;
|
|
42
54
|
const now = new Date().toISOString();
|
|
43
55
|
recoveries.push({
|
|
@@ -48,7 +60,7 @@ export function pendingTroubleshooterTasks(job) {
|
|
|
48
60
|
storySize: 'M',
|
|
49
61
|
projectName: job.title,
|
|
50
62
|
agentName: 'troubleshooter',
|
|
51
|
-
parentTaskId:
|
|
63
|
+
parentTaskId: task.id,
|
|
52
64
|
dependencies: [],
|
|
53
65
|
capabilities: ['troubleshoot', 'recovery'],
|
|
54
66
|
workerRequirements: { capabilities: ['model', 'local_repo', 'iterative_tools'], runtime: 'codex' },
|
package/package.json
CHANGED
|
@@ -8,15 +8,62 @@ You are a pure planning agent. Relay invokes you with the durable Job, existing
|
|
|
8
8
|
|
|
9
9
|
Relay persists each successful planner invocation as one planner round and creates the returned Tasks under that round.
|
|
10
10
|
|
|
11
|
+
## Planning contract
|
|
12
|
+
|
|
13
|
+
**Plan the whole objective before choosing Task boundaries.**
|
|
14
|
+
|
|
15
|
+
For every non-satisfied objective:
|
|
16
|
+
|
|
17
|
+
1. Reconstruct the end-to-end execution flow from the current durable state to objective completion.
|
|
18
|
+
2. Express that flow as a compact Mermaid graph in `assessment`.
|
|
19
|
+
3. Mark the points in that graph that deserve durable Task boundaries.
|
|
20
|
+
4. Create only the next useful executable Task batch from those boundaries.
|
|
21
|
+
|
|
22
|
+
A Task is **not** just a workflow step. Create a Task when its outcome deserves an independent durable lifecycle because it may need to be:
|
|
23
|
+
|
|
24
|
+
- discussed or referenced later;
|
|
25
|
+
- independently reviewed, approved, revised, or compared;
|
|
26
|
+
- blocked/retried/reconciled independently;
|
|
27
|
+
- waited on as a meaningful external dependency;
|
|
28
|
+
- executed in parallel with a separately meaningful outcome; or
|
|
29
|
+
- preserved as an independently useful deliverable or decision.
|
|
30
|
+
|
|
31
|
+
If an intermediate result does not need that independent lifecycle, keep it inside the executing Task's agent graph/workflow rather than promoting it to another Task.
|
|
32
|
+
|
|
33
|
+
Keep sibling Tasks at a comparable abstraction level. Do not mix a whole product milestone with tiny implementation steps as peers.
|
|
34
|
+
|
|
35
|
+
The graph is the explanation for the decomposition: every emitted Task should map to a meaningful boundary in the graph, and important graph boundaries should not be omitted accidentally.
|
|
36
|
+
|
|
37
|
+
### Examples
|
|
38
|
+
|
|
39
|
+
Prefer:
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
Task: Create and publish today's vlog
|
|
43
|
+
internal graph: collect media -> select story -> script -> edit -> captions -> QA -> publish
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
when nobody needs to separately review or discuss those intermediate outputs.
|
|
47
|
+
|
|
48
|
+
But when script approval is a real durable gate, prefer:
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
Task 1: Produce script for review
|
|
52
|
+
-> approval/revision boundary
|
|
53
|
+
Task 2: Produce and publish the approved vlog
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Do not create separate Tasks merely for "analyze", "format", "call tool", "check status", or other internal mechanics.
|
|
57
|
+
|
|
11
58
|
## Rules
|
|
12
59
|
|
|
13
60
|
- Treat durable Job/Task state as authoritative.
|
|
14
61
|
- Never invent completed work or evidence.
|
|
15
62
|
- First decide whether the objective is already satisfied.
|
|
16
63
|
- If satisfied, return `objective_status: "satisfied"` and `next_tasks: []`.
|
|
17
|
-
- Otherwise return only the next useful executable batch.
|
|
64
|
+
- Otherwise return only the next useful executable batch from the graph-derived Task boundaries.
|
|
18
65
|
- Do not repeat completed work or recreate an existing Task unless replacement work is explicitly needed.
|
|
19
|
-
- Prefer
|
|
66
|
+
- Prefer independently verifiable Tasks with stable IDs, but do not split work solely to make Tasks smaller.
|
|
20
67
|
- A Task describes what an agent must accomplish; never emit a shell command as a worker Task.
|
|
21
68
|
- Use dependencies only when execution order actually matters.
|
|
22
69
|
- Every child Task must declare `workerRequirements`; worker-router selects the worker and model-router selects provider/model/reasoning afterward.
|
|
@@ -30,4 +77,11 @@ Relay persists each successful planner invocation as one planner round and creat
|
|
|
30
77
|
|
|
31
78
|
Return exactly one JSON object with `objective_status`, `assessment`, and `next_tasks`.
|
|
32
79
|
|
|
80
|
+
For `objective_status: "in_progress"`, `assessment` must be Markdown containing:
|
|
81
|
+
|
|
82
|
+
1. a compact Mermaid graph of the full remaining objective flow;
|
|
83
|
+
2. a short explanation of the chosen durable Task boundaries.
|
|
84
|
+
|
|
85
|
+
For `objective_status: "satisfied"`, `assessment` may be a concise evidence-based completion assessment without a graph.
|
|
86
|
+
|
|
33
87
|
Each item in `next_tasks` contains `id`, `input`, `workerRequirements`, `output`, and `storySize`. It may also contain priority, projectName, agentName, dependencies, capabilities, maxAttempts, and timeoutMs. Adapter/routing fields are selected by Relay after planning; do not invent them.
|