harness_loop_engine/lib.rs
1//! # harness-loop-engine — loop engineering for harness-rs
2//!
3//! *Agent = Model + Harness.* A **harness** wraps a single agent call. A
4//! **loop** wraps the harness: it runs that call again and again, on a
5//! cadence, with state, verification, budgets, and gates — driving toward a
6//! goal over time instead of in one shot. This crate is harness-rs's loop
7//! layer.
8//!
9//! > "Loop engineering is replacing yourself as the person who prompts the
10//! > agent. You design the system that does it instead." — and you stay the
11//! > engineer responsible for that system.
12//!
13//! The building blocks already live elsewhere in harness-rs — scheduling
14//! (`harness-scheduler`), worktrees (`harness-sandbox`), sub-agents
15//! (`harness-loop`), memory (`harness-core`), MCP (`harness-mcp`). What this
16//! crate adds is the **orchestration discipline** that turns those parts
17//! into a loop you can trust:
18//!
19//! - [`LoopLevel`] — maturity levels **L1 (report) → L2 (assisted) → L3
20//! (unattended)**. A loop earns autonomy in stages.
21//! - [`HumanGate`] — the proceed-or-escalate decision, tied to the level.
22//! Built-ins: [`AlwaysEscalate`], [`AllowlistGate`], [`CallbackGate`].
23//! - [`ActionExecutor`] — the side-effect handoff after a verified L3
24//! auto-approval. Built-ins: [`ApprovalOnlyExecutor`],
25//! [`CallbackActionExecutor`].
26//! - [`TokenBudget`] — a per-round spend ceiling, because unattended loops
27//! spend without bound if you let them.
28//! - [`LoopSpec`] — the inert, serializable description of a loop.
29//! - [`LoopEngine`] — the runner: recall state → isolate → **maker**
30//! sub-agent → **checker** sub-agent → gate → record state.
31//! - [`LoopScheduler`] — runs loops on their cadence.
32//! - [`patterns`] — the seven named production loops (daily triage, PR
33//! babysitter, CI sweeper, …), each a ready-made [`LoopSpec`].
34//!
35//! ## The anatomical loop
36//!
37//! ```text
38//! schedule (cadence)
39//! │
40//! ▼
41//! recall STATE / memory ──► isolated worktree (sandbox)
42//! │ │
43//! │ ▼
44//! │ maker sub-agent (proposes)
45//! │ │
46//! │ ▼
47//! │ checker sub-agent (tests + gates)
48//! │ │
49//! │ ▼
50//! │ human gate? ──┬─ safe/allowlisted ─► action executor
51//! │ └─ risky/ambiguous ──► escalate
52//! ▼ │
53//! write STATE / memory ◄────────────────────────── recurse next tick
54//! ```
55//!
56//! ## Two debts to watch
57//!
58//! Loop engineering names two failure modes that accrue silently. This
59//! crate makes them *visible* rather than solving them — they are
60//! engineering responsibilities, not features:
61//!
62//! - **Intent debt** — the drift between what a loop was *meant* to do and
63//! what it actually does. Antidote: [`LoopSpec::intent`] is a required,
64//! one-sentence statement of purpose, injected into every maker turn and
65//! printed in every report. Review it as the loop evolves.
66//! - **Comprehension debt** — the gap between what the loop ships and what
67//! humans still understand about its behaviour. Antidote: the
68//! maker/checker split, the recorded state spine, and rendered reports
69//! keep a legible trail of every round.
70//!
71//! ## Safety stance
72//!
73//! Verification stays on you — unattended loops make unattended mistakes.
74//! Defaults are conservative: L1 makers are strictly read-only, the default
75//! gate for every level is [`AlwaysEscalate`], and L3 auto-proceed requires
76//! an explicit [`AllowlistGate`]. Graduate a loop's level only as you build
77//! trust in it.
78
79mod budget;
80mod engine;
81mod level;
82pub mod patterns;
83mod scheduler;
84mod spec;
85
86pub use budget::{BudgetLimit, BudgetState, TokenBudget};
87pub use engine::{
88 ActionError, ActionExecutor, ActionReceipt, ApprovalOnlyExecutor, CallbackActionExecutor,
89 LoopEngine, RoundOutcome, RoundReport,
90};
91pub use level::{
92 AllowlistGate, AlwaysEscalate, CallbackGate, GateDecision, HumanGate, LoopLevel,
93 ProposedAction, default_gate_for,
94};
95pub use scheduler::{LoopScheduler, LoopSink, StdoutSink};
96pub use spec::LoopSpec;