Skip to main content

Action

Struct Action 

Source
#[non_exhaustive]
pub struct Action {
Show 18 fields pub id: String, pub action_type: ActionType, pub tool: Option<String>, pub parameters: HashMap<String, Value>, pub preconditions: Vec<Precondition>, pub expected_effects: HashMap<String, Value>, pub state_dependencies: Vec<String>, pub read_set: Vec<String>, pub write_set: Vec<String>, pub assumptions: Vec<StateAssumption>, pub invocation_mode: ToolInvocationMode, pub reversibility: Reversibility, pub compensation: Option<Compensation>, pub idempotent: bool, pub max_retries: u32, pub failure_behavior: FailureBehavior, pub timeout_ms: Option<u64>, pub metadata: HashMap<String, Value>,
}
Expand description

A single unit of agent intent compiled into IR.

This is the core primitive. Models produce these (directly or via compilation), and the runtime validates and executes them.

§Construction

#[non_exhaustive], so from outside car-ir this must be built with Action::new (or Action::tool_call / Action::state_write / Action::state_read) and then mutated — struct-literal syntax, including functional update (..other), is rejected in other crates.

That is the point. car-ir is published on crates.io and re-exported by car-runtime, so while the wire format tolerates new fields (they carry #[serde(default)], and a proposal authored before a field existed still deserializes), the Rust API previously did not: every added field was a source-breaking error[E0063] for every out-of-tree consumer building an Action literal, and cost ~20 files of churn in-tree the last time it happened. Adding a field is now backwards-compatible in both directions. Parslee-ai/car#855.

Fields (Non-exhaustive)§

This struct is marked as non-exhaustive
Non-exhaustive structs could have additional fields added in future. Therefore, non-exhaustive structs cannot be constructed in external crates using the traditional Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.
§id: String§action_type: ActionType§tool: Option<String>§parameters: HashMap<String, Value>§preconditions: Vec<Precondition>§expected_effects: HashMap<String, Value>§state_dependencies: Vec<String>§read_set: Vec<String>

Explicit transactional read set — the state keys this action reads (survey §5.2.4: “each action should declare its read set, write set, assumptions, version dependencies”). When empty, the effective read set falls back to Action::effective_read_set (derived from state_dependencies + assumption keys), so existing proposals keep working. Used to detect read-write conflicts and stale reads across concurrent actions/agents.

§write_set: Vec<String>

Explicit transactional write set — the state keys this action writes. When empty, falls back to Action::effective_write_set (derived from expected_effects + a StateWrite’s key param).

§assumptions: Vec<StateAssumption>

Assumptions this action makes about shared state it did not itself produce — the basis for belief-divergence / stale-read detection in a multi-agent transaction (§5.2.4). An assumption can pin an expected value, a version the action read at, or both.

§invocation_mode: ToolInvocationMode

How a ToolCall runs: one_shot (default — dispatch awaits the result inline), or a detached mode (streaming / long_running) where dispatch starts the tool, returns a crate::ToolHandle as the action’s output, and the DAG proceeds without blocking on completion. Chunks/status are consumed via the handle (C2). Ignored for non-ToolCall actions.

§reversibility: Reversibility

The rollback contract for this action’s effects — can this be undone? Orthogonal to car_policy::PermissionTier, which answers the separate question of who may authorize this; the two were conflated in a single ladder until this field existed. See Reversibility for the axis, and for why the #[serde(default)] is the conservative Reversibility::Irreversible rather than the quiet-failing Reversibility::Reversible.

Nothing in the runtime gates on this yet — it is typed and audited, not enforced (see the reversibility module docs).

§compensation: Option<Compensation>

How to undo this action once it has already run. Meaningful only when reversibility is Reversibility::Compensable: a reversible action is undone by restoring its scope, and an irreversible one cannot be undone at all.

The pairing is not enforced by the type system — Compensable is a bare variant, not Compensable { compensation }, so Compensable + None is representable. That is a deliberate trade, argued in Compensation’s docs: making the state unrepresentable costs Reversibility its plain C-like shape, which every FFI surface mirrors as a string enum. Action::missing_required_compensation is the check that stands in for the type.

§idempotent: bool§max_retries: u32§failure_behavior: FailureBehavior§timeout_ms: Option<u64>§metadata: HashMap<String, Value>

Implementations§

Source§

impl Action

Source

pub fn new(action_type: ActionType) -> Action

A new action of action_type, with every optional field at its default and a freshly generated Action::id.

This is the only way to build an Action from outside car-ir. The struct is #[non_exhaustive] (see its docs), so struct-literal syntax — including functional update, ..other — is rejected in other crates. Set the fields you need afterwards; they are all public:

let mut a = Action::new(ActionType::ToolCall);
a.tool = Some("deploy".into());
a.idempotent = true;

There is deliberately no Default for Action. An action’s type decides what the rest of it means, and defaulting it would let Action::default() produce a ToolCall naming no tool — a value that is invalid the moment it exists, and that the validator would have to reject. Requiring the type at construction makes that unrepresentable.

Source

pub fn tool_call(tool: impl Into<String>) -> Action

An ActionType::ToolCall naming tool.

Source

pub fn state_write(key: impl Into<String>, value: Value) -> Action

An ActionType::StateWrite of value to key, carrying both the key/value parameters the executor reads and the expected_effects entry the static verifier reads.

Source

pub fn state_read(key: impl Into<String>) -> Action

Source

pub fn with_id(self, id: impl Into<String>) -> Action

Replace the generated Action::id. Chainable, so a caller that cares about the id can still write one expression.

Source

pub fn with_param(self, key: impl Into<String>, value: Value) -> Action

Set one parameter. Chainable.

Source

pub fn effective_write_set(&self) -> Vec<String>

The keys this action writes — the union of the explicit write_set with the keys derived from expected_effects and a StateWrite action’s key parameter. Union, not replacement: a partial write_set must never narrow the real write footprint, or a conflict on an unlisted side-effect key would go undetected (a false negative in the soundness-critical path). Proposals authored before the transactional fields existed still participate via the derived keys.

Source

pub fn effective_read_set(&self) -> Vec<String>

The keys this action reads — the union of the explicit read_set with state_dependencies, assumption keys, and a StateRead action’s key parameter. Union for the same soundness reason as Action::effective_write_set.

Source

pub fn missing_required_compensation(&self) -> bool

Whether this action claims Reversibility::Compensable but declares no Action::compensation — the one incoherent combination the enum could not exclude by construction (see Compensation for why the compensation is a sibling field rather than a variant payload).

It is a check, not a guarantee: it catches the missing declaration, not a declaration that names a tool which cannot actually reverse the effect. Nothing here can establish that a compensating call is a true inverse — that remains the author’s claim, exactly as expected_effects is.

Trait Implementations§

Source§

impl Clone for Action

Source§

fn clone(&self) -> Action

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Action

Source§

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

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for Action

Source§

fn deserialize<__D>( __deserializer: __D, ) -> Result<Action, <__D as Deserializer<'de>>::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl PartialEq for Action

Source§

fn eq(&self, other: &Action) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl Serialize for Action

Source§

fn serialize<__S>( &self, __serializer: __S, ) -> Result<<__S as Serializer>::Ok, <__S as Serializer>::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl StructuralPartialEq for Action

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<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> ErasedDestructor for T
where T: 'static,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

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> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
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> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
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<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

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