pub struct Runtime {
pub state: Arc<StateStore>,
pub tools: Arc<RwLock<HashMap<String, ToolSchema>>>,
pub policies: Arc<RwLock<PolicyEngine>>,
pub session_policies: Arc<RwLock<HashMap<String, Arc<RwLock<PolicyEngine>>>>>,
pub log: Arc<Mutex<EventLog>>,
pub rate_limiter: Arc<RateLimiter>,
pub result_cache: Arc<ResultCache>,
pub registry: Arc<ToolRegistry>,
/* private fields */
}Expand description
Common Agent Runtime — deterministic execution layer.
Lock ordering discipline (never hold multiple simultaneously, never hold sync locks across .await):
- capabilities (RwLock, read-only during execution)
- tools (RwLock, read-only during execution)
- policies (RwLock, read-only during execution)
- session_policies (RwLock to find the per-session engine, then read inner Arc)
- cost_budget (RwLock, read-only during execution)
- log (TokioMutex, acquired/released per event)
- tool_executor (TokioMutex, clone Arc and drop before await)
- idempotency_cache (TokioMutex, acquired/released per check)
StateStore uses parking_lot::Mutex (sync) — NEVER hold across .await points.
Fields§
§state: Arc<StateStore>§tools: Arc<RwLock<HashMap<String, ToolSchema>>>§policies: Arc<RwLock<PolicyEngine>>§session_policies: Arc<RwLock<HashMap<String, Arc<RwLock<PolicyEngine>>>>>Per-session policy registries. Hosts that multiplex multiple
concurrent agent sessions over a single Runtime (IDE-style
frontends with per-project rules, multi-tenant servers) call
Runtime::open_session to mint an id, then
Runtime::register_policy_in_session to attach session-scoped
rules. Validation under that session walks the global registry
AND the session’s — both must pass. Sessions can deny what
global allows; sessions cannot allow what global denies.
Closing a session drops its registry and any closures it holds.
See docs/proposals/per-session-policy-scoping.md.
log: Arc<Mutex<EventLog>>§rate_limiter: Arc<RateLimiter>§result_cache: Arc<ResultCache>§registry: Arc<ToolRegistry>Canonical tool registry (optional — new code should use this).
Implementations§
Source§impl Runtime
impl Runtime
pub fn new() -> Self
Create a runtime with shared state, event log, and policies. Each runtime gets its own tool set, executor, and idempotency cache.
Sourcepub async fn open_session(&self) -> String
pub async fn open_session(&self) -> String
Mint a new session id and pre-register an empty policy engine
under it. Hosts call this once per concurrent agent context
(an IDE project window, a multi-tenant client, etc.) and pair
it with Self::close_session when the context ends.
Returns the opaque id to pass to subsequent
Self::register_policy_in_session / Self::execute_with_session
calls. Ids are UUIDs so collisions across concurrent calls
don’t matter.
Sourcepub async fn close_session(&self, session_id: &str) -> bool
pub async fn close_session(&self, session_id: &str) -> bool
Drop the session and every policy scoped to it. Returns true if a session by that id existed; false if it didn’t (already closed, never opened, etc.). Idempotent in effect — closing a missing session is a no-op the caller is free to ignore.
Sourcepub async fn register_policy_in_session(
&self,
session_id: &str,
name: &str,
check: PolicyCheck,
description: &str,
) -> Result<(), String>
pub async fn register_policy_in_session( &self, session_id: &str, name: &str, check: PolicyCheck, description: &str, ) -> Result<(), String>
Register a policy under a specific session id. The policy applies only when a proposal is executed under that session; proposals executed without a session (the default) only see global policies.
Returns Err(...) if the session is unknown — callers either
forgot to call Self::open_session or are using a
stale/closed id.
Sourcepub async fn session_exists(&self, session_id: &str) -> bool
pub async fn session_exists(&self, session_id: &str) -> bool
True if a session with this id is currently open. Mostly for tests and FFI surface validation — production code should trust the id it just opened.
Sourcepub fn with_inference(self, engine: Arc<InferenceEngine>) -> Self
pub fn with_inference(self, engine: Arc<InferenceEngine>) -> Self
Attach a local inference engine. Registers infer, embed, classify
as built-in tools with real implementations.
Sourcepub fn with_learning(
self,
memgine: Arc<TokioMutex<MemgineEngine>>,
auto_distill: bool,
) -> Self
pub fn with_learning( self, memgine: Arc<TokioMutex<MemgineEngine>>, auto_distill: bool, ) -> Self
Attach a memgine for automatic skill learning after execution.
When auto_distill is true, execution traces are automatically distilled
into skills and domains are evolved when underperforming.
Sourcepub fn with_memgine(self, memgine: Arc<TokioMutex<MemgineEngine>>) -> Self
pub fn with_memgine(self, memgine: Arc<TokioMutex<MemgineEngine>>) -> Self
Attach a memgine with auto-distillation enabled (recommended default).
Sourcepub fn with_trajectory_store(self, store: Arc<TrajectoryStore>) -> Self
pub fn with_trajectory_store(self, store: Arc<TrajectoryStore>) -> Self
Attach a trajectory store for persisting execution traces.
pub fn with_executor(self, executor: Arc<dyn ToolExecutor>) -> Self
Sourcepub async fn set_executor(&self, executor: Arc<dyn ToolExecutor>)
pub async fn set_executor(&self, executor: Arc<dyn ToolExecutor>)
Set a tool executor for the next execute() call. Used by NAPI bindings where executor varies per call.
pub fn with_event_log(self, log: EventLog) -> Self
Sourcepub fn with_replan(
self,
callback: Arc<dyn ReplanCallback>,
config: ReplanConfig,
) -> Self
pub fn with_replan( self, callback: Arc<dyn ReplanCallback>, config: ReplanConfig, ) -> Self
Attach a replan callback for failure recovery (builder).
Sourcepub async fn set_replan_callback(&self, callback: Arc<dyn ReplanCallback>)
pub async fn set_replan_callback(&self, callback: Arc<dyn ReplanCallback>)
Set a replan callback at runtime.
Sourcepub async fn set_replan_config(&self, config: ReplanConfig)
pub async fn set_replan_config(&self, config: ReplanConfig)
Set replan configuration at runtime.
Sourcepub async fn register_tool(&self, name: &str)
pub async fn register_tool(&self, name: &str)
Register a tool with just a name (backward compatible).
Sourcepub async fn register_tool_schema(&self, schema: ToolSchema)
pub async fn register_tool_schema(&self, schema: ToolSchema)
Register a tool with full schema.
Sourcepub async fn register_tool_entry(&self, entry: ToolEntry)
pub async fn register_tool_entry(&self, entry: ToolEntry)
Register a tool via the canonical registry. This is the preferred way to register tools — it updates both the registry and the legacy tools HashMap for backward compatibility.
Sourcepub async fn register_agent_basics(&self)
pub async fn register_agent_basics(&self)
Register CAR’s built-in agent utility stdlib.
This is an opt-in convenience layer for common local-file and text tools. Existing runtimes remain unchanged until this is called.
Sourcepub async fn tool_schemas(&self) -> Vec<ToolSchema>
pub async fn tool_schemas(&self) -> Vec<ToolSchema>
Get all registered tool schemas (for model prompt generation).
Sourcepub async fn set_cost_budget(&self, budget: CostBudget)
pub async fn set_cost_budget(&self, budget: CostBudget)
Set a cost budget that limits proposal execution.
Sourcepub async fn set_capabilities(&self, caps: CapabilitySet)
pub async fn set_capabilities(&self, caps: CapabilitySet)
Set per-agent capability permissions that restrict tools, state keys, and action count.
Sourcepub async fn set_rate_limit(
&self,
tool: &str,
max_calls: u32,
interval_secs: f64,
)
pub async fn set_rate_limit( &self, tool: &str, max_calls: u32, interval_secs: f64, )
Set a per-tool rate limit (token bucket).
max_calls tokens are available per interval_secs window.
When the bucket is empty, dispatch() applies backpressure by
waiting until a token refills.
Sourcepub async fn enable_tool_cache(&self, tool: &str, ttl_secs: u64)
pub async fn enable_tool_cache(&self, tool: &str, ttl_secs: u64)
Enable cross-proposal result caching for a tool with a TTL in seconds.
Sourcepub async fn execute(&self, proposal: &ActionProposal) -> ProposalResult
pub async fn execute(&self, proposal: &ActionProposal) -> ProposalResult
Execute a proposal with automatic replanning on failure.
If a ReplanCallback is registered and max_replans > 0, the runtime
will catch abort failures, roll back state, ask the model for an
alternative proposal via the callback, and re-execute. This transforms
“execute-and-hope” into “execute-and-recover.”
If no callback is registered or max_replans == 0, behaves identically
to a single execute_inner() call (zero overhead, fully backward compatible).
Sourcepub async fn execute_with_session(
&self,
proposal: &ActionProposal,
session_id: &str,
) -> ProposalResult
pub async fn execute_with_session( &self, proposal: &ActionProposal, session_id: &str, ) -> ProposalResult
Execute a proposal scoped to a specific session id.
Validation walks the global policy registry plus the session’s own registry — both must pass for an action to run. Session policies can deny what global allows; they cannot allow what global denies (validation is conjunctive).
Returns the same ProposalResult shape as Self::execute.
Errors with an action-level rejection if the session id is
unknown — callers should check via Self::session_exists or
trust an id they minted via Self::open_session.
Sourcepub async fn execute_with_session_and_cancel(
&self,
proposal: &ActionProposal,
session_id: &str,
cancel: &CancellationToken,
) -> ProposalResult
pub async fn execute_with_session_and_cancel( &self, proposal: &ActionProposal, session_id: &str, cancel: &CancellationToken, ) -> ProposalResult
Combined session-scoped + cancellable execute. The session id
is passed verbatim to the per-action policy check; the cancel
token behaves identically to Self::execute_with_cancel.
Sourcepub async fn execute_with_cancel(
&self,
proposal: &ActionProposal,
cancel: &CancellationToken,
) -> ProposalResult
pub async fn execute_with_cancel( &self, proposal: &ActionProposal, cancel: &CancellationToken, ) -> ProposalResult
Execute a proposal with cooperative cancellation.
The runtime checks token.is_cancelled() at each DAG level
boundary. When set, every action that hadn’t yet started runs
is reported as Skipped with error = "canceled: ..." so
callers can distinguish “user pulled the plug” from “earlier
abort cascaded.” Actions already in flight continue to
completion — tool calls dispatched to user-provided executors
can’t be safely interrupted from the engine.
The CAR A2A bridge uses this so tasks/cancel produces a
ProposalResult with clean partial state rather than relying
on JoinHandle::abort to interrupt mid-await (which leaves
no record of which actions actually ran).
FFI exposure: this method is intentionally not surfaced
through the NAPI / PyO3 / car-server-core JSON-RPC bindings.
Those consumers (Node, Python, WebSocket) don’t currently
expose long-running async-task surfaces that need
cancellation; the bridge is the lone consumer. When a binding
gains a long-running task surface, the path is clear: add a
per-binding token registry keyed by some caller-provided id,
expose cancelExecution(id) / cancel_execution(id) /
proposal.cancel { id }, and have the runtime call
execute_with_cancel with the matching token. Skipping that
today avoids speculative API surface that bloats bindings
without a consumer.
Sourcepub async fn execute_scoped(
&self,
proposal: &ActionProposal,
scope: &RuntimeScope,
) -> ProposalResult
pub async fn execute_scoped( &self, proposal: &ActionProposal, scope: &RuntimeScope, ) -> ProposalResult
Execute a proposal with an attached [RuntimeScope]
(Parslee-ai/car#187 phase 3).
Same contract as Self::execute plus a per-execution
identity surface — typically built by the car-a2a dispatcher
from the verified Identity and cooperative a2a_caller
metadata on the inbound ActionProposal. The scope is
recorded on the event log so downstream audit / log analysis
can see which caller / tenant issued each action.
What this enforces today: scope is captured + logged.
Memgine queries and state-store ops still hit global
namespaces — those follow-ups are tracked under #187.
Tool / policy code that needs per-tenant behaviour right now
should keep reading proposal.context["a2a_caller_verified"]
directly (the phase 1 / 2 surface).
Sourcepub async fn execute_scoped_with_cancel(
&self,
proposal: &ActionProposal,
scope: &RuntimeScope,
cancel: &CancellationToken,
) -> ProposalResult
pub async fn execute_scoped_with_cancel( &self, proposal: &ActionProposal, scope: &RuntimeScope, cancel: &CancellationToken, ) -> ProposalResult
Combined scoped + cancellable execute. Mirrors the shape of
Self::execute_with_session_and_cancel for symmetry — both
add a side-channel (session id / scope) on top of the
cancellable form.
Sourcepub async fn plan_and_execute(
&self,
candidates: &[ActionProposal],
planner_config: Option<PlannerConfig>,
feedback: Option<&ToolFeedback>,
) -> ProposalResult
pub async fn plan_and_execute( &self, candidates: &[ActionProposal], planner_config: Option<PlannerConfig>, feedback: Option<&ToolFeedback>, ) -> ProposalResult
Score N candidate proposals, execute the best valid one, fall back to next-best on failure. Combines car-planner scoring with engine execution.
Returns the result from whichever proposal was executed (best or fallback). If all candidates fail verification, returns an error result for the first.
Sourcepub async fn save_checkpoint(&self) -> Checkpoint
pub async fn save_checkpoint(&self) -> Checkpoint
Save a checkpoint of the current runtime state.
Sourcepub async fn save_checkpoint_to_file(&self, path: &str) -> Result<(), String>
pub async fn save_checkpoint_to_file(&self, path: &str) -> Result<(), String>
Save checkpoint to a JSON file.
Sourcepub async fn load_checkpoint_from_file(
&self,
path: &str,
) -> Result<Checkpoint, String>
pub async fn load_checkpoint_from_file( &self, path: &str, ) -> Result<Checkpoint, String>
Load a checkpoint from a JSON file and restore state.
Sourcepub async fn restore_checkpoint(&self, checkpoint: &Checkpoint)
pub async fn restore_checkpoint(&self, checkpoint: &Checkpoint)
Restore runtime state from a checkpoint.
Sourcepub async fn register_subprocess_tool(&self, name: &str, tool: SubprocessTool)
pub async fn register_subprocess_tool(&self, name: &str, tool: SubprocessTool)
Register a subprocess tool and set up the subprocess executor. If no executor exists, creates a new SubprocessToolExecutor. If one already exists, creates a new SubprocessToolExecutor with the existing executor as fallback.
Trait Implementations§
Auto Trait Implementations§
impl !Freeze for Runtime
impl !RefUnwindSafe for Runtime
impl Send for Runtime
impl Sync for Runtime
impl Unpin for Runtime
impl UnsafeUnpin for Runtime
impl !UnwindSafe for Runtime
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more