Skip to main content

Effect

Trait Effect 

Source
pub trait Effect: Send + Sync {
    type Output: Serialize + DeserializeOwned + Send;

Show 16 methods // Required methods fn descriptor(&self) -> EffectDescriptor; fn perform<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<Self::Output, EffectError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; // Provided methods fn attach(&mut self, _provenance: &Provenance) { ... } fn gen_ai_operation(&self) -> Option<&'static str> { ... } fn mutates(&self) -> bool { ... } fn recovery(&self) -> Recovery { ... } fn retry(&self) -> RetryPolicy { ... } fn max_sensitivity(&self) -> Sensitivity { ... } fn sink_arguments(&self) -> Option<&Value> { ... } fn protected_fields(&self) -> &[ProtectedField] { ... } fn delegation_depth(&self) -> Option<usize> { ... } fn source(&self) -> SourceId { ... } fn trust(&self) -> Trust { ... } fn output_sensitivity(&self) -> Sensitivity { ... } fn spend(&self, _output: &Self::Output) -> Spend { ... } fn reconcile<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<Reconciliation<Self::Output>, EffectError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait { ... }
}
Expand description

Anything non-deterministic or externally visible.

Implemented by drivers (clock, RNG, MCP, A2A, model, timer) — never by skill authors, who reach effects through StepCtx.

Required Associated Types§

Source

type Output: Serialize + DeserializeOwned + Send

What comes back. Must round-trip through JSON: replay reconstructs it from the journal rather than from the driver.

Required Methods§

Source

fn descriptor(&self) -> EffectDescriptor

What this effect does. Hashed (with position) into the effect key.

Source

fn perform<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<Self::Output, EffectError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Do the thing. Called at most once per key per run; never called during replay.

Provided Methods§

Source

fn attach(&mut self, _provenance: &Provenance)

Receive the run-scoped provenance for this call.

Defaulted to a no-op, because most effects send nothing outward that a callee could check. The ones that do — a tool call, a peer call — store it and put it on the wire.

It arrives here rather than at construction because the EffectKey is part of it, and an effect has no business knowing its own key: the key includes the effect’s position in the run, which only the runtime knows. That is the same reasoning that turned Effect::key() into Effect::descriptor(), and it is why this is a hook rather than a constructor argument.

Source

fn gen_ai_operation(&self) -> Option<&'static str>

Which OpenTelemetry GenAI operation this effect is, if it is one.

Returned as the value of gen_ai.operation.name — execute_tool for a tool call, chat for a completion. Observability tooling keys on that attribute, so an effect that does not answer here is invisible as an agent operation even though its span is emitted: a trace shows the agent invocation and nothing about the calls inside it.

Defaulted to None, which is the honest answer for the effects that are not GenAI operations at all — reading the clock, sleeping, writing case state. Labelling those would make the convention meaningless.

The conventions are still pre-1.0, which is why the version this targets is pinned in telemetry::SEMCONV_VERSION rather than tracked.

Source

fn mutates(&self) -> bool

Whether this mutates external state.

Drives the recovery default and the policy engine’s resource.mutates attribute. Defaults to true: an effect that forgets to declare itself is treated as dangerous.

Source

fn recovery(&self) -> Recovery

What to do when the outcome is unknown — after a crash, or after an InDoubt failure. The two are the same situation reached from different directions.

Source

fn retry(&self) -> RetryPolicy

How many times to repeat this effect when it fails, and how far apart.

The policy is not the safety control — Recovery and the failure’s Disposition are, and they are consulted first. Raising max_attempts cannot make a mutating in-doubt call retryable; it only governs failures that are already safe to repeat.

Defaults to RetryPolicy::default — three attempts with exponential backoff. That default is safe for a mutating effect precisely because the disposition gate stands in front of it.

Source

fn max_sensitivity(&self) -> Sensitivity

The highest data sensitivity this sink may receive.

The runtime refuses to pass arguments above this ceiling, which is the control for the exfiltration path that actually matters: a legitimate-looking call carrying a secret read three steps earlier.

Source

fn sink_arguments(&self) -> Option<&Value>

The exact value this effect will send to its sink.

StepCtx::sink compares this value to the labeled value it checks. Returning None means the effect has not bound its outbound arguments and is therefore refused by sink. Without this binding a caller could present a harmless trusted value to the gate while the effect sent unrelated attacker-controlled arguments.

Source

fn protected_fields(&self) -> &[ProtectedField]

Field-specific source and sensitivity rules for a structured sink.

Declared selectors are enforced for every effect: a read-only URL, tenant, model, path, or query can still carry authority. A mutating effect with no protected fields additionally receives the conservative whole-object taint gate. Once fields are declared, those selectors carry the stricter trust/source checks while ordinary content may remain untrusted. This avoids broad whole-value releases merely to preserve a trusted recipient or amount beside untrusted descriptive text.

Source

fn delegation_depth(&self) -> Option<usize>

The resulting delegation depth when this effect hands authority onward.

None means the effect does not delegate. A peer call returns the depth of the attenuated chain it will put on the wire, allowing a manifest to enforce a ceiling at the last boundary before dispatch without teaching core about any particular peer protocol.

Source

fn source(&self) -> SourceId

The provenance identity an untrusted result of this effect carries.

This is the name a ProtectedField::from_sources rule matches, so its precision decides what a source rule can say. The default — effect:{kind} — names only the effect family, and a family is too coarse for the rule that matters: “the recipient must come from the CRM lookup” is unsatisfiable when every granted tool answers as effect:tool.call, because the rule then admits whichever tool an injected prompt reached first.

So the effects whose outputs feed authority-bearing fields override this with the identity an operator actually grants:

  • a tool call → its reference, tool://server/name;
  • a commission → agent/{capability};
  • a model completion → model:{provider}/{model}.

The default stays for effects nobody writes source rules about — the clock, a case read — where a finer name would be precision without a consumer.

Source

fn trust(&self) -> Trust

How much this effect’s output may be trusted.

Defaults to Trust::Untrusted, and the direction of that default is the point. An effect is how the deterministic zone reaches the outside world, so its result is the outside world’s data — a tool response, a peer’s answer, a model completion. Those are the three most important untrusted inputs an agent runtime handles, and the whole architecture rests on them being labelled at the source rather than remembered about later.

Getting this wrong in the safe direction produces spurious taint, which is an annoyance that shows up immediately as a refused sink. Getting it wrong the other way is a prompt injection reaching a mutating tool, which shows up as a wire transfer. So the effect that forgets to declare anything gets the conservative answer — the same rule Effect::recovery follows.

Declare Trust::Trusted only for effects that do not cross a trust boundary: the runtime’s own journaled clock, a seeded RNG, a durable timer. tests/guards/layering.rs requires each one to be named, so a fourth has to argue for itself.

Source

fn output_sensitivity(&self) -> Sensitivity

How sensitive this effect’s output is, at minimum.

The runtime takes the maximum of this and whatever the trust level implies — an untrusted result is already Internal. So this can raise sensitivity and never lower it, which is the only safe direction: an effect that could declare its output less sensitive than its provenance implies would be a laundering primitive with a polite name.

Declare it for effects that return things worth protecting: a vault read, a model completion over customer records, a peer’s answer about a named person. The egress ceiling on the next sink is what then does the work.

Source

fn spend(&self, _output: &Self::Output) -> Spend

What this effect consumed.

Reported after the fact because only then is it known, and journaled in the EffectDone record so replay adds up the same figures. Asking a provider what something cost at replay time would give a moving answer, and the budget verdict would move with it.

Source

fn reconcile<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<Reconciliation<Self::Output>, EffectError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Ask the provider whether a call landed.

Called only when recovery is Recovery::Reconcile and the outcome is genuinely unknown — after a crash between “sent” and “recorded”, or after an InDoubt failure. The two are the same situation, and this is the only thing that resolves either without guessing.

The probe must identify the call by something stable across attempts — an idempotency key, a client reference, an order id carried in the request. A probe that searches by timestamp or by “most recent” is not a probe; it is a guess with extra steps.

The result is journaled, so replay reads the verdict back rather than probing again. The default is Inconclusive: declaring Reconcile without implementing this escalates to an operator rather than silently deciding either way.

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementations on Foreign Types§

Source§

impl Effect for Box<dyn AnyEffect + '_>

So an erased effect dispatches exactly like a typed one, with no second code path in the runtime that could drift from the first.

Written over dyn AnyEffect + 'e rather than the 'static default: the executor re-enters itself through a commission, so this impl has to hold for whatever lifetime the surrounding future is inferred at, and pinning it to 'static makes that proof fail for every specific one.

Source§

type Output = Value

Source§

fn descriptor(&self) -> EffectDescriptor

Source§

fn attach(&mut self, provenance: &Provenance)

Source§

fn gen_ai_operation(&self) -> Option<&'static str>

Source§

fn mutates(&self) -> bool

Source§

fn recovery(&self) -> Recovery

Source§

fn retry(&self) -> RetryPolicy

Source§

fn max_sensitivity(&self) -> Sensitivity

Source§

fn sink_arguments(&self) -> Option<&Value>

Source§

fn protected_fields(&self) -> &[ProtectedField]

Source§

fn delegation_depth(&self) -> Option<usize>

Source§

fn source(&self) -> SourceId

Source§

fn trust(&self) -> Trust

Source§

fn output_sensitivity(&self) -> Sensitivity

Source§

fn spend(&self, output: &Value) -> Spend

Source§

fn perform<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<Value, EffectError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Source§

fn reconcile<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<Reconciliation<Value>, EffectError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Implementors§

Source§

impl Effect for Clock

Source§

impl Effect for DrawOnAuthority

Source§

impl Effect for Embed

Source§

impl Effect for GovernedFetch

Source§

impl Effect for McpPrompt

Source§

impl Effect for McpResource

Source§

impl Effect for McpTaskCancel

Source§

impl Effect for McpTaskPoll

Source§

impl Effect for McpTaskUpdate

Source§

impl Effect for ModelCall

Source§

impl Effect for OpenTask

Source§

impl Effect for PeerCall

Source§

impl Effect for PeerTaskCall

Source§

impl Effect for ReadCaseState

Source§

impl Effect for RecallMemory

Source§

impl Effect for RememberMemory

Source§

impl Effect for ResolveDeadline

Source§

impl Effect for SemanticRecall

Source§

impl Effect for SetCaseStatus

Source§

impl Effect for SweepExpiredMemory

Source§

impl Effect for ToolCall

Source§

impl Effect for TouchMemory

Source§

impl Effect for TransitionDeadline

Source§

impl Effect for WriteCaseState