Skip to main content

runx_contracts/
agent_context.rs

1//! Agent-context envelope (`runx.ai/spec/agent-context-envelope`): the bulky
2//! per-act execution context referenced from a receipt act's `context_ref`
3//! (instructions, inputs, current/historical artifact context, provenance, and
4//! the resolved skill profiles).
5//!
6//! Identity is the legacy bare `runx.ai/spec` `$id` (no `x-runx-schema`).
7use serde::{Deserialize, Serialize};
8
9use crate::JsonObject;
10use crate::output::OutputField;
11use crate::schema::{NonEmptyString, RunxSchema};
12use std::collections::BTreeMap;
13
14/// The artifact context entry version. Committed as `const: "1"`.
15#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
16pub enum ContextEntryVersion {
17    #[serde(rename = "1")]
18    V1,
19}
20
21/// The producer of a context artifact.
22#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
23#[serde(deny_unknown_fields)]
24pub struct ContextArtifactProducer {
25    pub skill: NonEmptyString,
26    pub runner: NonEmptyString,
27}
28
29/// Metadata for a context artifact. `step_id`, `parent_artifact_id`, and
30/// `receipt_id` are required-but-nullable (present on the wire, possibly null).
31#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
32#[serde(deny_unknown_fields)]
33pub struct ContextArtifactMeta {
34    pub artifact_id: NonEmptyString,
35    pub run_id: NonEmptyString,
36    pub step_id: Option<NonEmptyString>,
37    pub producer: ContextArtifactProducer,
38    pub created_at: NonEmptyString,
39    pub hash: NonEmptyString,
40    pub size_bytes: u64,
41    pub parent_artifact_id: Option<NonEmptyString>,
42    pub receipt_id: Option<NonEmptyString>,
43    pub redacted: bool,
44}
45
46/// A single artifact context entry. `type` is required-but-nullable.
47#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, RunxSchema)]
48#[serde(deny_unknown_fields)]
49pub struct ContextEntry {
50    #[serde(rename = "type")]
51    pub entry_type: Option<NonEmptyString>,
52    pub version: ContextEntryVersion,
53    pub data: JsonObject,
54    pub meta: ContextArtifactMeta,
55}
56
57/// One input/output provenance edge.
58#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
59#[serde(deny_unknown_fields)]
60pub struct ProvenanceEntry {
61    pub input: NonEmptyString,
62    pub output: NonEmptyString,
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub from_step: Option<String>,
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub artifact_id: Option<String>,
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub receipt_id: Option<String>,
69}
70
71/// A resolved profile sourced from a workspace file (memory, conventions,
72/// voice).
73#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
74#[serde(deny_unknown_fields)]
75pub struct ProfileFile {
76    pub root_path: NonEmptyString,
77    pub path: NonEmptyString,
78    pub sha256: NonEmptyString,
79    pub content: String,
80}
81
82/// The optional memory/conventions context block.
83#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
84#[serde(deny_unknown_fields)]
85pub struct AgentContextProfiles {
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub memory: Option<ProfileFile>,
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub conventions: Option<ProfileFile>,
90}
91
92/// The resolved quality profile source. `source` is committed as
93/// `const: "SKILL.md#quality-profile"`.
94#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
95pub enum QualityProfileSource {
96    #[serde(rename = "SKILL.md#quality-profile")]
97    SkillMd,
98}
99
100#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
101#[serde(deny_unknown_fields)]
102pub struct QualityProfile {
103    pub source: QualityProfileSource,
104    pub sha256: NonEmptyString,
105    pub content: String,
106}
107
108/// Where the skill executes from on disk.
109#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
110#[serde(deny_unknown_fields)]
111pub struct ExecutionLocation {
112    pub skill_directory: NonEmptyString,
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub tool_roots: Option<Vec<NonEmptyString>>,
115}
116
117#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, RunxSchema)]
118#[serde(deny_unknown_fields)]
119#[runx_schema(spec_id = "https://runx.ai/spec/agent-context-envelope.schema.json")]
120pub struct AgentContextEnvelope {
121    pub run_id: NonEmptyString,
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub step_id: Option<NonEmptyString>,
124    pub skill: NonEmptyString,
125    pub instructions: NonEmptyString,
126    pub inputs: JsonObject,
127    pub allowed_tools: Vec<NonEmptyString>,
128    pub current_context: Vec<ContextEntry>,
129    pub historical_context: Vec<ContextEntry>,
130    pub provenance: Vec<ProvenanceEntry>,
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub context: Option<AgentContextProfiles>,
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub voice_profile: Option<ProfileFile>,
135    #[serde(skip_serializing_if = "Option::is_none")]
136    pub quality_profile: Option<QualityProfile>,
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub execution_location: Option<ExecutionLocation>,
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub output: Option<BTreeMap<String, OutputField>>,
141    pub trust_boundary: NonEmptyString,
142}