mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-07-12 21:02:17 +02:00
feat(x): spawn-agent — run sub-agents as headless child turns
Adds the agent-as-tool capability deferred in turn-runtime-design §29.2:
a `spawn-agent` builtin that runs a sub-agent in its own standalone
headless turn and returns its final answer to the parent. Multiple
spawn calls in one assistant batch run concurrently (previous commit).
- RequestedAgent is now a union: by-id (unchanged shape) | inline
{name, instructions, model?, tools?}. Inline definitions persist
verbatim in turn_created and resolve to the same immutable snapshot.
- Agent resolution splits by variant: DispatchingAgentResolver narrows
the union once; InlineAgentResolver materializes inline specs
(builtin catalog validation, headless default profile when tools are
omitted); RealAgentResolver keeps the by-id path byte-identical. The
builtin→ToolDescriptor conversion is extracted to a shared helper.
- The spawn handler (RealToolRegistry branch → runSpawnedAgent) runs
the child via HeadlessAgentRunner on the parent's model by default,
clamps the model-call budget at 20, cascades the parent's abort
signal, and records {kind:"subagent", childTurnId} as durable tool
progress — the only parent→child link; no parentTurnId is added to
the schema. Task-level failures return as conversational isError
results, never terminal.
- Depth is capped at 1: both resolvers strip spawn-agent from children
(inline always; by-id via the new subagent composition flag) and the
handler refuses child-shaped parents outright.
- Renderer: spawn-agent calls render as a SubAgentBlock — a collapsed
status card that expands to the child's live transcript
(CompactConversation over sessions:getTurn, polled at 1s while
running; standalone child turns don't reach the session bus).
- The BuiltinTools entry gives copilot (and other catalog-attached
agents) the tool automatically; its execute is the degraded legacy
path only, since the turn runtime intercepts builtin:spawn-agent.
Schema note: RequestedAgent widened under schemaVersion 1 (pre-release)
— requires wiping ~/.rowboat/storage.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
d2b68a4684
commit
b62ed6a2f4
21 changed files with 1145 additions and 49 deletions
|
|
@ -27,7 +27,7 @@ export const ModelDescriptor = z.object({
|
|||
model: z.string(),
|
||||
});
|
||||
|
||||
export const RequestedAgent = z.object({
|
||||
export const AgentByIdRequest = z.object({
|
||||
agentId: z.string(),
|
||||
overrides: z
|
||||
.object({
|
||||
|
|
@ -42,6 +42,42 @@ export const RequestedAgent = z.object({
|
|||
.optional(),
|
||||
});
|
||||
|
||||
// An agent constructed at request time (sub-agents spawned by a parent turn).
|
||||
// Persisted verbatim in turn_created.agent.requested, so the definition
|
||||
// self-documents in the turn file; the resolver materializes it into the same
|
||||
// immutable ResolvedAgent snapshot as a stored agent.
|
||||
export const InlineAgentSpec = z.object({
|
||||
name: z.string(),
|
||||
instructions: z.string(),
|
||||
model: ModelDescriptor.optional(),
|
||||
// Builtin tool names; resolution validates against the live catalog and
|
||||
// substitutes the default headless profile when omitted.
|
||||
tools: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
export const InlineAgentRequest = z.object({
|
||||
inline: InlineAgentSpec,
|
||||
});
|
||||
|
||||
// The builtin that spawns sub-agent turns. Named here (not in core) because
|
||||
// resolvers, the tool registry, and the renderer's card dispatch all key on
|
||||
// it, and children must never receive it (depth is capped at 1).
|
||||
export const SPAWN_AGENT_TOOL_NAME = "spawn-agent";
|
||||
|
||||
// The ResolvedAgent.agentId convention for inline agents; also what sessions
|
||||
// denormalize into their index for inline-agent turns.
|
||||
export function inlineAgentId(name: string): string {
|
||||
return `inline:${name}`;
|
||||
}
|
||||
|
||||
export const RequestedAgent = z.union([AgentByIdRequest, InlineAgentRequest]);
|
||||
|
||||
export function isInlineAgentRequest(
|
||||
requested: z.infer<typeof RequestedAgent>,
|
||||
): requested is z.infer<typeof InlineAgentRequest> {
|
||||
return "inline" in requested;
|
||||
}
|
||||
|
||||
export const ToolDescriptor = z.object({
|
||||
toolId: z.string(),
|
||||
name: z.string(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue