Skip to main content

agent_sdk_core/records/
context.rs

1//! Durable and observable SDK records. Use these DTOs for events, journals, effects,
2//! context, output, and feature evidence. Constructing records is data-only;
3//! persistence, publication, and external actions happen through ports or application
4//! coordinators. This file contains the context portion of that contract.
5//!
6use serde::{Deserialize, Serialize};
7
8use crate::{
9    content::ContentRef,
10    domain::{
11        AgentError, ContentId, ContextItemId, ContextProjectionId, DestinationRef, EntityKind,
12        EntityRef, LineageRef, MessageId, PolicyKind, PolicyRef, PrivacyClass, RetentionClass,
13        SourceKind, SourceRef, TrustClass,
14    },
15    error::{AgentErrorKind, RetryClassification},
16    ids::{IdValidationError, validate_identifier},
17};
18
19#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
20#[serde(transparent)]
21/// Carries the context contribution id record payload for journal, event, or fixture surfaces.
22/// 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.
23pub struct ContextContributionId(String);
24
25impl ContextContributionId {
26    /// Creates a new records::context value with explicit
27    /// caller-provided inputs. This constructor is data-only and
28    /// performs no I/O or external side effects.
29    ///
30    /// # Panics
31    ///
32    /// Panics if constructor invariants fail, such as invalid identifier
33    /// text or constructor-specific bounds. Use a fallible constructor such as
34    /// `try_new` when one is available for untrusted input.
35    pub fn new(value: impl Into<String>) -> Self {
36        Self::try_new(value).expect("ContextContributionId must be valid")
37    }
38
39    /// Creates a new records::context value after validation. Returns
40    /// an SDK error instead of panicking when the identifier or input
41    /// does not satisfy the contract.
42    pub fn try_new(value: impl Into<String>) -> Result<Self, IdValidationError> {
43        let value = value.into();
44        validate_identifier(&value)?;
45        Ok(Self(value))
46    }
47
48    /// Returns this value as str. The accessor is side-effect free and
49    /// keeps ownership with the caller.
50    pub fn as_str(&self) -> &str {
51        &self.0
52    }
53}
54
55#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
56#[serde(rename_all = "snake_case")]
57/// Enumerates the finite agent message role cases.
58/// Serialized names are part of the SDK contract; update fixtures when variants change.
59pub enum AgentMessageRole {
60    /// Use this variant when the contract needs to represent system; selecting it has no side effect by itself.
61    System,
62    /// Use this variant when the contract needs to represent developer; selecting it has no side effect by itself.
63    Developer,
64    /// Use this variant when the contract needs to represent user; selecting it has no side effect by itself.
65    User,
66    /// Use this variant when the contract needs to represent assistant; selecting it has no side effect by itself.
67    Assistant,
68    /// Use this variant when the contract needs to represent tool; selecting it has no side effect by itself.
69    Tool,
70}
71
72#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
73#[serde(rename_all = "snake_case")]
74/// Enumerates the finite agent message part cases.
75/// Serialized names are part of the SDK contract; update fixtures when variants change.
76pub enum AgentMessagePart {
77    /// Use this variant when the contract needs to represent text; selecting it has no side effect by itself.
78    Text {
79        /// Text used by this record or request.
80        text: String,
81    },
82    /// Use this variant when the contract needs to represent content ref; selecting it has no side effect by itself.
83    ContentRef {
84        /// Content reference where payload bytes or structured tool output
85        /// are stored.
86        content_ref: ContentRef,
87    },
88    /// Use this variant when the contract needs to represent redacted; selecting it has no side effect by itself.
89    Redacted {
90        /// Redacted human-readable summary safe for events, telemetry, and
91        /// logs.
92        redacted_summary: String,
93        /// Content reference where payload bytes or structured tool output
94        /// are stored.
95        content_ref: Option<ContentRef>,
96    },
97}
98
99#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
100/// Carries the agent message record payload for journal, event, or fixture surfaces.
101/// 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.
102pub struct AgentMessage {
103    /// Message identifier for transcript, projection, or provider-response
104    /// lineage.
105    pub message_id: MessageId,
106    /// Role used by this record or request.
107    pub role: AgentMessageRole,
108    /// Collection of parts values.
109    /// Ordering and membership should be treated as part of the serialized contract when
110    /// relevant.
111    pub parts: Vec<AgentMessagePart>,
112    /// Typed producer ref reference. Resolving or executing it is a separate
113    /// policy-gated step.
114    pub producer_ref: EntityRef,
115    /// Typed source reference that records where this item originated.
116    pub source_ref: SourceRef,
117    /// Typed destination reference that records where this item is being sent
118    /// or projected.
119    pub destination_ref: Option<DestinationRef>,
120    /// Policy references that govern admission, projection, execution, or
121    /// delivery.
122    pub policy_refs: Vec<PolicyRef>,
123    /// Privacy class used for projection, telemetry, and raw-content access
124    /// decisions.
125    pub privacy_class: PrivacyClass,
126    /// Retention class used by hosts and sinks when storing or exporting this
127    /// item.
128    pub retention_class: RetentionClass,
129    /// Trust class used when deciding whether context or capabilities may be
130    /// admitted.
131    pub trust_class: TrustClass,
132    /// Typed lineage refs references. Resolving them is separate from
133    /// constructing this record.
134    pub lineage_refs: Vec<LineageRef>,
135    /// Redacted human-readable summary safe for events, telemetry, and logs.
136    pub redacted_summary: String,
137    /// Whether provider projected is enabled.
138    /// Policy, validation, or routing code uses this flag to choose the explicit behavior.
139    pub provider_projected: bool,
140}
141
142impl AgentMessage {
143    /// Builds the user text value with the documented defaults.
144    /// This is data-only and does not perform I/O, call host ports, append journals, publish
145    /// events, or start processes.
146    pub fn user_text(
147        message_id: MessageId,
148        text: impl Into<String>,
149        source_ref: SourceRef,
150        policy_ref: PolicyRef,
151    ) -> Self {
152        let text = text.into();
153        Self {
154            message_id: message_id.clone(),
155            role: AgentMessageRole::User,
156            parts: vec![AgentMessagePart::Text { text }],
157            producer_ref: EntityRef::message(message_id),
158            source_ref,
159            destination_ref: None,
160            policy_refs: vec![policy_ref],
161            privacy_class: PrivacyClass::ContentRefsOnly,
162            retention_class: RetentionClass::RunScoped,
163            trust_class: TrustClass::UserProvided,
164            lineage_refs: Vec::new(),
165            redacted_summary: "user text message".to_string(),
166            provider_projected: false,
167        }
168    }
169}
170
171#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
172#[serde(rename_all = "snake_case")]
173/// Enumerates the finite context contribution kind cases.
174/// Serialized names are part of the SDK contract; update fixtures when variants change.
175pub enum ContextContributionKind {
176    /// Use this variant when the contract needs to represent user input; selecting it has no side effect by itself.
177    UserInput,
178    /// Use this variant when the contract needs to represent host context; selecting it has no side effect by itself.
179    HostContext,
180    /// Use this variant when the contract needs to represent memory recall; selecting it has no side effect by itself.
181    MemoryRecall,
182    /// Use this variant when the contract needs to represent tool result; selecting it has no side effect by itself.
183    ToolResult,
184    /// Use this variant when the contract needs to represent skill result; selecting it has no side effect by itself.
185    SkillResult,
186    /// Use this variant when the contract needs to represent file context; selecting it has no side effect by itself.
187    FileContext,
188    /// Use this variant when the contract needs to represent remote channel; selecting it has no side effect by itself.
189    RemoteChannel,
190    /// Use this variant when the contract needs to represent agent pool message; selecting it has no side effect by itself.
191    AgentPoolMessage,
192    /// Use this variant when the contract needs to represent subagent handoff; selecting it has no side effect by itself.
193    SubagentHandoff,
194    /// Use this variant when the contract needs to represent compaction summary; selecting it has no side effect by itself.
195    CompactionSummary,
196    /// Use this variant when the contract needs to represent system instruction; selecting it has no side effect by itself.
197    SystemInstruction,
198    /// Use this variant when the contract needs to represent output schema hint; selecting it has no side effect by itself.
199    OutputSchemaHint,
200}
201
202#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
203/// Carries the context contribution record payload for journal, event, or fixture surfaces.
204/// 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.
205pub struct ContextContribution {
206    /// Stable contribution id used for typed lineage, lookup, or dedupe.
207    pub contribution_id: ContextContributionId,
208    /// Kind/category for this record, capability, event, or detected
209    /// resource.
210    pub kind: ContextContributionKind,
211    /// Typed producer ref reference. Resolving or executing it is a separate
212    /// policy-gated step.
213    pub producer_ref: EntityRef,
214    /// Typed source reference that records where this item originated.
215    pub source_ref: SourceRef,
216    /// Content reference where payload bytes or structured tool output are
217    /// stored.
218    pub content_ref: Option<ContentRef>,
219    /// Redacted summary for display, logs, events, or telemetry.
220    /// It should describe the value without exposing raw private content.
221    pub inline_redacted_summary: Option<String>,
222    /// Source refs this value was derived from.
223    /// Use them to trace provenance without embedding raw source content.
224    pub derived_from: Vec<EntityRef>,
225    /// Policy references that govern admission, projection, execution, or
226    /// delivery.
227    pub policy_refs: Vec<PolicyRef>,
228    /// Privacy class used for projection, telemetry, and raw-content access
229    /// decisions.
230    pub privacy_class: PrivacyClass,
231    /// Retention class used by hosts and sinks when storing or exporting this
232    /// item.
233    pub retention_class: RetentionClass,
234    /// Trust class used when deciding whether context or capabilities may be
235    /// admitted.
236    pub trust_class: TrustClass,
237    /// Optional budget hint value.
238    /// When absent, callers should use the documented default or skip that optional behavior.
239    pub budget_hint: Option<ContextBudgetHint>,
240    /// Whether required is enabled.
241    /// Policy, validation, or routing code uses this flag to choose the explicit behavior.
242    pub required: bool,
243    /// Whether protected is enabled.
244    /// Policy, validation, or routing code uses this flag to choose the explicit behavior.
245    pub protected: bool,
246    /// Redacted human-readable summary safe for events, telemetry, and logs.
247    pub redacted_summary: String,
248}
249
250impl ContextContribution {
251    /// Creates a new records::context value with explicit
252    /// caller-provided inputs. This constructor is data-only and
253    /// performs no I/O or external side effects.
254    pub fn new(
255        contribution_id: ContextContributionId,
256        kind: ContextContributionKind,
257        producer_ref: EntityRef,
258        source_ref: SourceRef,
259        policy_ref: PolicyRef,
260        redacted_summary: impl Into<String>,
261    ) -> Self {
262        Self {
263            contribution_id,
264            kind,
265            producer_ref,
266            source_ref,
267            content_ref: None,
268            inline_redacted_summary: None,
269            derived_from: Vec::new(),
270            policy_refs: vec![policy_ref],
271            privacy_class: PrivacyClass::ContentRefsOnly,
272            retention_class: RetentionClass::RunScoped,
273            trust_class: TrustClass::HostProvided,
274            budget_hint: None,
275            required: false,
276            protected: false,
277            redacted_summary: redacted_summary.into(),
278        }
279    }
280
281    /// Returns this value with its content ref setting replaced. The
282    /// method follows builder-style data construction and does not
283    /// execute external work.
284    pub fn with_content_ref(mut self, content_ref: ContentRef) -> Self {
285        self.content_ref = Some(content_ref);
286        self
287    }
288
289    /// Returns an updated value with required configured.
290    /// This is data-only and does not perform I/O, call host ports, append journals, publish
291    /// events, or start processes.
292    pub fn required(mut self) -> Self {
293        self.required = true;
294        self
295    }
296
297    /// Returns an updated value with protected configured.
298    /// This is data-only and does not perform I/O, call host ports, append journals, publish
299    /// events, or start processes.
300    pub fn protected(mut self) -> Self {
301        self.protected = true;
302        self
303    }
304}
305
306#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
307/// Carries the context budget hint record payload for journal, event, or fixture surfaces.
308/// 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.
309pub struct ContextBudgetHint {
310    /// Optional token estimate value.
311    /// When absent, callers should use the documented default or skip that optional behavior.
312    pub token_estimate: Option<u32>,
313    /// Byte size or byte limit for byte estimate.
314    /// Use it to enforce bounded reads, writes, summaries, or parser output.
315    pub byte_estimate: Option<u64>,
316    /// Priority used by this record or request.
317    pub priority: u16,
318}
319
320#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
321#[serde(rename_all = "snake_case")]
322/// Enumerates the finite projection role cases.
323/// Serialized names are part of the SDK contract; update fixtures when variants change.
324pub enum ProjectionRole {
325    /// Use this variant when the contract needs to represent system; selecting it has no side effect by itself.
326    System,
327    /// Use this variant when the contract needs to represent developer; selecting it has no side effect by itself.
328    Developer,
329    /// Use this variant when the contract needs to represent user; selecting it has no side effect by itself.
330    User,
331    /// Use this variant when the contract needs to represent assistant context; selecting it has no side effect by itself.
332    AssistantContext,
333    /// Use this variant when the contract needs to represent tool result; selecting it has no side effect by itself.
334    ToolResult,
335    /// Use this variant when the contract needs to represent schema hint; selecting it has no side effect by itself.
336    SchemaHint,
337}
338
339#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
340#[serde(rename_all = "snake_case")]
341/// Enumerates the finite context selection reason cases.
342/// Serialized names are part of the SDK contract; update fixtures when variants change.
343pub enum ContextSelectionReason {
344    /// Use this variant when the contract needs to represent required; selecting it has no side effect by itself.
345    Required,
346    /// Use this variant when the contract needs to represent pinned; selecting it has no side effect by itself.
347    Pinned,
348    /// Use this variant when the contract needs to represent relevant; selecting it has no side effect by itself.
349    Relevant,
350    /// Use this variant when the contract needs to represent recent; selecting it has no side effect by itself.
351    Recent,
352    /// Use this variant when the contract needs to represent tool result required; selecting it has no side effect by itself.
353    ToolResultRequired,
354    /// Use this variant when the contract needs to represent compacted; selecting it has no side effect by itself.
355    Compacted,
356    /// Use this variant when the contract needs to represent redacted; selecting it has no side effect by itself.
357    Redacted,
358    /// Use this variant when the contract needs to represent omitted budget; selecting it has no side effect by itself.
359    OmittedBudget,
360    /// Use this variant when the contract needs to represent omitted policy; selecting it has no side effect by itself.
361    OmittedPolicy,
362    /// Use this variant when the contract needs to represent omitted duplicate; selecting it has no side effect by itself.
363    OmittedDuplicate,
364    /// Use this variant when the contract needs to represent omitted trust; selecting it has no side effect by itself.
365    OmittedTrust,
366    /// Use this variant when the contract needs to represent omitted stale; selecting it has no side effect by itself.
367    OmittedStale,
368    /// Use this variant when the contract needs to represent omitted missing ref; selecting it has no side effect by itself.
369    OmittedMissingRef,
370    /// Use this variant when the contract needs to represent protected omitted by policy; selecting it has no side effect by itself.
371    ProtectedOmittedByPolicy,
372}
373
374impl ContextSelectionReason {
375    /// Reports whether this value is included. The check is pure and
376    /// does not mutate SDK or host state.
377    pub fn is_included(&self) -> bool {
378        matches!(
379            self,
380            Self::Required
381                | Self::Pinned
382                | Self::Relevant
383                | Self::Recent
384                | Self::ToolResultRequired
385                | Self::Compacted
386                | Self::Redacted
387        )
388    }
389}
390
391#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
392/// Carries the context selection decision record payload for journal, event, or fixture surfaces.
393/// 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.
394pub struct ContextSelectionDecision {
395    /// Stable contribution id used for typed lineage, lookup, or dedupe.
396    pub contribution_id: Option<ContextContributionId>,
397    /// Stable context item id used for typed lineage, lookup, or dedupe.
398    pub context_item_id: Option<ContextItemId>,
399    /// Redacted explanation for a denial, failure, status, or package delta.
400    pub reason: ContextSelectionReason,
401    /// Typed producer ref reference. Resolving or executing it is a separate
402    /// policy-gated step.
403    pub producer_ref: EntityRef,
404    /// Typed source reference that records where this item originated.
405    pub source_ref: SourceRef,
406    /// Content reference where payload bytes or structured tool output are
407    /// stored.
408    pub content_ref: Option<ContentRef>,
409    /// Policy references that govern admission, projection, execution, or
410    /// delivery.
411    pub policy_refs: Vec<PolicyRef>,
412    /// Privacy class used for projection, telemetry, and raw-content access
413    /// decisions.
414    pub privacy_class: PrivacyClass,
415    /// Retention class used by hosts and sinks when storing or exporting this
416    /// item.
417    pub retention_class: RetentionClass,
418    /// Trust class used when deciding whether context or capabilities may be
419    /// admitted.
420    pub trust_class: TrustClass,
421    /// Whether required is enabled.
422    /// Policy, validation, or routing code uses this flag to choose the explicit behavior.
423    pub required: bool,
424    /// Whether protected is enabled.
425    /// Policy, validation, or routing code uses this flag to choose the explicit behavior.
426    pub protected: bool,
427    /// Redacted human-readable summary safe for events, telemetry, and logs.
428    pub redacted_summary: String,
429}
430
431impl ContextSelectionDecision {
432    /// Builds the selected value.
433    /// This is data construction and performs no I/O, journal append, event publication, or
434    /// process work.
435    pub fn selected(contribution: &ContextContribution, context_item_id: ContextItemId) -> Self {
436        Self::from_contribution(
437            contribution,
438            Some(context_item_id),
439            if contribution.required {
440                ContextSelectionReason::Required
441            } else {
442                ContextSelectionReason::Relevant
443            },
444        )
445    }
446
447    /// Builds the omitted value.
448    /// This is data construction and performs no I/O, journal append, event publication, or
449    /// process work.
450    pub fn omitted(contribution: &ContextContribution, reason: ContextSelectionReason) -> Self {
451        Self::from_contribution(contribution, None, reason)
452    }
453
454    fn from_contribution(
455        contribution: &ContextContribution,
456        context_item_id: Option<ContextItemId>,
457        reason: ContextSelectionReason,
458    ) -> Self {
459        Self {
460            contribution_id: Some(contribution.contribution_id.clone()),
461            context_item_id,
462            reason,
463            producer_ref: contribution.producer_ref.clone(),
464            source_ref: contribution.source_ref.clone(),
465            content_ref: contribution.content_ref.clone(),
466            policy_refs: contribution.policy_refs.clone(),
467            privacy_class: contribution.privacy_class,
468            retention_class: contribution.retention_class,
469            trust_class: contribution.trust_class,
470            required: contribution.required,
471            protected: contribution.protected,
472            redacted_summary: contribution.redacted_summary.clone(),
473        }
474    }
475}
476
477#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
478/// Carries the context item record payload for journal, event, or fixture surfaces.
479/// 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.
480pub struct ContextItem {
481    /// Stable context item id used for typed lineage, lookup, or dedupe.
482    pub context_item_id: ContextItemId,
483    /// Stable contribution id used for typed lineage, lookup, or dedupe.
484    pub contribution_id: Option<ContextContributionId>,
485    /// Kind/category for this record, capability, event, or detected
486    /// resource.
487    pub kind: ContextContributionKind,
488    /// Typed producer ref reference. Resolving or executing it is a separate
489    /// policy-gated step.
490    pub producer_ref: EntityRef,
491    /// Typed source reference that records where this item originated.
492    pub source_ref: SourceRef,
493    /// Typed destination reference that records where this item is being sent
494    /// or projected.
495    pub destination_ref: DestinationRef,
496    /// Content reference where payload bytes or structured tool output are
497    /// stored.
498    pub content_ref: Option<ContentRef>,
499    /// Redacted summary for display, logs, events, or telemetry.
500    /// It should describe the value without exposing raw private content.
501    pub inline_redacted_summary: Option<String>,
502    /// Source refs this value was derived from.
503    /// Use them to trace provenance without embedding raw source content.
504    pub derived_from: Vec<EntityRef>,
505    /// Projection controls for exposing data to a provider or subscriber.
506    /// Use it to keep provider-visible data separate from private SDK state.
507    pub projection_role: ProjectionRole,
508    /// Policy references that govern admission, projection, execution, or
509    /// delivery.
510    pub policy_refs: Vec<PolicyRef>,
511    /// Privacy class used for projection, telemetry, and raw-content access
512    /// decisions.
513    pub privacy_class: PrivacyClass,
514    /// Retention class used by hosts and sinks when storing or exporting this
515    /// item.
516    pub retention_class: RetentionClass,
517    /// Trust class used when deciding whether context or capabilities may be
518    /// admitted.
519    pub trust_class: TrustClass,
520    /// Optional budget hint value.
521    /// When absent, callers should use the documented default or skip that optional behavior.
522    pub budget_hint: Option<ContextBudgetHint>,
523    /// Selection used by this record or request.
524    pub selection: ContextSelectionDecision,
525    /// Typed lineage refs references. Resolving them is separate from
526    /// constructing this record.
527    pub lineage_refs: Vec<LineageRef>,
528    /// Redacted human-readable summary safe for events, telemetry, and logs.
529    pub redacted_summary: String,
530}
531
532impl ContextItem {
533    /// Builds the admit value.
534    /// This is data construction and performs no I/O, journal append, event publication, or
535    /// process work.
536    pub fn admit(
537        contribution: ContextContribution,
538        context_item_id: ContextItemId,
539        destination_ref: DestinationRef,
540        projection_role: ProjectionRole,
541    ) -> Self {
542        let selection = ContextSelectionDecision::selected(&contribution, context_item_id.clone());
543        Self {
544            context_item_id,
545            contribution_id: Some(contribution.contribution_id),
546            kind: contribution.kind,
547            producer_ref: contribution.producer_ref,
548            source_ref: contribution.source_ref,
549            destination_ref,
550            content_ref: contribution.content_ref,
551            inline_redacted_summary: contribution.inline_redacted_summary,
552            derived_from: contribution.derived_from,
553            projection_role,
554            policy_refs: contribution.policy_refs,
555            privacy_class: contribution.privacy_class,
556            retention_class: contribution.retention_class,
557            trust_class: contribution.trust_class,
558            budget_hint: contribution.budget_hint,
559            selection,
560            lineage_refs: Vec::new(),
561            redacted_summary: contribution.redacted_summary,
562        }
563    }
564
565    /// Builds the provider part value.
566    /// This is data construction and performs no I/O, journal append, event publication, or
567    /// process work.
568    pub fn provider_part(&self) -> ProjectedContextPart {
569        ProjectedContextPart {
570            context_item_id: self.context_item_id.clone(),
571            role: self.projection_role.clone(),
572            content_ref: self.content_ref.clone(),
573            text: self.inline_redacted_summary.clone(),
574            redacted_summary: self.redacted_summary.clone(),
575            raw_content_included: false,
576            privacy_class: self.privacy_class,
577            retention_class: self.retention_class,
578        }
579    }
580}
581
582#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
583/// Carries the projected context part record payload for journal, event, or fixture surfaces.
584/// 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.
585pub struct ProjectedContextPart {
586    /// Stable context item id used for typed lineage, lookup, or dedupe.
587    pub context_item_id: ContextItemId,
588    /// Role used by this record or request.
589    pub role: ProjectionRole,
590    /// Content reference where payload bytes or structured tool output are
591    /// stored.
592    pub content_ref: Option<ContentRef>,
593    /// Optional text value.
594    /// When absent, callers should use the documented default or skip that optional behavior.
595    pub text: Option<String>,
596    /// Redacted human-readable summary safe for events, telemetry, and logs.
597    pub redacted_summary: String,
598    /// Raw content or raw-content control for this value.
599    /// Use it only when policy explicitly allows raw content capture or delivery.
600    pub raw_content_included: bool,
601    /// Privacy class used for projection, telemetry, and raw-content access
602    /// decisions.
603    pub privacy_class: PrivacyClass,
604    /// Retention class used by hosts and sinks when storing or exporting this
605    /// item.
606    pub retention_class: RetentionClass,
607}
608
609#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
610/// Carries the context budget summary record payload for journal, event, or fixture surfaces.
611/// 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.
612pub struct ContextBudgetSummary {
613    /// Maximum allowed tokens.
614    /// Use it to keep execution, output, or diagnostics bounded.
615    pub max_tokens: Option<u32>,
616    /// Used tokens used by this record or request.
617    pub used_tokens: u32,
618    /// Maximum allowed items.
619    /// Use it to keep execution, output, or diagnostics bounded.
620    pub max_items: Option<u32>,
621    /// Included items used by this record or request.
622    pub included_items: u32,
623    /// Omitted items used by this record or request.
624    pub omitted_items: u32,
625}
626
627#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
628/// Carries the context projection audit record payload for journal, event, or fixture surfaces.
629/// 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.
630pub struct ContextProjectionAudit {
631    /// Stable projection id used for typed lineage, lookup, or dedupe.
632    pub projection_id: ContextProjectionId,
633    /// Identifiers used to select or correlate source message values.
634    /// Use them for typed lookup, filtering, or lineage instead of stringly typed matching.
635    pub source_message_ids: Vec<MessageId>,
636    /// Count of candidate items observed or included in this record.
637    pub candidate_count: u32,
638    /// Count of included items observed or included in this record.
639    pub included_count: u32,
640    /// Count of omitted items observed or included in this record.
641    pub omitted_count: u32,
642    /// Count of compacted items observed or included in this record.
643    pub compacted_count: u32,
644    /// Count of redacted items observed or included in this record.
645    pub redacted_count: u32,
646    /// Count of policy denied items observed or included in this record.
647    pub policy_denied_count: u32,
648    /// Count of budget denied items observed or included in this record.
649    pub budget_denied_count: u32,
650    /// Count of missing ref items observed or included in this record.
651    pub missing_ref_count: u32,
652    /// Count of protected omitted items observed or included in this record.
653    pub protected_omitted_count: u32,
654    /// Collection of decisions values.
655    /// Ordering and membership should be treated as part of the serialized contract when
656    /// relevant.
657    pub decisions: Vec<ContextSelectionDecision>,
658    /// Budget used by this record or request.
659    pub budget: ContextBudgetSummary,
660    /// Policy references that govern admission, projection, execution, or
661    /// delivery.
662    pub policy_refs: Vec<PolicyRef>,
663    /// Stable redaction policy id used for typed lineage, lookup, or dedupe.
664    pub redaction_policy_id: PolicyRef,
665    /// Fingerprint of the runtime package snapshot in force when this value was produced.
666    /// Use it for replay, dedupe, and package-lineage checks; the field is evidence and does
667    /// not execute package behavior.
668    pub runtime_package_fingerprint: String,
669}
670
671impl ContextProjectionAudit {
672    /// Constructs this value from decisions. Use it when adapting
673    /// canonical SDK records without introducing a second behavior
674    /// path.
675    pub fn from_decisions(
676        projection_id: ContextProjectionId,
677        source_message_ids: Vec<MessageId>,
678        decisions: Vec<ContextSelectionDecision>,
679        mut budget: ContextBudgetSummary,
680        redaction_policy_id: PolicyRef,
681        runtime_package_fingerprint: impl Into<String>,
682    ) -> Self {
683        let mut audit = Self {
684            projection_id,
685            source_message_ids,
686            candidate_count: decisions.len() as u32,
687            included_count: 0,
688            omitted_count: 0,
689            compacted_count: 0,
690            redacted_count: 0,
691            policy_denied_count: 0,
692            budget_denied_count: 0,
693            missing_ref_count: 0,
694            protected_omitted_count: 0,
695            decisions,
696            budget: ContextBudgetSummary::default(),
697            policy_refs: Vec::new(),
698            redaction_policy_id,
699            runtime_package_fingerprint: runtime_package_fingerprint.into(),
700        };
701
702        for decision in &audit.decisions {
703            if decision.reason.is_included() {
704                audit.included_count += 1;
705            } else {
706                audit.omitted_count += 1;
707            }
708            match decision.reason {
709                ContextSelectionReason::Compacted => audit.compacted_count += 1,
710                ContextSelectionReason::Redacted => audit.redacted_count += 1,
711                ContextSelectionReason::OmittedPolicy
712                | ContextSelectionReason::ProtectedOmittedByPolicy => {
713                    audit.policy_denied_count += 1
714                }
715                ContextSelectionReason::OmittedBudget => audit.budget_denied_count += 1,
716                ContextSelectionReason::OmittedMissingRef => audit.missing_ref_count += 1,
717                _ => {}
718            }
719            if decision.protected && !decision.reason.is_included() {
720                audit.protected_omitted_count += 1;
721            }
722            for policy_ref in &decision.policy_refs {
723                if !audit
724                    .policy_refs
725                    .iter()
726                    .any(|existing| existing == policy_ref)
727                {
728                    audit.policy_refs.push(policy_ref.clone());
729                }
730            }
731        }
732
733        budget.included_items = audit.included_count;
734        budget.omitted_items = audit.omitted_count;
735        audit.budget = budget;
736        audit
737    }
738
739    /// Returns has blocking missing required ref for the current value.
740    /// This is a read-only or data-construction helper unless the method body explicitly calls
741    /// a port or store.
742    pub fn has_blocking_missing_required_ref(&self) -> bool {
743        self.decisions.iter().any(|decision| {
744            decision.required && decision.reason == ContextSelectionReason::OmittedMissingRef
745        })
746    }
747
748    /// Returns has protected omission for this records::context value without
749    /// performing external I/O.
750    pub fn has_protected_omission(&self) -> bool {
751        self.protected_omitted_count > 0
752    }
753}
754
755#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
756/// Carries the context projection record payload for journal, event, or fixture surfaces.
757/// 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.
758pub struct ContextProjection {
759    /// Stable projection id used for typed lineage, lookup, or dedupe.
760    pub projection_id: ContextProjectionId,
761    /// Collection of source messages values.
762    /// Ordering and membership should be treated as part of the serialized contract when
763    /// relevant.
764    pub source_messages: Vec<AgentMessage>,
765    /// Bounded items included in this record. Limits and truncation are
766    /// represented by companion metadata when applicable.
767    pub items: Vec<ContextItem>,
768    /// Collection of projected parts values.
769    /// Ordering and membership should be treated as part of the serialized contract when
770    /// relevant.
771    pub projected_parts: Vec<ProjectedContextPart>,
772    /// Audit used by this record or request.
773    pub audit: ContextProjectionAudit,
774    /// Provider destination used by this record or request.
775    pub provider_destination: DestinationRef,
776}
777
778impl Default for ContextProjection {
779    fn default() -> Self {
780        let projection_id = ContextProjectionId::new("context.projection.default");
781        let destination = DestinationRef::with_kind(
782            crate::domain::DestinationKind::Provider,
783            "destination.provider.default",
784        );
785        let redaction = PolicyRef::with_kind(PolicyKind::Redaction, "policy.redaction.default");
786        Self {
787            projection_id: projection_id.clone(),
788            source_messages: Vec::new(),
789            items: Vec::new(),
790            projected_parts: Vec::new(),
791            audit: ContextProjectionAudit::from_decisions(
792                projection_id,
793                Vec::new(),
794                Vec::new(),
795                ContextBudgetSummary::default(),
796                redaction,
797                "runtime.package.default",
798            ),
799            provider_destination: destination,
800        }
801    }
802}
803
804impl ContextProjection {
805    /// Finishes builder validation and returns the configured value.
806    /// This is data-only unless the surrounding builder explicitly
807    /// documents adapter or store access.
808    #[expect(
809        clippy::too_many_arguments,
810        reason = "ContextProjection::build intentionally captures all projection inputs; a projection builder should be designed as a separate API pass"
811    )]
812    pub fn build(
813        projection_id: ContextProjectionId,
814        source_messages: Vec<AgentMessage>,
815        items: Vec<ContextItem>,
816        omitted: Vec<ContextSelectionDecision>,
817        provider_destination: DestinationRef,
818        budget: ContextBudgetSummary,
819        redaction_policy_id: PolicyRef,
820        runtime_package_fingerprint: impl Into<String>,
821    ) -> Result<Self, AgentError> {
822        let mut decisions = Vec::with_capacity(items.len() + omitted.len());
823        decisions.extend(items.iter().map(|item| item.selection.clone()));
824        decisions.extend(omitted);
825        let audit = ContextProjectionAudit::from_decisions(
826            projection_id.clone(),
827            source_messages
828                .iter()
829                .map(|message| message.message_id.clone())
830                .collect(),
831            decisions,
832            budget,
833            redaction_policy_id,
834            runtime_package_fingerprint,
835        );
836        if audit.has_blocking_missing_required_ref() || audit.has_protected_omission() {
837            return Err(AgentError::new(
838                AgentErrorKind::ProjectionFailure,
839                RetryClassification::RepairNeeded,
840                "context projection blocked by required missing ref or protected omission",
841            ));
842        }
843
844        let projected_parts = items.iter().map(ContextItem::provider_part).collect();
845        Ok(Self {
846            projection_id,
847            source_messages,
848            items,
849            projected_parts,
850            audit,
851            provider_destination,
852        })
853    }
854
855    /// Computes or returns provider visible content ids for the
856    /// records::context contract without external I/O or side effects.
857    pub fn provider_visible_content_ids(&self) -> Vec<ContentId> {
858        self.projected_parts
859            .iter()
860            .filter_map(|part| {
861                part.content_ref
862                    .as_ref()
863                    .map(|content_ref| content_ref.content_id.clone())
864            })
865            .collect()
866    }
867}
868
869/// Returns sdk context policy ref derived from the supplied state.
870/// This is data-only and does not perform I/O, call host ports, append journals, publish
871/// events, or start processes.
872pub fn sdk_context_policy_ref() -> PolicyRef {
873    PolicyRef::with_kind(PolicyKind::Context, "policy.context.sdk.default")
874}
875
876/// Returns sdk source ref derived from the supplied state.
877/// This is data-only and does not perform I/O, call host ports, append journals, publish
878/// events, or start processes.
879pub fn sdk_source_ref() -> SourceRef {
880    SourceRef::with_kind(SourceKind::Sdk, "source.sdk.context")
881}
882
883/// Returns contribution entity ref derived from the supplied state.
884/// This is data-only and does not perform I/O, call host ports, append journals, publish
885/// events, or start processes.
886pub fn contribution_entity_ref(contribution_id: &ContextContributionId) -> EntityRef {
887    EntityRef::new(EntityKind::ContextContribution, contribution_id.as_str())
888}