feat(agent): Pi subagents via agent.spawn / agent.await

- agent_run gains depth / spawn_reason (0029); agent_run_event types
  subagent_spawned / subagent_event / subagent_completed
- agent.spawn (propose, medium, replay never): allowlist ⊆ parent, mode not
  above parent, budget carved from parent's remainder, idempotent per run+input;
  settings.pi.subagents {enabled, maxConcurrent, maxDepth} (subagentsEnabled kept as alias)
- child runs claim a concurrency slot under an advisory lock (waitForFact while
  queued); agent.await polls business tables without calling the model
- parent abort cascades to all descendants; parent runtime budget counts
  descendant usage; child events mirrored into the parent stream by parent seq
- authorize caps every child call by all ancestors' allowlists/kinds
- session page shows parent link, inherited info and the subagent tree
- e2e E11; runbook section on subagents

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-28 02:49:37 +08:00
1 parent daa85a8bb4
commit 459991421e
27 files changed
+1720 -32

No files matched your search

@@ -2,6 +2,7 @@ import { CapabilityError, type CapabilityRegistry } from "@umt/agent";
import { capabilitySpec, type TaskStatus } from "@umt/contracts";
import { sql, type TaskRow } from "@umt/db";
import type { ServerContext } from "../types.ts";
import { cascadeCancel } from "./subagents.ts";
// 任务类能力:task.inspect / task.retry / task.cancel。业务任务行(task 表)是时间线投影,
// 引擎只管推进;重试 = 按原输入重新入队(副作用由各处理器的操作账本去重),取消 = 写取消标志 + 引擎取消。
@@ -172,6 +173,7 @@ export async function cancelTask(ctx: ServerContext, taskId: string, reason: str
.where("id", "=", t.subject_id)
.where("cancel_requested_at", "is", null)
.execute();
if (t.kind === "agentRun" && t.subject_id) await cascadeCancel(ctx, t.subject_id, reason);
let cancelled = false;
if (t.engine_run_id) cancelled = await ctx.engine.cancel(t.engine_run_id, reason).catch(() => false);
const row = await ctx.db