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

+58
View File
@@ -186,6 +186,56 @@ export const InventoryQueryOutput = z.object({
),
});
// ── 子代理(W7-B) ──
/** 子会话预算:缺省按父会话剩余预算的一半划拨;超过剩余预算直接拒绝 */
export const SubagentBudget = z.object({
maxToolCalls: z.number().int().positive().optional(),
maxTokens: z.number().int().positive().optional(),
maxCostUsd: z.number().positive().optional(),
maxDurationSec: z.number().int().positive().optional(),
});
export const AgentSpawnInput = z.object({
goal: z.string().trim().min(1).max(4000),
/** 为什么要派生(展示在会话树里) */
reason: z.string().max(500).optional(),
/** 必须是父会话能力白名单的子集;缺省继承父会话白名单 */
capabilityAllowlist: z.array(z.string()).min(1).max(50).optional(),
budget: SubagentBudget.optional(),
/** 不得高于父会话:plan_only 的父会话只能派生 plan_only 子会话;缺省同父会话 */
mode: z.enum(["execute", "plan_only"]).optional(),
/** 覆盖子会话使用的 Agent 上游;缺省同父会话 */
upstreamId: Id.optional(),
/** true:派生后等它结束再返回(等待不调用模型) */
await: z.boolean().default(false),
timeoutSec: z.number().int().min(1).max(3600).default(600),
});
export const SubagentResult = z.object({
runId: Id,
goal: z.string(),
status: z.string(),
/** 汇报摘要(业务核查结论 + 未经核查的模型文本),已脱敏截断;未结束时为 null */
summary: z.string().nullable(),
toolCalls: NonNegInt,
usage: z.object({ totalTokens: NonNegInt, costUsd: z.number().nonnegative() }),
pending: z.boolean(),
});
export const AgentSpawnOutput = z.object({
runId: Id,
depth: NonNegInt,
mode: z.enum(["execute", "plan_only"]),
capabilities: z.array(z.string()),
budget: SubagentBudget,
/** await=true 时的结果 */
result: SubagentResult.nullable(),
});
export const AgentAwaitInput = z.object({
/** 缺省等本会话派生的全部子会话 */
runIds: z.array(Id).min(1).max(20).optional(),
timeoutSec: z.number().int().min(1).max(3600).default(600),
});
export const AgentAwaitOutput = z.object({ results: z.array(SubagentResult), allDone: z.boolean() });
/** 能力 ID → 输入/输出 schema(server 注册处理器、agent 生成工具定义时共用) */
export const CAPABILITY_IO = {
"catalog.lookup": { input: CatalogLookupInput, output: CatalogLookupOutput },
@@ -203,6 +253,8 @@ export const CAPABILITY_IO = {
"policy.preview": { input: PolicyPreviewInput, output: PolicyPreviewOutput },
"policy.apply": { input: PolicyApplyInput, output: PolicyApplyOutput },
"inventory.query": { input: InventoryQueryInput, output: InventoryQueryOutput },
"agent.spawn": { input: AgentSpawnInput, output: AgentSpawnOutput },
"agent.await": { input: AgentAwaitInput, output: AgentAwaitOutput },
} as const satisfies Record<KnownCapabilityId, { input: z.ZodType; output: z.ZodType }>;
type CapabilityIo = typeof CAPABILITY_IO;
@@ -252,3 +304,9 @@ export type PolicyApplyInput = z.infer<typeof PolicyApplyInput>;
export type PolicyApplyOutput = z.infer<typeof PolicyApplyOutput>;
export type InventoryQueryInput = z.infer<typeof InventoryQueryInput>;
export type InventoryQueryOutput = z.infer<typeof InventoryQueryOutput>;
export type AgentSpawnInput = z.infer<typeof AgentSpawnInput>;
export type AgentSpawnOutput = z.infer<typeof AgentSpawnOutput>;
export type AgentAwaitInput = z.infer<typeof AgentAwaitInput>;
export type AgentAwaitOutput = z.infer<typeof AgentAwaitOutput>;
export type SubagentResult = z.infer<typeof SubagentResult>;
export type SubagentBudget = z.infer<typeof SubagentBudget>;