Skip to main content

ag_ui/server/emit/
subagent.rs

1//! Scoping a subagent invocation.
2
3use std::ops::{Deref, DerefMut};
4
5use crate::{Event, SubagentFinishedEvent, SubagentOutcome, SubagentRunId, SubagentStartedEvent};
6use serde_json::Value;
7
8use crate::server::context::RunContext;
9use crate::server::error::Result;
10
11/// One open subagent invocation.
12///
13/// Created by [`RunContext::subagent`](crate::server::RunContext::subagent).
14/// `SUBAGENT_STARTED` has already gone out; `Drop` emits `SUBAGENT_FINISHED`
15/// with a success outcome, including on the early return that a `?`
16/// produces.
17///
18/// A subagent is a *scope*, like a step: everything emitted through the
19/// handle comes out attributed to it. The handle therefore dereferences to
20/// the run context — messages, tool calls, reasoning, steps and nested
21/// subagents all open through it, and every event they produce carries this
22/// invocation's `subagentRunId` without the agent saying so:
23///
24/// ```
25/// # use ag_ui::{Event, EventType, RunAgentInput, TextMessageRole};
26/// # use ag_ui::server::RunContext;
27/// # let (mut ctx, mut events) = RunContext::<()>::new(RunAgentInput::new("t", "r"))?;
28/// {
29///     let mut researcher = ctx.subagent("researcher")?;
30///     researcher.say("Three sources found.")?;   // attributed, through Deref
31///     researcher.finish_with(serde_json::json!({ "sources": 3 }))?;
32/// }
33/// ctx.say("Thanks.")?;                            // the parent's own, untagged
34///
35/// let events = events.drain();
36/// assert_eq!(events[0].event_type(), EventType::SubagentStarted);
37/// assert_eq!(events[1].subagent_run_id().map(|id| id.as_str()), Some("r-sub-1"));
38/// assert_eq!(events[4].event_type(), EventType::SubagentFinished);
39/// assert_eq!(events[5].subagent_run_id(), None);
40/// # Ok::<(), ag_ui::server::Error>(())
41/// ```
42///
43/// Nesting is automatic: a subagent opened through a handle gets the handle's
44/// id as its `parentSubagentRunId`. Two subagents cannot be open at once
45/// through handles — the second `subagent()` is a borrow-check error, as
46/// everything overlapping is here. For subagents that genuinely stream
47/// concurrently, tag events yourself and emit them interleaved; see the
48/// [module docs](crate::server::emit#subagents).
49///
50/// # Ending it
51///
52/// The terminator names the subagent it closes and is not itself attributed
53/// to it, so every method here restores the enclosing attribution *before*
54/// emitting. `Drop` cannot tell success from failure: on the error path you
55/// care about, call [`fail`](Self::fail) — or [`suspend`](Self::suspend) when
56/// the run is about to pause on an interrupt the subagent raised.
57#[derive(Debug)]
58pub struct SubagentHandle<'a, S> {
59    ctx: &'a mut RunContext<S>,
60    id: SubagentRunId,
61    name: String,
62    /// The attribution in force before this subagent, restored on close.
63    previous: Option<SubagentRunId>,
64    ended: bool,
65}
66
67impl<'a, S> SubagentHandle<'a, S> {
68    /// Emits `SUBAGENT_STARTED` and scopes the context to the new subagent.
69    ///
70    /// A `parent_subagent_run_id` the caller left absent is filled from the
71    /// enclosing scope, which is what makes nesting through `Deref` correct
72    /// without the agent naming the parent.
73    pub(crate) fn start(
74        ctx: &'a mut RunContext<S>,
75        mut started: SubagentStartedEvent,
76    ) -> Result<Self> {
77        if started.parent_subagent_run_id.is_none() {
78            started.parent_subagent_run_id = ctx.subagent_run_id().cloned();
79        }
80        let id = started.subagent_run_id.clone();
81        let name = started.name.clone();
82        ctx.emit(started.into())?;
83        let previous = ctx.set_attribution(Some(id.clone()));
84        Ok(Self {
85            ctx,
86            id,
87            name,
88            previous,
89            ended: false,
90        })
91    }
92
93    /// The id every event emitted through this handle carries.
94    pub fn id(&self) -> &SubagentRunId {
95        &self.id
96    }
97
98    /// The subagent's declared name, as announced.
99    pub fn name(&self) -> &str {
100        &self.name
101    }
102
103    /// Leaves the scope: the enclosing attribution is back in force, and the
104    /// terminator about to be emitted belongs to it.
105    fn leave(&mut self) {
106        self.ended = true;
107        let previous = self.previous.take();
108        self.ctx.set_attribution(previous);
109    }
110
111    /// Emits `SUBAGENT_FINISHED` with a success outcome and consumes the
112    /// handle.
113    ///
114    /// Only worth calling over letting the handle drop when you want to see
115    /// the error: `Drop` cannot report one.
116    pub fn finish(mut self) -> Result<()> {
117        self.leave();
118        self.ctx
119            .emit(Event::subagent_finished_success(self.id.clone()))
120    }
121
122    /// Emits `SUBAGENT_FINISHED` carrying a completion payload — the
123    /// subagent's counterpart of `RUN_FINISHED.result`.
124    pub fn finish_with(mut self, result: impl Into<Value>) -> Result<()> {
125        self.leave();
126        self.ctx.emit(
127            SubagentFinishedEvent::new(self.id.clone())
128                .with_result(result)
129                .with_outcome(SubagentOutcome::Success)
130                .into(),
131        )
132    }
133
134    /// Emits `SUBAGENT_FINISHED` with a suspended outcome: the subagent is
135    /// waiting on `interrupt_ids`, which the run is about to return in an
136    /// interrupt outcome.
137    ///
138    /// Build each interrupt with
139    /// [`Interrupt::with_subagent_run_id`](crate::Interrupt::with_subagent_run_id)
140    /// so a client can render it inside this subagent's group, and announce
141    /// the same id again on the resuming run to continue the invocation.
142    pub fn suspend(mut self, interrupt_ids: impl Into<Vec<String>>) -> Result<()> {
143        self.leave();
144        self.ctx.emit(Event::subagent_finished_suspended(
145            self.id.clone(),
146            interrupt_ids,
147        ))
148    }
149
150    /// Emits `SUBAGENT_ERROR` and consumes the handle.
151    pub fn fail(mut self, message: impl Into<String>) -> Result<()> {
152        self.leave();
153        self.ctx
154            .emit(Event::subagent_error(self.id.clone(), message))
155    }
156
157    /// Emits `SUBAGENT_ERROR` with a machine-readable code.
158    pub fn fail_with_code(
159        mut self,
160        message: impl Into<String>,
161        code: impl Into<String>,
162    ) -> Result<()> {
163        self.leave();
164        self.ctx.emit(
165            crate::SubagentErrorEvent::new(self.id.clone(), message)
166                .with_code(code)
167                .into(),
168        )
169    }
170}
171
172impl<S> Deref for SubagentHandle<'_, S> {
173    type Target = RunContext<S>;
174
175    fn deref(&self) -> &Self::Target {
176        self.ctx
177    }
178}
179
180impl<S> DerefMut for SubagentHandle<'_, S> {
181    fn deref_mut(&mut self) -> &mut Self::Target {
182        self.ctx
183    }
184}
185
186impl<S> Drop for SubagentHandle<'_, S> {
187    fn drop(&mut self) {
188        if !self.ended {
189            self.leave();
190            // Nowhere to report a failure to; a dead channel or a cancelled run
191            // makes the terminator moot anyway.
192            let _ = self
193                .ctx
194                .emit(Event::subagent_finished_success(self.id.clone()));
195        }
196    }
197}