salamander/agent/mod.rs
1//! The agent-memory vocabulary — SalamanderDB's labeled beachhead (P1).
2//!
3//! Everything here is a *provided module over the generic engine*: a
4//! concrete payload type ([`EventBody`]), projections that fold it
5//! ([`SessionProjection`], [`KvProjection`]), and the session/fork
6//! operations that only make sense for an agent session. The core engine
7//! ([`crate::Salamander`], [`crate::Event`], [`crate::Projection`]) never
8//! references any of it — "SQLite for event-sourced state, built first for
9//! agent memory", not built *around* it.
10//!
11//! `session_view` and `fork` live here as an `impl Salamander<EventBody>`
12//! block: they read the `SessionStarted` / `ToolCall` vocabulary, so they
13//! are typed as operations on an agent database specifically, not on the
14//! generic engine.
15
16pub mod kv;
17pub mod session;
18
19use serde::{Deserialize, Serialize};
20
21use crate::projection::Projection;
22use crate::{BranchId, BranchInfo, BranchName, Metadata, Result, Salamander};
23
24pub use kv::KvProjection;
25pub use session::{
26 PendingToolCall, SessionProjection, SessionState, SessionStatus, TranscriptEntry,
27};
28
29/// A `Salamander` specialized to the agent vocabulary. Agent users open
30/// this instead of spelling out `Salamander<EventBody>` — "zero friction"
31/// (Phase 1.5 spec, WP-1 step 3).
32pub type AgentDb = Salamander<EventBody>;
33
34/// The speaker of a [`EventBody::ModelTurn`].
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
36pub enum Role {
37 /// A system prompt or instruction.
38 System,
39 /// A message from the end user.
40 User,
41 /// A message from the model.
42 Assistant,
43 /// Output attributed to a tool.
44 Tool,
45}
46
47/// The built-in agent-memory payload: a key/value vocabulary plus the
48/// typed events of an agent session (turns, tool calls, decisions). This is
49/// one provided payload over the generic engine — you can define your own
50/// instead.
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub enum EventBody {
53 /// Set `key` to `value` in the key/value projection.
54 Put {
55 /// The key to set.
56 key: String,
57 /// The value to store.
58 value: Vec<u8>,
59 },
60 /// Remove `key` from the key/value projection.
61 Delete {
62 /// The key to remove.
63 key: String,
64 },
65
66 /// Marks the start of an agent session.
67 SessionStarted {
68 /// Identifier of the agent.
69 agent_id: String,
70 /// Hash of the agent's configuration at session start.
71 config_hash: String,
72 },
73 /// One conversational turn.
74 ModelTurn {
75 /// Who produced the turn.
76 role: Role,
77 /// The turn's text content.
78 content: String,
79 /// The model that produced it.
80 model: String,
81 },
82 /// An invocation of a tool.
83 ToolCall {
84 /// Correlates this call with its [`EventBody::ToolResult`].
85 call_id: String,
86 /// Name of the tool invoked.
87 tool: String,
88 /// JSON-encoded arguments.
89 args_json: String,
90 },
91 /// The result of a [`EventBody::ToolCall`].
92 ToolResult {
93 /// The `call_id` of the originating call.
94 call_id: String,
95 /// Whether the call succeeded.
96 ok: bool,
97 /// The result content.
98 content: String,
99 },
100 /// A recorded decision — the natural fork point in a session.
101 Decision {
102 /// One-line summary of the decision.
103 summary: String,
104 /// Why it was made.
105 rationale: String,
106 },
107 /// Marks the end of a session.
108 SessionEnded {
109 /// Why the session ended.
110 reason: String,
111 },
112}
113
114/// Agent-vocabulary operations. These are inherent methods on the engine
115/// *specialized* to `EventBody`, so they are only in scope for an
116/// `AgentDb` — a `Salamander<MyOwnPayload>` never sees `fork` or
117/// `session_view`, which is exactly the P1 boundary made real in the type
118/// system.
119impl Salamander<EventBody> {
120 /// A `SessionProjection` for `namespace` on the default branch.
121 pub fn session_view(&self, namespace: &str) -> Result<SessionProjection> {
122 self.session_view_on_branch(BranchId::ZERO, namespace)
123 }
124
125 /// A [`SessionProjection`] for `namespace` on a specific branch.
126 pub fn session_view_on_branch(
127 &self,
128 branch: BranchId,
129 namespace: &str,
130 ) -> Result<SessionProjection> {
131 let mut projection = SessionProjection::new(namespace);
132 self.replay_branch(branch, namespace, 0..self.head(), |event| {
133 projection.apply(event);
134 })?;
135 Ok(projection)
136 }
137
138 /// Create an engine-owned branch at `n` while retaining `namespace` as
139 /// the session stream name on both histories.
140 pub fn fork(&mut self, namespace: &str, n: u64) -> Result<BranchInfo> {
141 let mut metadata = Metadata::new();
142 metadata.insert("session_stream".into(), namespace.as_bytes().to_vec());
143 self.fork_branch(
144 BranchId::ZERO,
145 n,
146 BranchName::new(format!("{namespace}-fork-{n}"))?,
147 metadata,
148 )
149 }
150}