Skip to main content

agent_frame/
root.rs

1//! Roots: **which** event caused a derivation, not merely what kind it was.
2//!
3//! An earlier revision of this kernel made `Root` an enum of three kinds and
4//! stored the kind on the unit. That cannot answer the question a forensic
5//! consumer actually asks — *which operator turn caused this?* — because every
6//! turn of the same kind is the same value. A kind is a category; provenance
7//! needs an address.
8//!
9//! So the kind stays, demoted to a field of a [`RootEvent`] that has its own
10//! [`ContentId`], and a unit references that id. "Caused by an operator prompt"
11//! becomes "caused by *this* operator prompt", and the difference is checkable.
12
13use content_addressable::{ContentAddressable, ContentError, ContentId, RawContentId};
14use serde::{Deserialize, Serialize};
15
16/// The kind of thing that rooted a derivation.
17///
18/// Note what is absent: there is **no** `ModelOutput` constructor. A model's
19/// assertion is never a provenance root, and that is enforced by the type
20/// rather than by a rule.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
22#[serde(rename_all = "snake_case")]
23pub enum RootKind {
24    /// The human SAID: intent expressed in language. Needs adjudication.
25    OperatorPrompt,
26    /// The human DID: intent expressed as control. Unambiguous by construction.
27    UserAction,
28    /// The machine decided: a budget threshold, a retry, an automatic seal.
29    HarnessEvent,
30}
31
32/// A specific rooting event, addressed by its own content.
33///
34/// # Why `seq`
35///
36/// An operator can type "run the tests" twice in one session. Those are two
37/// turns, and a unit rooted in the second must not be indistinguishable from
38/// one rooted in the first. Content alone collapses them; `seq` is what keeps
39/// "which turn" answerable. It is the session-local ordinal of the event, so it
40/// is deterministic — a replay of the same session mints the same ids.
41#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
42#[serde(deny_unknown_fields)]
43pub struct RootEvent {
44    /// What kind of event this was.
45    pub kind: RootKind,
46    /// The event's own material — the prompt bytes, the action record, the
47    /// harness event record. A [`RawContentId`] because this is an opaque byte
48    /// string, not a canonical structured value.
49    pub content: RawContentId,
50    /// Session-local ordinal, so two identical events remain distinct.
51    pub seq: u64,
52}
53
54impl RootEvent {
55    /// Address a rooting event by hashing its material.
56    #[must_use]
57    pub fn new(kind: RootKind, material: &[u8], seq: u64) -> Self {
58        Self {
59            kind,
60            content: RawContentId::from_content(material),
61            seq,
62        }
63    }
64
65    /// This event's address — what a [`crate::Unit`] references.
66    ///
67    /// # Errors
68    ///
69    /// Propagates an encoding failure from the canonical form.
70    pub fn id(&self) -> Result<ContentId, ContentError> {
71        self.content_id()
72    }
73}
74
75impl ContentAddressable for RootEvent {
76    fn canonical_form(&self) -> Result<Vec<u8>, ContentError> {
77        content_addressable::canonical::to_canonical_dagcbor(self)
78    }
79}