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:
1 parent
daa85a8bb4
commit
459991421e
27 files changed
+1720
-32
No files matched your search
@@ -68,6 +68,10 @@ export const AgentRun = EntityBase.extend({
|
||||
mode: AgentRunMode,
|
||||
/** 后台触发按根因去重(如 investigate:<decisionId>);用户委托为 null */
|
||||
dedupeKey: z.string().nullable(),
|
||||
/** 派生深度:用户/后台发起的会话为 0,agent.spawn 派生的子会话 = 父 + 1 */
|
||||
depth: NonNegInt,
|
||||
/** 父会话派生时给的理由 */
|
||||
spawnReason: z.string().nullable(),
|
||||
});
|
||||
export type AgentRun = z.infer<typeof AgentRun>;
|
||||
export const AgentRunCreate = AgentRun.pick({
|
||||
@@ -314,6 +318,8 @@ export const AgentRunListQuery = z.object({
|
||||
limit: z.coerce.number().int().min(1).max(200).default(50),
|
||||
status: AgentRunStatus.optional(),
|
||||
trigger: AgentTrigger.optional(),
|
||||
/** 只列某个会话直接派生的子会话 */
|
||||
parentRunId: Id.optional(),
|
||||
});
|
||||
export type AgentRunListQuery = z.infer<typeof AgentRunListQuery>;
|
||||
|
||||
@@ -366,8 +372,42 @@ export const AgentRunMessage = EntityBase.extend({
|
||||
});
|
||||
export type AgentRunMessage = z.infer<typeof AgentRunMessage>;
|
||||
|
||||
/** 会话树节点(父会话 / 子会话,子会话页与父会话页互相跳转) */
|
||||
export const AgentRunNode = z.object({
|
||||
id: Id,
|
||||
parentRunId: Id.nullable(),
|
||||
goal: z.string(),
|
||||
status: AgentRunStatus,
|
||||
mode: AgentRunMode,
|
||||
depth: NonNegInt,
|
||||
spawnReason: z.string().nullable(),
|
||||
toolCallCount: NonNegInt,
|
||||
usage: AgentUsage,
|
||||
summary: z.string().nullable(),
|
||||
/** 能力白名单(继承信息展示) */
|
||||
capabilities: z.array(z.string()),
|
||||
budget: AgentBudget,
|
||||
createdAt: Timestamp,
|
||||
finishedAt: Timestamp.nullable(),
|
||||
});
|
||||
export type AgentRunNode = z.infer<typeof AgentRunNode>;
|
||||
|
||||
/** 本会话 + 全部子孙会话的用量合计(父会话总预算含子会话用量) */
|
||||
export const AgentFamilyUsage = z.object({
|
||||
runs: NonNegInt,
|
||||
toolCalls: NonNegInt,
|
||||
totalTokens: NonNegInt,
|
||||
costUsd: z.number().nonnegative(),
|
||||
});
|
||||
export type AgentFamilyUsage = z.infer<typeof AgentFamilyUsage>;
|
||||
|
||||
export const AgentRunDetail = z.object({
|
||||
run: AgentRun,
|
||||
/** 父会话(子会话才有) */
|
||||
parent: AgentRunNode.nullable().optional(),
|
||||
/** 全部子孙会话(扁平,按 parentRunId 组树) */
|
||||
children: z.array(AgentRunNode).optional(),
|
||||
familyUsage: AgentFamilyUsage.optional(),
|
||||
events: z.array(AgentRunEventRow),
|
||||
/** 下一页起点(传给 afterSeq);null 表示已到最新 */
|
||||
nextAfterSeq: z.number().int().nullable(),
|
||||
|
||||
Reference in new issue
Block a user