Skip to main content

agent_sdk_core/records/
effect.rs

1//! Shared effect-intent and effect-result records. Use these records before and after
2//! externally visible work such as tools, output delivery, extension actions, or
3//! child starts. The records themselves do not execute the effect.
4//!
5use serde::{Deserialize, Serialize};
6
7use crate::domain::{
8    ContentRef, DedupeKey, DestinationRef, EffectId, EntityRef, IdempotencyKey, PolicyRef,
9    SourceRef,
10};
11
12#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
13#[serde(rename_all = "snake_case")]
14/// Enumerates the finite effect kind cases.
15/// Serialized names are part of the SDK contract; update fixtures when variants change.
16pub enum EffectKind {
17    /// Use this variant when the contract needs to represent provider request; selecting it has no side effect by itself.
18    ProviderRequest,
19    /// Use this variant when the contract needs to represent tool execution; selecting it has no side effect by itself.
20    ToolExecution,
21    /// Use this variant when the contract needs to represent approval dispatch; selecting it has no side effect by itself.
22    ApprovalDispatch,
23    /// Use this variant when the contract needs to represent memory write; selecting it has no side effect by itself.
24    MemoryWrite,
25    /// Use this variant when the contract needs to represent extension action; selecting it has no side effect by itself.
26    ExtensionAction,
27    /// Use this variant when the contract needs to represent output delivery; selecting it has no side effect by itself.
28    OutputDelivery,
29    /// Use this variant when the contract needs to represent file write; selecting it has no side effect by itself.
30    FileWrite,
31    /// Use this variant when the contract needs to represent process start; selecting it has no side effect by itself.
32    ProcessStart,
33    /// Use this variant when the contract needs to represent process signal; selecting it has no side effect by itself.
34    ProcessSignal,
35    /// Use this variant when the contract needs to represent isolated process start; selecting it has no side effect by itself.
36    IsolatedProcessStart,
37    /// Use this variant when the contract needs to represent child agent start; selecting it has no side effect by itself.
38    ChildAgentStart,
39    /// Use this variant when the contract needs to represent run message delivery; selecting it has no side effect by itself.
40    RunMessageDelivery,
41    /// Use this variant when the contract needs to represent child artifact shutdown; selecting it has no side effect by itself.
42    ChildArtifactShutdown,
43    /// Use this variant when the contract needs to represent detach transfer; selecting it has no side effect by itself.
44    DetachTransfer,
45    /// Use this variant when the contract needs to represent hook mutation; selecting it has no side effect by itself.
46    HookMutation,
47}
48
49#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
50/// Carries the effect intent record payload for journal, event, or fixture surfaces.
51/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
52pub struct EffectIntent {
53    /// Stable effect id used for typed lineage, lookup, or dedupe.
54    pub effect_id: EffectId,
55    /// Kind/category for this record, capability, event, or detected
56    /// resource.
57    pub kind: EffectKind,
58    /// Typed subject ref reference. Resolving or executing it is a separate
59    /// policy-gated step.
60    pub subject_ref: EntityRef,
61    /// Source label or ref for this item; it is metadata and does not fetch
62    /// content by itself.
63    pub source: SourceRef,
64    #[serde(skip_serializing_if = "Option::is_none")]
65    /// Destination label or ref for this item; it is metadata and does not
66    /// deliver content by itself.
67    pub destination: Option<DestinationRef>,
68    #[serde(default, skip_serializing_if = "Vec::is_empty")]
69    /// Policy references that govern admission, projection, execution, or
70    /// delivery.
71    pub policy_refs: Vec<PolicyRef>,
72    #[serde(skip_serializing_if = "Option::is_none")]
73    /// Idempotency setting or key for deduping retries.
74    /// Use it to prevent duplicate side effects during replay or repair.
75    pub idempotency_key: Option<IdempotencyKey>,
76    #[serde(skip_serializing_if = "Option::is_none")]
77    /// Dedupe policy or key for a side-effecting operation.
78    /// Replay and repair use it to avoid sending or executing the same effect twice.
79    pub dedupe_key: Option<DedupeKey>,
80    #[serde(default, skip_serializing_if = "Vec::is_empty")]
81    /// Content references associated with this record; resolving them is a
82    /// separate policy-gated step.
83    pub content_refs: Vec<ContentRef>,
84    /// Redacted human-readable summary safe for events, telemetry, and logs.
85    pub redacted_summary: String,
86}
87
88impl EffectIntent {
89    /// Creates a new records::effect value with explicit
90    /// caller-provided inputs. This constructor is data-only and
91    /// performs no I/O or external side effects.
92    pub fn new(
93        effect_id: EffectId,
94        kind: EffectKind,
95        subject_ref: EntityRef,
96        source: SourceRef,
97        redacted_summary: impl Into<String>,
98    ) -> Self {
99        Self {
100            effect_id,
101            kind,
102            subject_ref,
103            source,
104            destination: None,
105            policy_refs: Vec::new(),
106            idempotency_key: None,
107            dedupe_key: None,
108            content_refs: Vec::new(),
109            redacted_summary: redacted_summary.into(),
110        }
111    }
112}
113
114#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
115#[serde(rename_all = "snake_case")]
116/// Enumerates the finite effect terminal status cases.
117/// Serialized names are part of the SDK contract; update fixtures when variants change.
118pub enum EffectTerminalStatus {
119    /// Use this variant when the contract needs to represent completed; selecting it has no side effect by itself.
120    Completed,
121    /// Use this variant when the contract needs to represent failed; selecting it has no side effect by itself.
122    Failed,
123    /// Use this variant when the contract needs to represent timed out; selecting it has no side effect by itself.
124    TimedOut,
125    /// Use this variant when the contract needs to represent cancelled; selecting it has no side effect by itself.
126    Cancelled,
127    /// Use this variant when the contract needs to represent denied before execution; selecting it has no side effect by itself.
128    DeniedBeforeExecution,
129    /// Use this variant when the contract needs to represent unknown; selecting it has no side effect by itself.
130    Unknown,
131}
132
133#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
134/// Carries the effect result record payload for journal, event, or fixture surfaces.
135/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
136pub struct EffectResult {
137    /// Stable effect id used for typed lineage, lookup, or dedupe.
138    pub effect_id: EffectId,
139    /// Terminal status used by this record or request.
140    pub terminal_status: EffectTerminalStatus,
141    #[serde(skip_serializing_if = "Option::is_none")]
142    /// Stable external operation id used for typed lineage, lookup, or
143    /// dedupe.
144    pub external_operation_id: Option<String>,
145    #[serde(skip_serializing_if = "Option::is_none")]
146    /// Typed reconciliation ref reference. Resolving or executing it is a
147    /// separate policy-gated step.
148    pub reconciliation_ref: Option<String>,
149    #[serde(skip_serializing_if = "Option::is_none")]
150    /// Typed error ref reference. Resolving or executing it is a separate
151    /// policy-gated step.
152    pub error_ref: Option<String>,
153    #[serde(default, skip_serializing_if = "Vec::is_empty")]
154    /// Content references associated with this record; resolving them is a
155    /// separate policy-gated step.
156    pub content_refs: Vec<ContentRef>,
157    /// Redacted human-readable summary safe for events, telemetry, and logs.
158    pub redacted_summary: String,
159}
160
161impl EffectResult {
162    /// Returns an updated value with completed configured.
163    /// This is data-only and does not perform I/O, call host ports, append journals, publish
164    /// events, or start processes.
165    pub fn completed(effect_id: EffectId, redacted_summary: impl Into<String>) -> Self {
166        Self {
167            effect_id,
168            terminal_status: EffectTerminalStatus::Completed,
169            external_operation_id: None,
170            reconciliation_ref: None,
171            error_ref: None,
172            content_refs: Vec::new(),
173            redacted_summary: redacted_summary.into(),
174        }
175    }
176
177    /// Builds the unknown record or result value.
178    /// This is data-only and does not perform I/O, call host ports, append journals, publish
179    /// events, or start processes.
180    pub fn unknown(effect_id: EffectId, redacted_summary: impl Into<String>) -> Self {
181        Self {
182            effect_id,
183            terminal_status: EffectTerminalStatus::Unknown,
184            external_operation_id: None,
185            reconciliation_ref: None,
186            error_ref: None,
187            content_refs: Vec::new(),
188            redacted_summary: redacted_summary.into(),
189        }
190    }
191}