pub struct SubagentHandle<'a, S> { /* private fields */ }Expand description
One open subagent invocation.
Created by RunContext::subagent.
SUBAGENT_STARTED has already gone out; Drop emits SUBAGENT_FINISHED
with a success outcome, including on the early return that a ?
produces.
A subagent is a scope, like a step: everything emitted through the
handle comes out attributed to it. The handle therefore dereferences to
the run context — messages, tool calls, reasoning, steps and nested
subagents all open through it, and every event they produce carries this
invocation’s subagentRunId without the agent saying so:
{
let mut researcher = ctx.subagent("researcher")?;
researcher.say("Three sources found.")?; // attributed, through Deref
researcher.finish_with(serde_json::json!({ "sources": 3 }))?;
}
ctx.say("Thanks.")?; // the parent's own, untagged
let events = events.drain();
assert_eq!(events[0].event_type(), EventType::SubagentStarted);
assert_eq!(events[1].subagent_run_id().map(|id| id.as_str()), Some("r-sub-1"));
assert_eq!(events[4].event_type(), EventType::SubagentFinished);
assert_eq!(events[5].subagent_run_id(), None);Nesting is automatic: a subagent opened through a handle gets the handle’s
id as its parentSubagentRunId. Two subagents cannot be open at once
through handles — the second subagent() is a borrow-check error, as
everything overlapping is here. For subagents that genuinely stream
concurrently, tag events yourself and emit them interleaved; see the
module docs.
§Ending it
The terminator names the subagent it closes and is not itself attributed
to it, so every method here restores the enclosing attribution before
emitting. Drop cannot tell success from failure: on the error path you
care about, call fail — or suspend when
the run is about to pause on an interrupt the subagent raised.
Implementations§
Source§impl<'a, S> SubagentHandle<'a, S>
impl<'a, S> SubagentHandle<'a, S>
Sourcepub fn id(&self) -> &SubagentRunId
pub fn id(&self) -> &SubagentRunId
The id every event emitted through this handle carries.
Sourcepub fn finish(self) -> Result<()>
pub fn finish(self) -> Result<()>
Emits SUBAGENT_FINISHED with a success outcome and consumes the
handle.
Only worth calling over letting the handle drop when you want to see
the error: Drop cannot report one.
Sourcepub fn finish_with(self, result: impl Into<Value>) -> Result<()>
pub fn finish_with(self, result: impl Into<Value>) -> Result<()>
Emits SUBAGENT_FINISHED carrying a completion payload — the
subagent’s counterpart of RUN_FINISHED.result.
Sourcepub fn suspend(self, interrupt_ids: impl Into<Vec<String>>) -> Result<()>
pub fn suspend(self, interrupt_ids: impl Into<Vec<String>>) -> Result<()>
Emits SUBAGENT_FINISHED with a suspended outcome: the subagent is
waiting on interrupt_ids, which the run is about to return in an
interrupt outcome.
Build each interrupt with
Interrupt::with_subagent_run_id
so a client can render it inside this subagent’s group, and announce
the same id again on the resuming run to continue the invocation.
Methods from Deref<Target = RunContext<S>>§
Sourcepub fn state_mut(&mut self) -> &mut S
pub fn state_mut(&mut self) -> &mut S
The typed state, mutably. Nothing is emitted until you call
publish_state.
Sourcepub fn set_state(&mut self, state: &S) -> Result<()>
pub fn set_state(&mut self, state: &S) -> Result<()>
Replaces the state and publishes the change.
The first publish of a run is a STATE_SNAPSHOT; later ones are a
STATE_DELTA unless the patch would be no smaller than the snapshot.
See StateManager.
Sourcepub fn update_state(&mut self, update: impl FnOnce(&mut S)) -> Result<()>
pub fn update_state(&mut self, update: impl FnOnce(&mut S)) -> Result<()>
Mutates the state in place and publishes the change.
#[derive(Default, Serialize, Deserialize)]
struct Draft { revision: u32 }
ctx.update_state(|draft| draft.revision += 1)?;
assert_eq!(ctx.state().revision, 1);Sourcepub fn publish_state(&mut self) -> Result<()>
pub fn publish_state(&mut self) -> Result<()>
Publishes whatever state_mut left behind.
A no-op when nothing changed since the last publish.
Sourcepub fn input(&self) -> &RunAgentInput
pub fn input(&self) -> &RunAgentInput
The whole request, for anything the accessors do not cover.
Sourcepub fn parent_run_id(&self) -> Option<&RunId>
pub fn parent_run_id(&self) -> Option<&RunId>
The run that spawned this one, for nested agents.
Sourcepub fn last_user_text(&self) -> Option<String>
pub fn last_user_text(&self) -> Option<String>
What the user said last, as text.
The turn an agent is almost always answering. Non-text parts of a
multimodal message are dropped — see
UserContent::to_text; reach into
RunContext::messages directly if the images matter.
None when the history holds no user message at all, which is distinct
from a user who sent an empty one.
let mut input = RunAgentInput::new("thread-1", "run-1");
input.messages = vec![Message::user("msg-1", "add milk")];
let (ctx, _events) = RunContext::<()>::new(input)?;
assert_eq!(ctx.last_user_text().as_deref(), Some("add milk"));Sourcepub fn forwarded_props(&self) -> &Value
pub fn forwarded_props(&self) -> &Value
Arbitrary passthrough properties, opaque to the protocol.
Sourcepub fn resume(&self) -> &[ResumeEntry]
pub fn resume(&self) -> &[ResumeEntry]
Answers to the interrupts a previous run paused on.
Empty unless this request resumes a paused run.
Sourcepub fn resume_for(&self, interrupt_id: &str) -> Option<&ResumeEntry>
pub fn resume_for(&self, interrupt_id: &str) -> Option<&ResumeEntry>
The answer to one interrupt, by its Interrupt::id.
Sourcepub fn is_cancelled(&self) -> bool
pub fn is_cancelled(&self) -> bool
Whether the run has been cancelled.
Sourcepub fn check_cancelled(&self) -> Result<()>
pub fn check_cancelled(&self) -> Result<()>
Error::Cancelled once the run is cancelled, for use with ?.
Sourcepub fn cancel_token(&self) -> CancellationToken
pub fn cancel_token(&self) -> CancellationToken
A handle a transport can trip on client disconnect.
Sourcepub fn until_cancelled<F: Future>(
&self,
future: F,
) -> impl Future<Output = Option<F::Output>>
pub fn until_cancelled<F: Future>( &self, future: F, ) -> impl Future<Output = Option<F::Output>>
Races future against cancellation, returning None if cancellation
won.
The way to make a long model call interruptible:
let answer = ctx
.until_cancelled(async { "the model's reply" })
.await
.ok_or(Error::Cancelled)?;
assert_eq!(answer, "the model's reply");Deliberately not an async fn: that would capture &self in the
returned future, and a future holding a borrow of the run context is
only Send if the context is Sync — which it is not, since a
transformer only has to be Send.
Sourcepub fn emit(&mut self, event: Event) -> Result<()>
pub fn emit(&mut self, event: Event) -> Result<()>
Emits an event as-is — the escape hatch under the typed emitters.
Everything the handles emit goes through here, so a raw event is transformed and verified like any other.
Sourcepub fn new_message_id(&mut self) -> MessageId
pub fn new_message_id(&mut self) -> MessageId
A fresh message id, unique within the run.
Derived from the run id and a counter rather than a UUID: the protocol
asks for opaque strings, this crate takes no uuid dependency, and a
deterministic id makes a recorded stream diffable. Pass your own id to
message_with_id when you need one.
Sourcepub fn new_tool_call_id(&mut self) -> ToolCallId
pub fn new_tool_call_id(&mut self) -> ToolCallId
A fresh tool call id, unique within the run.
Sourcepub fn new_subagent_run_id(&mut self) -> SubagentRunId
pub fn new_subagent_run_id(&mut self) -> SubagentRunId
A fresh subagent invocation id, unique within the run.
Derived like the others, from the run id and a counter — so, like
message ids, it is unique across runs only while run ids are. A
resuming run that continues a suspended subagent should reuse the
suspended id instead — see subagent_with.
Sourcepub fn subagent_run_id(&self) -> Option<&SubagentRunId>
pub fn subagent_run_id(&self) -> Option<&SubagentRunId>
The subagent everything emitted right now is attributed to — None
outside any subagent scope.
Sourcepub fn subagent(
&mut self,
name: impl Into<String>,
) -> Result<SubagentHandle<'_, S>>
pub fn subagent( &mut self, name: impl Into<String>, ) -> Result<SubagentHandle<'_, S>>
Announces a subagent under a fresh id and scopes everything emitted
through the returned handle to it — SUBAGENT_STARTED now,
SUBAGENT_FINISHED when the handle drops.
name is the subagent’s reusable type or name, for display; the id is
this invocation’s alone. See SubagentHandle.
Sourcepub fn subagent_with(
&mut self,
started: SubagentStartedEvent,
) -> Result<SubagentHandle<'_, S>>
pub fn subagent_with( &mut self, started: SubagentStartedEvent, ) -> Result<SubagentHandle<'_, S>>
The same, from an announcement you built — for a description, an explicit id, or the agents-as-tools links.
A parent_subagent_run_id left absent is filled from the enclosing
scope, so nesting needs no help. An explicit id is how a resuming run
continues a subagent that suspended: announce the id the suspended
invocation had, and a client transitions its group from waiting back
to running rather than drawing a second one.
let mut call = ctx.tool_call("task")?;
call.args(r#"{"brief":"find sources"}"#)?;
let (call_id, result_id) = (call.id().clone(), call.result_message_id().clone());
call.end()?; // the client sees the call close…
let announce = SubagentStartedEvent::new("researcher-7", "researcher")
.with_parent_tool_call(call_id.clone());
let mut researcher = ctx.subagent_with(announce)?;
researcher.say("Three sources found.")?; // …then the subagent it spawned…
researcher.finish()?;
ctx.emit(Event::tool_call_result(result_id, call_id, "3 sources"))?; // …then its result
let types: Vec<_> = events.drain().iter().map(Event::event_type).collect();
assert_eq!(types[3], EventType::SubagentStarted);
assert_eq!(types[7], EventType::SubagentFinished);
assert_eq!(types[8], EventType::ToolCallResult);Sourcepub fn assistant_message(&mut self) -> Result<MessageHandle<'_, S>>
pub fn assistant_message(&mut self) -> Result<MessageHandle<'_, S>>
Opens an assistant message under a fresh id — TEXT_MESSAGE_START.
Sourcepub fn message(&mut self, role: TextMessageRole) -> Result<MessageHandle<'_, S>>
pub fn message(&mut self, role: TextMessageRole) -> Result<MessageHandle<'_, S>>
Opens a message with the given role under a fresh id.
Sourcepub fn message_with_id(
&mut self,
id: impl Into<MessageId>,
role: TextMessageRole,
) -> Result<MessageHandle<'_, S>>
pub fn message_with_id( &mut self, id: impl Into<MessageId>, role: TextMessageRole, ) -> Result<MessageHandle<'_, S>>
Opens a message under an id you choose.
Sourcepub fn say(&mut self, text: impl Into<String>) -> Result<MessageId>
pub fn say(&mut self, text: impl Into<String>) -> Result<MessageId>
Emits a whole assistant message — start, content, end — and returns its id.
Sourcepub fn reasoning(&mut self) -> Result<ReasoningHandle<'_, S>>
pub fn reasoning(&mut self) -> Result<ReasoningHandle<'_, S>>
Opens a reasoning block under a fresh id — REASONING_START.
Sourcepub fn reasoning_with_id(
&mut self,
id: impl Into<MessageId>,
) -> Result<ReasoningHandle<'_, S>>
pub fn reasoning_with_id( &mut self, id: impl Into<MessageId>, ) -> Result<ReasoningHandle<'_, S>>
Opens a reasoning block under an id you choose.
Sourcepub fn think(&mut self, text: impl Into<String>) -> Result<MessageId>
pub fn think(&mut self, text: impl Into<String>) -> Result<MessageId>
Emits a whole reasoning block in one call and returns its id.
Sourcepub fn tool_call(&mut self, name: &str) -> Result<ToolCallHandle<'_, S>>
pub fn tool_call(&mut self, name: &str) -> Result<ToolCallHandle<'_, S>>
Opens a call to name under a fresh id — TOOL_CALL_START.
Sourcepub fn tool_call_with_id(
&mut self,
id: impl Into<ToolCallId>,
name: &str,
) -> Result<ToolCallHandle<'_, S>>
pub fn tool_call_with_id( &mut self, id: impl Into<ToolCallId>, name: &str, ) -> Result<ToolCallHandle<'_, S>>
Opens a call to name under an id you choose.