1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
//! # agentplane
//!
//! A durable, replayable, policy-governed runtime for agents whose steps invoke
//! non-deterministic models and mutate real systems.
//!
//! One sentence carries the rest:
//!
//! > **The journal is the plan of record.** Orchestration is deterministic and
//! > replayable; every non-deterministic act — inference, tool call, clock, RNG,
//! > deadline resolution — is an [`Effect`](core::Effect) performed *at most
//! > once*, journaled, and read back on replay.
//!
//! ## The determinism boundary
//!
//! ```text
//! ┌──────────────── DETERMINISTIC ZONE ────────────────┐
//! │ plan traversal · guards · retry decisions · budget │
//! │ policy evaluation · label joins · record upcasting │
//! │ │
//! │ Replay re-executes this and MUST reproduce the │
//! │ identical sequence of effect keys. │
//! └───────────────────────┬─────────────────────────────┘
//! │ cx.effect(…)
//! ┌───────────────────────▼─────────────────────────────┐
//! │ NON-DETERMINISTIC ZONE │
//! │ inference · tools · clock · RNG · network · humans │
//! │ │
//! │ Executed at most once. Journaled. Replay reads. │
//! └──────────────────────────────────────────────────────┘
//! ```
//!
//! Three layers enforce it, because convention is not enforcement:
//!
//! 1. **Lint gating** — `clippy.toml` denies `SystemTime::now`, `rand::random`,
//! `Ulid::new` and friends crate-wide.
//! 2. **Effect-key verification** — on replay, a recomputed key that differs
//! from the journaled one quarantines the run rather than diverging silently
//! ([`core::StepError::NonDeterminism`]).
//! 3. **Storage constraints** — the journal's unique index makes "an effect is
//! started at most once per run" a database invariant, not a code path.
//!
//! ## Example
//!
//! ```no_run
//! use agentplane::core::{Outcome, Skill, SkillDescriptor, Tainted};
//! use agentplane::journal::JournalStore;
//! use agentplane::runtime::{Mode, Runtime, StepCtx};
//! use std::sync::Arc;
//!
//! #[derive(Debug)]
//! struct Greet;
//!
//! #[async_trait::async_trait]
//! impl Skill for Greet {
//! fn descriptor(&self) -> SkillDescriptor {
//! SkillDescriptor::new("greet").provides("demo.greet")
//! }
//!
//! async fn invoke(
//! &self,
//! cx: &mut StepCtx<'_>,
//! input: Tainted<serde_json::Value>,
//! ) -> Result<Outcome, agentplane::core::SkillError> {
//! // `now()` is a journaled effect: on replay it returns the recorded
//! // instant rather than reading the clock again.
//! let at = cx.now().await?;
//! Ok(Outcome::done(input.map(|v| serde_json::json!({
//! "greeted": v, "at": at.to_string(),
//! }))))
//! }
//! }
//!
//! # async fn run(store: Arc<dyn JournalStore>) -> Result<(), Box<dyn std::error::Error>> {
//! // With the default features, `agentplane::store::RedbStore` is one.
//! let runtime = Runtime::builder(store).skill(Greet).build();
//! let outcome = runtime.run("greet", Tainted::trusted(serde_json::json!({"name": "world"}))).await?;
//!
//! // Replaying re-executes the deterministic zone and reads every effect back
//! // from the journal. No clock is read; no tool is called twice. `Strict`
//! // additionally fails if this build wants an effect the journal lacks.
//! runtime.replay(outcome.run_id, Mode::Strict).await?;
//! # Ok(())
//! # }
//! ```
// A backend feature must deliver its backend.
//
// `store` was declared under `redb` alone while holding both backends, so
// `--no-default-features --features postgres` compiled cleanly, pulled in
// `tokio-postgres` and `deadpool-postgres`, and exposed *no store module at
// all* — the Postgres deployment paid for three dependency crates and could not
// name `PostgresStore`. `just features` reported success throughout, because
// building a feature and reaching what it names are different questions and it
// only ever asked the first.
//
// These make the compiler ask the second. Naming the type is what a consumer
// does, so a gate that configures it out fails here rather than in their editor.
const _: fn = ;
const _: fn = ;
pub use crate;
pub use crate;
pub use crate;
/// The names every program needs, so the first one is one `use` line.
///
/// ```
/// use agentplane::prelude::*;
/// ```
///
/// # What is in here, and the rule
///
/// A prelude earns its place by being *predictable*, so this one is chosen by a
/// stated rule rather than by taste: **a name belongs here if a program that
/// does nothing unusual needs it.** Measured, not guessed — every name below
/// appears in a third or more of the crate's own examples, and the set is
/// exactly what the getting-started program imports, which is why that program
/// now opens with one line instead of five.
///
/// The four groups are the four things any program touches: the skill you write
/// (`Skill`, `SkillDescriptor`, `SkillError`, `Outcome`), the context it is
/// handed (`StepCtx`), the labels its data carries (`Tainted`, `Trust`,
/// `Sensitivity`), and the plane that runs it (`Runtime`, `RunStatus`, `Mode`,
/// `JournalStore`, and the default store).
///
/// # What is deliberately left out
///
/// Names that are common in this crate but collision-prone in somebody else's:
/// `Record`, `Digest`, `Label`, `Capability`, `Seq`. A prelude that shadows a
/// user's own `Record` costs more than the import it saved, and each of those is
/// one explicit `use` away. Anything feature-gated beyond the default backend is
/// out for the same reason a glob would be: what a prelude imports must not
/// depend on which features happen to be on, or the same `use` line means
/// different things in two crates.
///
/// Everything here is also reachable by its full path; the prelude adds no API.