localharness/lib.rs
1//! # localharness — agents that own themselves
2//!
3//! One Rust crate that's both an agent SDK — streaming text, custom tools,
4//! safety policies, background triggers, MCP, context compaction, all from a
5//! single `cargo add` with zero external binaries — and (on `wasm32` with
6//! `browser-app`) the same loop compiled into a wallet-owning, self-sovereign
7//! agent that runs in the browser.
8//!
9//! ## Quick start
10//!
11//! ```rust,no_run
12//! use localharness::{Agent, GeminiAgentConfig};
13//!
14//! # async fn run() -> localharness::Result<()> {
15//! let cfg = GeminiAgentConfig::new(std::env::var("GEMINI_API_KEY").unwrap())
16//! .with_system_instructions("You are a concise code reviewer.");
17//!
18//! let agent = Agent::start_gemini(cfg).await?;
19//! let response = agent.chat("What is 2+2?").await?;
20//! println!("{}", response.text().await?);
21//! agent.shutdown().await?;
22//! # Ok(())
23//! # }
24//! ```
25//!
26//! ## Layers
27//!
28//! | Layer | Type | Purpose |
29//! |-------|------|---------|
30//! | 1 | [`Agent`] | High-level facade: connect, chat, shutdown. |
31//! | 2 | [`Conversation`] / [`ChatResponse`] | Stateful session, multi-cursor streams. |
32//! | 3 | [`connections::Connection`] | Transport abstraction. |
33//! | aux | [`Filesystem`] | What the 8 fs-shaped built-in tools call into; swap the impl to target OPFS, an in-memory FS, etc. |
34//!
35//! [`Agent`]: agent::Agent
36//! [`Conversation`]: conversation::Conversation
37//! [`ChatResponse`]: conversation::ChatResponse
38//! [`connections::Connection`]: connections::Connection
39//! [`Filesystem`]: filesystem::Filesystem
40//!
41//! ## Stability & MSRV
42//!
43//! MSRV is Rust 1.85 (edition 2024); raising it is a minor-version bump. The 1.0
44//! semver promise COVERS the agent-SDK surface: the layer seams (`Agent` + the
45//! `*AgentConfig`s, `Conversation`/`ChatResponse`/`ChatCursor`,
46//! `connections::Connection`/`ConnectionStrategy`); the extension traits
47//! (`tools::Tool`/`ToolRunner`, `hooks`, `policy`, `triggers`); the wire-neutral
48//! types (`content`, `types`, `error::Error`/`Result`, `filesystem::Filesystem` + the
49//! 8 fs builtins, the named `builtins`); and the root-re-exported backend
50//! constructors + `*BackendConfig`/`*Connection`/`*ConnectionStrategy`.
51//!
52//! NOT covered (may change in any release, semver-exempt): the `wallet` feature —
53//! the entire `registry` surface is coupled to live on-chain diamond addresses +
54//! facets that churn via `diamondCut`, plus `wallet`/`tempo_tx`; the per-backend
55//! `backends::*::{wire,api,compaction}` modules; opaque history byte formats
56//! (`history_bytes`/`set_history_bytes`); the `browser-app` app (wasm-only, private);
57//! the `local` feature; and the platform compiler/runtime helpers (`rustlite`,
58//! `soliditylite`, `bashlite`, `raster`, `compose`). Growable public structs/enums
59//! carry `#[non_exhaustive]` (or grow only via `..Default::default()`-friendly
60//! fields) so additive changes stay non-breaking.
61
62// On wasm32 the crate is single-threaded (browser) and intentionally
63// uses `Arc` over non-Send/Sync values via the `MaybeSendSync` marker
64// (see `runtime.rs`). Clippy's `arc_with_non_send_sync` fires on every
65// such use; it's by design on this target, so silence it crate-wide for
66// wasm rather than peppering `#[allow]` across the modules.
67#![cfg_attr(target_arch = "wasm32", allow(clippy::arc_with_non_send_sync))]
68
69// On wasm32 the full architecture (Agent → Conversation → Connection) compiles:
70// the trait bounds use the `MaybeSendSync` marker (runtime.rs) so every
71// `#[async_trait]` is `?Send` on wasm, and the agent/conversation/connections
72// modules are declared unconditionally. Only `run_command` + the MCP stdio bridge
73// are `feature = "native"`-gated; the browser app supplies its own OPFS filesystem.
74/// Layer-1 agent facade: connect, chat, shutdown.
75pub mod agent;
76/// Backend implementations (Gemini, MCP).
77pub mod backends;
78/// The crate-wide built-in tool registry (fs tools, ask_question, finish,
79/// call_agent, ...) — backend-neutral; every backend registers from here.
80/// Formerly `backends::gemini::tools` (the re-export shim is gone).
81pub mod builtins;
82/// Transport abstraction traits.
83pub mod connections;
84/// Multimodal input primitives (text, images, documents, audio, video).
85pub mod content;
86/// Stateful conversation session with multi-cursor streaming.
87pub mod conversation;
88/// Typed error hierarchy.
89pub mod error;
90/// The one `LHxxxx` error-code registry (compile / runtime / tx-revert).
91pub mod error_codes;
92/// Filesystem abstraction for built-in fs tools.
93pub mod filesystem;
94pub(crate) mod runtime;
95/// Hook traits for observing and gating agent events.
96pub mod hooks;
97/// Declarative tool-execution policy engine.
98pub mod policy;
99/// Custom tool registration and dispatch.
100pub mod tools;
101/// Background triggers that push messages into the agent.
102pub mod triggers;
103/// Public boundary types (steps, tool calls, usage, config, etc.).
104pub mod types;
105
106/// Rust-subset to wasm compiler.
107pub mod rustlite;
108
109/// bashlite — a tiny, total, sandboxed shell that scripts the platform's
110/// filesystem in one pass (the cost unlock: a multi-step fs chore collapses
111/// from N LLM rounds to ONE `execute_script` tool call). Native-testable core
112/// over a [`bashlite::BashHost`] trait. See `src/bashlite/` + `design/bashlite.md`.
113pub mod bashlite;
114
115/// Solidity/EVM-subset to EVM-bytecode compiler foundation (the EVM analog of
116/// [`rustlite`]): a bytecode assembler + worked dispatch/init scaffolding. See
117/// `design/soliditylite.md`.
118pub mod soliditylite;
119
120/// Pure framebuffer rasterization + `Viewport` (the host::compose geometry
121/// foundation; native-testable, used by `app::display`). See `src/raster.rs`.
122pub mod raster;
123
124/// Pure HTML → framebuffer rasterizer (block-level text subset over the
125/// [`raster`] bitmap font; native-testable, zero web-sys). Hoisted out of
126/// `app::display` (roadmap R5). See `src/html_fb.rs`.
127pub mod html_fb;
128
129/// Compositor scheduling for `host::compose` — the deferred-mutation module
130/// table (native-testable control flow). See `src/compose.rs`.
131pub mod compose;
132
133/// Pure hex / address / amount encoding helpers (native-testable). Hoisted out
134/// of `app::events` so they run under `cargo test`. See `src/encoding.rs`.
135pub mod encoding;
136
137/// Pure, deterministic CONVERGENT reconcile for cross-device shared-folder sync
138/// (native-testable). Hoisted out of `app::sharedfs_sync` so the convergence /
139/// symmetry property runs under `cargo test`. See `src/sharedfs_reconcile.rs`.
140pub mod sharedfs_reconcile;
141
142/// Pure signed-envelope layer for on-chain WebRTC signaling blobs — the SDP
143/// sealing/sender-authentication core (native-testable; needs `wallet` for
144/// k256). Hoisted out of `app::teams_sync` so the seal/unseal round-trip and
145/// tamper/forgery rejection run under `cargo test`. See `src/signaling_seal.rs`.
146#[cfg(feature = "wallet")]
147pub mod signaling_seal;
148
149/// Pure Last-Writer-Wins key/value CRDT for SessionRoom shared state (#22):
150/// folds a set of decrypted ops into a converged map (order-independent,
151/// idempotent, optional TTL). Native-testable. See `src/kv_reduce.rs`.
152pub mod kv_reduce;
153
154/// Pure decision core for a decentralized scheduler keeper (krafto feedback #1.5,
155/// the P2P answer to the centralized Vercel cron): which due `ScheduleFacet` jobs
156/// THIS keeper should fire this tick — deterministic fair assignment (no
157/// thundering herd) + rank-staggered backoff (liveness if a peer is offline).
158/// Native-testable, zero chain/P2P deps. See `src/keeper.rs`.
159pub mod keeper;
160
161/// Pure work-cycle decision core for an autonomous company of role-agents
162/// (`design/autonomous-business/`): allocate a funded task to the best-fit
163/// role-agent, judge the delivered result, pay the worker, and attest the
164/// outcome — modeling one claim→work→judge→pay→attest cycle as DATA (every
165/// side effect is an [`work_cycle::Action`] descriptor the caller maps onto a
166/// real `registry` bounty/x402/attest call). Native-testable, zero chain deps.
167/// See `src/work_cycle.rs`.
168pub mod work_cycle;
169
170/// PURE PLANNING SHELL over [`work_cycle`]: a [`work_cycle_runtime::Reader`]
171/// (no `registry`/`wallet` dep) feeds the read-only company view into
172/// [`work_cycle_runtime::plan_cycle`], which runs [`work_cycle::step`] to
173/// quiescence and returns a [`work_cycle_runtime::CyclePlan`] of WHAT WOULD
174/// HAPPEN — preview ONLY, executes/broadcasts NOTHING. The greenlight-gated
175/// executor that maps each planned [`work_cycle::Action`] onto its sponsored
176/// `registry` call is deferred. Native-testable. See `src/work_cycle_runtime.rs`.
177pub mod work_cycle_runtime;
178
179/// Pure economics decision core for the Accounting (CFO / Treasurer) role of an
180/// autonomous company (`design/autonomous-business/roles/accounting.md`): a
181/// period [`accounting::Ledger`] (treasury + costs + earned revenue + SEED, held
182/// apart) plus pure judgements — [`accounting::net_position`] (signed, seed
183/// EXCLUDED), [`accounting::runway_cycles`], [`accounting::breakeven_price`],
184/// [`accounting::is_solvent`] / [`accounting::is_self_funding`]. Honest about the
185/// inherited "seed-capitalized, not self-funding" constraint. Native-testable,
186/// zero chain deps. See `src/accounting.rs`.
187pub mod accounting;
188
189/// PURE MULTI-CYCLE FORECAST core: runs [`work_cycle::step`] forward over N
190/// cycles from a [`simulation::SimConfig`] and projects the company's trajectory
191/// — per-cycle [`simulation::CycleSnapshot`]s (treasury, throughput, net
192/// position) plus the run [`simulation::Forecast`] (total accepted, runway
193/// [`simulation::Forecast::ran_out_at`], final [`accounting::Ledger`]). Each
194/// cycle injects the off-core delivery at a fixed assumed quality, advances one
195/// transition, and books revenue/costs. Preview ONLY — executes/broadcasts
196/// NOTHING. Native-testable, zero chain deps. See `src/simulation.rs`.
197pub mod simulation;
198
199/// Pure role-fit scoring core for the HR (People Ops / Recruiting) role of an
200/// autonomous company (`design/autonomous-business/roles/hr.md`): score + rank
201/// candidate agents against an open seat ([`hiring::RoleNeed`]) by exact role +
202/// proven reputation ([`hiring::score_candidate`] / [`hiring::rank_candidates`]).
203/// A [`hiring::Candidate`] mirrors a [`work_cycle::WorkerState`] (with a [`From`]
204/// impl) and ranks the same way [`work_cycle::assign_next_task`] allocates, so HR
205/// ranking and work-cycle allocation agree. Native-testable. See `src/hiring.rs`.
206pub mod hiring;
207
208/// SessionRoom op sealing/opening + deterministic per-room key derivation (#22):
209/// AES-256-GCM confidentiality under `K_room` inside a writer-signed,
210/// room-bound `signaling_seal` envelope. Needs `wallet` for k256/keccak.
211/// Native-testable. See `src/kv_room.rs`.
212#[cfg(feature = "wallet")]
213pub mod kv_room;
214
215/// Pure typed-confirmation challenge gate for destructive tools
216/// (native-testable, `turn_flow` hoisting pattern): single-use random nonce
217/// bound to exact tool+args, valid only when typed by the USER. Enforced by
218/// `app::chat::confirm_guard` at the dispatch layer. See `src/confirm.rs`.
219pub mod confirm;
220
221/// Static safety lint for agent-authored facet cuts (SolidityLite §7 Layer 1):
222/// reserved-selector denylist + clash + `_init==0`. Pure + native-testable;
223/// wired into `localharness facet cut` as a pre-flight. See `src/cut_guard.rs`.
224pub mod cut_guard;
225
226/// Pure turn-outcome classification for the continuous-execution chat loop
227/// (native-testable). Hoisted out of `app::chat` so its guard tests run under
228/// `cargo test`. See `src/turn_flow.rs`.
229pub mod turn_flow;
230
231/// Pure state machine for the turn-stage micro-pipeline ("paying → thinking
232/// → streaming") shown inside a pending assistant turn (native-testable,
233/// same hoisting pattern as `turn_flow`). See `src/turn_stage.rs`.
234pub mod turn_stage;
235
236/// Single-table tool parameters: the `tool_params!` macro generates BOTH the
237/// typed args struct AND the Gemini-safe wire `input_schema` from ONE table
238/// (schema↔parse drift impossible by construction; zero deps, wasm-clean).
239/// Migrated wasm-gated chat tools hoist their tables here (the `turn_flow`
240/// pattern) so plain `cargo test` byte-checks their schemas. Opt-in per tool.
241/// See `src/tool_params.rs`.
242pub mod tool_params;
243
244/// Pure DIFFICULTY ROUTER core (native-testable): classifies each chat turn
245/// into a [`difficulty::TurnTier`] (Light / Standard / Heavy) and maps it to a
246/// model preference + [`types::ThinkingLevel`], so the in-tab agent can route
247/// cheap/minimal-thinking turns away from the premium tier reserved for
248/// build/debug. Wired into `app::chat` per-turn. See `src/difficulty.rs`.
249pub mod difficulty;
250
251/// Pure INTENT-ROUTER core (native-testable): classifies a chat message as
252/// [`router::Route::Free`] (balance/UI-command/docs-FAQ — answered locally,
253/// zero `$LH`) or `Metered` (the normal ~1 `$LH` model turn). Exact-allowlist
254/// conservative by contract; `'!'` always forces Metered. The
255/// [`router::IntentClassifier`] trait is the seam a local-model classifier
256/// (in-browser Gemma) can slot into later. Wired in `app::chat::router_wire`.
257/// See `src/router.rs`.
258pub mod router;
259
260/// Pure lessons-blob merging + prompt-section composition for the agent
261/// LESSONS LOOP (native-testable). The browser `record_lesson` tool, the
262/// headless CLI `call`, and the proxy scheduler worker all fold its output
263/// into the system prompt. See `src/lessons.rs`.
264pub mod lessons;
265
266/// Pure agent-skills blob (JSON array) upsert/remove + prompt-section
267/// composition for the agent SKILLS LOOP (native-testable). The browser
268/// `create_skill` tool, the headless CLI `call`, and the proxy scheduler worker
269/// all fold its output into the system prompt. See `src/skills.rs`.
270pub mod skills;
271
272/// Pure subdomain-name validation (native-testable) — the single source of
273/// truth shared by the browser create tools and kept in sync with the
274/// on-chain `LocalharnessRegistryFacet._isValidName` rule. See `src/subdomain.rs`.
275pub mod subdomain;
276
277/// THE single source of truth for the drift-prone FACTS mirrored across the
278/// three managed docs (`web/skill.md`, `web/llms.txt`, `README.md`): chain
279/// addresses (from `registry::chain`), the crate version, `$LH` pricing, the
280/// agent tool list, and the CLI command list. `cargo run --bin gen-docs` fills
281/// each doc's `<!-- GEN:key -->` block from here; a `cargo test` drift gate +
282/// the release pre-flight enforce sync. Gated on `wallet` (it reads
283/// `registry::chain`) AND `not(wasm32)`: it only feeds the native gen-docs bin +
284/// the native drift test, so excluding it from the wasm build keeps the testnet
285/// `MODERATO` strings (which `render_chains` documents) out of the prod bundle —
286/// the last non-test wasm referencer of `MODERATO`. See `docs/SOP-doc-integrity.md`.
287#[cfg(all(feature = "wallet", not(target_arch = "wasm32")))]
288pub mod docs_manifest;
289
290// Inline SVG QR-code generation for the app's share surfaces (device
291// pairing, publish share, `?invite=` links). Feature-gated like `app`
292// but NOT wasm-gated, so its unit test runs under a native
293// `cargo test --features browser-app` (the `turn_flow` hoisting pattern).
294#[cfg(feature = "browser-app")]
295mod qr;
296
297// Apex fresh-visitor landing markup — hoisted out of the wasm-gated `app/`
298// tree (the raster.rs/compose.rs pattern) so the SHIPPING markup also
299// renders natively: `cargo test --features browser-app landing_preview`
300// writes `target/landing-preview.html` for screenshot review. The `test`
301// arm keeps non-test native builds free of dead-code (only the wasm app
302// and the preview test consume it).
303#[cfg(all(feature = "browser-app", any(target_arch = "wasm32", test)))]
304mod landing;
305
306// The browser-resident IDE. Gated on the `browser-app` feature AND a
307// wasm target, so a native `cargo add localharness` never compiles it.
308#[cfg(all(feature = "browser-app", target_arch = "wasm32"))]
309mod app;
310
311// M6 spike: in-browser secp256k1 keypair via alloy's local signer.
312// Pure-compute (no HTTP, no JS deps), so it builds on every target.
313/// Secp256k1 keypair, BIP-39 mnemonics, and RLP encoding.
314#[cfg(feature = "wallet")]
315pub mod wallet;
316
317// JSON-RPC client for the `LocalharnessRegistry` diamond on Tempo
318// Moderato. Read-only views (`check_name`, `owner_of_name`,
319// `tba_of_name`, `list_owned_tokens`) work on every target; the
320// sponsored writes sign with a `k256` key (needs the wallet feature)
321// and use `tokio::time::sleep` on native / `setTimeout` on wasm to
322// poll the receipt. The diamond's address is baked in as
323// `registry::REGISTRY_ADDRESS()`; the RPC URL is `registry::RPC_URL()`.
324/// JSON-RPC client for the on-chain registry diamond.
325#[cfg(feature = "wallet")]
326pub mod registry;
327
328// Tempo Transaction encoder (tx type 0x76). Implements Tempo's native
329// account-abstraction tx format so users can pay fees in $LH instead
330// of native and so a project-controlled fee_payer can sponsor user
331// txs without users holding any balance. Wire format per
332// docs.tempo.xyz/protocol/transactions/spec-tempo-transaction.
333/// Tempo Transaction (tx type 0x76) encoder for native account abstraction.
334#[cfg(feature = "wallet")]
335pub mod tempo_tx;
336
337/// App-injected x402 payment-signing hook (lets the backend `call_agent`
338/// tool sign payments using the app-layer wallet).
339pub mod x402_hook;
340
341pub use agent::{Agent, AgentConfig, GeminiAgentConfig, MockAgentConfig};
342#[cfg(feature = "anthropic")]
343pub use agent::AnthropicAgentConfig;
344#[cfg(feature = "openai")]
345pub use agent::OpenAiAgentConfig;
346#[cfg(feature = "local")]
347pub use agent::LocalAgentConfig;
348pub use backends::gemini::{
349 decode_transcript_bytes, GeminiBackendConfig, GeminiConnection, GeminiConnectionStrategy,
350};
351pub use backends::mock::{
352 MockConnection, MockConnectionBuilder, MockConnectionStrategy, MockRunners, ScriptedTurn,
353};
354#[cfg(feature = "anthropic")]
355pub use backends::anthropic::{
356 AnthropicBackendConfig, AnthropicConnection, AnthropicConnectionStrategy, AnthropicRunners,
357};
358#[cfg(feature = "openai")]
359pub use backends::openai::{
360 OpenAiBackendConfig, OpenAiConnection, OpenAiConnectionStrategy, OpenAiRunners,
361};
362#[cfg(feature = "native")]
363pub use backends::mcp::{McpBridge, McpClient, McpToolDecl};
364pub use connections::{Connection, ConnectionStrategy};
365pub use content::{Content, Media, MediaKind, Part};
366pub use conversation::{ChatCursor, ChatResponse, Conversation};
367pub use error::{Error, Result};
368pub use filesystem::{DirEntry, EntryKind, Filesystem, Metadata, SharedFilesystem, WalkEntry};
369#[cfg(feature = "native")]
370pub use filesystem::NativeFilesystem;
371pub use hooks::{
372 HookContext, HookRunner, OnSessionEndHook, OnSessionStartHook, OperationContext,
373 PostToolCallHook, PostTurnHook, PreToolCallDecideHook, PreTurnHook, SessionContext,
374 TurnContext,
375};
376pub use policy::{
377 allow_all, deny_all, enforce, evaluate, is_path_in_workspace, secure_normalize_path,
378 workspace_only, AskUserHandler, Decision, Policy, Predicate,
379};
380pub use tools::{ClosureTool, Tool, ToolContext, ToolRunner};
381pub use triggers::{every, Trigger, TriggerContext, TriggerRunner};
382pub use types::{
383 BuiltinTool, CapabilitiesConfig, HookResult, Step, StepSource, StepStatus, StepTarget,
384 StepType, StreamChunk, SystemInstructions, ThinkingLevel, ToolCall, ToolResult,
385 TranscriptEntry, TranscriptRole, TriggerDelivery, UsageMetadata,
386};
387
388// NOT public API: re-exports the `tool_params!` macro expansion depends on, so
389// the macro works in downstream crates without them naming serde_json.
390#[doc(hidden)]
391pub mod __private {
392 pub use serde_json;
393}