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
//! # 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", 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(())
//! # }
//! ```
pub use crate;
pub use crate;
pub use crate;