feat(agent): agent.await suspends the session instead of holding a worker slot
When children are still running, agent.await records the wait, closes the session (the tool call stays pending in the JSONL) and drive returns suspended. runAgent waits with waitForFact for the children to settle, the deadline, or cancellation, then reopens the session; agent.await is replay-safe, so resume replays it and the model gets the real result. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
3e37ac8ae7
commit
d64f735810
4 files changed
+129
-24
No files matched your search
@@ -17,12 +17,15 @@ import {
|
||||
import { buildGoalReport } from "./report.ts";
|
||||
import { type AgentEnv, type DriveResult, drive, goalSettled } from "./runner.ts";
|
||||
import { finalize } from "./service.ts";
|
||||
import { catchUpChildren, claimSubagentSlot } from "./subagents.ts";
|
||||
import { awaitReady, catchUpChildren, claimSubagentSlot } from "./subagents.ts";
|
||||
|
||||
// 持久任务:runAgent(驱动一次有界 Agent 会话 → 等目标关联的下载/入库落定 → 以业务表汇报)
|
||||
// 与 resolveDecision(decision.created → 自动调查;decision.updated(resolved)→ 执行所选处理)。
|
||||
|
||||
const id = z.string().uuid();
|
||||
/** agent.await 挂起时的唤醒轮询间隔与单个会话最多挂起次数 */
|
||||
const AWAIT_POLL_SEC = 2;
|
||||
const MAX_AWAIT_SUSPENDS = 100;
|
||||
const DB_RETRY = { maximumAttempts: 3, initialInterval: "1s", backoffCoefficient: 2, maximumInterval: "30s" } as const;
|
||||
|
||||
export function registerAgentTasks(registry: TaskRegistry, env: () => AgentEnv) {
|
||||
@@ -72,7 +75,30 @@ export function registerAgentTasks(registry: TaskRegistry, env: () => AgentEnv)
|
||||
: "claimed";
|
||||
if (slot === null)
|
||||
res = { status: "failed", reason: "error", budget: null, message: "等待子会话名额超时", finalText: "" };
|
||||
else res = await t.step.run("drive", () => drive(e, e.ctx.capabilities, runId, t.abortSignal));
|
||||
else {
|
||||
res = await t.step.run("drive", () => drive(e, e.ctx.capabilities, runId, t.abortSignal));
|
||||
// agent.await 挂起:会话已关闭,waitForFact 等子会话落定 / 超时 / 取消(不占 worker、不占模型),
|
||||
// 再重开会话 resume(挂起的 agent.await 重放一次拿到结果)
|
||||
const resumedAwaits: string[] = [];
|
||||
for (let n = 1; res.status === "suspended" && res.suspension; n++) {
|
||||
const sus = res.suspension;
|
||||
if (n > MAX_AWAIT_SUSPENDS) {
|
||||
res = { status: "failed", reason: "error", budget: null, message: "等待子会话次数过多", finalText: "" };
|
||||
break;
|
||||
}
|
||||
const waitMs = Math.max(0, Date.parse(sus.deadline) - Date.now());
|
||||
await t.step.waitForFact<true>(`subagent-await-${n}`, {
|
||||
check: () => awaitReady(e.ctx, runId, sus),
|
||||
timeout: `${AWAIT_POLL_SEC}s`,
|
||||
maxRounds: Math.max(1, Math.ceil(waitMs / 1000 / AWAIT_POLL_SEC) + 1),
|
||||
});
|
||||
resumedAwaits.push(sus.invocationId);
|
||||
const resumed = [...resumedAwaits];
|
||||
res = await t.step.run(`drive-${n}`, () =>
|
||||
drive(e, e.ctx.capabilities, runId, t.abortSignal, { resumedAwaits: resumed }),
|
||||
);
|
||||
}
|
||||
}
|
||||
let settled: AgentGoalReport | null = null;
|
||||
const run = await e.ctx.db
|
||||
.selectFrom("agent_run")
|
||||
|
||||
Reference in new issue
Block a user