pub struct Session<'a> { /* private fields */ }Expand description
The durable state of an agent session: the scoped tool catalogue, the
resource-awareness map, and the conversation transcript — everything that
persists across turns. A once-mode / per-event run is a session of exactly
one turn (run_loop); a warm continue-session runs many turns over the
same transcript, each new event appended via Session::deliver before
another Session::run_turn.
Implementations§
Source§impl<'a> Session<'a>
impl<'a> Session<'a>
Sourcepub fn prepare(
servers: &'a [McpClient],
input: &LoopInput,
self_handler: &mut dyn SelfHandler,
) -> Result<Session<'a>, LoopAbort>
pub fn prepare( servers: &'a [McpClient], input: &LoopInput, self_handler: &mut dyn SelfHandler, ) -> Result<Session<'a>, LoopAbort>
Assemble a session: the tool catalogue (MCP tools + self-tools, plus
resource.read when resources exist), the resource awareness note, and
the opening transcript (system prompt + seed + the instruction as the
first user turn). Resources are split deliberately: listing them makes
the model aware of what exists, while reading one is an explicit tool
call, so a large resource enters the context only when actually wanted.
Sourcepub fn refresh_tools(
&mut self,
self_handler: &mut dyn SelfHandler,
) -> Result<(), LoopAbort>
pub fn refresh_tools( &mut self, self_handler: &mut dyn SelfHandler, ) -> Result<(), LoopAbort>
Rebuild the MCP side of the tool catalogue from the servers’ CURRENT
tools/list. Called at a turn boundary after an inbound
notifications/tools/list_changed, so a long-lived continue-session
tracks a server whose tool set changed instead of holding a stale
catalogue for its whole life. Self-tools and resource.read are
re-merged and the transcript is untouched, so a refresh costs no context.
Sourcepub fn tools_len(&self) -> usize
pub fn tools_len(&self) -> usize
The current catalogue size (observability for the live refresh).
Sourcepub fn tool_class(&self, name: &str) -> ToolClass
pub fn tool_class(&self, name: &str) -> ToolClass
Classify a catalogue tool by its seam: a name routed to an MCP server is
ToolClass::Mcp, dispatched back to that server; every other catalogue
entry is agentd’s own ToolClass::SelfControl surface — the self-tools
plus resource.read. The routing map IS the MCP-tool set, and the two
classes are assembled by different code paths ([build_catalogue] versus
the SelfHandler merge), which makes this an authoritative and testable
boundary between “tools from a registered server” and “agentd’s own
orchestration primitives”.
Callers must pass a name drawn from Session::tools: a name absent from
the catalogue classifies as SelfControl, since it is by definition not a
routed server tool, so classifying an arbitrary string is meaningless.
Sourcepub fn tool_permitted(&self, name: &str) -> bool
pub fn tool_permitted(&self, name: &str) -> bool
Whether this session’s GRANT admits name. Every grant must admit it; an
ungranted session — one with no parent narrowing — admits everything.
The catalogue is already filtered, so this is a second gate at dispatch
time, for the model that names a tool anyway: a hallucinated name, or one
remembered from a transcript written when the catalogue was wider.
Sourcepub fn deliver(&mut self, content: &str)
pub fn deliver(&mut self, content: &str)
Append the next event as a new user turn — the delivery point for a warm continue-session. The transcript, which is the model’s memory of the session, carries forward, so the next turn continues the conversation rather than starting over.
Sourcepub fn set_model(&mut self, model: &str)
pub fn set_model(&mut self, model: &str)
Adopt a new model for subsequent turns. The transcript is UNTOUCHED —
only the model dialed for the NEXT turn changes, and a turn already in
flight runs to completion on the model it started with. The model is
what each request’s model field carries.
Sourcepub fn model(&self) -> &str
pub fn model(&self) -> &str
The current model dialed for the next turn. Used to decide whether a pending swap actually changes the model: an endpoint repoint that leaves the model alone needs no turn restart, since the output would match.
Sourcepub fn transcript_len(&self) -> usize
pub fn transcript_len(&self) -> usize
The number of transcript messages so far — a cheap pre-turn marker for the
restart-turn policy: snapshot this before a turn, then
truncate_transcript back to it to discard
the swapped turn’s appended messages and re-run from the same pre-turn state.
Sourcepub fn truncate_transcript(&mut self, len: usize)
pub fn truncate_transcript(&mut self, len: usize)
Truncate the transcript back to len for a restart-turn: drop every
message a discarded turn appended, restoring the exact pre-turn
transcript so the turn can be re-run on the new model. A no-op when len
is already at or past the current length — this never grows the
transcript, so a stale marker cannot resurrect dropped messages.
Sourcepub fn run_turn(
&mut self,
intel: &IntelClient,
self_handler: &mut dyn SelfHandler,
log: &Logger,
budget: &mut Budget,
cancel: Option<&Arc<AtomicBool>>,
) -> Result<(Outcome, Usage), LoopAbort>
pub fn run_turn( &mut self, intel: &IntelClient, self_handler: &mut dyn SelfHandler, log: &Logger, budget: &mut Budget, cancel: Option<&Arc<AtomicBool>>, ) -> Result<(Outcome, Usage), LoopAbort>
Run one turn: the ReAct loop over the persistent transcript until a
terminal status, bounded by budget. cancel is polled at each turn
boundary. Every assistant/tool message (including the final answer) is
appended to the transcript, so a subsequent turn continues the same
conversation.
Returns the turn’s Outcome together with the turn’s token Usage —
the sum of every model call in this turn. The control layer rolls this
DELTA up to the supervisor as
crate::subagent::protocol::AgentMsg::Usage, which is what makes
hierarchical token accounting (agentd_tokens_total) add up. The loop
itself never touches the control channel: the up handle stays in
control.rs, so the loop stays runnable in-process and in a child alike.