Skip to main content

AgentRuntime

Struct AgentRuntime 

Source
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

Source

pub fn builder() -> AgentRuntimeBuilder

Create a new AgentRuntimeBuilder.

Source

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.

Source

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.

Source

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).

Source

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.

Source

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”).

Source

pub fn transcript(&self) -> &[Message]

Return a reference to the accumulated transcript.

Source

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.

Source

pub fn set_transcript(&mut self, transcript: Vec<Message>)

Replace the current transcript (useful for restoring from a saved session).

Source

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.

Source

pub fn kernel(&self) -> &AgentKernel

Return a reference to the inner kernel.

Source

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).

Source

pub fn event_sink(&self) -> &dyn EventSink

Return the event sink currently in use.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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).

Source

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.

Source

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.

Source

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.

Source

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).

Source

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).

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn current_goal(&self) -> Option<GoalState>

Return a clone of the current goal state (or None).

Source

pub async fn set_goal(&self, condition: String, max_turns: u32)

Set a new active goal. Emits AgentEvent::GoalSet via the event sink.

Source

pub async fn clear_goal(&self)

Clear the active goal. Emits AgentEvent::GoalCleared.

Source

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:

  1. run(prompt) — execute one agent turn.
  2. Increment GoalState.turns.
  3. If turns >= max_turns → emit GoalCleared (budget exceeded), break.
  4. Call GoalEvaluator::evaluate(condition, transcript_tail).
  5. If achieved → emit GoalAchieved, break.
  6. Else → emit GoalContinuing { reason }, continue with auto-prompt.
Source

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.

Source

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:

  1. The WakeupSlot for an explicit wakeup request
  2. The BackgroundJobManager for 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.

Source

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.

Source

pub fn checkpoints_enabled(&self) -> bool

Whether checkpoint snapshots are active.

Source

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).

Trait Implementations§

Source§

impl Debug for AgentRuntime

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<A, B, T> HttpServerConnExec<A, B> for T
where B: Body,

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoMaybeUndefined<T> for T

Source§

fn into_maybe_undefined(self) -> MaybeUndefined<T>

Converts this value into a three-state builder argument.
Source§

impl<T> IntoOption<T> for T

Source§

fn into_option(self) -> Option<T>

Converts this value into an optional builder argument.
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more