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}