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>,
pub tool_handles: Arc<ToolHandleRegistry>,
/* 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).
tool_handles: Arc<ToolHandleRegistry>Detached tool invocations (C2): a ToolCall with a
streaming/long_running invocation mode is registered here and
its handle returned as the action’s output while the DAG proceeds.
Chunks are drained via Runtime::tool_poll, cancelled via
Runtime::tool_cancel, and fanned out to
Runtime::subscribe_tool_events subscribers.
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 register_tool_deny_in_session(
&self,
session_id: &str,
name: &str,
tool: &str,
check: PolicyCheck,
description: &str,
) -> Result<(), String>
pub async fn register_tool_deny_in_session( &self, session_id: &str, name: &str, tool: &str, check: PolicyCheck, description: &str, ) -> Result<(), String>
Self::register_policy_in_session for a check that forbids tool
outright, so PolicyEngine::blanket_denied_tools can read it back.
Separate method rather than an extra parameter: the existing signature
is public and crosses the bindings, and this is purely additive.
Sourcepub async fn unregister_policy(
&self,
name: &str,
session_id: Option<&str>,
) -> Result<usize, String>
pub async fn unregister_policy( &self, name: &str, session_id: Option<&str>, ) -> Result<usize, String>
Remove a policy by name. session_id targets a session’s policy set;
None targets the global one.
Returns how many policies were dropped, or Err if session_id names a
session that doesn’t exist. Session-scoped policies could always be
dropped wholesale by Self::close_session, but a global policy had
no removal path at all — once registered it lived until the process
exited, so a mistyped or over-broad rule could only be cleared by
restarting the daemon (Parslee-ai/car#623).
Sourcepub async fn list_policies(
&self,
session_id: Option<&str>,
) -> Result<Vec<(String, String)>, String>
pub async fn list_policies( &self, session_id: Option<&str>, ) -> Result<Vec<(String, String)>, String>
Registered policies as (name, description). session_id lists a
session’s set; None lists the global one. Without this a client could
register a policy but never ask what was in force, so a rejection could
not be explained beyond its single message (Parslee-ai/car#623).
Sourcepub async fn load_project_policies(
&self,
car_dir: impl AsRef<Path>,
) -> Result<usize, PolicyLoadError>
pub async fn load_project_policies( &self, car_dir: impl AsRef<Path>, ) -> Result<usize, PolicyLoadError>
Load declarative deny rules from a project’s .car/policies/
directory and register them on the global policy engine (EPIC A /
task A2).
car_dir is a .car directory; this looks for
car_dir/policies/*.toml. The caller chooses the directory and
there is no walk-up here — this joins the path it is given, once.
The two production callers pick differently: the daemon passes
$HOME/.car, and the assistant passes its working directory’s
.car (--dir, else cwd). So a rule file at a repository root does
not govern a car do run started from a subdirectory. Say which
directory you mean at the call site rather than assuming discovery.
A missing directory is not an error (returns 0). A malformed rule file is an error — a dropped security rule must surface loudly. Returns the number of rules registered.
These rules are additive on top of any code-registered policies, and
every one of them is a prohibition — though allow_tool_param states
its prohibition as an allowlist, denying everything about its tool
that it does not name. Once A9 makes policy violations blocking at
admission, a matching action refuses the proposal.
Sourcepub async fn install_information_flow_gate(
&self,
car_dir: impl AsRef<Path>,
) -> Result<(), FlowLoadError>
pub async fn install_information_flow_gate( &self, car_dir: impl AsRef<Path>, ) -> Result<(), FlowLoadError>
Load information-flow tool labels from a project’s .car directory
and register the information-flow admission gate (EPIC A / A3+A4).
Reads car_dir/tool-labels.json (merged over built-in defaults) and
registers an crate::flow::InformationFlowGate so every admitted
proposal is checked for data exfiltration (blocked) and forbidden
tool orderings (escalated to approval). A missing labels file is
fine — the built-in defaults still mark the network tools as sinks.
A malformed file is a loud error.
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_message_sink(self, sink: Arc<dyn MessageSink>) -> Self
pub fn with_message_sink(self, sink: Arc<dyn MessageSink>) -> Self
Attach an outbound message sink, making messaging.send a real tool on
this runtime.
Registering the schema here — and ONLY here — is deliberate. A tool the runtime cannot execute must not be advertised to the model: without a sink the dispatch arm can only return an error, and a model that keeps seeing “message the human” in its tool list will keep trying to use it. So sink and schema arrive together, and neither exists alone.
The entry is AskUser with side effects: reaching a human is
irreversible, so the conservative default is the right one, and hosts
that have their own consent model can re-register with a different
permission. category = "messaging" groups it for the capability
surfaces that filter by category.
Follows Self::with_inference’s non-async registration pattern
(try_write during construction, while the locks are uncontended) —
making the builders async would break every Runtime::new().with_…()
chain in the workspace for no gain.
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.
Sourcepub fn trajectory_store(&self) -> Option<&Arc<TrajectoryStore>>
pub fn trajectory_store(&self) -> Option<&Arc<TrajectoryStore>>
The attached trajectory store, if any.
Writing traces was always the point of the store; reading them back is what makes them a feedback signal rather than an audit log.
Sourcepub fn tool_feedback(&self, window_days: u32) -> Option<ToolFeedback>
pub fn tool_feedback(&self, window_days: u32) -> Option<ToolFeedback>
Per-tool success rates observed over the last window_days, or None
when no trajectory store is attached.
Dispatch-conditional (see
ToolFeedback::dispatched_from_trajectories)
— rejected and skipped actions are excluded, because the tool never ran
and a consumer that models rejection separately would otherwise count
it twice.
The window exists because a success rate is a claim about how a tool behaves now. An API that was broken for a week six months ago and has been fixed since should not still be dragging its own rate down, and an unbounded history makes the rate progressively less responsive to exactly the recent change an operator is trying to see. 30 days is the suggested default at the call sites.
Reads from disk on every call — parsing is bounded by the window (day files outside it are skipped by filename, never opened), and the callers are interactive rather than hot-path. If that stops being true, cache here rather than at each call site.
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.
Sourcepub fn with_substrate(self, substrate: Arc<dyn Substrate>) -> Self
pub fn with_substrate(self, substrate: Arc<dyn Substrate>) -> Self
Bind the execution substrate the side-effecting built-in tools
(read_file/write_file/edit_file/list_dir/find_files/
grep_files) act within (builder). Defaults to
crate::substrate::LocalSubstrate; bind e.g.
crate::substrate::McpSubstrate to make those tools hit a VM.
calculate stays pure and ignores the substrate.
Sourcepub async fn set_substrate(&self, substrate: Arc<dyn Substrate>)
pub async fn set_substrate(&self, substrate: Arc<dyn Substrate>)
Set the execution substrate at runtime.
pub fn with_event_log(self, log: EventLog) -> Self
Bind an event log the caller already holds a handle to.
Self::with_event_log takes ownership, which leaves no way for
anything outside the runtime to read what was recorded. That was fine
while the log was observability-only, but a model-callable events
surface has to run inside a ToolExecutor — and the executor is
constructed before the runtime that would own it, so the two can only
meet through a handle created first and shared into both
(Parslee-ai/car#815).
Sourcepub fn event_log_handle(&self) -> Arc<TokioMutex<EventLog>> ⓘ
pub fn event_log_handle(&self) -> Arc<TokioMutex<EventLog>> ⓘ
The runtime’s event log handle, for callers that need to read the record the runtime is writing.
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 set_transaction_check_mode(&self, mode: TransactionCheckMode)
pub async fn set_transaction_check_mode(&self, mode: TransactionCheckMode)
Set the pre-execution transactional conflict-check mode (survey
§4.3/§5.2.4). Off (default) preserves prior behavior; Warn
records conflicts as telemetry; Strict rejects a conflicting
proposal before executing it.
Sourcepub async fn register_admission_gate(&self, gate: Arc<dyn AdmissionGate>)
pub async fn register_admission_gate(&self, gate: Arc<dyn AdmissionGate>)
Register a proposal-admission gate (EPIC A / task A1).
Gates run during admission, before any action executes, in the order they were registered. Each is a verified pre-execution safety check — information-flow (A4), concurrency (A5), blocking-policy (A9) — that can block a proposal or escalate it to human approval. Registering no gates leaves behavior unchanged.
Sourcepub async fn clear_admission_gates(&self)
pub async fn clear_admission_gates(&self)
Remove all registered admission gates (primarily for tests and reconfiguration). After this, proposal admission reverts to the transactional pre-check only.
Sourcepub async fn admission_gate_count(&self) -> usize
pub async fn admission_gate_count(&self) -> usize
The number of currently-registered admission gates.
Sourcepub async fn admission_gate_names(&self) -> Vec<String>
pub async fn admission_gate_names(&self) -> Vec<String>
The name() of every registered admission gate, in registration order.
Prefer this over Self::admission_gate_count when asserting that a
particular gate is installed: a bare count couples the assertion to
every other gate the runtime happens to register, so adding one breaks
unrelated tests that only cared about their own.
Sourcepub async fn install_intent_gate(&self, config: IntentGateConfig)
pub async fn install_intent_gate(&self, config: IntentGateConfig)
Register the VIGIL intent gate (arXiv 2601.05755 — the live
verify-before-commit call-site). Installs
crate::intent_gate::IntentGate as an admission gate: a
forbidden capability or a tool-stream-influenced out-of-intent
action hard-rejects the proposal; untainted drift escalates to
the durable approval flow (A7) by content-bound fingerprint.
Replans are covered automatically (gates re-run on every
replanned proposal).
This is also where the runtime’s crate::taint::TaintLedger is
installed. From here on, every successful action records whether its
result was tainted and which state keys it wrote, and the gate marks
any incoming action that READS a tainted key as tool-stream-
influenced. That closes the two holes the static untrusted_tools
list leaves open: a trusted tool laundering attacker-controlled
content out of state, and a replanned proposal that carries no
dependency edge back to the poisoned action. Installing no intent
gate installs no ledger, so nothing changes for a runtime that
doesn’t configure VIGIL.
Calling this again REPLACES the installed gate; it does not add a
second one. Reinstalling is a real operation — an operator editing
.car/intent.json and re-running
Runtime::install_intent_gate_from_project lands here — and each
call builds a fresh ledger that becomes the only one the executor
writes to. Appending would leave the previous gate registered while
holding an orphaned ledger, and because gate aggregation is
fail-closed that frozen gate’s verdict would still win: every key
tainted before the reload would stay tainted forever, no trusted
overwrite could clear it, and legitimate out-of-intent work would
hard-refuse with no approval path. So the runtime holds exactly one
intent gate, and it is always the one bound to the live ledger. The
replacement keeps the previous gate’s position in the admission
order, so a reload never silently reorders the other gates.
Sourcepub async fn taint_ledger(&self) -> Option<Arc<TaintLedger>>
pub async fn taint_ledger(&self) -> Option<Arc<TaintLedger>>
The runtime’s taint provenance ledger, when an intent gate is
installed (see Runtime::install_intent_gate). None otherwise —
the ledger is strictly opt-in, alongside VIGIL.
Sourcepub async fn install_intent_gate_from_project(
&self,
car_dir: impl AsRef<Path>,
) -> Result<bool, IntentLoadError>
pub async fn install_intent_gate_from_project( &self, car_dir: impl AsRef<Path>, ) -> Result<bool, IntentLoadError>
Load .car/intent.json from car_dir and install the intent
gate when present. Absent file → Ok(false) (opt-in, ungated);
malformed file → loud error, never a silently-ungated session.
Sourcepub async fn install_skill_ceiling_gate(&self) -> bool
pub async fn install_skill_ceiling_gate(&self) -> bool
Register the skill deployment-tier ceiling gate (EPIC A / A8).
Requires a memgine to be attached (skills live there). When a
proposal names its driving skill in context["skill"], the gate
caps the proposal’s actions at that skill’s persisted
deployment_tier, escalating an over-ceiling action to the durable
approval flow (A7). Returns false if no memgine is attached.
Sourcepub async fn tool_poll(&self, handle_id: &str) -> Option<ToolPollResult>
pub async fn tool_poll(&self, handle_id: &str) -> Option<ToolPollResult>
Drain buffered chunks + status for a detached tool invocation (C2).
None for an unknown or fully-consumed handle. See
crate::tool_handles::ToolHandleRegistry::poll for the
consume-on-terminal contract.
Sourcepub async fn tool_cancel(&self, handle_id: &str) -> bool
pub async fn tool_cancel(&self, handle_id: &str) -> bool
Request cancellation of a detached tool invocation (C2): fires the
handle’s cancel token (dropping the executor’s chunk receiver) and
seals its status as cancelled unless already terminal. Returns
false for an unknown handle.
Sourcepub fn subscribe_tool_events(&self) -> Receiver<ToolStreamEvent>
pub fn subscribe_tool_events(&self) -> Receiver<ToolStreamEvent>
Subscribe to the live car_ir::ToolStreamEvent fanout for all
detached tool invocations on this runtime (C2). The WS layer
forwards these as tools.stream.event notifications.
Sourcepub async fn enable_event_log_hash_chaining(&self)
pub async fn enable_event_log_hash_chaining(&self)
Enable tamper-evident hash chaining on the event log (EPIC A / A9).
Every event appended from now on is linked to its predecessor by a
content hash, so an after-the-fact edit to a chained event — or an
interior deletion/reordering — is detectable via
Runtime::verify_event_log_chain. Truncation at either end of the
log (dropping a prefix or suffix wholesale) is NOT detectable — the
chain has no anchored head hash and no trusted tail witness; that is
out of scope until the chain head is anchored. Opt-in: existing logs
stay byte-identical until enabled.
Sourcepub async fn verify_event_log_chain(&self) -> Result<usize, usize>
pub async fn verify_event_log_chain(&self) -> Result<usize, usize>
Verify the event log’s tamper-evidence chain (EPIC A / A9). Returns
Ok(n) for n verified chained events, or Err(index) naming the
first event whose hash/linkage doesn’t match — the point of an
interior edit, deletion, or reordering. Head/tail truncation is not
detectable (no anchored head hash; the first chained event’s
prev_hash is taken on trust) — see EventLog::verify_chain.
Sourcepub async fn set_approval_ledger_path(
&self,
path: impl Into<PathBuf>,
) -> Result<()>
pub async fn set_approval_ledger_path( &self, path: impl Into<PathBuf>, ) -> Result<()>
Install a durable HITL approval ledger backed by a JSONL journal
(EPIC A / A7). Loads any existing decisions so approvals survive a
restart, then resolves future admission-gate NeedsApproval
verdicts against it. The canonical daemon path is
~/.car/approvals.jsonl.
Sourcepub async fn set_approval_ledger_in_memory(&self)
pub async fn set_approval_ledger_in_memory(&self)
Use an in-memory approval ledger (no persistence) — primarily for tests and ephemeral runtimes.
Sourcepub async fn approve_admission(
&self,
fingerprint: &str,
reviewer: &str,
reason: &str,
) -> Result<(), String>
pub async fn approve_admission( &self, fingerprint: &str, reviewer: &str, reason: &str, ) -> Result<(), String>
Record a human approval for an admission fingerprint (EPIC A / A7). A subsequently re-submitted proposal whose escalation matches this fingerprint is admitted without asking again.
Sourcepub async fn reject_admission(
&self,
fingerprint: &str,
reviewer: &str,
reason: &str,
) -> Result<(), String>
pub async fn reject_admission( &self, fingerprint: &str, reviewer: &str, reason: &str, ) -> Result<(), String>
Record a human rejection for an admission fingerprint (EPIC A / A7). A proposal whose escalation matches a rejected fingerprint is blocked outright.
Sourcepub async fn set_idempotency_cache_path(
&self,
path: impl Into<PathBuf>,
) -> Result<usize>
pub async fn set_idempotency_cache_path( &self, path: impl Into<PathBuf>, ) -> Result<usize>
Back the idempotency cache with a durable JSONL journal (EPIC A / C3).
Loads any existing entries into the in-memory cache, then persists
future idempotent results (and rollback invalidations, as
tombstones) to the journal. After a crash-restart, re-submitting a
completed idempotent action returns the cached result instead of
re-executing it — preventing duplicate external side effects. The
canonical daemon path is ~/.car/idempotency.jsonl. Returns the
number of live entries loaded.
Sourcepub async fn admission_decision(
&self,
fingerprint: &str,
) -> Option<ApprovalDecision>
pub async fn admission_decision( &self, fingerprint: &str, ) -> Option<ApprovalDecision>
Look up the current decision for an admission fingerprint, if any.
Sourcepub async fn verify_tool_receipts(
&self,
claims: &[ToolClaim],
proposal_id: Option<&str>,
) -> ReceiptReport
pub async fn verify_tool_receipts( &self, claims: &[ToolClaim], proposal_id: Option<&str>, ) -> ReceiptReport
Verify a model’s tool-use claims against the runtime’s own execution
receipts (EPIC A / A6 — arXiv 2603.10060). The runtime owns tool
execution and logs it, so it holds unforgeable ground truth: this
projects car_eventlog::tool_receipts::ToolReceipts from the event
log and cross-checks the supplied claims, catching a fabricated tool
reference, a misstated result count, or a false “found nothing”.
proposal_id scopes the cross-check window to a single proposal’s
events (pass the proposal whose response the claims came from) — a
claim is never judged against another run’s receipts. The check is
retention-coherent (review A6): when the log has trimmed events and
the window can’t be proven complete (unscoped, or the proposal’s
ProposalReceived marker — which precedes every receipt of that
proposal — was itself evicted), a claim without a receipt comes back
in ReceiptReport::ungroundable (“window evicted”) instead of being
mis-flagged fabricated_tool_reference.
Returns the car_eventlog::tool_receipts::ReceiptReport; when it is not grounded, a
ToolReceiptHallucination event is emitted so the caller’s
verdict→action loop (reject/flag the response) is auditable.
Deterministic, zero-inference. Claims arrive structured — CAR’s
thesis is that intent is structured IR, so a caller extracts claims
from the model’s tool_calls / IR rather than regexing prose.
Sourcepub async fn set_harness_config(&self, cfg: HarnessConfig)
pub async fn set_harness_config(&self, cfg: HarnessConfig)
Install a harness operating config — the live end of the Evolution
Agent loop (survey §3.5). After the meta-agent’s HarnessConfig::apply
produces a governed, regression-gated config, hand it here to take
effect: max_retries/retry_backoff_ms drive the per-action retry
loop, and planning_max_replans is mapped onto the replan budget.
Every HarnessConfig knob is consumed here — none is aspirational.
Sourcepub async fn harness_config(&self) -> Option<HarnessConfig>
pub async fn harness_config(&self) -> Option<HarnessConfig>
The current harness operating config, if one has been installed.
Sourcepub async fn update_harness_config<R>(
&self,
f: impl FnOnce(&mut HarnessConfig) -> R,
) -> R
pub async fn update_harness_config<R>( &self, f: impl FnOnce(&mut HarnessConfig) -> R, ) -> R
Atomically read-modify-write the harness operating config under ONE
write lock (installing the default first when none is set). The
get→mutate→set alternative is a lost-update race when two requests
mutate concurrently — the daemon’s evolution.run harness-apply path
uses this instead (kernel review S3). Keeps the same replan-budget
propagation as Self::set_harness_config.
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 unregister_tool(&self, name: &str) -> bool
pub async fn unregister_tool(&self, name: &str) -> bool
Remove a tool from both the canonical registry and the legacy
tools schema map, so the model no longer sees it and the
validator no longer accepts it. Used when a remote MCP connector
tool is disabled or its connector is removed. Returns true if the
tool was present in either store.
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_session_and_stable_replan_id(
&self,
proposal: &ActionProposal,
session_id: &str,
) -> ProposalResult
pub async fn execute_with_session_and_stable_replan_id( &self, proposal: &ActionProposal, session_id: &str, ) -> ProposalResult
Execute an active-run proposal while requiring every accepted replan to retain the authenticated proposal id already claimed by the server.
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.
pub async fn execute_with_stable_replan_id( &self, proposal: &ActionProposal, ) -> ProposalResult
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.
pub async fn execute_scoped_with_stable_replan_id( &self, proposal: &ActionProposal, scope: &RuntimeScope, ) -> ProposalResult
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.
Source§impl Runtime
impl Runtime
Sourcepub async fn gather_goal_inputs(&self, gather: &GoalGather) -> GoalInputs
pub async fn gather_goal_inputs(&self, gather: &GoalGather) -> GoalInputs
Project a GoalInputs from this runtime’s ground truth plus the
caller-supplied checks. Reads receipts from the event log
(Runtime::verify_tool_receipts), the live state snapshot, and — when
a proposal is supplied — transactional consistency. Never fabricates a
signal: a check whose input wasn’t requested is left None, so the
pure evaluator fails it closed.
Sourcepub async fn record_goal_evaluated(
&self,
goal: &str,
condition: &GoalCondition,
iteration: u32,
met: bool,
grounded: bool,
reason: &str,
model: &str,
)
pub async fn record_goal_evaluated( &self, goal: &str, condition: &GoalCondition, iteration: u32, met: bool, grounded: bool, reason: &str, model: &str, )
Append a durable goal-verifier result to the runtime event log.
This is the audit counterpart to the live GoalEvaluated stream event:
hosts can miss a websocket frame, but the session journal still records
why the verifier continued or accepted the run. model/model_tier
attribute the verdict to the model that produced the turn, so an
ungrounded completion is queryable as “fails on local, passes on cloud”
— the same provenance record_turn_completed stamps on the default path.
Sourcepub async fn record_turn_completed(
&self,
decision: &str,
stop_reason: Option<&str>,
was_truncated: bool,
turns: u32,
model: &str,
)
pub async fn record_turn_completed( &self, decision: &str, stop_reason: Option<&str>, was_truncated: bool, turns: u32, model: &str, )
Append a durable completion-decision record for an assistant/coder turn
loop. The default (goal-less) loop declares success the instant the model
emits no tool calls, with no truncation or outcome check — so a truncated
or turn-capped run is indistinguishable from a real finish at the
terminal. Recording decision/stop_reason/was_truncated/turns makes
that decision a first-class, queryable event: the false-success and
never-finished signals live here on the ungrounded default path, which
emits no GoalEvaluated.
Trait Implementations§
Auto Trait Implementations§
impl !Freeze for Runtime
impl !RefUnwindSafe for Runtime
impl !UnwindSafe for Runtime
impl Send for Runtime
impl Sync for Runtime
impl Unpin for Runtime
impl UnsafeUnpin 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
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
impl<T> ErasedDestructor for Twhere
T: 'static,
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