@loopingai/core 0.5.0 → 0.6.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/README.md +6 -4
- package/dist/a2a/agent-stub.d.ts +0 -1
- package/dist/a2a/agent-stub.js +0 -1
- package/dist/a2a/caller.d.ts +0 -1
- package/dist/a2a/caller.js +0 -1
- package/dist/a2a/card.d.ts +0 -1
- package/dist/a2a/card.js +0 -1
- package/dist/a2a/context.d.ts +0 -1
- package/dist/a2a/context.js +0 -1
- package/dist/a2a/executor.d.ts +0 -1
- package/dist/a2a/executor.js +0 -1
- package/dist/a2a/index.d.ts +0 -1
- package/dist/a2a/index.js +0 -1
- package/dist/a2a/notify.d.ts +4 -4
- package/dist/a2a/notify.js +4 -4
- package/dist/a2a/parts.d.ts +0 -1
- package/dist/a2a/parts.js +0 -1
- package/dist/a2a/push.d.ts +0 -1
- package/dist/a2a/push.js +0 -1
- package/dist/a2a/task-store.d.ts +0 -1
- package/dist/a2a/task-store.js +0 -1
- package/dist/a2a/task.d.ts +0 -1
- package/dist/a2a/task.js +0 -1
- package/dist/a2a/verify.d.ts +0 -1
- package/dist/a2a/verify.js +0 -1
- package/dist/agent/anthropic/index.d.ts +15 -0
- package/dist/agent/anthropic/index.js +19 -0
- package/dist/agent/anthropic/language-model.d.ts +59 -0
- package/dist/agent/anthropic/language-model.js +442 -0
- package/dist/agent/anthropic/prompt.d.ts +84 -0
- package/dist/agent/anthropic/prompt.js +541 -0
- package/dist/agent/anthropic/runtime.d.ts +79 -0
- package/dist/agent/anthropic/runtime.js +130 -0
- package/dist/agent/budget.d.ts +0 -1
- package/dist/agent/budget.js +0 -1
- package/dist/agent/control.d.ts +0 -1
- package/dist/agent/control.js +10 -10
- package/dist/agent/errors.d.ts +85 -0
- package/dist/agent/errors.js +64 -0
- package/dist/agent/final-reply.d.ts +14 -14
- package/dist/agent/final-reply.js +28 -12
- package/dist/agent/history.d.ts +3 -4
- package/dist/agent/history.js +2 -3
- package/dist/agent/index.d.ts +4 -3
- package/dist/agent/index.js +4 -3
- package/dist/agent/inference.d.ts +58 -2
- package/dist/agent/inference.js +44 -1
- package/dist/agent/model.d.ts +42 -26
- package/dist/agent/model.js +1 -49
- package/dist/agent/session.d.ts +6 -8
- package/dist/agent/session.js +3 -4
- package/dist/agent/workers-ai/index.d.ts +23 -0
- package/dist/agent/workers-ai/index.js +23 -0
- package/dist/agent/workers-ai/runtime.d.ts +42 -0
- package/dist/agent/workers-ai/runtime.js +63 -0
- package/dist/config.d.ts +49 -16
- package/dist/config.js +30 -2
- package/dist/contract/index.d.ts +0 -1
- package/dist/contract/index.js +0 -1
- package/dist/contract/plugin.d.ts +63 -4
- package/dist/contract/plugin.js +76 -1
- package/dist/contract/recipe.d.ts +16 -18
- package/dist/contract/recipe.js +0 -1
- package/dist/contract/validation.d.ts +0 -1
- package/dist/contract/validation.js +0 -1
- package/dist/db/db.d.ts +0 -2
- package/dist/db/db.js +0 -1
- package/dist/db/index.d.ts +0 -1
- package/dist/db/index.js +0 -1
- package/dist/db/migrations/index.d.ts +0 -1
- package/dist/db/migrations/index.js +8 -2
- package/dist/db/models/subtasks.d.ts +24 -26
- package/dist/db/models/subtasks.js +33 -77
- package/dist/db/models/tasks.d.ts +0 -1
- package/dist/db/models/tasks.js +0 -1
- package/dist/db/schema.d.ts +2 -22
- package/dist/db/schema.js +2 -5
- package/dist/env.d.ts +0 -1
- package/dist/env.js +0 -1
- package/dist/host/agent.d.ts +58 -5
- package/dist/host/agent.js +63 -10
- package/dist/host/index.d.ts +0 -1
- package/dist/host/index.js +0 -1
- package/dist/host/plugin-host.d.ts +0 -1
- package/dist/host/plugin-host.js +0 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.js +2 -3
- package/dist/platform.d.ts +74 -12
- package/dist/platform.js +76 -14
- package/dist/round/agent.d.ts +36 -32
- package/dist/round/agent.js +61 -90
- package/dist/round/index.d.ts +3 -3
- package/dist/round/index.js +2 -3
- package/dist/round/policy.d.ts +2 -3
- package/dist/round/policy.js +0 -1
- package/dist/round/subagent.d.ts +19 -2
- package/dist/round/subagent.js +22 -6
- package/dist/round/turn.d.ts +32 -14
- package/dist/round/turn.js +83 -17
- package/dist/round/workflow.d.ts +30 -9
- package/dist/round/workflow.js +146 -78
- package/dist/runtime/index.d.ts +4 -3
- package/dist/runtime/index.js +6 -1
- package/dist/runtime/tool-families.d.ts +0 -1
- package/dist/runtime/tool-families.js +0 -1
- package/dist/subagent/fingerprint.d.ts +2 -3
- package/dist/subagent/fingerprint.js +8 -18
- package/dist/subagent/index.d.ts +6 -5
- package/dist/subagent/index.js +8 -7
- package/dist/subagent/prompt.d.ts +4 -6
- package/dist/subagent/prompt.js +0 -9
- package/dist/subagent/run.d.ts +8 -2
- package/dist/subagent/run.js +59 -10
- package/dist/subagent/workspace.d.ts +0 -1
- package/dist/subagent/workspace.js +0 -1
- package/dist/subtasks/catalog.d.ts +1 -2
- package/dist/subtasks/catalog.js +1 -2
- package/dist/subtasks/decomposition.d.ts +16 -21
- package/dist/subtasks/decomposition.js +27 -76
- package/dist/subtasks/delegate.d.ts +20 -2
- package/dist/subtasks/delegate.js +21 -17
- package/dist/subtasks/index.d.ts +1 -3
- package/dist/subtasks/index.js +1 -3
- package/dist/subtasks/subtask-types.d.ts +0 -9
- package/dist/subtasks/subtask-types.js +0 -8
- package/dist/subtasks/types.d.ts +45 -71
- package/dist/subtasks/types.js +0 -1
- package/dist/testing/auth.d.ts +0 -1
- package/dist/testing/auth.js +0 -1
- package/dist/testing/do.d.ts +0 -1
- package/dist/testing/do.js +0 -1
- package/dist/testing/fake-session.d.ts +0 -1
- package/dist/testing/fake-session.js +0 -1
- package/dist/testing/fixtures.d.ts +0 -1
- package/dist/testing/fixtures.js +0 -1
- package/dist/testing/harness.d.ts +0 -1
- package/dist/testing/harness.js +0 -1
- package/dist/testing/index.d.ts +0 -1
- package/dist/testing/index.js +0 -1
- package/dist/testing/mock-model.d.ts +35 -1
- package/dist/testing/mock-model.js +75 -1
- package/dist/testing/node.d.ts +0 -1
- package/dist/testing/node.js +0 -1
- package/dist/testing/vcr-global-setup.d.ts +1 -4
- package/dist/testing/vcr-global-setup.js +1 -4
- package/dist/testing/vcr-shared.d.ts +0 -1
- package/dist/testing/vcr-shared.js +0 -1
- package/dist/testing/vcr-spec.d.ts +0 -1
- package/dist/testing/vcr-spec.js +0 -1
- package/dist/testing/vcr-store.d.ts +0 -1
- package/dist/testing/vcr-store.js +0 -1
- package/dist/testing/vcr.d.ts +0 -1
- package/dist/testing/vcr.js +0 -1
- package/dist/worker/define-agent.d.ts +0 -1
- package/dist/worker/define-agent.js +0 -1
- package/dist/worker/index.d.ts +5 -13
- package/dist/worker/index.js +5 -13
- package/package.json +20 -2
- package/dist/a2a/agent-stub.d.ts.map +0 -1
- package/dist/a2a/agent-stub.js.map +0 -1
- package/dist/a2a/caller.d.ts.map +0 -1
- package/dist/a2a/caller.js.map +0 -1
- package/dist/a2a/card.d.ts.map +0 -1
- package/dist/a2a/card.js.map +0 -1
- package/dist/a2a/context.d.ts.map +0 -1
- package/dist/a2a/context.js.map +0 -1
- package/dist/a2a/executor.d.ts.map +0 -1
- package/dist/a2a/executor.js.map +0 -1
- package/dist/a2a/index.d.ts.map +0 -1
- package/dist/a2a/index.js.map +0 -1
- package/dist/a2a/notify.d.ts.map +0 -1
- package/dist/a2a/notify.js.map +0 -1
- package/dist/a2a/parts.d.ts.map +0 -1
- package/dist/a2a/parts.js.map +0 -1
- package/dist/a2a/push.d.ts.map +0 -1
- package/dist/a2a/push.js.map +0 -1
- package/dist/a2a/task-store.d.ts.map +0 -1
- package/dist/a2a/task-store.js.map +0 -1
- package/dist/a2a/task.d.ts.map +0 -1
- package/dist/a2a/task.js.map +0 -1
- package/dist/a2a/verify.d.ts.map +0 -1
- package/dist/a2a/verify.js.map +0 -1
- package/dist/agent/budget.d.ts.map +0 -1
- package/dist/agent/budget.js.map +0 -1
- package/dist/agent/control.d.ts.map +0 -1
- package/dist/agent/control.js.map +0 -1
- package/dist/agent/final-reply.d.ts.map +0 -1
- package/dist/agent/final-reply.js.map +0 -1
- package/dist/agent/history.d.ts.map +0 -1
- package/dist/agent/history.js.map +0 -1
- package/dist/agent/index.d.ts.map +0 -1
- package/dist/agent/index.js.map +0 -1
- package/dist/agent/inference.d.ts.map +0 -1
- package/dist/agent/inference.js.map +0 -1
- package/dist/agent/model.d.ts.map +0 -1
- package/dist/agent/model.js.map +0 -1
- package/dist/agent/session.d.ts.map +0 -1
- package/dist/agent/session.js.map +0 -1
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/contract/index.d.ts.map +0 -1
- package/dist/contract/index.js.map +0 -1
- package/dist/contract/plugin.d.ts.map +0 -1
- package/dist/contract/plugin.js.map +0 -1
- package/dist/contract/recipe.d.ts.map +0 -1
- package/dist/contract/recipe.js.map +0 -1
- package/dist/contract/validation.d.ts.map +0 -1
- package/dist/contract/validation.js.map +0 -1
- package/dist/db/db.d.ts.map +0 -1
- package/dist/db/db.js.map +0 -1
- package/dist/db/index.d.ts.map +0 -1
- package/dist/db/index.js.map +0 -1
- package/dist/db/migrations/index.d.ts.map +0 -1
- package/dist/db/migrations/index.js.map +0 -1
- package/dist/db/models/subtasks.d.ts.map +0 -1
- package/dist/db/models/subtasks.js.map +0 -1
- package/dist/db/models/tasks.d.ts.map +0 -1
- package/dist/db/models/tasks.js.map +0 -1
- package/dist/db/schema.d.ts.map +0 -1
- package/dist/db/schema.js.map +0 -1
- package/dist/env.d.ts.map +0 -1
- package/dist/env.js.map +0 -1
- package/dist/host/agent.d.ts.map +0 -1
- package/dist/host/agent.js.map +0 -1
- package/dist/host/index.d.ts.map +0 -1
- package/dist/host/index.js.map +0 -1
- package/dist/host/plugin-host.d.ts.map +0 -1
- package/dist/host/plugin-host.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/platform.d.ts.map +0 -1
- package/dist/platform.js.map +0 -1
- package/dist/round/agent.d.ts.map +0 -1
- package/dist/round/agent.js.map +0 -1
- package/dist/round/index.d.ts.map +0 -1
- package/dist/round/index.js.map +0 -1
- package/dist/round/policy.d.ts.map +0 -1
- package/dist/round/policy.js.map +0 -1
- package/dist/round/subagent.d.ts.map +0 -1
- package/dist/round/subagent.js.map +0 -1
- package/dist/round/turn.d.ts.map +0 -1
- package/dist/round/turn.js.map +0 -1
- package/dist/round/workflow.d.ts.map +0 -1
- package/dist/round/workflow.js.map +0 -1
- package/dist/runtime/index.d.ts.map +0 -1
- package/dist/runtime/index.js.map +0 -1
- package/dist/runtime/tool-families.d.ts.map +0 -1
- package/dist/runtime/tool-families.js.map +0 -1
- package/dist/subagent/fingerprint.d.ts.map +0 -1
- package/dist/subagent/fingerprint.js.map +0 -1
- package/dist/subagent/index.d.ts.map +0 -1
- package/dist/subagent/index.js.map +0 -1
- package/dist/subagent/prompt.d.ts.map +0 -1
- package/dist/subagent/prompt.js.map +0 -1
- package/dist/subagent/run.d.ts.map +0 -1
- package/dist/subagent/run.js.map +0 -1
- package/dist/subagent/workspace.d.ts.map +0 -1
- package/dist/subagent/workspace.js.map +0 -1
- package/dist/subtasks/catalog.d.ts.map +0 -1
- package/dist/subtasks/catalog.js.map +0 -1
- package/dist/subtasks/decomposition.d.ts.map +0 -1
- package/dist/subtasks/decomposition.js.map +0 -1
- package/dist/subtasks/delegate.d.ts.map +0 -1
- package/dist/subtasks/delegate.js.map +0 -1
- package/dist/subtasks/index.d.ts.map +0 -1
- package/dist/subtasks/index.js.map +0 -1
- package/dist/subtasks/scheduler.d.ts +0 -49
- package/dist/subtasks/scheduler.d.ts.map +0 -1
- package/dist/subtasks/scheduler.js +0 -48
- package/dist/subtasks/scheduler.js.map +0 -1
- package/dist/subtasks/subtask-types.d.ts.map +0 -1
- package/dist/subtasks/subtask-types.js.map +0 -1
- package/dist/subtasks/types.d.ts.map +0 -1
- package/dist/subtasks/types.js.map +0 -1
- package/dist/testing/auth.d.ts.map +0 -1
- package/dist/testing/auth.js.map +0 -1
- package/dist/testing/do.d.ts.map +0 -1
- package/dist/testing/do.js.map +0 -1
- package/dist/testing/fake-session.d.ts.map +0 -1
- package/dist/testing/fake-session.js.map +0 -1
- package/dist/testing/fixtures.d.ts.map +0 -1
- package/dist/testing/fixtures.js.map +0 -1
- package/dist/testing/harness.d.ts.map +0 -1
- package/dist/testing/harness.js.map +0 -1
- package/dist/testing/index.d.ts.map +0 -1
- package/dist/testing/index.js.map +0 -1
- package/dist/testing/mock-model.d.ts.map +0 -1
- package/dist/testing/mock-model.js.map +0 -1
- package/dist/testing/node.d.ts.map +0 -1
- package/dist/testing/node.js.map +0 -1
- package/dist/testing/vcr-global-setup.d.ts.map +0 -1
- package/dist/testing/vcr-global-setup.js.map +0 -1
- package/dist/testing/vcr-shared.d.ts.map +0 -1
- package/dist/testing/vcr-shared.js.map +0 -1
- package/dist/testing/vcr-spec.d.ts.map +0 -1
- package/dist/testing/vcr-spec.js.map +0 -1
- package/dist/testing/vcr-store.d.ts.map +0 -1
- package/dist/testing/vcr-store.js.map +0 -1
- package/dist/testing/vcr.d.ts.map +0 -1
- package/dist/testing/vcr.js.map +0 -1
- package/dist/worker/define-agent.d.ts.map +0 -1
- package/dist/worker/define-agent.js.map +0 -1
- package/dist/worker/index.d.ts.map +0 -1
- package/dist/worker/index.js.map +0 -1
package/dist/round/turn.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { generateText, hasToolCall, isStepCount } from "ai";
|
|
2
2
|
import { appendOnce } from "../agent/session.js";
|
|
3
3
|
import { deterministicSessionMessage, finalReplyMessageId, parseRoundAckMessageId, roundAckMessageId, sessionText, taskUserMessageId } from "../agent/history.js";
|
|
4
|
-
import { buildIntermediateContentHandler, isTransientAiError } from "../agent/inference.js";
|
|
4
|
+
import { buildIntermediateContentHandler, isTransientAiError, nonRecoverableKind } from "../agent/inference.js";
|
|
5
5
|
import { controlTools, controlToolSet } from "../agent/control.js";
|
|
6
6
|
import { FINAL_REPLY_TOOL_NAME } from "../agent/final-reply.js";
|
|
7
7
|
import { stepAllowance } from "../agent/budget.js";
|
|
@@ -240,9 +240,12 @@ async function attempt(args, control, model, instructions, messages) {
|
|
|
240
240
|
// change here.
|
|
241
241
|
...control.map((c) => hasToolCall(c.name))
|
|
242
242
|
],
|
|
243
|
-
//
|
|
244
|
-
//
|
|
245
|
-
|
|
243
|
+
// Retries on *this* model before the slot is given up, honouring the
|
|
244
|
+
// provider's own `retry-after`. Not a duplicate of the fallback: the
|
|
245
|
+
// fallback answers "this model cannot do it", and a 429 says "not yet" —
|
|
246
|
+
// and when both slots share a credential the fallback cannot even answer
|
|
247
|
+
// that. See `ModelConfig.maxRetries`.
|
|
248
|
+
maxRetries: args.maxRetries,
|
|
246
249
|
// Charged here rather than from `result.steps` so a throw mid-loop still
|
|
247
250
|
// bills the steps already spent — the `catch` below has no `result` to read.
|
|
248
251
|
onStepEnd: async (step) => {
|
|
@@ -329,9 +332,16 @@ const MAX_REPAIR_ATTEMPTS = 3;
|
|
|
329
332
|
* The id a repaired exchange is anchored on, derived from the Task and round like
|
|
330
333
|
* every other id here. Suffixed per repair, so several rejected calls can sit in
|
|
331
334
|
* one attempt's messages without colliding.
|
|
335
|
+
*
|
|
336
|
+
* Underscore-separated for the same reason as
|
|
337
|
+
* {@link file://../subtasks/delegate.ts delegateToolCallId}: this reaches a
|
|
338
|
+
* provider as a `tool_use.id`, and Anthropic rejects anything outside
|
|
339
|
+
* `^[a-zA-Z0-9_-]+$`. A repair exchange is exactly the moment a round is already
|
|
340
|
+
* in trouble, so an id that 400s here turns a recoverable bad call into a dead
|
|
341
|
+
* round.
|
|
332
342
|
*/
|
|
333
343
|
function controlCallId(taskId, round) {
|
|
334
|
-
return `
|
|
344
|
+
return `task_${taskId}_round_${round}_control`;
|
|
335
345
|
}
|
|
336
346
|
/**
|
|
337
347
|
* A rejected control call paired with its rejection, as the exchange the model has
|
|
@@ -400,7 +410,20 @@ function repairExchange(toolCallId, rejected, error) {
|
|
|
400
410
|
* Throws only on a transient platform fault (for the Workflow step to retry).
|
|
401
411
|
* A deterministic failure that outlasts every repair on both slots, with durable
|
|
402
412
|
* work behind it, degrades to {@link joinSuccessfulBranches} rather than discarding
|
|
403
|
-
* completed branches; with nothing behind it, it resolves to
|
|
413
|
+
* completed branches; with nothing behind it, it resolves to
|
|
414
|
+
* `{ status: "failed", kind: "exhausted" }`.
|
|
415
|
+
*
|
|
416
|
+
* The third failure is neither, and it short-circuits the *model* recoveries
|
|
417
|
+
* above: a {@link nonRecoverableKind} error ends the round from wherever it
|
|
418
|
+
* happens, carrying that kind — without repairing and **without reaching the
|
|
419
|
+
* fallback slot**, both of which would only present the same dead credential
|
|
420
|
+
* again. See that function for why the transient/deterministic split cannot
|
|
421
|
+
* express it.
|
|
422
|
+
*
|
|
423
|
+
* It does **not** skip the deterministic join. That path needs no credential —
|
|
424
|
+
* it is string concatenation over rows that are already durable — so completed
|
|
425
|
+
* branches are still delivered, and the credential fault reaches the operator
|
|
426
|
+
* through the log rather than by throwing away finished work.
|
|
404
427
|
*/
|
|
405
428
|
export async function runTurn(args) {
|
|
406
429
|
const { session, taskId, round, text, systemSuffix, models, branches } = args;
|
|
@@ -434,6 +457,34 @@ export async function runTurn(args) {
|
|
|
434
457
|
// and so is a repair.
|
|
435
458
|
const outcome = await attempt(args, control, model, system, slotMessages);
|
|
436
459
|
if (!outcome.ok) {
|
|
460
|
+
// Before anything else, and before the fallback slot exists as an
|
|
461
|
+
// option: a failure nothing can clear ends the round here. Repairing
|
|
462
|
+
// asks a dead credential to try again; falling through spends the
|
|
463
|
+
// second slot presenting the *same* dead credential. Both are pure
|
|
464
|
+
// cost, and both delay the only useful outcome — telling an operator
|
|
465
|
+
// what to fix.
|
|
466
|
+
const nonRecoverable = nonRecoverableKind(outcome.error);
|
|
467
|
+
if (nonRecoverable) {
|
|
468
|
+
console.error("[turn] non-recoverable model failure", {
|
|
469
|
+
taskId,
|
|
470
|
+
round,
|
|
471
|
+
model: modelId,
|
|
472
|
+
kind: nonRecoverable,
|
|
473
|
+
error: String(outcome.error)
|
|
474
|
+
});
|
|
475
|
+
// What ends here is *inference*, not the round's ability to answer.
|
|
476
|
+
// Branches that already completed are durable rows, and joining them
|
|
477
|
+
// costs no credential — so the same rescue the exhausted path takes
|
|
478
|
+
// applies, and the operator hears about the fault from the log above.
|
|
479
|
+
const joined = await deterministicJoin(args);
|
|
480
|
+
if (joined)
|
|
481
|
+
return joined;
|
|
482
|
+
return {
|
|
483
|
+
status: "failed",
|
|
484
|
+
kind: nonRecoverable,
|
|
485
|
+
error: String(outcome.error)
|
|
486
|
+
};
|
|
487
|
+
}
|
|
437
488
|
errors.push(outcome.error);
|
|
438
489
|
const { rejected } = outcome;
|
|
439
490
|
diagnostics.push(rejected
|
|
@@ -458,7 +509,7 @@ export async function runTurn(args) {
|
|
|
458
509
|
if (rejected &&
|
|
459
510
|
repair < MAX_REPAIR_ATTEMPTS &&
|
|
460
511
|
args.budget.spent < args.budget.allowance) {
|
|
461
|
-
slotMessages.push(...repairExchange(`${controlCallId(taskId, round)}
|
|
512
|
+
slotMessages.push(...repairExchange(`${controlCallId(taskId, round)}_repair_${repair}`, rejected, outcome.error));
|
|
462
513
|
continue;
|
|
463
514
|
}
|
|
464
515
|
break;
|
|
@@ -484,14 +535,29 @@ export async function runTurn(args) {
|
|
|
484
535
|
const detail = `round ${round} exhausted both models — ${diagnostics.join("; ")}`;
|
|
485
536
|
// Both models failed deterministically. Any branch results behind us are durable
|
|
486
537
|
// and useful; deliver them joined rather than failing a Task whose work is done.
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
538
|
+
const joined = await deterministicJoin(args);
|
|
539
|
+
if (joined)
|
|
540
|
+
return joined;
|
|
541
|
+
return { status: "failed", kind: "exhausted", error: detail };
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Deliver the branch results this round already has, when no model will produce
|
|
545
|
+
* an answer over them.
|
|
546
|
+
*
|
|
547
|
+
* The one recovery on this file that needs **no** model: a filter, a join and a
|
|
548
|
+
* durable append. That is why both failure paths reach it — a ladder that ran out
|
|
549
|
+
* of attempts, and one that stopped on a fault no attempt could clear. Neither
|
|
550
|
+
* has an answer to write; both have work worth returning.
|
|
551
|
+
*
|
|
552
|
+
* `undefined` when nothing completed, which is the caller's signal to fail with
|
|
553
|
+
* its own kind. No branches means nothing to join, and a Task with no work behind
|
|
554
|
+
* it should not report success.
|
|
555
|
+
*/
|
|
556
|
+
async function deterministicJoin(args) {
|
|
557
|
+
const { session, taskId, round, branches } = args;
|
|
558
|
+
if (!branches.some((b) => b.status === "completed"))
|
|
559
|
+
return undefined;
|
|
560
|
+
console.warn("[turn] falling back to deterministic join", { taskId, round });
|
|
561
|
+
const reply = await appendOnce(session, deterministicSessionMessage(finalReplyMessageId(taskId), "assistant", joinSuccessfulBranches(branches, args.partialNote)));
|
|
562
|
+
return { status: "replied", reply };
|
|
496
563
|
}
|
|
497
|
-
//# sourceMappingURL=turn.js.map
|
package/dist/round/workflow.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { WorkflowStep } from "cloudflare:workers";
|
|
2
2
|
import type { CoreConfig } from "../config.js";
|
|
3
3
|
import type { GatewayIdentity } from "../a2a/verify.js";
|
|
4
|
+
import type { RoundFailureKind } from "../agent/inference.js";
|
|
4
5
|
import type { RoundAgentBase } from "./agent.js";
|
|
5
6
|
import type { RoundPolicy } from "./policy.js";
|
|
6
7
|
/**
|
|
@@ -15,10 +16,10 @@ import type { RoundPolicy } from "./policy.js";
|
|
|
15
16
|
* 1. **Round** — one main-agent inference that either answers the user (the Task
|
|
16
17
|
* is done) or delegates durable Subtasks plus the acknowledgment the user sees
|
|
17
18
|
* while they run.
|
|
18
|
-
* 2. **Execute** — a delegating round's
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* 2. **Execute** — a delegating round's Subtasks all run at once, each in an
|
|
20
|
+
* isolated managed subagent. Then the loop returns to 1, where the model sees
|
|
21
|
+
* the results and decides again — answer, or delegate once more. Sequencing
|
|
22
|
+
* lives here, in the loop, not inside a round.
|
|
22
23
|
* 3. **Deliver** — persist the terminal Task, then POST a signed callback.
|
|
23
24
|
*
|
|
24
25
|
* The main agent is never forced either way. A round that has run out of budget —
|
|
@@ -66,12 +67,34 @@ export interface HandleTaskParams {
|
|
|
66
67
|
* 250-line orchestration.
|
|
67
68
|
*/
|
|
68
69
|
export interface HandleTaskDeps {
|
|
69
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* Route to the right DO class for the verified caller.
|
|
72
|
+
*
|
|
73
|
+
* Called **once per step body**, not once per run, so it must stay a cheap
|
|
74
|
+
* pure lookup — a namespace `get`, nothing cached and nothing awaited. See
|
|
75
|
+
* {@link ResolveAgent} for why the result must never be hoisted.
|
|
76
|
+
*/
|
|
70
77
|
resolveAgent: (identity: GatewayIdentity) => AgentStub;
|
|
71
78
|
/** Resolved config — the loop reads `mainAgentLimits` and `maxSubtasks`. */
|
|
72
79
|
config: CoreConfig;
|
|
73
80
|
/** The user-facing copy. Only `copy.taskFailed` is read out here. */
|
|
74
81
|
policy: RoundPolicy;
|
|
82
|
+
/**
|
|
83
|
+
* Terminal copy for a round that produced no answer, by {@link
|
|
84
|
+
* RoundFailureKind} — an expired credential, models that could not do it, and
|
|
85
|
+
* whatever that union grows to cover.
|
|
86
|
+
*
|
|
87
|
+
* A hook rather than more `RoundPolicy` copy, because the useful words are
|
|
88
|
+
* deployment-specific ("run `claude setup-token`, then
|
|
89
|
+
* `wrangler secret put …`") and most agents cannot hit these conditions at
|
|
90
|
+
* all. Returning `undefined` — or omitting this — falls back to
|
|
91
|
+
* `policy.copy.taskFailed`, so an agent that does not care changes nothing,
|
|
92
|
+
* and one that only cares about *some* kinds answers for those alone.
|
|
93
|
+
*
|
|
94
|
+
* Core still owns the delivery: this supplies only the message, so the
|
|
95
|
+
* guarded write that doubles as the cancellation check stays in one place.
|
|
96
|
+
*/
|
|
97
|
+
failureCopy?: (kind: RoundFailureKind, detail: string) => string | undefined;
|
|
75
98
|
/**
|
|
76
99
|
* The deployment's Ed25519 private JWK, for the terminal callback. Passed
|
|
77
100
|
* rather than read off a module-scope `env` so this stays a pure function of
|
|
@@ -100,10 +123,8 @@ type AgentStub = DurableObjectStub<RoundAgentBase>;
|
|
|
100
123
|
* references to them.
|
|
101
124
|
*
|
|
102
125
|
* **Step names are durable cache keys.** Everything inside the round loop carries
|
|
103
|
-
* its round for that reason: `turn:<round>`, `deadline:<round>`,
|
|
104
|
-
* `
|
|
105
|
-
* its effect on replay.
|
|
126
|
+
* its round for that reason: `turn:<round>`, `deadline:<round>`, `scan:<round>`,
|
|
127
|
+
* `cancel:<round>`. Renaming one silently re-runs its effect on replay.
|
|
106
128
|
*/
|
|
107
129
|
export declare function runHandleTask(p: HandleTaskParams, step: WorkflowStep, deps: HandleTaskDeps): Promise<void>;
|
|
108
130
|
export {};
|
|
109
|
-
//# sourceMappingURL=workflow.d.ts.map
|
package/dist/round/workflow.js
CHANGED
|
@@ -1,7 +1,65 @@
|
|
|
1
|
-
import { MAX_CHUNKS_PER_BRANCH } from "../platform.js";
|
|
1
|
+
import { MAX_CHUNKS_PER_BRANCH, STEP_TIMEOUT_MS } from "../platform.js";
|
|
2
2
|
import { buildCompletedTask, buildFailedTask } from "../a2a/notify.js";
|
|
3
3
|
import { createPushChannel } from "../a2a/push.js";
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* What the long steps configure instead of inheriting.
|
|
6
|
+
*
|
|
7
|
+
* Both halves of this were previously left at Workflows' defaults, and both
|
|
8
|
+
* defaults were wrong for this workload.
|
|
9
|
+
*
|
|
10
|
+
* **`timeout`.** The default is ten minutes. Nothing here passed a config, so that
|
|
11
|
+
* default silently became the ceiling {@link CHUNK_SOFT_MS} was sized against —
|
|
12
|
+
* see the note on `STEP_TIMEOUT_MS` in `platform.ts` for what that cost. A step
|
|
13
|
+
* here holds a model call and its provider retries, or a container command running
|
|
14
|
+
* a project's test suite; neither fits in ten minutes reliably and neither uses
|
|
15
|
+
* meaningful CPU while it waits.
|
|
16
|
+
*
|
|
17
|
+
* **`retries`.** The default is five attempts with exponential backoff from ten
|
|
18
|
+
* seconds. The failure documented on {@link ResolveAgent} is what that produces
|
|
19
|
+
* when the fault is not transient: five retries against a severed stub, each
|
|
20
|
+
* failing in under 10ms, spread across 160 seconds of backoff that bought nothing.
|
|
21
|
+
* Three attempts still cover a genuinely transient fault — the model call has its
|
|
22
|
+
* own provider-level retry underneath this — and a flat five-second delay stops a
|
|
23
|
+
* fast, permanent failure from being paid for at exponential rates.
|
|
24
|
+
*
|
|
25
|
+
* Deliberately applied only to the chunk steps — see {@link turnStep} for why a
|
|
26
|
+
* round does not share it. The short bookkeeping steps (`working`, `deadline:`,
|
|
27
|
+
* `scan:`, `complete`, `notify`) are sub-second projections where the defaults
|
|
28
|
+
* are fine and a shared config would only hide that.
|
|
29
|
+
*/
|
|
30
|
+
const CHUNK_STEP = {
|
|
31
|
+
timeout: STEP_TIMEOUT_MS,
|
|
32
|
+
retries: { limit: 3, delay: 5_000, backoff: "constant" }
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* The same retries, and a timeout a **round** can actually be measured against.
|
|
36
|
+
*
|
|
37
|
+
* A chunk and a round are bounded by different things, and sharing one constant
|
|
38
|
+
* hid that. A chunk has {@link CHUNK_SOFT_MS}: it checkpoints and hands back a
|
|
39
|
+
* fresh step, so `STEP_TIMEOUT_MS` is a ceiling it is sized to stay under. A
|
|
40
|
+
* round has no soft deadline at all — `runTurn` runs up to
|
|
41
|
+
* `mainAgentLimits.maxTurns` sequential model-plus-tool steps in one
|
|
42
|
+
* `generateText`, and its only bound is that step count. Twenty turns whose
|
|
43
|
+
* tools each take the {@link file://../platform.ts MAX_TOOL_CALL_MS} they are
|
|
44
|
+
* permitted is hours, not half an hour, so a perfectly legal round could be
|
|
45
|
+
* killed and replayed whole.
|
|
46
|
+
*
|
|
47
|
+
* So the ceiling comes from the agent's own patience: a round cannot usefully
|
|
48
|
+
* outlive the wall clock its Task is allowed, because the `deadline:` step fails
|
|
49
|
+
* the Task at that point anyway. Floored at `STEP_TIMEOUT_MS` so a deliberately
|
|
50
|
+
* tight `maxWallMs` cannot produce a step timeout shorter than the single tool
|
|
51
|
+
* call core tells hosts they may install.
|
|
52
|
+
*
|
|
53
|
+
* This remains a backstop against a hang, not a budget. What actually bounds
|
|
54
|
+
* what a round *spends* is `TurnBudget`, and what bounds the Task is
|
|
55
|
+
* `mainAgentLimits` — both of which are checked whatever this says.
|
|
56
|
+
*/
|
|
57
|
+
function turnStep(config) {
|
|
58
|
+
return {
|
|
59
|
+
...CHUNK_STEP,
|
|
60
|
+
timeout: Math.max(config.mainAgentLimits.maxWallMs, STEP_TIMEOUT_MS)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
5
63
|
/**
|
|
6
64
|
* The orchestration itself, split from the `WorkflowEntrypoint` wiring so it can
|
|
7
65
|
* be driven with a fake `step` in tests (workerd forbids constructing a
|
|
@@ -14,14 +72,15 @@ import { selectWave } from "../subtasks/scheduler.js";
|
|
|
14
72
|
* references to them.
|
|
15
73
|
*
|
|
16
74
|
* **Step names are durable cache keys.** Everything inside the round loop carries
|
|
17
|
-
* its round for that reason: `turn:<round>`, `deadline:<round>`,
|
|
18
|
-
* `
|
|
19
|
-
* its effect on replay.
|
|
75
|
+
* its round for that reason: `turn:<round>`, `deadline:<round>`, `scan:<round>`,
|
|
76
|
+
* `cancel:<round>`. Renaming one silently re-runs its effect on replay.
|
|
20
77
|
*/
|
|
21
78
|
export async function runHandleTask(p, step, deps) {
|
|
22
79
|
const limits = deps.config.mainAgentLimits;
|
|
23
|
-
// Pre-work. Routing is pure, so it needs no step of its own
|
|
24
|
-
|
|
80
|
+
// Pre-work. Routing is pure, so it needs no step of its own — but it is
|
|
81
|
+
// deliberately *not* resolved here into a value the steps below close over.
|
|
82
|
+
// See {@link ResolveAgent}.
|
|
83
|
+
const agent = () => deps.resolveAgent(p.identity);
|
|
25
84
|
const push = {
|
|
26
85
|
taskId: p.taskId,
|
|
27
86
|
contextId: p.contextId,
|
|
@@ -29,7 +88,7 @@ export async function runHandleTask(p, step, deps) {
|
|
|
29
88
|
pushToken: p.pushToken,
|
|
30
89
|
jku: p.jku
|
|
31
90
|
};
|
|
32
|
-
const started = await step.do("working", async () => (await
|
|
91
|
+
const started = await step.do("working", async () => (await agent().markWorking(p.taskId)) === "ok");
|
|
33
92
|
if (!started)
|
|
34
93
|
return;
|
|
35
94
|
// Main-agent turns spent so far, across every round. Summed from cached step
|
|
@@ -78,11 +137,11 @@ export async function runHandleTask(p, step, deps) {
|
|
|
78
137
|
// durable work to fall back on) and routes to failed delivery; a transient
|
|
79
138
|
// fault throws and the step retries, recovering from the durable rows with no
|
|
80
139
|
// second inference.
|
|
81
|
-
const turn = await step.do(`turn:${round}`, async () => {
|
|
140
|
+
const turn = await step.do(`turn:${round}`, turnStep(deps.config), async () => {
|
|
82
141
|
// Projected to a plain object: an RPC return carries a `Disposable` brand a
|
|
83
142
|
// step result cannot serialize. Every branch must carry `turns` — a field
|
|
84
143
|
// this projection drops is a field the budget never sees.
|
|
85
|
-
const result = await
|
|
144
|
+
const result = await agent().runTaskTurn({
|
|
86
145
|
taskId: p.taskId,
|
|
87
146
|
text: p.text,
|
|
88
147
|
identity: p.identity,
|
|
@@ -100,6 +159,7 @@ export async function runHandleTask(p, step, deps) {
|
|
|
100
159
|
if (result.status === "failed")
|
|
101
160
|
return {
|
|
102
161
|
status: result.status,
|
|
162
|
+
kind: result.kind,
|
|
103
163
|
error: result.error,
|
|
104
164
|
turns: result.turns
|
|
105
165
|
};
|
|
@@ -108,27 +168,33 @@ export async function runHandleTask(p, step, deps) {
|
|
|
108
168
|
turnsUsed += turn.turns;
|
|
109
169
|
if (turn.status === "canceled")
|
|
110
170
|
return;
|
|
171
|
+
// The round produced no answer. `kind` is the whole difference between the
|
|
172
|
+
// two ways that happens — models that could not do it, versus a fault that
|
|
173
|
+
// stopped the round on its first attempt and that only a human can clear —
|
|
174
|
+
// and it exists to be turned into words the reader can act on. Same
|
|
175
|
+
// delivery either way; the diagnostic is logged, never shown.
|
|
111
176
|
if (turn.status === "failed") {
|
|
112
177
|
console.error("[handle-task] round failed", {
|
|
113
178
|
taskId: p.taskId,
|
|
114
179
|
round,
|
|
180
|
+
kind: turn.kind,
|
|
115
181
|
error: turn.error
|
|
116
182
|
});
|
|
117
|
-
await deliver(p, step,
|
|
183
|
+
await deliver(p, step, agent, null, deps, {
|
|
184
|
+
kind: turn.kind,
|
|
185
|
+
detail: turn.error
|
|
186
|
+
});
|
|
118
187
|
return;
|
|
119
188
|
}
|
|
120
189
|
if (turn.status === "replied") {
|
|
121
|
-
await deliver(p, step,
|
|
190
|
+
await deliver(p, step, agent, turn.reply, deps);
|
|
122
191
|
return;
|
|
123
192
|
}
|
|
124
|
-
// Delegated: run this round's
|
|
125
|
-
|
|
193
|
+
// Delegated: run this round's Subtasks, then loop and let the model decide
|
|
194
|
+
// again.
|
|
195
|
+
const executed = await executeSubtasks(p, step, agent, round, push);
|
|
126
196
|
if (executed === "canceled")
|
|
127
197
|
return;
|
|
128
|
-
if (executed === "stuck") {
|
|
129
|
-
await deliver(p, step, stub, null, deps);
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
198
|
}
|
|
133
199
|
// Unreachable: a `final` round is handed only `final_reply`, so it either
|
|
134
200
|
// answers or fails, and both return above. Reaching here means a round
|
|
@@ -136,61 +202,52 @@ export async function runHandleTask(p, step, deps) {
|
|
|
136
202
|
console.error("[handle-task] round budget exhausted without a reply", {
|
|
137
203
|
taskId: p.taskId
|
|
138
204
|
});
|
|
139
|
-
await deliver(p, step,
|
|
205
|
+
await deliver(p, step, agent, null, deps);
|
|
140
206
|
}
|
|
141
207
|
/**
|
|
142
|
-
*
|
|
208
|
+
* Run every Subtask one round delegated, concurrently, to termination.
|
|
143
209
|
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
210
|
+
* **One pass is the whole thing.** A round's Subtasks are independent of one
|
|
211
|
+
* another, so they are all runnable the moment they exist, and `runBranch` is
|
|
212
|
+
* contractually obliged to leave its row terminal — it resolves a deterministic
|
|
213
|
+
* failure itself and has a `fail:<id>` backstop once the retries are gone. So
|
|
214
|
+
* there is nothing left to re-scan afterwards, and no way for this to make no
|
|
215
|
+
* progress. Sequencing between units of work is the round loop's job.
|
|
149
216
|
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
217
|
+
* Both step names carry the round, because step names are durable cache keys: two
|
|
218
|
+
* rounds of the same Task reusing `scan` would replay the first round's cached
|
|
219
|
+
* answer into the second.
|
|
153
220
|
*/
|
|
154
|
-
async function
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
221
|
+
async function executeSubtasks(p, step, agent, round, push) {
|
|
222
|
+
// One durable step: `scanSubtasks` reports cancellation and returns the ids
|
|
223
|
+
// still owing an outcome — one round trip, one consistent answer. It writes
|
|
224
|
+
// nothing, so a replay that re-runs it costs only the read.
|
|
225
|
+
const scan = await step.do(`scan:${round}`, async () => {
|
|
226
|
+
const result = await agent().scanSubtasks(p.taskId, round);
|
|
227
|
+
return result.canceled
|
|
228
|
+
? { canceled: true, ids: [] }
|
|
229
|
+
: { canceled: false, ids: result.ids };
|
|
230
|
+
});
|
|
231
|
+
if (scan.canceled) {
|
|
232
|
+
await step.do(`cancel:${round}`, async () => {
|
|
233
|
+
await agent().cancelPendingSubtasks(p.taskId);
|
|
164
234
|
});
|
|
165
|
-
|
|
166
|
-
await step.do(`cancel:${round}:${wave}`, async () => {
|
|
167
|
-
await stub.cancelPendingSubtasks(p.taskId);
|
|
168
|
-
});
|
|
169
|
-
return "canceled";
|
|
170
|
-
}
|
|
171
|
-
const decision = selectWave(scan.nodes);
|
|
172
|
-
if (decision.kind === "done")
|
|
173
|
-
return "done";
|
|
174
|
-
if (decision.kind === "stuck") {
|
|
175
|
-
console.error("[handle-task] subtask DAG made no progress", {
|
|
176
|
-
taskId: p.taskId,
|
|
177
|
-
round,
|
|
178
|
-
wave,
|
|
179
|
-
active: decision.active
|
|
180
|
-
});
|
|
181
|
-
return "stuck";
|
|
182
|
-
}
|
|
183
|
-
// Every dependency-ready node runs concurrently — the per-round Subtask
|
|
184
|
-
// maximum is the only fan-out bound. `runBranch` never rejects, so a single
|
|
185
|
-
// branch cannot fast-fail `Promise.all` and strand its siblings' durable
|
|
186
|
-
// results.
|
|
187
|
-
await Promise.all(decision.ids.map((id) => runBranch(p, step, stub, id, push)));
|
|
235
|
+
return "canceled";
|
|
188
236
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
237
|
+
// Every Subtask runs concurrently — the per-round Subtask maximum is the only
|
|
238
|
+
// fan-out bound. `runBranch` never rejects, so a single branch cannot fast-fail
|
|
239
|
+
// `Promise.all` and strand its siblings' durable results.
|
|
240
|
+
//
|
|
241
|
+
// A cancellation arriving mid-pass is still honored, just not from here:
|
|
242
|
+
// `onTaskCanceled` aborts the live children *and* transitions every row still
|
|
243
|
+
// `pending` in the same sweep, and `executeSubtaskChunk` re-checks before
|
|
244
|
+
// publishing. That transition is what lets this pass end without a second
|
|
245
|
+
// scan. Without it, a branch whose RPC had not yet claimed its row when the
|
|
246
|
+
// cancellation landed would return terminal while leaving the row `pending`,
|
|
247
|
+
// and — since the next round's turn reports `canceled` and the workflow exits
|
|
248
|
+
// — nothing would resolve it before the 30-day cleanup.
|
|
249
|
+
await Promise.all(scan.ids.map((id) => runBranch(p, step, agent, id, push)));
|
|
250
|
+
return "done";
|
|
194
251
|
}
|
|
195
252
|
/**
|
|
196
253
|
* Run one Subtask to termination as a sequence of durable **chunk** steps, and
|
|
@@ -200,7 +257,9 @@ async function executeDag(p, step, stub, round, push, deps) {
|
|
|
200
257
|
* `done` on chunk 0 (step `execute:<id>`); a long recipe yields `done: false` and
|
|
201
258
|
* the loop runs the next chunk (`execute:<id>:chunk:<n>`) until it terminates.
|
|
202
259
|
* Each chunk is its own retryable step, and the child resumes from its
|
|
203
|
-
* checkpoint — so no step approaches the
|
|
260
|
+
* checkpoint — so no step approaches the {@link CHUNK_STEP} timeout. `CHUNK_SOFT_MS`
|
|
261
|
+
* is what holds that true, and is sized against it rather than the other way
|
|
262
|
+
* round; a boundary here is not free, so it wants to be rare, not frequent.
|
|
204
263
|
*
|
|
205
264
|
* It resolves a deterministic branch failure into a `failed` row itself and
|
|
206
265
|
* throws only on a transient fault (retry me) or a lifecycle bug. So a throw that
|
|
@@ -218,15 +277,15 @@ async function executeDag(p, step, stub, round, push, deps) {
|
|
|
218
277
|
* Step ids are unique across rounds (SQLite assigns them), so these names need no
|
|
219
278
|
* round prefix.
|
|
220
279
|
*/
|
|
221
|
-
async function runBranch(p, step,
|
|
280
|
+
async function runBranch(p, step, agent, id, push) {
|
|
222
281
|
try {
|
|
223
282
|
for (let chunk = 0; chunk < MAX_CHUNKS_PER_BRANCH; chunk++) {
|
|
224
283
|
// Chunk 0 keeps the plain `execute:<id>` step name so single-chunk branches
|
|
225
284
|
// replay identically; later chunks append `:chunk:<n>`.
|
|
226
285
|
const stepName = chunk === 0 ? `execute:${id}` : `execute:${id}:chunk:${chunk}`;
|
|
227
|
-
const done = await step.do(stepName, async () => {
|
|
286
|
+
const done = await step.do(stepName, CHUNK_STEP, async () => {
|
|
228
287
|
// The DO posts any progress itself; the step returns only the verdict.
|
|
229
|
-
const outcome = await
|
|
288
|
+
const outcome = await agent().executeSubtaskChunk(id, chunk, push);
|
|
230
289
|
return outcome.done;
|
|
231
290
|
});
|
|
232
291
|
if (done)
|
|
@@ -239,7 +298,7 @@ async function runBranch(p, step, stub, id, push) {
|
|
|
239
298
|
subtaskId: id
|
|
240
299
|
});
|
|
241
300
|
await step.do(`fail:${id}`, async () => {
|
|
242
|
-
await
|
|
301
|
+
await agent().failSubtask(id, `execution exceeded ${MAX_CHUNKS_PER_BRANCH} chunks`);
|
|
243
302
|
});
|
|
244
303
|
}
|
|
245
304
|
catch (err) {
|
|
@@ -249,14 +308,20 @@ async function runBranch(p, step, stub, id, push) {
|
|
|
249
308
|
err: String(err)
|
|
250
309
|
});
|
|
251
310
|
await step.do(`fail:${id}`, async () => {
|
|
252
|
-
await
|
|
311
|
+
await agent().failSubtask(id, `execution exhausted retries: ${String(err)}`);
|
|
253
312
|
});
|
|
254
313
|
}
|
|
255
314
|
}
|
|
256
315
|
/**
|
|
257
316
|
* Persist the terminal Task, then notify the gateway. A null `reply` delivers a
|
|
258
317
|
* `failed` Task with the policy's user-safe text; the diagnostic is already
|
|
259
|
-
* logged.
|
|
318
|
+
* logged. Given a `failure`, the host's {@link HandleTaskDeps.failureCopy} may
|
|
319
|
+
* replace that text — same delivery, different words.
|
|
320
|
+
*
|
|
321
|
+
* `failure` is optional because only a round's own inference carries a kind. The
|
|
322
|
+
* other path here — a budget that ran out mid-delegation — is not a model failure
|
|
323
|
+
* and is deliberately not given a kind of its own until something needs to tell
|
|
324
|
+
* it apart.
|
|
260
325
|
*
|
|
261
326
|
* The Task is built **inside** the step and returned, so `notify` posts exactly
|
|
262
327
|
* what was persisted: building it in the body would re-stamp `new Date()` on
|
|
@@ -269,12 +334,16 @@ async function runBranch(p, step, stub, id, push) {
|
|
|
269
334
|
* `notify` — in which a `tasks/cancel` lands and the gateway still receives a
|
|
270
335
|
* `completed` callback. Keying the notify on "did the write apply" closes it.
|
|
271
336
|
*/
|
|
272
|
-
async function deliver(p, step,
|
|
337
|
+
async function deliver(p, step, agent, reply, deps, failure) {
|
|
338
|
+
// Resolved outside the step body so a replay cannot take a different branch
|
|
339
|
+
// than the write it is replaying.
|
|
340
|
+
const failedText = (failure && deps.failureCopy?.(failure.kind, failure.detail)) ||
|
|
341
|
+
deps.policy.copy.taskFailed;
|
|
273
342
|
const task = await step.do("complete", async () => {
|
|
274
343
|
const terminal = reply !== null
|
|
275
344
|
? buildCompletedTask(p.taskId, p.contextId, reply)
|
|
276
|
-
: buildFailedTask(p.taskId, p.contextId,
|
|
277
|
-
return (await
|
|
345
|
+
: buildFailedTask(p.taskId, p.contextId, failedText);
|
|
346
|
+
return (await agent().saveTask(terminal)) ? terminal : null;
|
|
278
347
|
});
|
|
279
348
|
if (!task)
|
|
280
349
|
return;
|
|
@@ -290,7 +359,7 @@ async function deliver(p, step, stub, reply, deps) {
|
|
|
290
359
|
// this Task's children were reclaimed.
|
|
291
360
|
try {
|
|
292
361
|
await step.do("sweep", async () => {
|
|
293
|
-
await
|
|
362
|
+
await agent().sweepTaskChildren(p.taskId);
|
|
294
363
|
});
|
|
295
364
|
}
|
|
296
365
|
catch (err) {
|
|
@@ -313,4 +382,3 @@ async function deliver(p, step, stub, reply, deps) {
|
|
|
313
382
|
}).deliver(task);
|
|
314
383
|
});
|
|
315
384
|
}
|
|
316
|
-
//# sourceMappingURL=workflow.js.map
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -59,8 +59,10 @@ export interface AgentRuntime {
|
|
|
59
59
|
/** Tools the installed plugins offer the *main* agent, merged. */
|
|
60
60
|
mainAgentTools(ctx: MainAgentToolContext): Promise<ToolSet>;
|
|
61
61
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
62
|
+
* The capability blocks for the main agent's soul, in plugin declaration
|
|
63
|
+
* order: each plugin's own {@link AgentPlugin.capability} and the one on its
|
|
64
|
+
* {@link AgentPlugin.subtaskType}, if it declares either. Returns `""` when
|
|
65
|
+
* none does, so a call site can append unconditionally.
|
|
64
66
|
*/
|
|
65
67
|
renderCapabilities(): string;
|
|
66
68
|
/**
|
|
@@ -123,4 +125,3 @@ export declare class RuntimeSetupError extends Error {
|
|
|
123
125
|
constructor(message: string);
|
|
124
126
|
}
|
|
125
127
|
export declare function createAgentRuntime(options: CreateAgentRuntimeOptions): AgentRuntime;
|
|
126
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/runtime/index.js
CHANGED
|
@@ -109,8 +109,14 @@ export function createAgentRuntime(options) {
|
|
|
109
109
|
renderCapabilities() {
|
|
110
110
|
const blocks = [];
|
|
111
111
|
for (const plugin of plugins) {
|
|
112
|
+
// Both blocks a plugin may declare, emitted adjacently: a plugin that
|
|
113
|
+
// declares a subtask type puts its capability on the *type*, and one
|
|
114
|
+
// that only offers main-agent tools puts it on the plugin. Declaring
|
|
115
|
+
// both is legal and means the model reads both.
|
|
112
116
|
if (plugin.capability)
|
|
113
117
|
blocks.push(plugin.capability);
|
|
118
|
+
if (plugin.subtaskType?.capability)
|
|
119
|
+
blocks.push(plugin.subtaskType.capability);
|
|
114
120
|
}
|
|
115
121
|
return blocks.join("\n\n");
|
|
116
122
|
},
|
|
@@ -178,4 +184,3 @@ export function createAgentRuntime(options) {
|
|
|
178
184
|
}
|
|
179
185
|
};
|
|
180
186
|
}
|
|
181
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -11,8 +11,8 @@ export declare const FINGERPRINT_VERSION = 1;
|
|
|
11
11
|
* Canonical JSON of the fields that define an execution's identity, rebuilt as
|
|
12
12
|
* literals in fixed key order so `JSON.stringify` is deterministic (object
|
|
13
13
|
* insertion order). Array order is semantic and preserved: the parent builds
|
|
14
|
-
* references
|
|
15
|
-
*
|
|
14
|
+
* references from ordinal-ordered rows, so a retry of the same execution is
|
|
15
|
+
* byte-identical.
|
|
16
16
|
*
|
|
17
17
|
* **Limits are canonicalized as *declared*, not as merged.** The predecessor
|
|
18
18
|
* merged them against the house baseline first, on the reasoning that `{}` and
|
|
@@ -35,4 +35,3 @@ export declare function canonicalRequest(request: RecipeExecutionRequest): strin
|
|
|
35
35
|
* fingerprinted, so the key matches exactly what the parent re-sends on retry.
|
|
36
36
|
*/
|
|
37
37
|
export declare function fingerprintRequest(request: RecipeExecutionRequest): Promise<string>;
|
|
38
|
-
//# sourceMappingURL=fingerprint.d.ts.map
|