open-standards/eoc/eoc-rs/crates/eoc-agent/src/lib.rs
Transaction Science 30370b1996
All checks were successful
standards workspaces build + test / jouleclaw-rs (pull_request) Successful in 1m48s
standards workspaces build + test / aac-rs (pull_request) Successful in 36s
standards workspaces build + test / joule-code-rs (pull_request) Successful in 37s
standards workspaces build + test / joulecontract-rs (pull_request) Successful in 34s
standards workspaces build + test / map-rs (pull_request) Successful in 31s
standards workspaces build + test / mesh-rs (pull_request) Successful in 25s
standards workspaces build + test / openpay (pull_request) Successful in 1m27s
standards workspaces build + test / eoc-rs (pull_request) Successful in 1m50s
standards workspaces build + test / jcp-rs (pull_request) Successful in 1m18s
standards workspaces build + test / proof-rs (pull_request) Successful in 1m12s
standards workspaces build + test / sandbox-rs (pull_request) Successful in 2m10s
standards workspaces build + test / smart-byte-rs (pull_request) Successful in 2m38s
standards workspaces build + test / arl-rs (pull_request) Successful in 2m6s
standards workspaces build + test / joule-ui-rs (pull_request) Successful in 2m35s
standards workspaces build + test / jouledesk-rs (pull_request) Successful in 2m39s
standards workspaces build + test / joulehook-rs (pull_request) Successful in 2m39s
standards workspaces build + test / sense-rs (pull_request) Successful in 2m33s
standards workspaces build + test / macOS energy backend (jcp-energy) (pull_request) Successful in 2m14s
eoc-agent: a local model's measured step is a measured span
LlmReply carried a bare `microjoules`, and charge_llm_step tagged every
model step Estimated. A backend that ran a model in this process and read
it on a live counter (an eoc-local backend's MeterSpan, now backed by
the per-process counter) had no way to say so. Its measured figure was
recorded as an estimate.

- LlmReply carries a JouleCost. LlmReply::new is the provider's estimate,
  as before, and LlmReply::with_cost gives a figure with its grade.
- charge_llm_step records the provider's cost as graded and charges the
  budget that figure.
- NeuralProvider wraps any in-process NeuralBackend as an LlmProvider.
  The reply's energy is the backend's own JouleCost. Tokens are counted
  from the text (approx_tokens, or the model's tokenizer through
  with_token_counter) and feed only the token budget.

A model served over the network still reports an estimate: it ran on
hardware no counter here reads.

Tests: an MlcBackend metered by an advancing counter gives a measured
span, and a held, failed or stub counter gives the estimate. A whole
ReAct run records the model step measured. Reverting charge_llm_step to
always-estimated fails both end-to-end tests.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-01 01:27:42 -04:00

59 lines
2.4 KiB
Rust

//! EOC — agentic loops with per-step energy accounting.
//!
//! Modern agent systems are not single-shot completions: they iterate
//! through *think → act → observe* cycles, sometimes branching, sometimes
//! reflecting, sometimes planning ahead. EOC takes that loop and threads
//! a **joule meter** through every step, so an agent's total cost is the
//! sum of (a) its model steps, each at the figure and grade its provider
//! reports: measured when a model running in this process was read by a
//! live counter ([`NeuralProvider`]), estimated for a model served over the
//! network; (b) tool-execution energy; and (c) any local inference performed
//! between steps.
//!
//! ## Loops shipped
//!
//! | Module | Pattern |
//! |--------------------------------|--------------------------------------|
//! | [`react`] | ReAct (Thought / Action / Observation) |
//! | [`reflexion`] | Reflexion (self-critique + episodic memory) |
//! | [`tot`] | Tree of Thoughts (BFS / DFS) |
//! | [`lats`] | LATS / MCTS-UCT |
//! | [`plan_execute`] | Plan-and-Execute (planner + worker) |
//! | [`codeact`] | CodeAct (tool calls expressed as code) |
//!
//! ## Budgets
//!
//! Every loop accepts a [`Budget`](agent::Budget) that combines
//! [`TokenBudget`](budget::TokenBudget), [`JouleBudget`](budget::JouleBudget),
//! and a tool-call cap. The first cap to be exhausted produces an
//! [`StopReason::BudgetExhausted`](agent::StopReason) — no loop in this
//! crate is allowed to run unbounded.
//!
//! ## No unsafe, no `unwrap` outside tests.
#![forbid(unsafe_code)]
#![warn(missing_docs)]
pub mod agent;
pub mod budget;
pub mod codeact;
pub mod error;
pub mod lats;
pub mod plan_execute;
pub mod provider;
pub mod react;
pub mod reflexion;
pub mod tot;
pub mod trace;
pub use agent::{Agent, AgentLoop, AgentStep, Budget, StopReason};
pub use budget::{JouleBudget, TokenBudget};
pub use codeact::CodeActLoop;
pub use error::{AgentError, AgentResult};
pub use lats::{LatsConfig, LatsLoop};
pub use plan_execute::{PlanExecuteLoop, Planner, Worker};
pub use provider::NeuralProvider;
pub use react::{ReActLoop, ReActStep};
pub use reflexion::{ReflexionLoop, ReflexionMemory};
pub use tot::{Search, ToTLoop, Thought};
pub use trace::{Span, Trace};