tinyagents/harness/subagent/types.rs
1//! Type definitions for first-class sub-agents.
2//!
3//! A [`SubAgent`] wraps an [`AgentHarness`] so it can be invoked as a *child
4//! run*: a fully independent agent loop that runs one level deeper in the
5//! recursion tree than its caller. [`SubAgentTool`] adapts a sub-agent into a
6//! [`Tool`] so a parent agent can call another agent the same way it calls any
7//! other tool — the key agent-calling-agent compositional pattern.
8//!
9//! All public items are re-exported through [`super`] so callers import from
10//! `crate::harness::subagent` directly. Implementations and tests live in the
11//! sibling `mod.rs` and `test.rs`.
12
13use std::sync::Arc;
14
15use serde_json::Value;
16
17use crate::harness::events::EventSink;
18use crate::harness::message::Message;
19use crate::harness::runtime::AgentHarness;
20
21/// The argument key a [`SubAgentTool`] reads the child input from.
22///
23/// When a parent model calls the tool, the harness passes the model-supplied
24/// JSON arguments. The tool reads the string field named by this constant as
25/// the child run's user prompt; if the arguments are a bare JSON string the
26/// whole string is used instead.
27pub const SUBAGENT_INPUT_FIELD: &str = "input";
28
29/// A reusable, named child agent built on top of an [`AgentHarness`].
30///
31/// A `SubAgent` bundles:
32/// - an `Arc<AgentHarness<State, Ctx>>` that drives the child agent loop,
33/// - a stable `name` and `description` (used for tool schemas and observability),
34/// - an optional `system_prompt` prepended to every child run as a system
35/// message (the "fixed prompt template").
36///
37/// Invoking a sub-agent always produces a *child run* one level deeper than the
38/// caller's depth. The harness's [`crate::harness::limits::RunLimits::max_depth`]
39/// cap bounds how deep nested sub-agents may recurse; an invocation whose child
40/// depth would exceed the cap fails with
41/// [`crate::error::TinyAgentsError::SubAgentDepth`].
42///
43/// `SubAgent` is cheap to clone-share via `Arc`; wrap it in an `Arc` to expose
44/// the same child agent through several [`SubAgentTool`]s.
45pub struct SubAgent<State: Send + Sync, Ctx: Send + Sync = ()> {
46 /// The harness that drives the child agent loop.
47 pub(crate) harness: Arc<AgentHarness<State, Ctx>>,
48 /// Stable identifier for the sub-agent (used as the default tool name).
49 pub(crate) name: String,
50 /// Human/model readable description of what the sub-agent does.
51 pub(crate) description: String,
52 /// Optional system prompt prepended to every child run.
53 pub(crate) system_prompt: Option<String>,
54}
55
56/// A persistent, *reusable* conversation with a single [`SubAgent`].
57///
58/// Where [`SubAgentTool`] runs a fresh, stateless child run per tool call, a
59/// `SubAgentSession` keeps the **same** underlying [`SubAgent`] (and therefore
60/// the same [`AgentHarness`]) alive across multiple turns and retains the full
61/// conversation transcript between them. This is *post-completion reuse*: the
62/// child run finishes normally, the orchestrator inspects/awaits human input,
63/// then calls the same sub-agent again — distinct from *steering*, which
64/// interrupts a still-running agent.
65///
66/// # Human-in-the-loop reuse flow
67///
68/// 1. `send` the first input (e.g. a user question). The session appends it to
69/// the retained transcript, runs the sub-agent over the full transcript, and
70/// folds the resulting assistant (and any tool) messages back in.
71/// 2. Inspect the returned [`AgentRun`] and obtain human input out-of-band.
72/// 3. Wrap that human input as a [`Message::user`] and `send` it again. Because
73/// the prior turn's messages are still in the transcript, the sub-agent
74/// answers *with full context* — without being killed and restarted.
75///
76/// Each send after the first emits [`AgentEvent::SubAgentReused`][reused]
77/// (alongside the usual [`SubAgentStarted`][started]/[`SubAgentCompleted`][completed]
78/// bracket) so reuse is observable in the event stream.
79///
80/// [reused]: crate::harness::events::AgentEvent::SubAgentReused
81/// [started]: crate::harness::events::AgentEvent::SubAgentStarted
82/// [completed]: crate::harness::events::AgentEvent::SubAgentCompleted
83pub struct SubAgentSession<State: Send + Sync, Ctx: Send + Sync = ()> {
84 /// The reused child agent. The same `Arc` is shared across every send, so
85 /// the underlying harness is never reconstructed.
86 pub(crate) subagent: Arc<SubAgent<State, Ctx>>,
87 /// The accumulating conversation transcript carried across sends.
88 pub(crate) transcript: Vec<Message>,
89 /// Number of completed sends (turns) so far.
90 pub(crate) turn: usize,
91 /// Caller depth the child runs at; the child run is created at
92 /// `parent_depth + 1` (default `0`, so the child runs at depth `1`).
93 pub(crate) parent_depth: usize,
94 /// Event sink the reuse lifecycle (and the child run's own events) are
95 /// emitted on. Defaults to a fresh, unsubscribed sink.
96 pub(crate) events: EventSink,
97 /// Whether the fixed system prompt has been seeded into the transcript yet
98 /// (it is prepended once, on the first send).
99 pub(crate) seeded: bool,
100}
101
102/// A [`Tool`] adapter that exposes a [`SubAgent`] to a parent agent — the
103/// surface that turns "agents calling agents" into an ordinary tool call.
104///
105/// [`Tool`]: crate::harness::tool::Tool
106///
107/// When the parent model calls this tool, [`SubAgentTool`] runs the wrapped
108/// sub-agent as a child run at the configured `parent_depth` and returns the
109/// child's final assistant text as the [`crate::harness::tool::ToolResult`]
110/// content. This makes an entire agent composable as a single tool call, so a
111/// model orchestrating tools is, transparently, a model orchestrating models.
112///
113/// Because the [`Tool`] trait gives `call` no access to the live parent
114/// [`crate::harness::context::RunContext`], the depth the child runs at is fixed
115/// at construction (`parent_depth`, default `0`). Nesting deeper sub-agents is
116/// expressed by constructing the inner tool with a larger `parent_depth`. For
117/// fully context-threaded invocation (reading the live parent depth) call
118/// [`SubAgent::invoke_in_parent`] directly instead of going through the tool.
119pub struct SubAgentTool<State: Send + Sync, Ctx: Send + Sync = ()> {
120 /// The wrapped child agent.
121 pub(crate) subagent: Arc<SubAgent<State, Ctx>>,
122 /// Tool name exposed to the model (defaults to the sub-agent name).
123 pub(crate) tool_name: String,
124 /// The caller depth this tool invokes the child at; the child runs at
125 /// `parent_depth + 1`.
126 pub(crate) parent_depth: usize,
127 /// JSON Schema describing the tool's model-visible arguments.
128 pub(crate) parameters: Value,
129}