pub struct AgentRuntime { /* private fields */ }Expand description
A stateful agent runtime that wraps AgentKernel.
AgentRuntime owns the conversation transcript and all cross-turn
configuration. Each call to run appends a user
message to the transcript, delegates to the kernel for one turn, and
appends the kernel’s new messages back to the transcript.
Implementations§
Source§impl AgentRuntime
impl AgentRuntime
Sourcepub fn builder() -> AgentRuntimeBuilder
pub fn builder() -> AgentRuntimeBuilder
Create a new AgentRuntimeBuilder.
Sourcepub async fn run(
&mut self,
user_text: impl Into<String>,
) -> Result<RuntimeOutcome>
pub async fn run( &mut self, user_text: impl Into<String>, ) -> Result<RuntimeOutcome>
Run one turn with the given user text.
Appends Message::user(text) to the transcript, delegates to the kernel,
appends the new messages back, and returns a RuntimeOutcome.
Goal 284: automatic pre/post checkpoints have been removed.
The agent must call checkpoint_save explicitly to record restore
points. outcome.checkpoint_id is always None here.
Sourcepub async fn close(&mut self, last_outcome: Option<&RuntimeOutcome>)
pub async fn close(&mut self, last_outcome: Option<&RuntimeOutcome>)
Signal that the session is permanently over and fire SessionEnd.
Call this exactly once, after the last run() or enqueue() call, to
give hooks a chance to do post-session cleanup. Calling run() after
close() is safe but SessionEnd will not fire again.
Sourcepub async fn maybe_compact_cross_turn(
&mut self,
last_usage: &TokenUsage,
) -> Result<()>
pub async fn maybe_compact_cross_turn( &mut self, last_usage: &TokenUsage, ) -> Result<()>
Run cross-turn compaction if threshold is exceeded, emitting boundary events.
This is the Wrapper’s responsibility — the kernel only does intra-turn trim.
The compaction summary is emitted as MessageAppended so it lands in the
on-disk jsonl. A CompactionBoundary event (g157) lets the reader skip
pre-compaction messages on resume.
last_usage is the TokenUsage from the turn that just completed.
Its prompt_tokens field is the actual prompt token count reported by the
API — when non-zero and compactor.threshold_prompt_tokens is set, the
token-based check takes priority over the character estimate (more reliable
for CJK content where the 4-char/token assumption significantly
underestimates token density). The cache_hit_tokens /
cache_miss_tokens fields are forwarded to the emitted
CompactionBoundary event for cache-telemetry (g336).
Sourcepub async fn enqueue(
&mut self,
text: impl Into<String>,
) -> Result<Option<RuntimeOutcome>>
pub async fn enqueue( &mut self, text: impl Into<String>, ) -> Result<Option<RuntimeOutcome>>
Enqueue a user message and drain the queue in FIFO order.
This is the preferred entry point for all interaction layers (TUI,
HTTP, CLI). Unlike calling run directly, enqueue
is safe to call while a turn is already in flight: the runtime is
single-threaded (&mut self), so multiple callers naturally
serialise. The queue ensures messages submitted before the runtime
is ready are not lost and are processed in order.
user sends A → enqueue(A) → run(A)
user sends B while A runs → enqueue(B) → queue=[B] (A already running via prior call)
A finishes → loop pops B → run(B)In practice the outer loop (drain_queue) is what creates this
ordering: a call to enqueue that arrives while another enqueue
is executing on the same runtime will block on &mut self borrow,
so the messages are processed strictly in order.
Sourcepub fn queue_len(&self) -> usize
pub fn queue_len(&self) -> usize
Number of messages currently waiting in the queue.
Callers can expose this to the UI (e.g. status bar: “+N queued”).
Sourcepub fn transcript(&self) -> &[Message]
pub fn transcript(&self) -> &[Message]
Return a reference to the accumulated transcript.
Sourcepub fn transcript_tail(&self, n: usize) -> &[Message]
pub fn transcript_tail(&self, n: usize) -> &[Message]
Return the most-recent n transcript messages, or the full
transcript if n >= transcript.len(). Returns an empty slice
when n == 0.
Used by the goal-loop judge (run_goal_loop) to keep the
per-turn evaluator payload bounded as the transcript grows.
Goal-260.
Sourcepub fn set_transcript(&mut self, transcript: Vec<Message>)
pub fn set_transcript(&mut self, transcript: Vec<Message>)
Replace the current transcript (useful for restoring from a saved session).
Sourcepub fn truncate_transcript(&mut self, len: usize)
pub fn truncate_transcript(&mut self, len: usize)
Discard all transcript messages after index len, restoring the
transcript to the state it had before a turn started. Used by the
TUI abort path to prevent orphan tool_call entries.
Sourcepub fn kernel(&self) -> &AgentKernel
pub fn kernel(&self) -> &AgentKernel
Return a reference to the inner kernel.
Sourcepub fn set_llm(&mut self, llm: Arc<dyn ChatProvider>)
pub fn set_llm(&mut self, llm: Arc<dyn ChatProvider>)
Hot-swap the LLM provider backing this runtime.
Delegates to AgentKernel::set_llm. Used by the TUI /model picker
to switch models mid-session. Must be called between turns (not while
run / enqueue / run_goal_loop is executing on this runtime).
Sourcepub fn event_sink(&self) -> &dyn EventSink
pub fn event_sink(&self) -> &dyn EventSink
Return the event sink currently in use.
Sourcepub fn set_interrupt_token(&mut self, token: CancellationToken)
pub fn set_interrupt_token(&mut self, token: CancellationToken)
Set a cancellation token that interrupts the current (or next) agent turn.
When the token is cancelled the step loop exits with
FinishReason::Cancelled at
the next step boundary. This method replaces any previously installed
token — call it before each run() so a fresh token is in place.
Sourcepub fn set_session_id(&mut self, id: impl Into<String>)
pub fn set_session_id(&mut self, id: impl Into<String>)
Set the session id used for tracing-span labels and turn log lines.
After this is called, every run() emits a tracing span record with
session_id=<id> and an info log line carrying the same field, so logs
and OTEL/Datadog traces can be filtered per session via
RUST_LOG=recursive[{session_id}]=debug or the session_id label.
Sourcepub fn set_event_sink(&mut self, sink: Arc<dyn EventSink>)
pub fn set_event_sink(&mut self, sink: Arc<dyn EventSink>)
Set a new event sink (useful for REPL mode between turns).
Replaces the sink AND re-registers the tools that hold an Arc<dyn EventSink>
— specifically TodoWriteTool (Goal-167)
and ExitPlanModeTool (Goal-165) —
so that AgentEvent::TodoUpdated and AgentEvent::PlanProposed reach the new
consumer (e.g. when the TUI swaps in a TuiEventSink after construction).
The side effect is intentional: every caller that swaps the sink (CLI per-turn,
HTTP per-session, TUI on backend init) expects those tools to forward events to
the new sink. The method name documents the side effect; callers that only want
to swap the sink without touching the tool registry must use
replace_event_sink instead.
Sourcepub fn replace_event_sink(&mut self, sink: Arc<dyn EventSink>)
pub fn replace_event_sink(&mut self, sink: Arc<dyn EventSink>)
Swap the event sink without re-registering any sink-dependent tools.
Use this when you know the new sink should only receive events emitted by
AgentRuntime itself (e.g. MessageAppended, TurnFinished, compaction
boundaries) and do not need the TodoUpdated / PlanProposed fan-out to
the new consumer. Most callers want set_event_sink
— its tool-reregistration side effect is what makes the TUI’s
TodoUpdated updates reach the live UI.
Added in the P0-2 cleanup so the implicit side effect has a non-side-effect sibling.
Sourcepub fn current_todos(&self) -> Vec<TodoItem>
pub fn current_todos(&self) -> Vec<TodoItem>
Goal-167: return a snapshot of the current agent task list.
Returns a clone of the list as it stands at call time. Returns an empty vec if the internal lock is poisoned.
Sourcepub fn set_permission_hook(&mut self, hook: Arc<dyn PermissionHook>)
pub fn set_permission_hook(&mut self, hook: Arc<dyn PermissionHook>)
Goal-161: attach a crate::tools::PermissionHook to the
underlying tool registry so every tool invocation passes through
the async permission gate before execution.
Sourcepub fn set_sdk_hook_forwarder(
&mut self,
forwarder: Option<Arc<dyn SdkHookForwarder>>,
)
pub fn set_sdk_hook_forwarder( &mut self, forwarder: Option<Arc<dyn SdkHookForwarder>>, )
Install a Claude SDK hook forwarder on the registry’s
[ExternalHookRunner] (control-channel hook_callback).
Sourcepub fn plan_approval_gate(&self) -> Arc<PlanApprovalGate> ⓘ
pub fn plan_approval_gate(&self) -> Arc<PlanApprovalGate> ⓘ
Return a shared reference to the plan-approval gate.
Callers (e.g. HTTP handlers) that need to inspect pending_plan or
call approve/reject without holding the runtime Mutex can clone
this Arc and operate on the gate directly.
Sourcepub fn plan_mode_request_gate(&self) -> Arc<PlanModeRequestGate> ⓘ
pub fn plan_mode_request_gate(&self) -> Arc<PlanModeRequestGate> ⓘ
Return a shared reference to the plan-mode-request gate (Goal-202).
The TUI backend’s run_turn_select_loop clones this arc so it can
forward ApprovePlanMode / RejectPlanMode user-actions to the gate
while the runtime is executing inside a spawned task.
Sourcepub fn confirm_plan(&mut self)
pub fn confirm_plan(&mut self)
Confirm the pending plan, allowing execution to proceed.
Wakes exit_plan_mode’s blocking wait via the Plan Mode 2.0 gate.
Sourcepub async fn compact_now(&mut self) -> Result<()>
pub async fn compact_now(&mut self) -> Result<()>
Force a compaction pass right now, regardless of the configured threshold. Useful for TUI / API surfaces that expose a manual “/compact” command.
No-op (returns Ok(())) when no compactor is configured or
when the transcript is too small to compact (fewer than
keep_recent_n + 2 messages).
Sourcepub async fn compact_partial_before(&mut self, pivot_index: usize) -> Result<()>
pub async fn compact_partial_before(&mut self, pivot_index: usize) -> Result<()>
Goal-342: partial compaction — summarise messages before a given
transcript index, keeping everything from pivot_index onward
(plus the compactor’s keep_recent_n safety margin) verbatim.
pivot_index is a transcript message index (0-based, same indexing
as transcript()). Uses Compactor::safe_split_point on the
sub-transcript [..=pivot_index] to find a safe split that never
breaks tool-call pairs (invariant #8).
No-op when no compactor is configured, pivot_index is out of
range, or the computed split is 0 (nothing to compact).
Sourcepub async fn compact_partial_after(&mut self, pivot_index: usize) -> Result<()>
pub async fn compact_partial_after(&mut self, pivot_index: usize) -> Result<()>
Goal-342: partial compaction — summarise messages after a given
transcript index, keeping everything before pivot_index verbatim.
pivot_index is a transcript message index (0-based). Backs up
from the pivot as needed to avoid splitting tool-call pairs
(invariant #8): any Tool or Assistant carrying tool_calls
at the boundary is included with its pair.
No-op when no compactor is configured or pivot_index is out of
range.
Sourcepub fn approve_plan_mode_request(&self)
pub fn approve_plan_mode_request(&self)
Goal-202: approve the plan-mode entry request.
Wakes RequestPlanModeTool’s blocking wait, returning {"approved": true}
to the LLM so it can proceed with enter_plan_mode.
Sourcepub fn reject_plan_mode_request(&self, reason: &str)
pub fn reject_plan_mode_request(&self, reason: &str)
Goal-202: reject the plan-mode entry request with a reason.
Wakes RequestPlanModeTool’s blocking wait, returning
{"approved": false, "reason": "..."} so the LLM can execute directly.
Sourcepub fn reject_plan(&mut self, reason: &str)
pub fn reject_plan(&mut self, reason: &str)
Reject the pending plan with a reason.
Injects a user message into the transcript and wakes exit_plan_mode’s
blocking wait (Plan Mode 2.0 gate) with the rejection reason.
Sourcepub fn current_goal(&self) -> Option<GoalState>
pub fn current_goal(&self) -> Option<GoalState>
Return a clone of the current goal state (or None).
Sourcepub async fn set_goal(&self, condition: String, max_turns: u32)
pub async fn set_goal(&self, condition: String, max_turns: u32)
Set a new active goal. Emits AgentEvent::GoalSet via the event sink.
Sourcepub async fn clear_goal(&self)
pub async fn clear_goal(&self)
Clear the active goal. Emits AgentEvent::GoalCleared.
Sourcepub async fn run_goal_loop(
&mut self,
initial_prompt: impl Into<String>,
condition: impl Into<String>,
max_turns: u32,
) -> Result<Vec<RuntimeOutcome>>
pub async fn run_goal_loop( &mut self, initial_prompt: impl Into<String>, condition: impl Into<String>, max_turns: u32, ) -> Result<Vec<RuntimeOutcome>>
Run a goal loop: execute turns until the judge says the condition is met, the turn budget is exhausted, or the goal is cleared externally.
Steps per iteration:
run(prompt)— execute one agent turn.- Increment
GoalState.turns. - If
turns >= max_turns→ emitGoalCleared(budget exceeded), break. - Call
GoalEvaluator::evaluate(condition, transcript_tail). - If
achieved→ emitGoalAchieved, break. - Else → emit
GoalContinuing { reason }, continue with auto-prompt.
Sourcepub async fn run_loop(
&mut self,
initial_goal: impl Into<String>,
wakeup_slot: &WakeupSlot,
) -> Result<Vec<RuntimeOutcome>>
pub async fn run_loop( &mut self, initial_goal: impl Into<String>, wakeup_slot: &WakeupSlot, ) -> Result<Vec<RuntimeOutcome>>
Run a loop: execute turns until the agent stops scheduling wakeups.
Between turns, sleeps for the requested delay. If the agent doesn’t
call schedule_wakeup during a turn, the loop ends.
The wakeup_slot should be the same slot registered with the
ScheduleWakeup tool in the agent’s tool registry.
Sourcepub async fn run_event_loop(
&mut self,
initial_goal: impl Into<String>,
wakeup_slot: &WakeupSlot,
bg_manager: Option<&Mutex<BackgroundJobManager>>,
) -> Result<Vec<RuntimeOutcome>>
pub async fn run_event_loop( &mut self, initial_goal: impl Into<String>, wakeup_slot: &WakeupSlot, bg_manager: Option<&Mutex<BackgroundJobManager>>, ) -> Result<Vec<RuntimeOutcome>>
Run a loop with background job awareness.
After each turn, checks both:
- The
WakeupSlotfor an explicit wakeup request - The
BackgroundJobManagerfor completed jobs
If a background job completed, its output is injected as the next turn’s goal. If a wakeup was scheduled, the runtime sleeps for the requested delay then continues. If neither is present, the loop ends.
Sourcepub fn enable_checkpoints(
&mut self,
shadow: Arc<ShadowRepo>,
session_id: impl Into<String>,
log_path: PathBuf,
touched_slot: Option<Arc<Mutex<TouchedFiles>>>,
) -> Result<()>
pub fn enable_checkpoints( &mut self, shadow: Arc<ShadowRepo>, session_id: impl Into<String>, log_path: PathBuf, touched_slot: Option<Arc<Mutex<TouchedFiles>>>, ) -> Result<()>
Bind this runtime to a checkpoint chain. With Goal 284,
automatic per-turn snapshots are removed. Checkpoints are
created only when the agent explicitly calls checkpoint_save.
Side effect: registers checkpoint_list, checkpoint_diff, and
checkpoint_save tools, scoped to this session, onto the kernel’s
tool registry.
Sourcepub fn checkpoints_enabled(&self) -> bool
pub fn checkpoints_enabled(&self) -> bool
Whether checkpoint snapshots are active.
Sourcepub fn turn_index(&self) -> usize
pub fn turn_index(&self) -> usize
Returns the 0-indexed counter that will be assigned to the next turn (i.e. the count of turns already executed).