Framework: build and run agents inside a Rust application with the everruns crate --- # Everruns Documentation > Tutorials, how-to guides, reference, and explanation for building and operating Everruns agents. Source: Everruns is a **durable agentic harness engine** built on Rust. It provides APIs for managing agents, sessions, and long-running tasks, with real-time SSE event streaming and PostgreSQL-backed durability. Build directly inside a Rust application with the [Everruns Framework](https://docs.everruns.com/framework/), or use the Platform and SDK documentation for a durable deployment and remote clients. To skip operating the Platform entirely, use the hosted edition at [Everruns Cloud](https://app.everruns.com). ## What kind of help do you need? The documentation is organised by what you’re trying to do. [Everruns Cloud](https://app.everruns.com) — Use the hosted Platform without running it yourself. Open in early access, free for now, bring your own model provider keys. [Framework](https://docs.everruns.com/framework/) — Build and run agents in a Rust process with the application-facing everruns crate. [Learn — Tutorials](https://docs.everruns.com/tutorials/run-an-agent/) — Step-by-step lessons that end with a running agent. Start here if you're new. [Do — How-to guides](https://docs.everruns.com/how-to/) — Task-oriented recipes for common problems. Use these when you know what you want to build. [Understand — Explanation](https://docs.everruns.com/explanation/) — Background and design rationale. Read these when reference docs aren't enough on their own. [Look up — Reference](https://docs.everruns.com/api/) — API endpoints, event types, capability catalog, CLI flags, environment variables. The dry stuff. ## Getting started fast 1. Choose [Everruns Cloud](https://app.everruns.com) for the hosted Platform, the [Framework](https://docs.everruns.com/framework/) for an in-process Rust application, or [Docker Compose](https://docs.everruns.com/getting-started/docker-compose/) to run the full Platform yourself. 2. Run the [Framework quickstart](https://docs.everruns.com/framework/quickstart/) or follow the [SDK tutorial](https://docs.everruns.com/tutorials/building-agents-using-sdk/). 3. Browse [How-to guides](https://docs.everruns.com/how-to/) to do something specific. ## Popular destinations [Core concepts](https://docs.everruns.com/explanation/concepts/) — Harness, agent, session, capability, event — what they are and how they compose. [Capabilities catalog](https://docs.everruns.com/capabilities/) — Every built-in capability with tools, parameters, and dependencies. [Event reference](https://docs.everruns.com/event-reference/) — Every event type in the Everruns event protocol, with payloads. [REST API reference](https://docs.everruns.com/api/) — OpenAPI-generated reference for every endpoint. [Everruns Framework](https://docs.everruns.com/framework/) — Application-facing Rust agents, models, tools, sessions, events, and extension points. [SDKs](https://docs.everruns.com/features/sdk/) — Official client libraries for Rust, Python, and TypeScript. [CLI](https://docs.everruns.com/features/cli/) — Command-line interface for managing agents, sessions, and conversations. [Architecture](https://docs.everruns.com/explanation/architecture/) — Control plane, workers, durable execution — and why. [Environment variables](https://docs.everruns.com/sre/environment-variables/) — Every configuration knob for the control plane and workers. ## More features [Agent triggers](https://docs.everruns.com/features/agent-triggers/) — Wake an agent on a recurring schedule and inspect its runs. [Session participants](https://docs.everruns.com/features/session-participants/) — Invite agents into a shared session and address one for a turn. [Agent and user memory](https://docs.everruns.com/features/memory-scopes/) — Learn the persistence, mount paths, and privacy rules for scoped memory. --- # Everruns Framework > Build and run agents inside a Rust application with the application-facing everruns crate. Source: The **Everruns Framework** is the application-facing [`everruns`](https://docs.rs/everruns) crate. Use it to describe agents, attach models and tools, run multi-turn sessions, observe events, and embed agent execution directly in a Rust process. ```rust use everruns::{Agent, Engine, OpenAI}; let agent = Agent::builder() .instructions("Answer in one short sentence.") .provider(OpenAI::from_env()?) .model("gpt-5.6-terra") .build()?; let engine = Engine::new(); let turn = engine.create(agent).send_and_wait("Say hello.").await?; println!("{}", turn.response); ``` No database, server or worker is required — an agent runs inside your process. A model provider is: pick one from [Supported providers](https://docs.everruns.com/framework/supported-providers/), or use the [test simulator](https://docs.everruns.com/framework/testing-and-simulation/) when writing tests. ## Choose the right surface | Surface | Use it for | | ------------------------ | --------------------------------------------------------------------------------- | | **Framework** | Rust applications that build and run agents in process through `everruns` | | **Advanced host crates** | Low-level execution-host composition through `everruns-host` and focused siblings | | **SDKs** | Remote clients that call a running Everruns server | | **Platform** | The control plane, server, workers, UI, and durable deployment | Normal library users should start with the Framework. Hosts that must replace storage or orchestration cross into [custom backends](https://docs.everruns.com/framework/custom-backends/). ## Start here * [Quickstart](https://docs.everruns.com/framework/quickstart/), install the crate and run an offline agent. * [Architecture](https://docs.everruns.com/framework/architecture/), understand Agent, Engine, Session, and the shared immediate/durable execution kernel. * [Agents](https://docs.everruns.com/framework/agents/), instructions, files, workspaces, MCP, plugins, and context inspection. * [Workspace security](https://docs.everruns.com/framework/workspace-security/), configure portable read and write scopes with secure defaults. * [Workspaces and Environments](https://docs.everruns.com/framework/workspaces-and-environments/), bind sessions to isolated or explicitly shared backend-owned heads. * [Models and providers](https://docs.everruns.com/framework/models-and-providers/), the model/provider split and the open provider boundary. * [Supported providers](https://docs.everruns.com/framework/supported-providers/), every driver that ships today and what each one supports. * [Direct model calls](https://docs.everruns.com/framework/direct-model-calls/), one prompt and one answer without an agent. * [Direct decision](https://docs.everruns.com/framework/direct-decisions/), a calibrated number rather than prose, without an agent. * [Model catalogs](https://docs.everruns.com/framework/model-catalogs/), ask a provider which models it offers and what each supports. * [Credentials](https://docs.everruns.com/framework/credentials/), each driver’s own vendor-standard environment variables. * [Tools and macros](https://docs.everruns.com/framework/tools-and-macros/), typed function tools through `everruns::tool`. * [Sessions](https://docs.everruns.com/framework/sessions/), independent, multi-turn conversations. * [Session work and wakes](https://docs.everruns.com/framework/background-work/), immediate and scheduled work with explicit delivery and restart semantics. * [Session History and Resume](https://docs.everruns.com/framework/session-history/), bounded transcript pages and typed continuation. * [Events and cancellation](https://docs.everruns.com/framework/events-and-cancellation/), observe a live turn and stop work cooperatively. * [Lifecycle hooks](https://docs.everruns.com/framework/lifecycle-hooks/), run awaited application behavior at execution boundaries. * [Answer agent questions](https://docs.everruns.com/framework/ask-user/), implement `AskUser` so your application answers the agent’s structured questions. * [Canonical events](https://docs.everruns.com/framework/canonical-events/), render or record bounded canonical event envelopes. * [Persistence](https://docs.everruns.com/framework/persistence/), Engine-lifetime memory and crash-durable local state. ## Extend and operate * [Custom providers](https://docs.everruns.com/framework/custom-providers/), attach a custom `ChatDriver` without changing a closed enum. * [Capabilities](https://docs.everruns.com/framework/advanced-capabilities/), configure the optional standard policy bundle and open references, or package typed tools with stable metadata and lifecycle context. * [Capability integrations](https://docs.everruns.com/framework/capability-integrations/), opt into filesystem, shell, web, Lua, and MCP implementation boundaries. * [Portable and hosted capabilities](https://docs.everruns.com/framework/capability-boundaries/), understand the Framework/Platform implementation boundary. * [Custom backends](https://docs.everruns.com/framework/custom-backends/), cross into low-level host composition deliberately. * [Testing and simulation](https://docs.everruns.com/framework/testing-and-simulation/), deterministic tests without credentials. * [Runnable examples](https://docs.everruns.com/framework/examples/), complete programs maintained with the crate. --- # Configure and author capabilities > Use one open AgentBuilder capability entrypoint for typed built-ins, dynamic references, and code-defined packages. Source: Every agent capability enters through `AgentBuilder::capability`. The method accepts the public, non-sealed `IntoCapability` contract, so Framework built-ins and third-party packages compose without adding a method or enum variant to `AgentBuilder`. Cargo features determine which environment-backed implementations a Framework binary contains. See [Capability integrations](https://docs.everruns.com/framework/capability-integrations/) for filesystem, Bashkit, web-fetch, Lua, and MCP boundaries; this page covers agent-level configuration and authoring after an implementation is available. ## Configure capabilities Use typed values when the Framework exposes a stable configuration, a `capability::Definition` for application code, and `CapabilityRef` when the ID and JSON arrive dynamically: ```rust use everruns::{ Agent, CapabilityRef, CompactionConfig, OpenAI, ToolSearch, }; use serde_json::json; let weather_definition = build_weather_capability(); let agent = Agent::builder() .instructions("Use configured capabilities when relevant.") .provider(OpenAI::from_env()?) .model("gpt-5.6-terra") .capability(CompactionConfig::new().budget_percent(0.85)) .capability(ToolSearch::automatic()) .capability(weather_definition) .capability( CapabilityRef::new("vendor.custom") .config(json!({ "mode": "database-driven" })), ) .build()?; ``` `ToolSearch::automatic` uses hosted deferred loading on supported models and the existing provider-neutral client-side implementation everywhere else. Its optional threshold and never-defer allowlist map to the built-in’s real configuration; there are no provider fields on the Framework value. `CapabilityRef` is the explicit escape hatch for database, plugin, or catalog configuration. Its ID stays open. An unknown ID is retained as a reference but contributes nothing until the selected host or plugin provides that implementation. This is not a function tool: ordinary functions remain on `AgentBuilder::tool` and `#[everruns::tool]`. The same rule applies to Everruns Platform capability IDs. The default Framework registry does not advertise or execute hosted knowledge, delegation, task, hook, or management capabilities. See [portable and hosted capabilities](https://docs.everruns.com/framework/capability-boundaries/). JSON capability config is not a credential store. Framework debug output redacts it, but a host may persist or inspect it; pass a provider-owned secret handle rather than API keys or tokens. Conversion is infallible. `AgentBuilder::build` validates ID syntax and the JSON object boundary, runs known built-in and declarative/plugin validators, and rejects duplicate IDs after built-in alias resolution. A code implementation cannot shadow a built-in or be paired with a second reference of the same ID; registrations never use last-write-wins behavior. Third-party typed values implement `IntoCapability` using only `everruns`: ```rust use everruns::{CapabilityRef, CapabilitySpec, IntoCapability}; struct VendorSearch { index: String, } impl IntoCapability for VendorSearch { fn into_capability(self) -> CapabilitySpec { CapabilityRef::new("vendor.search") .config(serde_json::json!({ "index": self.index })) .into() } } ``` No `everruns-core`, registry, store, or host dependency is needed. ## Choose the standard policy bundle The Framework’s default `builtins` feature links `everruns-builtins`, the backend-neutral implementation bundle for compaction, tool search, budgeting, loop/progress safeguards, prompt caching, tool-call repair, output handling, and guardrails. Linking the package has no registration side effect: each host constructs its registry explicitly, so a custom registry cannot be changed by dependency order. Applications that want only the open Framework contracts can disable default features and add the integrations they need. The policy bundle owns no network client, process runner, interpreter, database, or hosted service. Output persistence and distillation declare `session_file_system` as a host-provided dependency; enable them only in a composition that supplies that capability. The optional `ui-capabilities` feature also owns the namespaced `everruns_builtins::{openui,a2ui}` component catalogs and prompt generators; applications do not need separate UI-protocol crates. ## Choose an authoring level Use the smallest extension contract that fits the behavior you own. | Contract | `#[everruns::tool]` | `everruns::capability` | | ------------------------------------- | ----------------------------- | ----------------------------- | | Best for | One application function | A reusable capability package | | Typed input and result | Yes | Yes | | Generated input schema | Yes | Yes | | Inspectable output schema | No | Yes | | Multiple tools | Register functions separately | One stable capability id | | Capability instructions and metadata | No | Yes | | Session/workspace identity and locale | No | Curated `Context` accessors | | Progress events | No | `Context::progress` | | Child-work cancellation | Turn future only | `Context::cancellation` | | Backend/store/tenancy access | No | No | ## Ordinary tools Annotate a typed async function. Its doc comment becomes the description and its arguments become JSON Schema. ```rust /// Convert Celsius to Fahrenheit. #[everruns::tool] async fn fahrenheit(celsius: f64) -> f64 { celsius * 1.8 + 32.0 } let agent = everruns::Agent::builder() .instructions("Use the conversion tool.") .provider(everruns::OpenAI::from_env()?) .model("gpt-5.6-terra") .tool(fahrenheit()) .build()?; ``` Prefer this until you need a capability-level contract. ## Advanced capabilities An advanced capability is an immutable `capability::Definition`. It owns a stable id, catalog text, optional instructions and JSON metadata, and one or more typed handlers. `AgentBuilder::capability` installs its implementation on the private in-process runtime and activates that stable id once. ```rust use everruns::{Agent, OpenAI, capability}; #[derive(capability::Deserialize, capability::JsonSchema)] #[serde(crate = "everruns::capability::serde")] #[schemars(crate = "everruns::capability::schemars")] struct LookupInput { id: String, } #[derive(capability::Serialize, capability::JsonSchema)] #[serde(crate = "everruns::capability::serde")] #[schemars(crate = "everruns::capability::schemars")] struct Record { id: String, score: f64, labels: Vec, } struct Lookup; #[capability::async_trait] impl capability::Handler for Lookup { type Input = LookupInput; type Output = Record; type Error = capability::Error; fn name(&self) -> &str { "lookup_record" } fn description(&self) -> &str { "Look up one record by exact id." } fn hints(&self) -> capability::Hints { capability::Hints::default() .readonly(true) .idempotent(true) } async fn execute( &self, input: Self::Input, context: capability::Context, ) -> Result { context.progress("Looking up the record").await; if input.id != "rec_42" { return Err(capability::Error::user( "record_not_found", "No record has that id", ).details(capability::serde_json::json!({ "id": input.id }))); } Ok(Record { id: input.id, score: 0.98, labels: vec!["verified".into()], }) } } let records = capability::Definition::new( "records", "Records", "Application-owned record lookup.", ) .instructions("Use exact record ids and do not infer missing records.") .metadata(capability::serde_json::json!({ "owner": "risk" })) .tool(Lookup); let agent = Agent::builder() .instructions("Answer with verified record data.") .provider(OpenAI::from_env()?) .model("gpt-5.6-terra") .capability(records) .build()?; ``` Both input and output types must satisfy the compile-time protocol bounds. The generated schemas are available through `Definition::tools()[..].spec()` for tests, documentation, or a host catalog. Results are serialized directly to JSON, so structs, arrays, numbers, booleans, and null do not pass through a string conversion. ## Errors Return `capability::Error::user(code, message)` for an expected domain failure. Add bounded JSON details when they help the model recover. The code, message, and details travel through the model-visible tool-error channel. Return `capability::Error::internal(code, message)` for diagnostic details that are unsafe to show to the model, such as network internals or implementation bugs. The engine logs the diagnostic and gives the model a generic error; internal details do not cross the model boundary. Never include credential values or other secrets in any error because host logs may retain internal diagnostics. Custom application error enums can implement `Into` and be used as `Handler::Error`. ## Context, progress, and cancellation `capability::Context` exposes only stable lifecycle data: * opaque session and workspace ids; * the resolved locale, when present; * best-effort correlated `tool.progress` events; * a call-scoped cancellation signal. Observe progress through `Session::events()` and `SessionEventKind::ToolProgress`. Everruns does not currently expose a custom capability result-streaming protocol; return one typed result when execution finishes. Normal awaited work needs no cancellation branch. Cancelling a turn drops the handler future. Clone `context.cancellation()` only into child tasks, processes, or watchers that might otherwise survive after `execute` is dropped. The signal fires on cancellation and on every other call completion path. ## Security boundary The advanced SPI intentionally does not export provider credentials, stores, tenant or organization objects, registries, payment authority, filesystem backends, or other host services. Pass application-owned clients or state into your handler struct when constructing the definition. A handler is trusted application code and retains whatever process authority those values provide; `Hints` describe behavior but do not enforce authorization, egress, or approval policy. Apply authorization, egress policy, timeouts, and input bounds at those application boundaries, and never place secrets in capability or tool metadata. For a complete provider-backed program, run the [`advanced_capability` example](https://github.com/everruns/everruns/blob/main/crates/everruns/examples/advanced_capability.rs). Some built-ins expect the application to supply behavior rather than configuration. `ask_user` is one: it needs someone to answer, so `AgentBuilder::ask_user` takes a responder instead of a config value. See [Answer agent questions](https://docs.everruns.com/framework/ask-user/). --- # Agents > Describe an agent with instructions, a model, tools, files, integrations, and an optional workspace. Source: An `Agent` is an immutable, validated application description. Pass it to an application-owned engine to create independent sessions. ```rust use everruns::{Agent, McpServer, OpenAI}; let agent = Agent::builder() .name("researcher") .instructions("Research carefully and cite the evidence you used.") .provider(OpenAI::from_env()?) .model("gpt-5.6-terra") .file("brief.md", "Investigate the supplied question.") .readonly_file("policy.md", "Never expose secrets.") .mcp_server(McpServer::http("catalog", "https://example.com/mcp")) .build()?; ``` Builder validation catches blank instructions, a missing model, duplicate providers, tools, or capabilities, invalid tool schemas, invalid capability IDs/configuration, implementation collisions, and invalid MCP configuration before a session starts. Configure typed built-ins, code-defined packages, and dynamic references through the single `capability(...)` entrypoint; see [Configure and author capabilities](https://docs.everruns.com/framework/advanced-capabilities/). ## Files and workspaces * `file(path, content)` seeds an editable file. * `readonly_file(path, content)` seeds a file the agent may read but not change. * `workspace(root)` exposes one trusted real-disk root as `/workspace`. Choose workspace roots from trusted application configuration. Model output and untrusted request fields must not select executable paths or host directories. The underlying filesystem boundary rejects traversal and symlink escape. Use a [`WorkspacePolicy`](https://docs.everruns.com/framework/workspace-security/) to configure portable read, write, hidden-path, and recursive-delete restrictions. ## MCP and plugins `McpServer::http` adds a remote Streamable HTTP server. Headers may be supplied by the host and are redacted from `Debug`. Local-process MCP is separately feature-gated with `mcp-stdio`; its command, arguments, and environment are trusted host configuration. `AgentBuilder::plugin(path)` loads a local plugin directory and returns a typed error if it cannot be compiled. Non-fatal compiler warnings remain visible in the application-facing session context. ## Inspect effective context Inspect the next model call before or after a turn: ```rust let engine = Engine::new(); let session = engine.create(agent); let context = session.inspect().await?; println!("messages: {}", context.messages.len()); println!("tools: {}", context.tools.len()); ``` Inspection uses the same assembly path as execution, including MCP discovery, plugin prompt contributions, message filters, and model selection. --- # Framework Architecture > Understand Agent, Engine, Session, and how immediate and durable execution share one kernel. Source: Everruns has one turn model and two ways to execute it. A library application uses the concrete `everruns::Engine` in its own process. The Everruns Platform uses server and worker services with durable checkpoints. Both paths converge on the same `everruns-engine` Input/Reason/Act state machine. ![Framework execution architecture](https://docs.everruns.com/_astro/architecture.Db7hOeqJ_1JbXYr.svg) ## Public Framework objects | Object | Responsibility | | ------------- | -------------------------------------------------------------------------------------------------------------- | | `Agent` | Immutable behavior: instructions, model and provider, tools, capabilities, files, and lifecycle hooks | | `Engine` | Concrete process-local owner of Agent snapshots, session identity, backends, history, and resume authority | | `Session` | First-class, engine-bound conversation used for turns, steering, events, cancellation, inspection, and history | | `Environment` | Session resources, including one exact backend-owned workspace head and typed extensions | New Framework code creates and resumes sessions through an Engine: ```rust use everruns::{Agent, Engine, OpenAI}; let agent = Agent::builder() .instructions("Answer concisely.") .provider(OpenAI::from_env()?) .model("gpt-5.6-terra") .build()?; let engine = Engine::new(); let session = engine.create(agent); let session_id = session.session_id(); let turn = session.send_and_wait("Begin.").await?; assert!(turn.success); drop(session); let resumed = engine.resume(session_id).await?; assert_eq!(resumed.session_id(), session_id); ``` `InMemoryEngine` remains a compatibility alias. It is not a second engine implementation; use `Engine` in new 0.18 code. ## Two execution paths, one kernel The library path is immediate. `everruns::Engine` uses `everruns-host` to run `InProcessExecution` in the caller’s process. It can be entirely volatile or use the local profile for crash-durable canonical events. The Platform path is distributed and checkpointed. The server schedules work, workers resolve host services and effects, and `everruns-durable` advances a `DurableExecution` across persisted phase boundaries. PostgreSQL remains the source of recovery state. Neither path owns a private copy of the turn algorithm. `everruns-engine` owns the `Execution` contract, `TurnExecution` state, Input/Reason/Act atoms, phase ordering, and effect production. Immediate and durable adapters select where state lives and how work is scheduled. ## Choose a recovery boundary * **Volatile Framework:** `Engine::new()` is offline and database-free. The creating Engine can resume a dropped Session, but process exit loses it. * **Local crash-durable Framework:** `LocalConfig` stores canonical events and session identity locally. Rebuild the trusted Agent configuration, attach it to a new Engine, and resume by typed `SessionId`. * **Distributed durable Platform:** server and workers checkpoint workflow state in PostgreSQL and recover across process or worker loss. Applications call it through the remote API or SDKs rather than configuring the facade Engine. See [Persistence](https://docs.everruns.com/framework/persistence/) and [Session History and Resume](https://docs.everruns.com/framework/session-history/) for the exact application lifecycle. ## Extension boundaries Normal applications depend on `everruns`. `everruns::Engine` is concrete and is not implemented by applications. Provider integrations implement the open `ChatDriver` boundary, while canonical storage hosts can implement `EventLog`/`EventReader` through `everruns-host`. An application that is itself an execution host may compose `everruns-engine::Execution` with `everruns-host` or `everruns-durable`. That is an advanced deployment boundary: preserve event ordering, workspace isolation, credential separation, cancellation, and committed effect semantics. Start with [Custom Backends](https://docs.everruns.com/framework/custom-backends/) before crossing it. --- # Answer an agent's questions > Implement the AskUser trait so an embedding application can answer an agent's structured questions from its own interface. Source: An agent with the [Ask User](https://docs.everruns.com/capabilities/ask-user/) capability can ask the person it is working with a small batch of structured questions and wait for the answer. In a hosted product the browser renders that card. In an embedding application there is no browser, so the application answers — which is what the `AskUser` trait is for. ```rust use everruns::ask_user::{Answer, AnsweredBy, AskUser, Outcome, Question, Status, async_trait}; use everruns::{Agent, Model}; struct HouseRules; #[async_trait] impl AskUser for HouseRules { async fn ask(&self, questions: &[Question]) -> Outcome { let answers = questions .iter() .map(|question| Answer { id: question.id.clone().unwrap_or_default(), selected: question .options .iter() .find(|option| option.is_default) .or_else(|| question.options.first()) .map(|option| option.label.clone()) .into_iter() .collect(), other_text: None, secret_ref: None, }) .collect(); Outcome { status: Status::Answered, answered_by: AnsweredBy::Unattended, answers, } } } let agent = Agent::builder() .instructions("Confirm deployment choices before acting.") .model(Model::simulated("Done.")) .ask_user(HouseRules) .build()?; ``` `AgentBuilder::ask_user` registers the responder and enables the capability in one call. The responder runs **inside** the tool call, so the turn never parks waiting for an external result — the agent asks, your code answers, and the turn continues. ## Without a responder `.capability("ask_user")` on its own uses `DefaultsResponder`: it applies the options the model marked as recommended, falls back to the first option, and reports `AnsweredBy::Unattended`. Headless runs resolve immediately rather than waiting out the timeout for somebody who is not there. ## Report who answered `answered_by` is part of the contract, not decoration: | Value | Meaning | | ------------ | --------------------------------------------- | | `User` | A person actually chose this | | `Timeout` | The deadline passed and a default was applied | | `Unattended` | Nobody could be asked; a default was applied | Report `User` only when a person really answered. An agent that reads a fallback as a considered choice acts with more confidence than the answer earns, and that is the failure this field exists to prevent. ## A worked responder [`examples/weekend-concierge-host`](https://github.com/everruns/everruns/tree/main/examples/weekend-concierge-host) implements `TerminalResponder` over stdin: numbered options, comma-separated toggles for a multi-select, a free-text path when the question allows one, and terminal echo turned off for a credential. ```plaintext [Energy] How much energy does the group have on Friday? *1. Up for anything — Games, noise, moving around. 2. Low-key — Sitting, talking, snacks. 3. Something else > ``` Two details in it are worth copying into any responder: **An empty answer takes the declared default** rather than returning nothing. A question the model asked and nobody addressed is something it cannot distinguish from a deliberate skip. **A secret answer carries a reference, never a value.** `Answer` has no `value` field at all, so there is no path from a collected credential into the transcript. The host keeps the value; the agent gets `session:MY_TOKEN` and tools resolve it by name. ```rust Answer { id, selected: Vec::new(), other_text: None, secret_ref: Some(everruns::ask_user::session_secret_ref("MY_TOKEN")), } ``` For the smallest possible version, [`crates/everruns/examples/ask_user.rs`](https://github.com/everruns/everruns/tree/main/crates/everruns/examples/ask_user.rs) runs a responder and the unattended path side by side and prints what each decided. ## Questions are not permission `ask_user` auto-resolves, so it is for decisions and preferences only. A destructive, irreversible, or outward-facing action needs `request_approval`, whose wait does not auto-resolve. See [the boundary](https://docs.everruns.com/capabilities/ask-user/#not-a-consent-gate). ## See Also * [Ask User capability](https://docs.everruns.com/capabilities/ask-user/), the contract and its limits * [Configure and author capabilities](https://docs.everruns.com/framework/advanced-capabilities/) * [Lifecycle hooks](https://docs.everruns.com/framework/lifecycle-hooks/), for intercepting tool calls rather than answering them --- # Session work and wakes > Run immediate and scheduled background work with explicit delivery and restart semantics. Source: # Session work and wakes `everruns::work` lets an application request and handle work owned by a session without importing runtime registries, platform stores, or task-kind constants. The application chooses its own work kinds and JSON payloads. ```rust use std::time::Duration; use everruns::work::{TaskOutcome, TaskRequest, WakePolicy, WorkQueue}; use serde_json::json; let queue = WorkQueue::in_memory(); let work = queue.for_session("session_123"); work.submit( TaskRequest::new("thumbnail", json!({ "image": "cover.png" })) .idempotency_key("thumbnail:cover.png") .wake_policy(WakePolicy::OnCompletion), ).await?; for delivery in queue.claim_due(Duration::from_secs(30), 16).await? { // Route on delivery.task.kind and check cancellation before side effects. queue.finish( &delivery, TaskOutcome::success(json!({ "path": "cover-thumb.png" })), ).await?; } ``` The runnable version is [`session_work.rs`](https://github.com/everruns/everruns/blob/main/crates/everruns/examples/session_work.rs). ## Contract * **Ownership:** every task and wake has one opaque `session_id`. A `SessionWork` handle fixes that owner for all requests and reads. * **Persistence and restart:** `WorkQueue::in_memory()` is process-local and database-free. State survives replacing the queue only while the same `Arc` is retained. It does not survive a process restart. A host that needs durable recovery supplies a `WorkBackend`. * **Scheduling:** `Immediate` work is claimable now; `At(SystemTime)` work is not claimable early. Both are one-shot requests. The host owns polling; recurring calendars and schedule runners stay host concerns. There is no hidden scheduler or database in the default build. * **Delivery:** task and wake claims are leased and at least once. If a process stops before settlement or acknowledgment, the item is claimable after the lease expires. Each retry has a new token and attempt; stale attempts cannot settle newer work. * **Idempotency:** task keys and direct-wake keys each have a session-scoped namespace. Repeating the same request returns the original task or wake. Reusing a key for different input fails with `IdempotencyConflict`. Workers should also deduplicate external side effects on the stable task id or submission key. * **Cancellation:** pending work cancels immediately. Running work records cancellation intent; the worker checks the latest task snapshot, stops cooperatively, then reports `TaskOutcome::Canceled`. A task may still succeed if it passes its safe cancellation point first. * **Wakes:** applications can request an immediate wake directly. A task can also create one atomic completion wake with `WakePolicy::OnCompletion`. Wakes use the same lease/retry/acknowledgment model as tasks. Durable platform scheduling, retention, distributed polling, and multi-host coordination belong in the host’s `WorkBackend`; they are not enabled by the offline Framework default. The in-memory backend retains accepted payloads until it is dropped and does not enforce admission quotas, so hosted providers must apply tenant authorization, payload limits, quotas, and retention at their own boundary. --- # Canonical Framework events > Observe a complete agent turn through a bounded typed/raw bridge while keeping durability, live delivery, and derived history distinct. Source: `Session::events()` installs an in-process subscriber without exposing runtime event buses or core event types. Subscribe before `Session::run()` so the stream sees the turn from its first event. ```rust use everruns::prelude::*; let agent = Agent::builder() .instructions("Answer concisely.") .provider(OpenAI::from_env()?) .model("gpt-5.6-terra") .build()?; let engine = Engine::new(); let mut session = engine.create(agent); let mut events = session.events(); let observer = tokio::spawn(async move { let mut canonical_events = Vec::new(); while let Some(event) = events.recv().await? { // Recording for replay, so take the canonical envelope: it withholds // nothing. `as_json()` carries the reviewed projection instead. canonical_events.push(event.canonical_json().clone()); // Typed rendering for common terminal/service UI concerns. match event.kind { SessionEventKind::TextDelta { delta } => print!("{delta}"), SessionEventKind::ToolStarted { tool_name, .. } => { eprintln!("starting {tool_name}"); } SessionEventKind::ToolCompleted { tool_name, success, .. } => eprintln!("{tool_name}: {success}"), SessionEventKind::TurnFailed { error } => eprintln!("failed: {error}"), SessionEventKind::TurnCancelled => eprintln!("cancelled"), _ => {} } } Ok::<_, EventStreamError>(canonical_events) }); let turn = session.run("hello").await?; drop(session); // closes the subscriber once buffered events are drained let recorded = observer.await??; assert!(!recorded.is_empty()); ``` ## One protocol, two views `SessionEventKind` is a convenience projection for application renderers. It promotes assistant output lifecycle and deltas, model reasoning/generation, tool lifecycle/progress/output, and turn terminal states. It is non-exhaustive, so match it with a fallback arm. `SessionEvent` exposes two surfaces, and which one you want depends on whether you are rendering or recording. `SessionEvent::as_json()` and `SessionEvent::data()` return the **reviewed** form: the event envelope — id, type, timestamp, optional persisted sequence, correlation context, metadata, tags — with a `data` payload holding only the fields promoted onto `SessionEventKind`. Nothing else reaches it, so a field added inside the runtime cannot become part of the Framework’s public surface, or travel to wherever your application forwards these envelopes, without being promoted first. This is the form to log, forward, or expose to clients. `SessionEvent::canonical_json()` returns the **canonical** envelope with the complete payload: prompts, tool arguments, tool results, structured assistant messages, and the payloads of event types this version does not recognize. Nothing observable is lost — this is the form for recording, auditing, and replay. It follows the runtime’s internal shape rather than the Framework’s reviewed surface, so treat what you read from it as unstable, and do not forward it anywhere the conversation itself should not go. Live `output.message.delta` envelopes omit the redundant `data.accumulated` prefix on both surfaces; retaining every growing prefix in a slow subscriber’s buffer would use quadratic memory. Concatenate the typed `TextDelta::delta` values to reconstruct streamed text, or use the subsequent `output.message.completed` event for the complete message. Model-generation accounting — model, provider, token counts, cost, and duration — is promoted onto `SessionEventKind::ModelGeneration`, so tracking spend never requires the unstable surface. This bridge does not define a second wire schema. The canonical event contract, compatibility rules, and lifecycle semantics remain documented in [Events](https://docs.everruns.com/explanation/events/). ## Durability, observation, and derived history These roles are deliberately separate: * `EventLog` is the host’s sole durable conversation write authority. It stores complete canonical event envelopes and provides bounded cursor replay. * `EventSink` is the host’s post-commit, nonblocking live-delivery boundary. `Session::events()` exposes that observation path as an ergonomic `EventStream` subscriber. Neither sink nor subscriber is durable or authoritative. * `EventHistory` is one read-only message projection rebuilt from `EventLog` replay. It is an index/view, never a second writable message store. Framework applications read that bounded projection through [`Session::history()`](https://docs.everruns.com/framework/session-history/). It pages messages from a stable event-log snapshot; it does not maintain or write an independent transcript. Rebuild a transcript in persisted sequence order from `input.message`, `output.message.completed`, and relevant `tool.completed` events. An `output.message.replaced` event alone creates no history message; the subsequent completed message contains the safe replacement. If a crash leaves a replacement without completion, replay correctly omits that incomplete output. The Framework stream exposes canonical payloads, subject to the live-delta exception above, and introduces no independent writable message history. Canonical recordings can contain user messages, agent instructions, model inputs, tool arguments, and tool results. Treat them as application data with the same access controls and retention policy as the session itself; do not log them indiscriminately. Model and tool text is untrusted: terminal renderers should strip or escape control sequences, and web renderers should escape it as content rather than interpreting it as markup or commands. Provider credentials are not part of the event protocol. ## Implementing a custom event log An advanced host can store canonical events itself. `everruns-host` exposes `EventReader` and `EventLog` as a public SPI: an external crate implements both against its own storage and supplies the result to composition through `HostBackends::with_event_log`. No in-crate access is required, cursors and pages are built with `EventCursor::continuation`, `EventCursor::after`, and `EventPage::new`, which validate the shared invariants. Three request shapes are distinguished by `EventReadRequest::cursor()`: * no cursor is an initial read that captures the session’s current high-watermark and reports it as `EventPage::snapshot_high_watermark()`; * a cursor whose `snapshot_high_watermark()` is `Some` is a continuation pinned to that snapshot, so appends committed later stay invisible and paging neither skips nor duplicates; * a cursor whose `snapshot_high_watermark()` is `None`, built by `EventCursor::after`, is a poll that captures a fresh snapshot and therefore does observe those later appends. ```rust use async_trait::async_trait; use everruns_core::events::{Event, EventRequest}; use everruns_provider::typed_id::EventId; use everruns_host::{ EventCursor, EventDurability, EventLog, EventLogError, EventPage, EventReadRequest, EventReader, }; #[async_trait] impl EventReader for MyEventLog { async fn read_page(&self, request: EventReadRequest) -> Result { let session_id = request.session_id(); let current_high = self.high_watermark(session_id); let (after, snapshot) = match request.cursor() { None => (0, current_high), Some(cursor) => { if cursor.session_id() != session_id { return Err(EventLogError::CrossSessionCursor { detail: "cursor belongs to another session".into(), }); } match cursor.snapshot_high_watermark() { Some(snapshot) if snapshot > current_high => { return Err(EventLogError::ExpiredCursor { detail: "cursor snapshot is not available".into(), }); } // Pinned continuation, then the polling form. Some(snapshot) => (cursor.after_sequence(), snapshot), None => (cursor.after_sequence(), current_high), } } }; let limit = request.limit().get(); let mut events = self.events_in(session_id, after, snapshot, limit + 1); let has_more = events.len() > limit; if has_more { events.pop(); } let next_cursor = has_more .then(|| { let last = events.last().and_then(|event: &Event| event.sequence).unwrap_or(after); EventCursor::continuation(session_id, last, snapshot) }) .transpose()?; EventPage::new(events, next_cursor, snapshot) } } #[async_trait] impl EventLog for MyEventLog { async fn append(&self, request: EventRequest) -> Result { if request.is_ephemeral() { return Err(EventLogError::InvalidAppend { detail: "ephemeral events are sink-only".into(), }); } // The log owns identity: assign the event id and the next per-session // sequence, persist, and return the finalized canonical envelope. let sequence = self.next_sequence(request.session_id); let event = request.into_event(EventId::new(), sequence); self.persist(&event)?; Ok(event) } fn durability(&self) -> EventDurability { EventDurability::CrashDurable } } ``` The contract an implementation must uphold: * an accepted append owns id and sequence assignment and returns the finalized canonical `Event`, visible to the next read of that session; * durable sequences are unique and strictly increasing per session, and need not be contiguous, gaps are expected when a reader projects an append-only physical log into a filtered logical event sequence; * a continuation stays pinned to the first page’s high-watermark and cannot observe concurrent appends; a poll cursor can; * cursor/session mismatches and inconsistent positions return the typed `EventLogError` variants above rather than panicking; * the log is append-only. There is no truncate, rewind, or mutation contract, and `EventHistory` remains a read-only projection rather than a second writable message store. `crates/everruns/tests/fixtures/external-consumer/event-log` in the repository is a complete out-of-workspace implementation exercised by repository CI. ## Ordering and bounded delivery A subscriber receives events in channel arrival order and each session has its own stream. The canonical `sequence` field is a replay position, not a live delivery counter: durable events carry `Some(sequence)` and live-only ephemeral events such as streaming deltas carry no sequence. Persisted sequences increase monotonically per session and may have gaps. Ephemeral events do not consume replay positions. The live stream has a bounded buffer and never applies backpressure to the agent turn. A dropped or slow subscriber cannot stall model or tool execution. If a subscriber falls behind, `recv()` and `try_recv()` return `EventStreamError::Lagged { missed }`; loss is never hidden. The next receive can continue from the oldest retained event, but the renderer must treat its live projection as incomplete. Streaming deltas are provisional and sink-only. Completed assistant/tool events are authoritative, and an output-replacement event means accumulated text for that message must be discarded. Durable events reach the live sink only after their log append commits; ephemeral events go directly to the sink and never enter history. After live lag, a Framework application can rebuild its persisted transcript with bounded [`Session::history()` pages](https://docs.everruns.com/framework/session-history/). That projection excludes ephemeral deltas by design. Applications that need raw durable envelopes rather than derived messages can provide and read an `EventLog` through the advanced `everruns-host` SPI; neither recovery path relies on the in-process subscriber. ## Cancellation and failure Pass a `CancellationToken` through `RunOptions` to stop a turn. Cancellation produces both a `Turn` with `TurnStopReason::Cancelled` and a correlated `turn.cancelled` event carrying the same `turn_id`. Runtime failures remain available through `Session::run()`’s outcome/error semantics and the event stream. Subscribe before running and continue draining the stream after the run resolves to retain the terminal failure event and its full structured payload. The complete runnable example is [`canonical_events.rs`](https://github.com/everruns/everruns/blob/main/crates/everruns/examples/canonical_events.rs): ```bash cargo run -p everruns --example canonical_events ``` --- # Portable and hosted capabilities > Understand which capabilities run in the Framework and which require the Everruns Platform host. Source: The Framework advertises only capabilities that can run through its portable, in-process host contract. A capability reference remains an open value, so an application may retain configuration for an ID supplied by another host or plugin, but an unknown or hosted-only ID contributes no prompt, tools, or behavior in the default Framework runtime. ## Portable Framework capabilities Portable built-ins use services available from the Framework runtime or from an explicit application integration. Examples include files, session storage, current time, compaction, tool search, skills, and application-authored capabilities. Register application behavior with `#[everruns::tool]` or `everruns::capability` rather than depending on product internals. ## Hosted Platform capabilities Knowledge Bases and Knowledge Indexes, Memories, subagents and agent handoff, background/session tasks and schedules, user hooks, model scouting, OpenRouter workspace management, citations, and platform-management tools need hosted persistence or orchestration. Their implementations and narrow service contracts live in `everruns-platform`; the server and worker product presets register them explicitly. This boundary prevents the public Framework from promising tools whose stores, tenant scope, workers, or authorization services are absent. It does not change persisted capability IDs or JSON configuration. A specialized low-level host can depend on `everruns-platform`, install the required services, and select the hosted registry deliberately. For application-owned behavior, continue with [advanced capabilities](https://docs.everruns.com/framework/advanced-capabilities/). For low-level host composition, see [custom backends](https://docs.everruns.com/framework/custom-backends/). --- # Capability integrations > Select filesystem, shell, web, Lua, and MCP implementation boundaries without pulling them into the Everruns kernel. Source: The Framework separates capability contracts from environment-backed implementations. `everruns-core` defines capability, tool, filesystem, egress, and MCP invocation contracts; focused crates own code that touches an interpreter, network transport, local process, or session filesystem. This keeps a custom host’s dependency and trust boundaries visible in `Cargo.toml`. It also prevents a core registry from silently granting an execution or network surface. ## Framework features | `everruns` feature | Default | Implementation | Effect boundary | | ------------------ | ------: | ---------------------------------- | ----------------------------------------------------------------------- | | `filesystem` | Yes | `everruns-integrations-filesystem` | Host-provided, session-scoped filesystem only | | `bashkit` | No | `everruns-integrations-bashkit` | Sandboxed shell; HTTP remains capability-config and egress-policy gated | | `web-fetch` | No | `everruns-integrations-web-fetch` | FetchKit requests through the host egress contract | | `lua` | No | `everruns-integrations-lua` | Vendored Lua 5.4 sandbox; also requires `FEATURE_LUA=true` at runtime | | `mcp` | No | `everruns-mcp` | Remote HTTP MCP through the host egress contract | | `mcp-stdio` | No | `everruns-mcp` | Adds local-process MCP servers and implies `mcp` | The default is offline: the filesystem capability can only use the session-filesystem implementation supplied by the host. Shell, web, Lua, MCP, and local-process transports require explicit features. ```toml [dependencies] everruns = { version = "0.17", features = ["bashkit", "web-fetch"] } ``` Enabling an implementation does not activate it on every agent. Add the matching capability reference to the agent, and retain the documented role, network-access, and runtime feature gates. In particular, Bashkit, web fetch, and Lua remain high-risk capabilities in the hosted product. ## Provider integrations Integration packages can expose typed values through `IntoCapability`. Brave Search supports the ordinary Framework builder: ```rust use everruns::{Agent, OpenAI}; use everruns_integrations_brave_search::BraveSearch; let agent = Agent::builder() .instructions("Search and cite primary sources.") .provider(OpenAI::from_env()?) .model("gpt-5.6-terra") .capability(BraveSearch::from_env()?) .build()?; ``` Depend on `everruns-integrations-brave-search` with `default-features = false` to omit Platform connector registration. The Framework adapter reads `BRAVE_SEARCH_API_KEY` at construction; `BraveSearch::new` accepts an explicit application-owned key. Keys are retained privately by the client, never placed in capability JSON. Hosted execution continues to resolve connections and session secrets at tool execution time. Both paths use the same search schema and operation. Framework calls use the application’s direct HTTP client. ## Advanced host composition Advanced embedders select integrations on `everruns-host` and build the runtime registry through `everruns_host::runtime_capability_registry()`: ```toml [dependencies] everruns-core = "0.17" everruns-host = { version = "0.17", features = ["filesystem", "web-fetch"] } ``` ```rust let registry = everruns_host::runtime_capability_registry(); let egress = everruns_host::runtime_egress_service(); assert!(registry.has("session_file_system")); assert!(registry.has("web_fetch")); assert!(!registry.has("bashkit_shell")); ``` If the host starts from a caller-owned registry, preserve it and apply the same feature-selected integrations with `everruns_host::compose_runtime_capability_registry(registry)`. Hosted server and worker composition uses `everruns_platform::capabilities::hosted_capability_registry_for_grade` with the platform’s `environment-capabilities` feature. That preset preserves the hosted catalog while keeping the implementations outside core. Depend directly on a focused crate when you need its public implementation types. The former core paths move as follows: | Former public path | New public path | | ------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------- | | `everruns_core::FileSystemCapability` and filesystem tools | `everruns_integrations_filesystem::*` | | `everruns_core::BashkitShellCapability`, `BashTool`, and adapter | `everruns_integrations_bashkit::*` | | `everruns_core::WebFetchCapability`, `WebFetchTool`, and bot-auth helpers | `everruns_integrations_web_fetch::*` | | `everruns_core::LuaCapability` and `LuaCodeModeCapability` | `everruns_integrations_lua::*` | | `everruns_core::McpCapability` and MCP capability-ID helpers | `everruns_mcp::*` | | `everruns_core::DirectEgressService` | `everruns_host::DirectEgressService` with `direct-egress` | | `everruns_core::SystemEmailConfig` and Resend types | `everruns_platform::*` | | `everruns_core::ModelScoutCapability` and `OpenRouterWorkspaceCapability` | `everruns_integrations_openrouter::*` | | `everruns_core::OpenRouterServerToolsCapability` | `everruns_integrations_openrouter::OpenRouterServerToolsCapability` | | `everruns_core::{HumanIntentCapability, InfinityContextCapability, SkillsCapability, AttachSkillCapability, ToolApprovalCapability}` | `everruns_builtins::*` | | `everruns_core::{OpenUiCapability, A2UiCapability}` | `everruns_builtins::*` with `ui-capabilities` | | `everruns_core::skill::ProcessCommandExecutor` | `everruns_host::ProcessCommandExecutor` with the host `process` feature | Continue with [Configure and author capabilities](https://docs.everruns.com/framework/advanced-capabilities/) for agent-level activation or [Custom backends](https://docs.everruns.com/framework/custom-backends/) for host-level storage and orchestration. --- # Credentials > Each driver declares the environment variables its own vendor SDK reads, and the Framework resolves them through one shared path. Source: Every provider driver declares the environment variables it reads, on its own descriptor, following **its vendor’s own SDK convention**. There is no Everruns naming scheme to learn: if your shell already runs the `openai` CLI, the AWS CLI, or an Azure service principal, it already configures the matching driver. ```rust use everruns::{Agent, OpenAI}; // Reads OPENAI_API_KEY, and OPENAI_BASE_URL when set. let agent = Agent::builder() .instructions("Be concise.") .provider(OpenAI::from_env()?) .model("gpt-5.6-terra") .build()?; ``` Each driver crate offers the same entry point, returning a ready `Provider`. The facade bundles OpenAI behind its `openai` feature; other drivers are separate crates you add as dependencies: ```rust use everruns::{Agent, Model}; // Reads ANTHROPIC_API_KEY, and ANTHROPIC_BASE_URL when set. let model = Model::new("claude-sonnet-5", everruns_anthropic::from_env("anthropic")?); ``` ## What each driver declares | Driver | Credential | Endpoint | | ------------------------- | --------------------------------------------------------------------------------------------------------- | ------------------------- | | OpenAI | `OPENAI_API_KEY` | `OPENAI_BASE_URL` | | OpenAI (Chat Completions) | `OPENAI_API_KEY` | `OPENAI_BASE_URL` | | Azure OpenAI | `AZURE_OPENAI_API_KEY` | — (see below) | | Anthropic | `ANTHROPIC_API_KEY` | — (see below) | | Google Gemini | `GEMINI_API_KEY`, or `GOOGLE_API_KEY` | `GEMINI_BASE_URL` | | OpenRouter | `OPENROUTER_API_KEY` | `OPENROUTER_BASE_URL` | | Fireworks AI | `FIREWORKS_API_KEY` | `FIREWORKS_BASE_URL` | | Meta Model API | `LLAMA_API_KEY`, or `META_API_KEY` | `LLAMA_BASE_URL` | | AWS Bedrock | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION` (or `AWS_DEFAULT_REGION`), `AWS_SESSION_TOKEN` | — (the region selects it) | | Microsoft MAI | `AZURE_AI_API_KEY`, **or** `AZURE_TENANT_ID` + `AZURE_CLIENT_ID` + `AZURE_CLIENT_SECRET` | `AZURE_AI_ENDPOINT` | A driver is not limited to one key. Bedrock needs four AWS fields; MAI accepts either a resource key or a full Entra ID service principal. Alternates listed with “or” are variables the vendor itself also honors, tried in the order shown — not a second credential. Anthropic and Azure OpenAI declare no endpoint variable on purpose. A `base_url` here is the *versioned* API root — drivers append bare operation paths to it, and the defaults end in `/v1` — whereas `ANTHROPIC_BASE_URL` and `AZURE_OPENAI_ENDPOINT` name the bare host, because those SDKs add the version segment themselves. Importing either verbatim would resolve to `https://api.anthropic.com/messages` and fail. Point those drivers at a proxy with an explicit `Provider::new(...).base_url(...)`, or through the Settings UI, instead. A credential resolves whole or not at all. If any required variable is missing the driver is simply not configured from the environment, rather than being half-configured into a provider that fails at its first request. A shell carrying `AWS_REGION` but no AWS keys does not configure Bedrock, and a half-populated Entra block does not configure MAI — the same rule the schema applies to an operator-entered form. This table is pinned by a test against the drivers’ own declarations, so it cannot drift from what they read. ## Custom drivers A [custom driver](https://docs.everruns.com/framework/custom-providers/) declares its variables the same way, on the credential field itself: ```rust use everruns::{CredentialFormSchema, DriverDescriptor, DriverId, FormField}; DriverDescriptor { display_name: "Acme".into(), credential_schema: CredentialFormSchema { fields: vec![ FormField::password("api_key", "API Key") .required() .env("ACME_API_KEY"), ], instructions_markdown: "Create a key in the Acme console.".into(), }, base_url_env: Some("ACME_BASE_URL".into()), ..DriverDescriptor::chat_only(DriverId::external("acme"), factory) } ``` A driver that declares nothing is never configured from the environment, whatever its id is spelled. That is the safe default: the registry’s built-in schema declares no variable, so a driver opts in by naming what its vendor reads. ## Server deployments never read the environment Credential loading is an injected concern, not something a driver does. A driver only *declares* names; the declaration reads nothing and is simply never consulted on the server. `EnvCredentialProvider` is the single place in the workspace that pairs a driver’s declarations with a real environment lookup, and it is for standalone, CLI, and development use. The multitenant server resolves credentials from its encrypted database and constructs no `CredentialProvider` at all. This is the fail-closed Key Resolution Contract: a platform-level key reachable from a shared host environment would silently fund tenant execution. So `OpenAI::from_env`, each driver’s `from_env`, and `EnvCredentialProvider` belong in your own binaries and dev entrypoints. Hosted deployments configure providers through the Settings UI, which renders the same declared schema. ## Resolving credentials yourself To build the provider without going through a driver crate’s `from_env` — a custom `ProviderKey`, or your own credential source — resolve against the descriptor: ```rust use everruns::{CredentialProvider, EnvCredentialProvider, provider_from_env}; let driver = everruns_anthropic::descriptor(); // What this driver reads, for an error message or a setup check. let names = driver.declared_env_vars(); // The same resolution `from_env` performs. let provider = provider_from_env(&driver, "primary")?; // Or inspect the resolved fields first. if let Some(credentials) = EnvCredentialProvider.resolve(&driver) { let _ = credentials.api_key(); } ``` `ProviderCredentials` carries every declared field, so multi-field drivers stay expressible; `document()` produces the exact credential shape the server stores, which is why an env-resolved credential and an operator-entered one reach the driver through one path. --- # Custom Backends > Decide when a Framework application should cross into low-level execution-host composition. Source: Most applications should use `everruns::Agent`, `Model`, and `Session`. That surface deliberately hides stored harness records, platform registries, backend stores, worker phases, and durable scheduling topology. Cross into low-level composition only when your application is itself an execution host, for example, a server, evaluation harness, research runtime, or specialized embedder that must replace storage or orchestration components. ## Host-level choices The low-level crates expose focused contracts for: * core agent, event, capability, and provider values; * the shared Input/Reason/Act kernel and sans-I/O turn planner; * runtime host phases, canonical event history, and in-memory reference stores; * local SQLite-backed task and schedule state; * platform/control-plane entities and durable deployment components. An advanced host depends on `everruns` plus `everruns-host` and the focused crates it actually needs. `everruns-host` is the only low-level host boundary: there is no separate runtime crate. It is healthy for such a host to use low-level extension traits; the goal is not to re-export every backend through one facade. ## Two engine boundaries `everruns::Engine` is a concrete application object that owns Agent snapshots, sessions, history, and resume authority. It is the normal Framework entrypoint, not an extension trait. Applications do not implement it. `everruns-engine` is the lower-level shared execution kernel. Advanced hosts compose its `Execution` contract and serializable `TurnExecution` state machine, `InputAtom`/`ReasonAtom`/`ActAtom`, and phase values. The immediate implementation lives in `everruns-host`; the checkpointed implementation lives in `everruns-durable`. Both use narrow contracts from `everruns-core`. The kernel has no dependency on host, platform, server, worker, or durable crates. Do not copy state advancement or the phase loop into a custom backend; implement the execution boundary and keep deployment-specific service selection in the host. See [Framework Architecture](https://docs.everruns.com/framework/architecture/) for the complete layer map and the distinction between immediate and durable execution. Conversation persistence is the one backend with a single write path. Replace it by implementing the canonical `EventLog`/`EventReader` SPI and passing it to `HostBackends::with_event_log`; the required snapshot, continuation, and polling behavior is specified in [Implementing a custom event log](https://docs.everruns.com/framework/canonical-events/#implementing-a-custom-event-log). ## Security boundary Backend replacement does not relax tenant, credential, filesystem, or tool execution boundaries. Preserve event ordering, credential redaction, workspace containment, and cancellation behavior when adapting the host. A custom backend must fail explicitly when it cannot satisfy a required contract. --- # Custom Providers > Implement and attach a custom model provider through the open Framework driver boundary. Source: Use a custom provider when an application talks to a model service that the Framework does not configure for you. The extension boundary is the public `ChatDriver` trait plus a `Provider` value. The agent selects that provider’s model with a plain credential-free string id. At a high level: ```rust use everruns::{Agent, BuildError, ChatDriver, Provider}; fn agent_for(driver: impl ChatDriver) -> Result { Agent::builder() .instructions("Use the company model gateway.") .provider(Provider::new("company-gateway", driver)) .model("assistant-v2") .build() } ``` A driver implements the streaming chat-completion contract. It receives the resolved endpoint, model-facing messages (`everruns::llm::Message`), and call configuration, and returns an `LlmResponseStream`. Exact trait methods and event shapes live in the [`everruns::ChatDriver` API reference](https://docs.rs/everruns/latest/everruns/trait.ChatDriver.html). Keep credential lookup and refresh in trusted host/provider configuration. Model ids must remain safe to log, compare, store, and pass across application boundaries. Provider errors should preserve useful decisions without including secrets. A driver registered through a `DriverDescriptor` also declares which environment variables it reads, on its own credential fields. Declaring is inert — the driver never reads them — and it is what lets a caller resolve the provider from the environment without any central name mapping. See [Credentials](https://docs.everruns.com/framework/credentials/). Use focused provider crates when they already implement the protocol you need. Custom backends and provider registry topology belong to [low-level host composition](https://docs.everruns.com/framework/custom-backends/), not ordinary model selection. --- # Direct Decisions > Ask Decisions for a number instead of prose, without building an agent. Source: Some questions have typed answers. *Is this claim supported by the source? How severe is this complaint? Which queue does this ticket belong in?* A chat model answers those in prose, so the call site ends up with a prompt asking for JSON, a parser, and a fallback for when the parse fails. A decisions answers them as numbers instead, and the decision stays in your code. This is the counterpart to [direct model calls](https://docs.everruns.com/framework/direct-model-calls/): the same shape, a different contract. | | `Model` | `Decisions` | | ------------------- | ----------------- | ---------------------------- | | you send | messages | state plus typed questions | | you get back | text | calibrated numbers | | decides the outcome | the model’s words | your threshold, in your code | | streams | yes | no — one round trip | ## Quick start ```bash cargo add everruns --features typesafe cargo add tokio --features macros,rt-multi-thread export TYPESAFE_API_KEY=... # a key from typesafe.ai ``` ```rust use everruns::{Decisions, TypeSafeAI}; #[tokio::main] async fn main() -> Result<(), Box> { let decisions = Decisions::new("jev-latest", TypeSafeAI::from_env()?); let text = "CONGRATULATIONS! You've WON $1,000,000. Click here to claim your prize now!"; let spam = decisions.probability("Is this message spam?", text).await?; println!("spam: {spam:.2}"); if spam > 0.9 { println!("quarantined"); } Ok(()) } ``` ```text spam: 0.98 quarantined ``` One number, and your own `> 0.9` decides — the model reports how likely, not what to do. The same call answers `0.03` for “Standup moved to 10am.” and `0.74` for a bare “Claim your prize now!”; the middling ones are what a threshold is for. `--features typesafe` adds `TypeSafeAI` and the `Jev` capability. Without it `everruns` names no vendor: `Decisions::new` takes any `DecisionsService`. ## Three primitives A decision asks one or more questions about the same state. Each is one of three shapes: ```rust use everruns::Decisions; let answers = decisions .about("I've been on hold for two hours and my card was charged twice.") .noul("urgent", "Does this convey urgency?") .score( "severity", "How severe is the problem the writer describes?", [ "A minor annoyance", "A real problem with their account", "Serious harm requiring immediate action", ], ) .choice( "queue", "Which team should handle this message?", ["billing", "technical", "sales"], ) .send() .await?; let urgent: f64 = answers.probability("urgent")?; let queue: &str = answers.selected("queue")?; ``` * **`noul`** — whether something holds, as the probability of yes. A value near 0.5 means yes and no are near-equally likely, not “medium”. ([Noul](https://docs.typesafe.ai/primitives/noul)) * **`choice`** — exactly one option from your set, with the distribution behind it. Needs at least two options. ([Choice](https://docs.typesafe.ai/primitives/choice)) * **`score`** — a position along levels you define, lowest first. Needs at least two levels. ([Score](https://docs.typesafe.ai/primitives/score)) The three are System One’s own, so TypeSafe’s [Primitives](https://docs.typesafe.ai/primitives) documents what each answer means and how to choose between them, and [State](https://docs.typesafe.ai/concepts/state) covers what to put in the `about(...)` value. Everruns names them the same way rather than inventing synonyms. Questions in one call are answered **in parallel inside a single request**, so asking five costs one round trip, not five. TypeSafe calls leaning on that [speculative fan-out](https://docs.typesafe.ai/patterns/fan-out): ask the questions you *might* need, and let your code decide which ones mattered. ## Ids are yours; instructions are the model’s The id labels the answer for your code and is never sent to the model. A question whose meaning lives in its id asks nothing: ```rust // Wrong: the model never sees "is_the_joke_funny". .noul("is_the_joke_funny", "?") // Right: the instructions carry the question. .noul("funny", "Would a general audience laugh at this joke?") ``` The same applies to score levels. Describe concrete situations — “A minor annoyance” reads on its own where “2 out of 5” does not. ## Read the tail, not the average For “is there any serious hit here” rules, read the probability mass at or above a level rather than the weighted score. Something probably fine but possibly awful must not average into fine: ```rust // Not: answers.score("severity")? > 1.5 let serious = answers.tail("severity", 2)?; if serious > 0.3 { println!("escalated"); } ``` ## Errors `DecisionsError` separates configuration mistakes from service failures, the same split [`CompletionError`](https://docs.everruns.com/framework/direct-model-calls/#errors) makes: * `MissingService` — the decisions was built without a service to reach. * `NoQuestions` — the decision was sent with nothing to ask. * `Unconfigured` — the service exists but the deployment never configured its credential, so it would answer nothing. * `NoSuchAnswer(id)` — you read an id that was not asked, or read an answer as the wrong shape (a `score` as a probability). * `Call(..)` — the service call failed, carrying the `AgentLoopError`. The first three are caught before any request leaves the process. `Unconfigured` is worth handling separately: a guardrail treats it as fail-open, but a direct caller usually wants to know the number never arrived rather than read a confident-looking default. ## Going lower `Decision` is a thin value-first layer over `DecisionsService`, which is public. Applications that already hold a service — or implement their own, over a different vendor or a local model — can call it directly with `everruns`’s `DecisionRequest`, `DecisionQuestion`, and `DecisionAnswer` re-exports: ```rust use everruns::{DecisionQuestion, DecisionRequest, DecisionsService}; let outcome = service .evaluate( DecisionRequest::new("Claim your prize now!") .ask("spam", DecisionQuestion::noul("Is this message spam?")), ) .await?; ``` That surface is the contract itself: every question type and the full `DecisionOutcome`, including usage, with nothing defaulted for you. Implementing `DecisionsService` is also how a different decisions — another vendor, or a fine-tuned local model — plugs into the same `Decisions`, guardrails included. ## Choosing a model The model is named up front, the way [`Model::new`](https://docs.everruns.com/framework/direct-model-calls/) names one: the service is transport, and the model is the thing that answers. There is no default to inherit without noticing, because a threshold calibrated against one version is not evidence about the next. Ids are the provider’s own, so they are spelled the way the vendor spells them. `jev-latest` is TypeSafe’s alias for the current Jev, so it tracks whatever the current version is; an exact id like `jev-1.13.0` pins one, so a vendor update cannot move your thresholds under you. Bare `jev` is not an id the API knows — nothing here rewrites what you pass. Ask for the alias and read back what answered, which is the id to pin once a threshold is calibrated: ```rust let version = answers.model(); // "jev-1.13.0" for a "jev-latest" request ``` A single call can name a different model with the same method on the request builder, and it wins for that call: ```rust let answers = decisions .about("...") .noul("urgent", "Does this convey urgency?") .model("jev-1.13.0") .send() .await?; ``` A deployment that must pin a model does so by never exposing the knob in the config an agent writes — not by the type being unable to carry one, because there will be other decision services and other models. A deployment running the Everruns platform configures a separate `UTILITY_TYPESAFE_API_KEY` for its [guardrails](https://docs.everruns.com/capabilities/guardrails/) — a different account from the one an embedding application holds. ## Giving an agent the decisions Everything above is agentless: your code asks, your code decides. The other half is letting an *agent* classify as part of its own work — checking a claim against a source before citing it, rating a draft before sending it. `Jev` is the same decisions as a capability, so an agent gets it as a tool: ```rust use everruns::{Agent, Engine, Jev, Model, OpenAI}; let agent = Agent::builder() .name("reviewer") .instructions( "You review copy. When asked how something reads, measure it with \ jev_decision and report the numbers rather than judging by eye.", ) .model(Model::new("gpt-5.6-terra", OpenAI::from_env()?)) .capability(Jev::from_env()?) .build()?; let session = Engine::new().create(agent); let turn = session .run("Rate this subject line for pushiness: 'Act now before it is too late'") .await?; ``` The agent calls `jev_decision`, writing its own questions about whatever it is looking at, and gets the same calibrated numbers back. It is the identical tool the hosted [TypeSafe integration](https://docs.everruns.com/integrations/typesafe/) gives platform agents — same name, same schema — so behavior matches whether you embed the Framework or run on Everruns. Which one to reach for: | | you decide | the agent decides | | ------------ | -------------------------------------- | --------------------------------- | | **who asks** | your code writes the questions | the model writes the questions | | **use** | `Decisions` | the `Jev` capability | | **good for** | a policy check, a routing rule, a gate | verification inside a longer task | ## What stays with an agent A decision owns no session, no history, and no workspace, and runs no tools. Reach for an [agent](https://docs.everruns.com/framework/agents/) as soon as the work needs any of those. Typed output guarantees the interface, not the truth: validate thresholds against your own data and consequences. ## Testing without a credential `Decisions::simulated` returns a fixed number from an in-process stub. It is a **test double**, not a local decisions: it runs no inference, reads nothing from the state you pass it, and is not a way to classify without a provider. It exists so tests and examples can assert on the code around a decision without a network call or an API key. Real work always goes through a decisions service — `TypeSafeAI` above, or your own `DecisionsService`. ```rust use everruns::Decisions; let p = Decisions::simulated(0.93) .probability("Does this convey urgency?", "Two hours on hold.") .await?; assert!(p > 0.9); ``` Because the answer is fixed, a simulated decision proves your threshold logic runs — never that a real decisions would return that number. The runnable version of this page is [`direct_decisions.rs`](https://github.com/everruns/everruns/blob/main/crates/everruns/examples/direct_decisions.rs). It uses the stub by default so it runs with no key; pass `--live` (with `--features typesafe` and `TYPESAFE_API_KEY` set) to send the same questions to a real decisions. [`agent_decisions.rs`](https://github.com/everruns/everruns/blob/main/crates/everruns/examples/agent_decisions.rs) does the same for the agent path. --- # Direct Model Calls > Call a model once through the Framework's provider edge, without building an agent. Source: Some work is one prompt and one answer: classify a string, draft a summary, extract a field. That needs the provider edge — drivers, endpoints, credentials, retries, error classification — but none of the agent loop around it. `Model::complete` is the whole API for that case: ```rust use everruns::{Model, OpenAI}; let model = Model::new("gpt-5.6-terra", OpenAI::from_env()?); let answer = model.complete("Name the three primary colors.").await?; println!("{answer}"); ``` The model is the same value an [agent](https://docs.everruns.com/framework/agents/) takes, reached through the same [`Provider`](https://docs.everruns.com/framework/models-and-providers/) — which can also be asked [which models it offers](https://docs.everruns.com/framework/model-catalogs/). Nothing is persisted: a direct completion owns no session, no history, and no workspace. Reach for an agent as soon as the work needs tools, multiple turns, durability, or events. ## Testing without a provider `Model::simulated` returns canned responses from an in-process simulator (`everruns-llmsim`). It is a **test double**, not a local model: it runs no inference and is not a way to use Everruns without a model provider. It exists so tests and examples can assert on agent behavior without a network call or an API key. Real work always goes through a provider — see [Supported providers](https://docs.everruns.com/framework/supported-providers/). ```rust use everruns::Model; let answer = Model::simulated("4").complete("What is 2 + 2?").await?; assert_eq!(answer, "4"); ``` See [Testing and simulation](https://docs.everruns.com/framework/testing-and-simulation/) for scripted multi-response simulators. ## System messages, context, and controls `Model::completion` describes the call before sending it. Messages append in call order; each control maps to one provider request field and stays unset unless assigned, so the provider keeps its own defaults. ```rust use everruns::{Model, ReasoningEffort}; let response = model .completion() .system("Answer with a single word.") .user("What is the capital of France?") .max_tokens(16) .reasoning_effort(ReasoningEffort::Low) .send() .await?; println!("{}", response.text); println!("{:?} tokens", response.metadata.total_tokens); ``` `send` returns the full `LlmResponse` — text, reasoning artifacts, tool calls, and call metadata. `text()` returns only the answer text. Replay prior turns with `.assistant(...)`: the completion carries no history of its own, so context is whatever the call passes. When the model is a bare provider-visible id, attach the provider on the completion instead of the model: ```rust use everruns::{Model, OpenAI}; let answer = Model::from("gpt-5.6-terra") .completion() .provider(OpenAI::from_env()?) .user("Summarize this in one line: ...") .text() .await?; ``` ## Streaming `stream()` returns the provider’s events as they arrive, ending with a `Done` event carrying the call’s metadata: ```rust use everruns::{LlmStreamEvent, Model}; use futures::StreamExt; let mut stream = model.completion().user("Write a haiku.").stream().await?; while let Some(event) = stream.next().await { if let LlmStreamEvent::TextDelta(delta) = event? { print!("{delta}"); } } ``` ## Errors `CompletionError` separates configuration mistakes from provider failures: * `MissingProvider` — the model names an id but nothing says how to reach it. * `NoMessages` — the completion was sent empty. * `Call(..)` — the provider call failed, carrying the `AgentLoopError` and its full `LlmError` decision. The first two are caught before any request leaves the process. ## Going lower `Completion` is a thin value-first layer over `Provider`, which is public. Applications that already hold a `Provider` — or implement their own [`ChatDriver`](https://docs.everruns.com/framework/custom-providers/) — can call it directly with `everruns::llm`’s `Message` and `MessageRole`, plus the crate-root `LlmCallConfig` and `LlmResponse` re-exports: ```rust use everruns::llm::{Message, MessageRole}; use everruns::{LlmCallConfig, Provider}; let response = provider .chat_completion( vec![Message::text(MessageRole::User, "What is 2 + 2?")], &LlmCallConfig::new("gpt-5.6-terra"), ) .await?; ``` That surface is the driver boundary itself: every field of `LlmCallConfig`, including tool definitions, is available, and nothing is defaulted for you. The runnable version of this page is [`direct_llm.rs`](https://github.com/everruns/everruns/blob/main/crates/everruns/examples/direct_llm.rs), which runs offline without an API key. --- # Events and Cancellation > Subscribe to live Framework session events and cancel a turn cooperatively. Source: Subscribe before sending a message to observe its live event projection: ```rust use everruns::{Agent, Engine, OpenAI}; let agent = Agent::builder() .instructions("Be concise.") .provider(OpenAI::from_env()?) .model("gpt-5.6-terra") .build()?; let session = Engine::new().create(agent); let mut events = session.events(); let pending = session.send("Start.").await?; while let Some(event) = events.recv().await? { println!("{}", event.event_type()); if event.turn_id.as_deref() == Some(&pending.turn_id) && event.kind.is_terminal() { break; } } let turn = pending.wait().await?; assert!(turn.success); ``` Known events have typed `SessionEventKind` values. Unknown canonical event types are preserved as `Other` with their payload, so the projection does not silently drop information. The feed is live and non-blocking: `send` starts execution in the session’s background actor, a slow or dropped consumer does not stop the turn, and the feed is not a durable replay API. Use [lifecycle hooks](https://docs.everruns.com/framework/lifecycle-hooks/) instead when application work must be awaited at an execution boundary or its failure must affect the run. See [Canonical Framework events](https://docs.everruns.com/framework/canonical-events/) for canonical envelopes, live-delta memory bounds, explicit lag handling, ordering, and the durability boundary. Use [Session History and Resume](https://docs.everruns.com/framework/session-history/) to rebuild a bounded persisted transcript after live lag or a process restart. ## Cancel a turn A message receipt exposes the specific accepting turn, so live applications can cancel without racing against whichever turn is active later: ```rust let pending = session.send("Start.").await?; pending.turn().cancel().await?; let cancelled = pending.wait().await?; assert!(!cancelled.success); ``` `run_with` retains cancellation-token convenience for request/response calls: ```rust use everruns::{CancellationToken, RunOptions}; let cancel = CancellationToken::new(); let options = RunOptions::new().cancel_token(cancel.clone()); cancel.cancel(); let turn = session.run_with("Stop before starting.", options).await?; assert!(!turn.success); ``` Cancellation is cooperative. Cancelling drops the in-flight turn future and tears down tool work through the same runtime path; it does not kill the host process or provide an independent transaction boundary. --- # Runnable Examples > Complete Framework programs, maintained and compiled with the everruns crate. Source: The [`crates/everruns/examples` catalog](https://github.com/everruns/everruns/tree/main/crates/everruns/examples) contains the maintained public examples. Each imports the `everruns` facade. ## Complete agents The root-level [`examples`](https://github.com/everruns/everruns/tree/main/examples) catalog contains six Framework walkthroughs. Each folder includes the program, instructions, fixtures where applicable, and recording scripts. Run them from a repository checkout: their dependencies point to the workspace crates. `cargo run` uses a real provider and can incur charges. CI tests offline tool behavior and recording logic; it does not establish the quality of a live model’s answer. | Example | Provider and model | What it does | | -------------------------------------------------------------------------------------------------- | ------------------------------- | ----------------------------------------------------------------------------------------- | | [Support Agent](https://docs.everruns.com/framework/examples/support-agent/) | OpenAI `gpt-5.6-terra` | Chooses between MFA recovery, lockout, and browser troubleshooting from facts and policy. | | [Everruns Support Agent](https://docs.everruns.com/framework/examples/everruns-support-agent/) | Anthropic `claude-opus-5-5` | Searches and reads citable official documentation snapshots. | | [Coding Review Agent](https://docs.everruns.com/framework/examples/coding-review-agent/) | Anthropic `claude-sonnet-5` | Reads a refund contract and executes a fixed regression before reporting a defect. | | [Research Agent](https://docs.everruns.com/framework/examples/research-agent/) | OpenRouter `z-ai/glm-5.2` | Searches and fetches primary sources before writing a cited brief. | | [Incident Commander Agent](https://docs.everruns.com/framework/examples/incident-commander-agent/) | Meta Model API `muse-spark-1.3` | Investigates fixture telemetry and persists an evidence-backed incident update. | Start with Support for typed tools, Research for reusable capabilities, or Code Review for restricted execution. Each walkthrough shows the agent builder and session loop, explains expected behavior, and documents what remains a fixture. These are in-memory sessions. For durability itself, use the session-history and workspace examples below. Importable hosted Platform definitions live separately in [`examples/agents`](https://github.com/everruns/everruns/tree/main/examples/agents). ## Execution runtimes | Example | Provider and model | What it does | | -------------------------------------------------------------------------------------- | --------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | [Bashkit Repo Agent](https://docs.everruns.com/framework/examples/bashkit-repo-agent/) | OpenAI `gpt-5.6-terra` | Cuts a release in a real repository with the sandboxed Bashkit shell as its only tool, then verifies the result on disk. | | [Foreman](https://docs.everruns.com/framework/examples/foreman-agent/) | TypeSafe `jev-latest` over an Everruns session, Codex, or yolop | Supervises a live coding session with nine decisions questions per reading, and stops, verifies, or finishes it from a deterministic policy. | ## Core crate catalog | Example | Demonstrates | Command | | ------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | | [`capability_configuration.rs`](https://github.com/everruns/everruns/blob/main/crates/everruns/examples/capability_configuration.rs) | Typed Compaction and ToolSearch, a code-defined Definition, and a dynamic third-party reference through one entrypoint | `cargo run -p everruns --example capability_configuration` | | [`workspace_policy.rs`](https://github.com/everruns/everruns/blob/main/crates/everruns/examples/workspace_policy.rs) | Safe workspace scopes and trusted starter files, fully offline | `cargo run -p everruns --example workspace_policy` | | [`direct_llm.rs`](https://github.com/everruns/everruns/blob/main/crates/everruns/examples/direct_llm.rs) | One-shot, builder, and streamed model calls with no agent, fully offline | `cargo run -p everruns --example direct_llm` | | [`direct_decisions.rs`](https://github.com/everruns/everruns/blob/main/crates/everruns/examples/direct_decisions.rs) | Typed questions and calibrated answers with no agent, fully offline | `cargo run -p everruns --example direct_decisions` | | [`agent_decisions.rs`](https://github.com/everruns/everruns/blob/main/crates/everruns/examples/agent_decisions.rs) | An agent that classifies with its own questions, offline by default | `cargo run -p everruns --example agent_decisions` | | [`live_session.rs`](https://github.com/everruns/everruns/blob/main/crates/everruns/examples/live_session.rs) | Non-blocking send, automatic steering, and optional waiting, fully offline | `cargo run -p everruns --example live_session` | | [`hello.rs`](https://github.com/everruns/everruns/blob/main/crates/everruns/examples/hello.rs) | Small live-provider agent | `cargo run -p everruns --features openai --example hello` | | [`production_agent.rs`](https://github.com/everruns/everruns/blob/main/crates/everruns/examples/production_agent.rs) | Tools, files, and production-style setup | `cargo run -p everruns --features openai --example production_agent` | | [`github_monitor.rs`](https://github.com/everruns/everruns/blob/main/crates/everruns/examples/github_monitor.rs) | Typed tools and an offline simulation mode | `cargo run -p everruns --features openai --example github_monitor -- --simulate` | | [`session_work.rs`](https://github.com/everruns/everruns/blob/main/crates/everruns/examples/session_work.rs) | Offline session work, leased delivery, and completion wakes | `cargo run -p everruns --example session_work` | | [`session_history.rs`](https://github.com/everruns/everruns/blob/main/crates/everruns/examples/session_history.rs) | Offline durable resume and bounded history pages | `cargo run -p everruns --features local --example session_history` | | [`engine_sessions.rs`](https://github.com/everruns/everruns/blob/main/crates/everruns/examples/engine_sessions.rs) | Concrete Engine ownership, isolated sessions, and engine-scoped resume | `cargo run -p everruns --example engine_sessions` | | [`workspace_heads.rs`](https://github.com/everruns/everruns/blob/main/crates/everruns/examples/workspace_heads.rs) | Isolated Git workspace heads, Environment binding, and durable reopening | `cargo run -p everruns --features local --example workspace_heads -- /path/to/repo /path/to/state` | | [`canonical_events.rs`](https://github.com/everruns/everruns/blob/main/crates/everruns/examples/canonical_events.rs) | Offline bounded recording and typed rendering of live events | `cargo run -p everruns --example canonical_events` | | [`subagents.rs`](https://github.com/everruns/everruns/blob/main/crates/everruns/examples/subagents.rs) | Public facade composition for delegated work | `cargo run -p everruns --features openai --example subagents` | | [`observe_and_cancel.rs`](https://github.com/everruns/everruns/blob/main/crates/everruns/examples/observe_and_cancel.rs) | Live events and cancellation | `cargo run -p everruns --features openai --example observe_and_cancel` | | [`advanced_capability.rs`](https://github.com/everruns/everruns/blob/main/crates/everruns/examples/advanced_capability.rs) | Code-defined capability through the unified `capability(...)` entrypoint | `cargo run -p everruns --features openai --example advanced_capability` | | [`lifecycle_hooks.rs`](https://github.com/everruns/everruns/blob/main/crates/everruns/examples/lifecycle_hooks.rs) | Awaited agent, turn, tool, and completion handlers | `cargo run -p everruns --features openai --example lifecycle_hooks` | Live-provider modes use `gpt-5.6-terra` and require `OPENAI_API_KEY`. `capability_configuration`, `canonical_events`, `direct_llm`, `engine_sessions`, `live_session`, `session_work`, `workspace_heads`, `workspace_policy`, and `session_history` are fully offline; the GitHub monitor also offers a simulated GitHub flow: ```bash cargo run -p everruns --features openai --example github_monitor -- --simulate ``` For copyable command details and behavior notes, use the [`examples/README.md`](https://github.com/everruns/everruns/blob/main/crates/everruns/examples/README.md) next to the source. Examples that demonstrate low-level host internals remain advanced-host examples, not alternative Framework entrypoints. --- # Bashkit Repo Agent > Modify and verify a disposable repository through a sandboxed shell. Source: [Browse the complete example](https://github.com/everruns/everruns/tree/main/examples/bashkit-repo-agent). Cut a release in a disposable repository through the sandboxed [Bashkit Shell](https://docs.everruns.com/capabilities/bashkit-shell/), then verify every claimed change directly from the host. This is a real `gpt-5.6-terra` agent, not a scripted turn. ![Bashkit Repo Agent terminal demo](https://raw.githubusercontent.com/everruns/everruns/main/examples/bashkit-repo-agent/demo/demo.gif) ## What you learn How to mount a narrow read-write workspace, give an agent one execution capability, accept an interactive task, and treat independent host assertions as the success condition. ## Scenario and expected outcome The bundled fixture is a two-crate Cargo workspace with three changelog fragments. The agent must cut release `0.2.0`: update both package versions and the path-dependency pin, create today’s changelog section, preserve the `0.1.0` history, and remove the folded fragments. After the turn, Rust code reopens the real working copy and fails the process if any invariant is false. A confident model answer cannot make a broken release pass. ## Run it Clone the repository and run from its root: ```bash export OPENAI_API_KEY="your-key" cargo run -p everruns-bashkit-repo-agent ``` Type the task interactively: ```bash cargo run -p everruns-bashkit-repo-agent -- --interactive ``` The default workspace is temporary and removed on exit. Pass a scratch directory to inspect the result afterwards: ```bash cargo run -p everruns-bashkit-repo-agent -- /tmp/release-run cargo run -p everruns-bashkit-repo-agent -- --interactive /tmp/release-run ``` The configured model is `gpt-5.6-terra`. Provider access and funded credits are required; missing credentials, unsuccessful turns, and failed disk assertions exit nonzero. Never pass a repository you care about: the example materializes its fixture into the target and the agent may change anything inside the mount. ## Build the agent The definition lives in `src/agent.rs`; the prompt and disposable repository live under `src/resources/`. Read-write access is explicit—the default workspace policy is read-only. ```rust pub fn build(api_key: String, workspace: &Path) -> Result { Agent::builder() .name("bashkit-repo-agent") .instructions(include_str!("resources/instructions.md")) .provider(OpenAI::new(api_key)) .model(MODEL) .max_iterations(12) .workspace(workspace) .workspace_policy(WorkspacePolicy::read_write()) .capability(BashkitShell::new()) .build() } ``` ## Run and verify The shared observer displays a bounded shell timeline and waits for a successful turn. The host then verifies the mounted files itself. ```rust let agent = agent::build(api_key, &workspace)?; let engine = Engine::new(); let session = engine.create(agent); demo::run(&session, &request).await?; verify_release(&workspace, &release_date)?; ``` Use `session.send_and_wait(&request).await?` when a live tool timeline is not needed. ## How Bashkit behaves Bashkit interprets shell scripts in-process against `/workspace`. The model gets no host filesystem outside that mount, network, Git credentials, or subprocess execution. Commands, loops, output, and script size are bounded. Repository text is treated as untrusted data rather than agent instructions. ## Validate it ```bash cargo test -p everruns-bashkit-repo-agent bash examples/bashkit-repo-agent/demo/record.sh --check ``` These checks validate argument handling, fixture state, host assertions, and the live-demo contract without provider credentials. They do not grade model quality. ## Demo and recording The screencast types the release task into the interactive binary, displays real `bashkit_shell` calls, and ends with host-side checks over the mutated files. It does not replay prepared output. Read the [captured transcript](https://github.com/everruns/everruns/blob/main/examples/bashkit-repo-agent/demo/transcript.txt) at your own pace. With VHS, ffmpeg, a VHS-compatible browser, and funded OpenAI credentials: ```bash bash examples/bashkit-repo-agent/demo/record.sh ``` The script uses `OPENAI_API_KEY` when exported, otherwise Doppler project `everruns-dev`, config `dev`. It updates the GIF and transcript only after the model turn and host verification succeed. ## Adapt it safely Replace the fixture, request acceptance criteria, and verifier together. Keep the mount disposable and narrow, start read-only unless mutation is required, and verify consequential claims outside the model/tool boundary. ## Boundaries This demonstrates one sandboxed repository mutation, not a general coding agent. It cannot fetch dependencies, run native programs, commit, or push. The Framework session is in-memory. ## Source map `src/main.rs`: input, session, and verification flow; `src/agent.rs`: agent definition; `src/fixture.rs`: fixture materialization and assertions; `src/resources/`: prompt and sample repository; `demo/`: live VHS recording and transcript. `examples/demo-support::shell` provides terminal presentation. --- # Coding Review Agent > Tool-based code inspection, a tightly scoped execution tool, and distinguishing test failure from tool failure. Source: [Browse the complete example](https://github.com/everruns/everruns/tree/main/examples/coding-review-agent). Review a refund implementation against a written contract, then execute a bundled regression test before reporting a defect. A proposed test is not treated as proof: the agent gets actual compiler and test output. ## What you learn Tool-based code inspection, a tightly scoped execution tool, and distinguishing test failure from tool failure. ## Scenario and expected outcome Two refunds of 1,000 cents are requested against a 1,000-cent payment. Each individual call caps its own amount, but the implementation retains no cumulative refunded balance. The regression fails with `left: 2000` and `right: 1000`. The review should connect that observed failure to the missing cumulative-refund state, describe the over-refund risk, and propose a minimal fix. It must not claim the code was changed or fixed. ## Run it Install Rust/Cargo (including rustc), clone the repository, and run from its root. These folders are self-contained **within the workspace**: their Cargo manifests reference the local Framework crates, so copying one folder alone is not sufficient. ```bash git clone https://github.com/everruns/everruns.git cd everruns export ANTHROPIC_API_KEY="your-key" cargo run -p everruns-coding-review-agent ``` The configured model is `claude-sonnet-5`. Provider access and funded credits are required; a model identifier alone does not grant access. Keep keys in your environment, not in source control. Missing variables, provider errors, or unsuccessful turns exit nonzero. Try a contrasting question: ```bash cargo run -p everruns-coding-review-agent -- "Read the contract and test. Reproduce the cumulative refund bug and explain the smallest safe fix." ``` ## Build the agent This is the actual builder from `src/main.rs`. The prompt is `src/instructions.md`. Tools/capabilities supply evidence and actions; the model chooses how to use them. ```rust let agent = Agent::builder() .name("coding-review-agent") .instructions(include_str!("instructions.md")) .provider(everruns_anthropic::provider("anthropic", api_key)) .model(MODEL) .max_iterations(12) .tool(tools::inspect_change()) .tool(tools::run_regression()) .build()?; ``` ## Send, observe, and wait The Framework interaction stays readable in `main.rs`. The shared demo helper subscribes before sending, filters events to this turn, shows bounded tool previews, waits for completion, and rejects unsuccessful turns. It changes presentation only; use `session.send_and_wait(question).await?` when you do not need the live tool timeline. ```rust let engine = Engine::new(); let session = engine.create(agent); println!("MODEL: {MODEL}"); demo::run(&session, question).await?; ``` This engine is in-memory. It does not demonstrate durable session storage; the Everruns Support example can explain that API, but does not itself persist its session. ## How the tools work `inspect_change` can read only `sample_payment.rs`, `contract.md`, and `regression.rs`. `run_regression` invokes `rustc --test` on that fixed trusted fixture, runs the temporary binary, and returns its exit code and assertion output. Compilation and execution have timeouts; temporary files are removed automatically. ## Validate the behavior ```bash cargo test -p everruns-coding-review-agent python3 examples/coding-review-agent/src/render_demo.py --check ``` The offline test compiles and runs the bundled regression and asserts the observed failure. `cargo test -p everruns-coding-review-agent` passes because it verifies reproduction of the intentionally buggy fixture. A failing subprocess is expected evidence, not a failing example test. CI runs these offline checks without provider credentials. Live model behavior is evaluated separately; passing tests is not proof of answer quality. ## Demo and recording ![Coding Review Agent recorded run](https://raw.githubusercontent.com/everruns/everruns/main/examples/coding-review-agent/src/demo.gif) Read the [captured transcript](https://github.com/everruns/everruns/blob/main/examples/coding-review-agent/src/demo.txt) at your own pace. The GIF is a paginated replay of an actual provider run, with waiting time removed. It is not interactive and does not show model reasoning. Result excerpts are shortened only for display. With credentials exported and Python 3, VHS, ffmpeg, and a VHS-compatible browser installed: ```bash cd examples/coding-review-agent bash src/record.sh ``` The script captures a successful run, generates correctly wrapped pages and page durations, and renders `src/demo.gif`. It preserves the previous transcript when the provider run fails. To replay an existing transcript without another model call, run `(cd src && python3 render_demo.py && vhs demo.tape)`. `src/demo.txt` retains the displayed output; `.demo-pages/` is generated and ignored. Inspect results before sharing: public/demo data is safe here, but adapting tools may expose private data. ## Adapt it Replace the fixture with a trusted review checkout and a restricted test selection. Use a sandbox before accepting arbitrary repositories, generated code, or model-selected commands. Add a second verified run after applying a fix in a separate approved workflow. ## Boundaries This is one deliberately buggy, trusted fixture—not a general-purpose coding agent. The execution tool takes no shell command or user-supplied path, and cannot edit code. Rust including `rustc` must be installed. ## Source map `src/main.rs`: agent and session; `src/tools.rs`: file allowlist and fixed regression execution; `src/sample_payment.rs`: buggy implementation; `src/contract.md`: required behavior; `src/regression.rs`: executable reproduction. `examples/demo-support` handles shared terminal presentation; `src/record.sh` and `src/render_demo.py` handle recording. --- # Everruns Support Agent > Search and read citable documentation before answering Framework questions. Source: [Browse the complete example](https://github.com/everruns/everruns/tree/main/examples/everruns-support-agent). Answer Framework questions by searching and reading a small, inspectable corpus of official documentation. The agent retrieves evidence before answering instead of routing keywords to prewritten responses. ![Everruns Support Agent terminal demo](https://raw.githubusercontent.com/everruns/everruns/main/examples/everruns-support-agent/demo/demo.gif) ## What you learn A two-step search/read tool interface, citable evidence, and an explicit boundary around the available knowledge. ## Scenario and expected outcome The default question asks how to resume a durable session after restarting a process. The agent should search for persistence and session history, read the relevant pages, then explain the local catalog, persisted `SessionId`, and agent reattachment requirements with source URLs. It must not imply that `Engine::new()` survives a restart. When the corpus lacks an answer, it should say so instead of inventing one. ## Run it Install Rust/Cargo, clone the repository, and run from its root. This folder is self-contained **within the workspace**: its Cargo manifest references local Framework crates, so copying the folder alone is not sufficient. ```bash git clone https://github.com/everruns/everruns.git cd everruns export ANTHROPIC_API_KEY="your-key" cargo run -p everruns-framework-support-agent ``` The configured model is `claude-opus-5-5`. Provider access and funded credits are required. Keep keys in your environment, not in source control. Missing credentials, provider errors, and unsuccessful turns exit nonzero. Try another question: ```bash cargo run -p everruns-framework-support-agent -- "How do I register a custom provider?" cargo run -p everruns-framework-support-agent -- "How can a tool return a structured error?" ``` Or type a question interactively: ```bash cargo run -p everruns-framework-support-agent -- --interactive ``` ## Build the agent The definition lives in `src/agent.rs`; `main.rs` only handles input and runs the session. The prompt and documentation corpus live under `src/resources/`. Tools retrieve evidence; Opus decides what to search, read, and explain. ```rust pub fn build(api_key: String) -> Result { Agent::builder() .name("everruns-support-agent") .instructions(include_str!("resources/instructions.md")) .provider(everruns_anthropic::provider("anthropic", api_key)) .model(MODEL) .max_iterations(12) .tool(tools::search_docs()) .tool(tools::read_doc()) .build() } ``` ## Send, observe, and wait The Framework interaction stays small in `main.rs`. The shared demo helper subscribes before sending, shows bounded tool previews, waits for completion, and rejects unsuccessful turns. Use `session.send_and_wait(&question).await?` when a live tool timeline is unnecessary. ```rust let agent = agent::build(api_key)?; let engine = Engine::new(); let session = engine.create(agent); println!("MODEL: {}", agent::MODEL); demo::run(&session, &question).await?; ``` This engine is in-memory. The agent can explain durable sessions from its corpus, but the example itself does not persist its session. ## How the tools work `search_docs` ranks matches against five bundled pages and returns page IDs, public URLs, and matching excerpts. `read_doc` accepts only those page IDs and returns the complete snapshot. It never accepts an arbitrary filesystem path. ## Validate the behavior ```bash cargo test -p everruns-framework-support-agent bash examples/everruns-support-agent/demo/record.sh --check ``` Tests cover content-based search, empty and unmatched queries, complete citable reads, arbitrary-path rejection, and interactive input validation. They do not grade the model’s answer; compare a live response with the expected outcome above. CI runs the offline checks without provider credentials. Live model behavior is evaluated separately. ## Demo and recording The screencast types a question into the same interactive binary shown above, then displays the actual Opus tool calls and answer. VHS hides most provider wait time but does not replace the model or tools with scripted output. Read the [captured transcript](https://github.com/everruns/everruns/blob/main/examples/everruns-support-agent/demo/transcript.txt) at your own pace. With credentials exported and VHS, ffmpeg, and a VHS-compatible browser installed: ```bash bash examples/everruns-support-agent/demo/record.sh ``` The script uses an exported `ANTHROPIC_API_KEY` when present, otherwise Doppler project `everruns-dev`, config `dev`. It updates `demo/demo.gif` and `demo/transcript.txt` only after a successful turn. ## Adapt it Replace the bundled pages with a versioned documentation index while retaining separate search and read operations. Attach source/version metadata, restrict reads to authorized documents, and treat retrieved text as untrusted evidence rather than instructions. ## Boundaries The corpus is a five-page snapshot from 2026-09-08, not a live search of docs.everruns.com. Provenance is recorded in `src/resources/docs/README.md`, and the snapshot can lag current APIs. ## Source map `src/main.rs`: input and session execution; `src/agent.rs`: agent definition; `src/tools.rs`: bounded documentation retrieval; `src/resources/`: prompt and documentation corpus; `demo/`: live VHS recording, transcript, and recording script. `examples/demo-support` handles shared terminal presentation. --- # Foreman > A decisions supervising a coding agent it never has to stop. Source: [Browse the complete example](https://github.com/everruns/everruns/tree/main/examples/foreman-agent). A fast decisions watching a slow coding agent, and a policy in ordinary Rust deciding what to do about the numbers. A Framework port of [thruwire/foreman](https://github.com/thruwire/foreman), which placed [TypeSafe’s Jev](https://docs.typesafe.ai/introduction) above a Codex worker and asked whether semantic supervision can run *while* the work happens. ![Foreman terminal demo](https://raw.githubusercontent.com/everruns/everruns/main/examples/foreman-agent/demo/demo.gif) ## What you learn How to run a worker session and observe it at the same time: a [`Decisions`](https://docs.everruns.com/framework/examples/) turning bounded evidence into nine probabilities in one request, and a deterministic policy that owns every threshold, every limit, and the closed vocabulary of things the supervisor may do. ## The two loops The worker keeps its own loop — an Everruns session, or an external CLI in a child process. `session.send` returns a receipt immediately, and a child process is simply left running; either way the supervisory loop reads the evidence beside the live work. Activity is debounced to a floor, and only a worker finishing bypasses it. Nothing stops for the factory to think, and a test holds that claim: it counts readings taken while a worker’s turn is unresolved and fails if supervision waits its turn. ## What it assesses Five questions describe the job (`implementation_complete`, `tests_sufficient`, `requirements_satisfied`, `needs_verification`, `ready_to_finish`) and four describe the floor right now (`meaningful_progress`, `worker_stuck`, `work_off_track`, `needs_human`). Each is a Noul — the probability that a yes/no statement is true — and all nine ride one request, because questions in a decision are answered independently and in parallel. ## What it may do about them The decisions only estimates. The policy decides, safety and hard limits before productivity: escalate when a person is needed or the iteration ceiling is reached, stop a worker that is off track or stuck, retry once after a stop, finish when the completion thresholds hold and verification is resolved, start one independent verifier when a check is warranted, otherwise start or continue work. Thresholds and limits are Foreman’s defaults and are overridable through `FOREMAN_*` environment variables. ## What it looks at Never the repository. One bounded snapshot per reading: worker status, elapsed time, tool calls and output tails, `git status`, a bounded `git diff`, the untracked paths a diff cannot show, recent session events, verification results, and the previous assessment and decision. An unbounded observation would make supervision as slow as the work it is watching. That snapshot goes to the decisions’s service on every reading, so a bounded slice of the repository leaves the machine on every run — point `--repo` at a private repository only if that is acceptable for it. `demo` works on a fixture it materializes itself, so it carries nothing of yours. The repository content in an observation is also untrusted input to the decisions, and that it can only produce a number is the point: the decisions never names an action, and every action the policy can take is in one readable file. ## Run it Foreman’s own two entry points, and they mean the same things here: ```bash cargo run -p everruns-foreman-agent --bin foreman -- demo foreman run --repo ./my-project --job "Add rate limiting, and test it." ``` Both are real runs — same worker, same decisions, same credentials. The only difference is who chose the repository and the job: | Command | Repository | Job | Needs | | --------------- | ------------------------------------------- | ----------------------- | --------------------------------- | | `foreman demo` | a bundled fixture, in a temporary directory | one it ships with | `TYPESAFE_API_KEY` + the worker’s | | `foreman run …` | yours, named by `--repo` | yours, named by `--job` | the same | `demo` exists because a fixed starting state makes the ending checkable: the job names a rate schedule, so at the end the repository either prices by weight or it does not. Those checks run at the bottom of the run and read the files, not the supervisor’s opinion of them. `demo` writes its fixture into a temporary directory unless `--repo` says otherwise, and `run` never writes a fixture at all — `--repo` is your project, and the only thing that touches it is the worker. It will be modified. ## One supervisor There is one, and it is always real: a `Decisions`, a budget, one request, nine answers. There is no offline mode and no second supervisor with fabricated numbers — every run asks a vendor the nine questions. CI cannot, so the test suite substitutes a different `DecisionsService`, the Framework’s own seam for answering typed questions without a vendor, rather than adding a branch to the supervisor. The stub receives the observation as JSON exactly as a vendor’s service does and answers from a table keyed by what is on the floor, so the test exercises the whole request path rather than bypassing it. ## The supervisor runs the tests itself It already runs `git` rather than asking the worker what changed, and `--tests` applies the same reasoning to the suite: a worker reporting its own green tests is a claim, and a host-run result is a fact. It lands in the observation as `test_results` — the field Foreman declares and never fills — and `tests_sufficient` moves on it. The suite runs once as a baseline before any worker starts, then whenever the floor is quiet; between times the last result is carried with its age, because a stale pass should not read as a fresh one. ## Who does the work `--worker` picks the crew. All three are watched identically, because what the supervisor reads is a bounded observation and the strongest evidence in one — the repository’s own diff — is gathered by the host either way. | `--worker` | What runs | Independent verification | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | | `session` (default) | An Everruns session on the [Bashkit Shell](https://docs.everruns.com/capabilities/bashkit-shell/), `meta/muse-spark-1.3-contributor` through OpenRouter | A second session under the default read-only workspace policy | | `codex` | `codex exec --cd … --sandbox workspace-write --color never --json …`, the line Foreman itself runs | The same CLI with `--sandbox read-only` | | `yolop` | `yolop -C … -p …`, its one-shot print interface | Mission only — yolop publishes no read-only mode | Anything else is a template: `--worker-command "mycli --cd {repo} --task {mission}"`, where both placeholders are substituted as whole arguments so no shell sees either. A session is observed through its own canonical event stream, which arrives already typed. A CLI offers none of that, so an external worker is observed through stdout and stderr, and a JSONL line that names its own `type` counts as a step. ## The floor The bundled fixture is a small shell project that prices every parcel at one flat rate; the job is to replace that with weight tiers and cover the boundaries. Shell, deliberately: `bash tests/run.sh` needs no framework, no interpreter and no network, so the same suite runs inside the Bashkit sandbox, on the host, and inside an external agent. On the session crew the coding worker mounts it read-write and the verifier mounts the same directory under the default read-only policy, so “independent check” is a property of the mount rather than a request in a prompt. The supervisor runs `git` itself rather than asking the worker what it did, which is also why an external CLI is supervised just as well as a session. ## What the Framework changes The architecture is Foreman’s; the runtime underneath it is not. A worker is a session rather than a subprocess, so stopping one is a cooperative turn cancellation instead of a signal to a process group. Evidence is the canonical event stream rather than parsed JSONL. A retry is a fresh session over the same workspace. Read-only verification is a workspace policy. Steering exists — sending into a live turn applies at the next iteration boundary — and is deliberately left out of the policy’s vocabulary so the comparison with the original stays honest. ## Limits This is an architectural experiment, and porting it does not make it a proven one. Decisions accuracy for this use is unproven and the thresholds are uncalibrated: false positives stop useful workers, false negatives let bad work continue. Observations are bounded and therefore incomplete. One coding worker runs at a time, a verifier reports evidence rather than proof, and the session state is in memory. --- # Host Shell Agent > Fix a failing Rust test suite by compiling and running it, inside a kernel-enforced boundary. Source: [Browse the complete example](https://github.com/everruns/everruns/tree/main/examples/host-shell-agent). Fix a failing Rust test suite by compiling and running it, through the [Host Shell](https://docs.everruns.com/capabilities/host-shell/) capability: real processes on this machine, bounded by a kernel policy. This is a real `gpt-5.6-terra` agent, not a scripted turn. ## What you learn How to give an agent the machine it is running on without giving it the machine: one execution capability, a kernel-enforced write and network boundary, and independent host assertions as the success condition. This is the example the sandboxed [Bashkit Shell](https://docs.everruns.com/capabilities/bashkit-shell/) cannot be. Compiling a crate and running its test binary needs a real toolchain and real child processes; an in-process interpreter over a virtual filesystem has neither. ## Scenario and expected outcome The bundled fixture is a small zero-dependency crate whose `chunk_count` drops the partial final chunk, so two tests fail. The agent must run the suite, read the failure, fix the source, and re-run until it passes. Before the agent starts, the example probes the same containment provider the capability uses and prints what it found: a write inside the workspace succeeds, a write outside it is refused by the kernel, an outbound socket is refused by the kernel. Not a description of the boundary, the boundary. After the turn, Rust code re-runs `cargo test` from the host and fails the process if the suite is still red, if an assertion was edited away, or if a test was marked `#[ignore]`. A confident model answer cannot make a red suite pass. ## Run it Clone the repository and run from its root: ```bash export OPENAI_API_KEY="your-key" cargo run -p everruns-host-shell-agent ``` Type the task interactively, or point it at a directory you keep: ```bash cargo run -p everruns-host-shell-agent -- --interactive cargo run -p everruns-host-shell-agent -- /tmp/chunker ``` With no directory, the fixture is materialized into a fresh temporary one and discarded afterwards. ## Requirements Kernel containment needs macOS, or Linux with Landlock ABI v3 fully enforced (Linux 6.2 or a backport). On a kernel that cannot enforce it, the provider fails closed and the example says so instead of running uncontained. On Linux the policy is applied by a helper process, and this example is its own: `main` routes a `__sandbox-exec` re-exec into the containment worker and names that through `SandboxLauncher::ReexecSelf`, which is what a single-binary embedder does. Nothing else needs to be on disk. --- # Incident Commander Agent > Multi-tool evidence gathering, distinguishing correlation from causation, and a narrowly scoped append-only side effect. Source: [Browse the complete example](https://github.com/everruns/everruns/tree/main/examples/incident-commander-agent). Investigate a fictional checkout alert using contrasting metrics, deployment history, logs, and a runbook; then persist an evidence-backed status update. The agent must investigate before recommending action. ## What you learn Multi-tool evidence gathering, distinguishing correlation from causation, and a narrowly scoped append-only side effect. ## Scenario and expected outcome Checkout errors rise from 0.2% to 18.4% after v43 reduces the payment timeout from 2,000 ms to 200 ms. Catalog traffic and database metrics stay normal; sampled logs show requests timing out just beyond the new deadline. Read metrics, deployments, logs, and the runbook. Identify the timeout reduction as a likely cause, not proven causation. Propose on-call ownership and approval for rollback consideration. Record that conclusion locally without claiming any production mitigation occurred. ## Run it Install Rust/Cargo, clone the repository, and run from its root. These folders are self-contained **within the workspace**: their Cargo manifests reference the local Framework crates, so copying one folder alone is not sufficient. ```bash git clone https://github.com/everruns/everruns.git cd everruns export MODEL_API_KEY="your-key" cargo run -p everruns-incident-commander-agent ``` The configured model is `muse-spark-1.3`. Provider access and funded credits are required; a model identifier alone does not grant access. Keep keys in your environment, not in source control. Missing variables, provider errors, or unsuccessful turns exit nonzero. Try a contrasting question: ```bash cargo run -p everruns-incident-commander-agent -- "Investigate checkout and explain what evidence argues against a database-wide incident. Record a concise update." ``` ## Build the agent This is the actual builder from `src/main.rs`. The prompt is `src/instructions.md`. Tools/capabilities supply evidence and actions; the model chooses how to use them. ```rust let agent = Agent::builder() .name("incident-commander-agent") .instructions(include_str!("instructions.md")) .provider(everruns_meta::provider("meta", api_key)) .model(MODEL) .max_iterations(12) .tool(tools::inspect_evidence()) .tool(tools::record_incident_update()) .build()?; ``` ## Send, observe, and wait The Framework interaction stays readable in `main.rs`. The shared demo helper subscribes before sending, filters events to this turn, shows bounded tool previews, waits for completion, and rejects unsuccessful turns. It changes presentation only; use `session.send_and_wait(question).await?` when you do not need the live tool timeline. ```rust let engine = Engine::new(); let session = engine.create(agent); println!("MODEL: {MODEL}"); demo::run(&session, question).await?; ``` This engine is in-memory. It does not demonstrate durable session storage; the Everruns Support example can explain that API, but does not itself persist its session. ## How the tools work `inspect_evidence` exposes only four named fixture categories. `record_incident_update` appends a non-empty update of at most 500 UTF-8 bytes to this example’s `src/incident.log`, normalizing newlines. There is deliberately no production rollback tool. ## Validate the behavior ```bash cargo test -p everruns-incident-commander-agent python3 examples/incident-commander-agent/src/render_demo.py --check ``` Tests verify that updates survive multiple writes, oversized/empty updates are rejected before a file is created, and evidence access is scoped. A live run should show all evidence reads followed by a persisted update that matches the observed facts. CI runs these offline checks without provider credentials. Live model behavior is evaluated separately; passing tests is not proof of answer quality. ## Demo and recording ![Incident Commander Agent recorded run](https://raw.githubusercontent.com/everruns/everruns/main/examples/incident-commander-agent/src/demo.gif) Read the [captured transcript](https://github.com/everruns/everruns/blob/main/examples/incident-commander-agent/src/demo.txt) at your own pace. The GIF is a paginated replay of an actual provider run, with waiting time removed. It is not interactive and does not show model reasoning. Result excerpts are shortened only for display. With credentials exported and Python 3, VHS, ffmpeg, and a VHS-compatible browser installed: ```bash cd examples/incident-commander-agent bash src/record.sh ``` The script captures a successful run, generates correctly wrapped pages and page durations, and renders `src/demo.gif`. It preserves the previous transcript when the provider run fails. To replay an existing transcript without another model call, run `(cd src && python3 render_demo.py && vhs demo.tape)`. `src/demo.txt` retains the displayed output; `.demo-pages/` is generated and ignored. Inspect results before sharing: public/demo data is safe here, but adapting tools may expose private data. ## Adapt it Replace each fixture with a read-only monitoring/deployment API scoped to the relevant service. Keep investigation separate from action. Add explicit approval and an audit trail before introducing any production mutation. ## Boundaries All telemetry is fictional. The log is a real local append-only artifact, ignored by Git; it contains exercise text and does not change production. Filesystem permissions and rotation are the application’s responsibility. ## Source map `src/main.rs`: agent and session; `src/tools.rs`: evidence allowlist and local recording; `src/metrics.txt`, `src/deployments.txt`, `src/logs.txt`, `src/runbook.md`: inspectable incident data. `examples/demo-support` handles shared terminal presentation; `src/record.sh` and `src/render_demo.py` handle recording. --- # Research Agent > Composing the first-party Brave Search and WebFetch capabilities, handling external evidence, and reporting uncertainty. Source: [Browse the complete example](https://github.com/everruns/everruns/tree/main/examples/research-agent). Search for relevant material, open primary sources, and synthesize a short, cited research brief. The model must read source content rather than treating search snippets as sufficient evidence. ## What you learn Composing the first-party Brave Search and WebFetch capabilities, handling external evidence, and reporting uncertainty. ## Scenario and expected outcome The default question asks what durable execution guarantees about retries and external side effects. A useful answer must distinguish replaying recorded results from the risk of retrying an unrecorded side effect. Read at least two primary sources successfully before answering. Cite those pages, distinguish documented guarantees from inference, and explain why idempotency can still be necessary. If a source is inaccessible, try another and disclose the gap. Search snippets alone do not meet the task. ## Run it Install Rust/Cargo, clone the repository, and run from its root. These folders are self-contained **within the workspace**: their Cargo manifests reference the local Framework crates, so copying one folder alone is not sufficient. ```bash git clone https://github.com/everruns/everruns.git cd everruns export OPENROUTER_API_KEY="your-key" export BRAVE_SEARCH_API_KEY="your-key" cargo run -p everruns-research-agent ``` The configured model is `z-ai/glm-5.2`. Provider access and funded credits are required; a model identifier alone does not grant access. Keep keys in your environment, not in source control. Missing variables, provider errors, or unsuccessful turns exit nonzero. Try a contrasting question: ```bash cargo run -p everruns-research-agent -- "Compare retry guarantees in Temporal Activities and Restate. Read official sources and give two findings plus one caveat." ``` ## Build the agent This is the actual builder from `src/main.rs`. The prompt is `src/instructions.md`. Tools/capabilities supply evidence and actions; the model chooses how to use them. ```rust let agent = Agent::builder() .name("research-agent") .instructions(include_str!("instructions.md")) .provider(everruns_openrouter::provider("openrouter", api_key)) .model(MODEL) .max_iterations(12) .capability(BraveSearch::from_env()?) .capability(everruns::WebFetch::new()) .build()?; ``` ## Send, observe, and wait The Framework interaction stays readable in `main.rs`. The shared demo helper subscribes before sending, filters events to this turn, shows bounded tool previews, waits for completion, and rejects unsuccessful turns. It changes presentation only; use `session.send_and_wait(question).await?` when you do not need the live tool timeline. ```rust let engine = Engine::new(); let session = engine.create(agent); println!("MODEL: {MODEL}"); demo::run(&session, question).await?; ``` This engine is in-memory. It does not demonstrate durable session storage; the Everruns Support example can explain that API, but does not itself persist its session. ## How the tools work `BraveSearch::from_env()` provides `brave_web_search`. `WebFetch::new()` provides `web_fetch`, enabled through the Framework `web-fetch` Cargo feature. Search chooses candidates; fetch retrieves page content using the existing integration’s egress controls. Download-to-file is not enabled. ## Validate the behavior ```bash cargo test -p everruns-research-agent python3 examples/research-agent/src/render_demo.py --check ``` Offline tests cover evidence-preview rendering and recording pagination; they do not perform web research. Validate a live run by checking successful `web_fetch` results for at least two primary sources and matching the final citations to pages actually read. Network/provider behavior and factual quality are not guaranteed by a green offline test. CI runs these offline checks without provider credentials. Live model behavior is evaluated separately; passing tests is not proof of answer quality. ## Demo and recording ![Research Agent recorded run](https://raw.githubusercontent.com/everruns/everruns/main/examples/research-agent/src/demo.gif) Read the [captured transcript](https://github.com/everruns/everruns/blob/main/examples/research-agent/src/demo.txt) at your own pace. The GIF is a paginated replay of an actual provider run, with waiting time removed. It is not interactive and does not show model reasoning. Result excerpts are shortened only for display. With credentials exported and Python 3, VHS, ffmpeg, and a VHS-compatible browser installed: ```bash cd examples/research-agent bash src/record.sh ``` The script captures a successful run, generates correctly wrapped pages and page durations, and renders `src/demo.gif`. It preserves the previous transcript when the provider run fails. To replay an existing transcript without another model call, run `(cd src && python3 render_demo.py && vhs demo.tape)`. `src/demo.txt` retains the displayed output; `.demo-pages/` is generated and ignored. Inspect results before sharing: public/demo data is safe here, but adapting tools may expose private data. ## Adapt it Narrow the research question and source policy, add an evidence store if results need to survive sessions, and validate citation coverage before publishing important findings. Treat fetched text as untrusted data, not as instructions. ## Boundaries Requires both OpenRouter and Brave Search credentials plus outbound HTTPS. Search and fetch can fail; twelve agent iterations cap the loop, not the bill. Word limits are instructions, not a hard output validator. This is a small research workflow, not an exhaustive literature review. ## Source map `src/main.rs`: agent, search/fetch capabilities, and session; `src/instructions.md`: primary-source and evidence policy. `examples/demo-support` handles bounded source previews and shared terminal presentation; `src/record.sh` and `src/render_demo.py` handle recording. --- # Support Agent > Typed read-only tools, separating facts from policy, and choosing different answers for different inputs. Source: [Browse the complete example](https://github.com/everruns/everruns/tree/main/examples/support-agent). Diagnose a sign-in problem by combining account facts with an explicit recovery policy. The interesting decision is whether the user needs MFA recovery, must wait for a lockout, or should try a clean browser session. ![Support Agent terminal demo](https://raw.githubusercontent.com/everruns/everruns/main/examples/support-agent/demo/demo.gif) ## What you learn Typed read-only tools, separating facts from policy, and choosing different answers for different inputs. ## Scenario and expected outcome The default customer reset their password, but has MFA enabled and neither an authenticator nor recovery codes. A password reset alone cannot solve this case. For `cust_mfa`, direct the user to verified identity recovery, explicitly noting that resetting a password does not disable MFA. Never request passwords or recovery codes. For `cust_locked`, explain the 15-minute wait. For `cust_browser`, suggest a private window and an escalation if it still fails. ## Run it Install Rust/Cargo, clone the repository, and run from its root. These folders are self-contained **within the workspace**: their Cargo manifests reference the local Framework crates, so copying one folder alone is not sufficient. ```bash git clone https://github.com/everruns/everruns.git cd everruns export OPENAI_API_KEY="your-key" cargo run -p everruns-support-agent ``` The configured model is `gpt-5.6-terra`. Provider access and funded credits are required; a model identifier alone does not grant access. Keep keys in your environment, not in source control. Missing variables, provider errors, or unsuccessful turns exit nonzero. Try a contrasting question: ```bash cargo run -p everruns-support-agent -- "cust_locked reset their password but cannot sign in. What should they do?" cargo run -p everruns-support-agent -- "cust_browser cannot sign in after a reset. What next?" ``` Or enter a question interactively: ```bash cargo run -p everruns-support-agent -- --interactive ``` ## Build the agent The agent definition lives in `src/agent.rs`; `main.rs` only handles input and runs the session. The prompt and bundled data live under `src/resources/`. Tools supply evidence; the model chooses how to use it. ```rust pub fn build(api_key: String) -> Result { Agent::builder() .name("support-agent") .instructions(include_str!("resources/instructions.md")) .provider(OpenAI::new(api_key)) .model(MODEL) .max_iterations(12) .tool(tools::lookup_customer()) .tool(tools::read_support_policy()) .build() } ``` ## Send, observe, and wait The Framework interaction stays readable in `main.rs`. The shared demo helper subscribes before sending, filters events to this turn, shows bounded tool previews, waits for completion, and rejects unsuccessful turns. It changes presentation only; use `session.send_and_wait(question).await?` when you do not need the live tool timeline. ```rust let agent = agent::build(api_key)?; let engine = Engine::new(); let session = engine.create(agent); println!("MODEL: {}", agent::MODEL); demo::run(&session, question).await?; ``` This engine is in-memory. It does not demonstrate durable session storage; the Everruns Support example can explain that API, but does not itself persist its session. ## How the tools work `lookup_customer` reads one of three fictional records from `src/resources/customers.json`. It returns facts, not a prewritten recommendation. `read_support_policy` returns the recovery rules from `src/resources/policy.md`. The model combines the two; no tool disables MFA or changes a real account. ## Validate the behavior ```bash cargo test -p everruns-support-agent bash examples/support-agent/demo/record.sh --check ``` Tests cover the distinct account states and rejection of unknown IDs. They do not grade the model’s recommendation: compare a live response with the expected outcomes above. CI runs these offline checks without provider credentials. Live model behavior is evaluated separately; passing tests is not proof of answer quality. ## Demo and recording The screencast runs the same `cargo run -q -p everruns-support-agent` command shown above. VHS hides most provider wait time but does not replace the model or tools with scripted output. Read the [captured transcript](https://github.com/everruns/everruns/blob/main/examples/support-agent/demo/transcript.txt) at your own pace. With credentials exported and VHS, ffmpeg, and a VHS-compatible browser installed: ```bash bash examples/support-agent/demo/record.sh ``` The recording script uses an exported `OPENAI_API_KEY` when present, otherwise Doppler project `everruns-dev`, config `dev`. It runs the real command inside VHS and updates `demo/demo.gif` plus `demo/transcript.txt` only after a successful turn. Public/demo data is safe here, but adapting tools may expose private data. ## Adapt it Replace the fixture lookup with your authorized customer-data service. Keep policy separate from account facts, scope lookups to the authenticated customer, and return only fields needed for the support decision. Add human approval before any account mutation. ## Boundaries All customers, policy rules, and support.example.com URLs are fictional. This is a read-only support exercise, not a live help desk. ## Source map `src/main.rs`: input and session execution; `src/agent.rs`: agent definition; `src/tools.rs`: bounded account lookup and policy tool; `src/resources/`: prompt and bundled support data; `demo/`: live VHS recording, transcript, and recording script. `examples/demo-support` handles shared terminal presentation. --- # Lifecycle Hooks > Run typed application handlers at agent, turn, tool, and completion boundaries. Source: Lifecycle hooks run trusted application code at defined execution boundaries. Register them on `Agent::builder()` when work must finish before execution can continue, or when an application needs a typed failure from a lifecycle action. ```rust use everruns::prelude::*; let agent = Agent::builder() .instructions("You are concise.") .provider(OpenAI::from_env()?) .model("gpt-5.6-terra") .on_agent_start(|context| async move { println!("starting {}", context.session_id); }) .on_turn_start(|context| async move { if context.input.content.is_empty() { Err("empty input") } else { Ok(()) } }) .on_completion(|context| async move { println!("turn stopped with {:?}", context.turn.stop_reason); }) .build()?; ``` Handlers are async `Fn` closures. An infallible handler returns `()`; a fallible handler returns `Result<(), E>` where `E` implements `Display`. Wrap synchronous work in an async block, such as `|context| async move { record(context) }`. ## Hooks or events? Hooks and [session events](https://docs.everruns.com/framework/events-and-cancellation/) serve different jobs. | | Lifecycle hooks | Session events | | --------------- | ---------------------------------------------- | -------------------------------------------- | | Purpose | Extend execution with application behavior | Observe execution for UI, telemetry, or logs | | Delivery | Awaited at a lifecycle boundary | Non-blocking stream from `Session::events()` | | Effect on a run | A pre-effect error may prevent its scoped work | Never changes or delays a run | | Registration | `AgentBuilder::on_*` | Subscribe on each `Session` | Do not register a hook merely to mirror the event feed. Use hooks when ordering or failure semantics matter; use events for observation. ## Lifecycle points Handlers at one lifecycle point run sequentially in builder registration order. | Builder method | Runs | Error behavior | | ---------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | | `on_agent_start` | Before the first turn attempted by each session | The first error returns `RunError::Hook`; the next run retries the complete chain | | `on_turn_start` | Before every turn enters the runtime | The first error returns `RunError::Hook` and prevents that turn | | `on_tool_start` | Before a model-requested tool call executes | The first error blocks only that call, skips later start handlers for it, and records a `HookFailure` | | `on_tool_end` | After a tool call reaches a terminal result, including a blocked call | Errors are isolated, recorded, and do not skip later end handlers | | `on_completion` | After a non-cancelled runtime turn reaches a terminal outcome | Errors are isolated, recorded, and do not skip later completion handlers | `Session::inspect()` may materialize a runtime but invokes no lifecycle handler. A successful agent-start chain runs once for that session. If it fails or is cancelled, the next run starts the complete chain again, so external agent-start effects should be idempotent. Tool-start runs after any earlier execution gates configured by the host. Tool-end runs for every call that reaches a terminal result, including a call blocked by an earlier gate; in that case the Framework tool-start handler might not have run. Independent calls in a parallel tool batch can run their hook chains concurrently. Completion receives terminal `Turn` values whether `turn.success` is true or false. A runtime error that produces no `Turn`, and a cancelled in-flight turn, do not run completion handlers. Every completion handler receives the same pre-completion `CompletionContext` snapshot. ## Failures and execution effects Hook contexts are owned, read-only snapshots. A hook cannot rewrite input, tool arguments, tool results, or the returned turn. Errors affect execution only where work has not happened: * agent-start and turn-start errors prevent the turn and return `RunError::Hook`; * tool-start errors prevent that one tool call and appear in `Turn::hook_failures`; * tool-end and completion errors cannot roll back completed work, so they are isolated in `Turn::hook_failures` and the remaining handlers still run. Each `HookFailure` identifies the lifecycle point and its zero-based handler index. Tool failures also identify the tool and call. A tool-start error shown to the model is deliberately generic; the handler’s detailed message remains application-facing on `HookFailure`. With no registered hooks, execution behavior is unchanged and `Turn::hook_failures` is empty. ## Cancellation, concurrency, and panics A token cancelled before `run_with` skips all handlers. Cancellation during agent-start, turn-start, or an in-flight tool chain drops the active handler future, skips the remaining turn work, and does not run completion. Side effects that finished before cancellation are not rolled back. The synthesized cancelled `Turn` does not report partial failures from the dropped in-flight hook chain. Once the runtime commits a turn, completion handlers finish in order even if that run token is then cancelled. This makes post-turn delivery predictable. The same `Fn` handlers are shared by every session. Separate sessions, and separate calls in a parallel tool batch, may invoke a handler concurrently. Protect shared mutable state inside the closure and do not depend on failure ordering across parallel tool calls. The Framework adds no hook timeout and does not catch panics. Apply an application timeout inside a handler when external work must be bounded; let ordinary Rust panic behavior handle programming defects. ## Sensitive data Lifecycle handlers are trusted in-process application code. Turn and tool contexts can contain user input, model-selected arguments, tool results, or backend error text. Do not log or export whole contexts without applying the same redaction and access controls as the underlying data. ## Runnable example The focused [`lifecycle_hooks.rs`](https://github.com/everruns/everruns/blob/main/crates/everruns/examples/lifecycle_hooks.rs) example registers all five lifecycle points around a typed tool: ```bash cargo run -p everruns --features openai --example lifecycle_hooks ``` It uses `gpt-5.6-terra` and requires `OPENAI_API_KEY`. --- # Model Catalogs > Ask a provider which models it offers, and read what each one supports. Source: Selecting a model needs an exact, provider-visible id. Anything that lets a person choose one — a picker, a `--model` flag, a settings page — needs the catalog behind it: which ids this provider serves, what they are called, and what each one supports. That is the same provider edge an [agent](https://docs.everruns.com/framework/agents/) or a [direct call](https://docs.everruns.com/framework/direct-model-calls/) uses, asked a different question: ```rust use everruns::{OpenAI, models}; for model in models::list(OpenAI::from_env()?).await? { println!("{} — {}", model.id(), model.display_name().unwrap_or("?")); } ``` Ids come back exactly as chat calls expect them, newest first, merged with the model profile registry so a provider that returns bare ids still renders human-readable names and descriptions. `list` is a provider call: it costs a round trip and catalogs change rarely, so cache the result instead of asking per keystroke. ## What each entry carries `ModelInfo` separates the id from everything around it. The id is the provider’s own; the rest is display and capability metadata, absent when neither the provider nor the registry knows it. ```rust use everruns::ModelInfo; fn describe(model: &ModelInfo) -> String { let name = model.display_name().unwrap_or(model.id()); let window = model.context_window().unwrap_or_default(); let tools = if model.supports_tools() { "tools" } else { "no tools" }; format!("{name}: {window} tokens, {tools}") } ``` * `id` — pass back unchanged. * `display_name`, `description` — for rendering. * `vendor` — who trained the model, which is not always who serves it: an aggregator offers many vendors’ models. * `context_window`, `supports_tools`, `supports_reasoning` — the common checks, from the profile registry. * `profile` — the full `ModelProfile` behind those: limits, per-million-token prices, modalities, and capability flags. Capability answers come from curated data, so `false` also covers “not in the registry”. Treat them as display hints rather than guarantees. ## From a selection to a run A selection converts straight back into the `Model` the rest of the API takes, bundled with the provider it was discovered through: ```rust use everruns::{Agent, OpenAI, models}; let catalog = models::list(OpenAI::from_env()?).await?; let picked = catalog .into_iter() .find(|model| model.supports_tools()) .ok_or("no tool-calling model")?; let agent = Agent::builder() .instructions("Be concise.") .model(picked.model()) .build()?; ``` No string handling, and nothing to reconfigure: the model already knows how to be reached. ## Providers without a catalog Not every provider can enumerate its models. That is not a failure of the request, so it is a distinct variant rather than an error to log: ```rust use everruns::{Provider, models}; let ids = match models::list(provider).await { Ok(catalog) => catalog.iter().map(|model| model.id().to_string()).collect(), // Keep the application's curated suggestions. Err(models::CatalogError::NoCatalog) => curated, Err(error) => return Err(error.into()), }; ``` `CatalogError::Call` carries the provider failure verbatim, with the full `LlmError` decision intact. ## Metadata without a provider call The profile registry is static data, so a model’s identity can be read offline: ```rust use everruns::{DriverId, models}; let profile = models::profile(&DriverId::OpenAI, "gpt-5.6-terra"); assert!(profile.is_some()); ``` A `Model` that bundles its provider answers the same question directly with `model.profile()`. A bare model id has no profile: nothing says which vendor’s registry to consult. ## Drivers and the vendor behind a provider A provider’s runtime key is the application’s own name for it, so `Provider::new("my-gateway", ...)` says nothing about which vendor’s models it serves. Declare the driver kind when they differ, and profile lookups resolve against the vendor: ```rust use everruns::{ChatDriver, DriverId, Provider}; let provider = Provider::new("my-gateway", driver) .base_url("https://gateway.example/v1") .with_driver_id(DriverId::OpenAI); ``` Unset, the driver kind falls back to the runtime key, which is the conventional case (`OpenAI::from_env()` and every driver crate’s `from_env` already declare it). A custom driver joins in by implementing `ChatDriver::list_models` and returning `DiscoveredModel` values; returning `None` — the default — is how a driver says it has no catalog. See [Custom providers](https://docs.everruns.com/framework/custom-providers/). The runnable version of this page is [`model_catalog.rs`](https://github.com/everruns/everruns/blob/main/crates/everruns/examples/model_catalog.rs), which runs offline without an API key. --- # Models and Providers > Select credential-free model identities and attach provider implementations to Framework agents. Source: The Framework separates **what model to use** from **how to reach it**: * `.model("id")` selects the provider-visible model with a credential-free string. * `Provider` supplies the driver, endpoint, and authentication needed by the host. * An agent currently accepts one provider, configured separately with `.provider(...)`. This boundary is open: a new provider does not require a new closed enum variant or provider-specific branch in application code. The Framework constructs its execution-facing model specification internally when the agent builds. ## OpenAI convenience With the `openai` feature, `OpenAI::from_env` reads `OPENAI_API_KEY` and the optional `OPENAI_BASE_URL`: ```rust use everruns::{Agent, OpenAI}; let agent = Agent::builder() .instructions("Be concise.") .provider(OpenAI::from_env()?) .model("gpt-5.6-terra") .build()?; ``` Use `OpenAI::new(key)` when the host already owns an explicitly resolved credential. Never put credentials in a model id, log them as model identity, or select provider behavior with vendor-specific detection. `from_env` is not OpenAI-specific: every driver declares the variables its own vendor SDK reads, and each driver crate exposes the same entry point. See [Credentials](https://docs.everruns.com/framework/credentials/) for the per-driver table. ## Explicit assembly Applications with their own driver can use the shared boundary directly: ```rust use everruns::{Agent, BuildError, ChatDriver, Provider}; fn agent_for(driver: impl ChatDriver) -> Result { Agent::builder() .instructions("Use the configured provider.") .provider(Provider::new("acme", driver)) .model("assistant-v1") .build() } ``` For a complete driver boundary, see [Custom providers](https://docs.everruns.com/framework/custom-providers/). To call a model once without building an agent, see [Direct model calls](https://docs.everruns.com/framework/direct-model-calls/). To ask a provider which models it offers, and what each one supports, see [Model catalogs](https://docs.everruns.com/framework/model-catalogs/). ## Simulated models, for tests ```rust use everruns::{Agent, Model}; let agent = Agent::builder() .instructions("Answer deterministically.") .model(Model::simulated("fixed response")) .build()?; ``` `Model::simulated` is backed by the focused `everruns-llmsim` crate. It is a **test double that runs no inference** — it replays canned responses so tests can assert on agent behavior without a network call or an API key. It is not a local model and not a way to run Everruns without a provider. Depend on the crate directly when building a low-level host or scripting multi-turn provider behavior; ordinary Framework applications need only `everruns`. For real work, pick a provider from [Supported providers](https://docs.everruns.com/framework/supported-providers/). --- # Persistence > Choose volatile memory, crash-durable local state, or the distributed durable Platform. Source: Framework history is a read-only projection of canonical events. Normal execution has one write path, the engine’s event log, so a resumed session and a running session cannot disagree about the conversation. | Deployment | Conversation state | Recovery boundary | Use when | | --------------------------- | ------------------------------------------------------------- | ------------------------------- | --------------------------------------------------------- | | `Engine::new()` | Volatile memory | One Engine in one process | Embedding, tests, and short-lived tools | | `Engine` with `LocalConfig` | Crash-durable local canonical events and catalog | One trusted application process | Desktop apps, CLIs, and single-node services | | Everruns Platform | PostgreSQL-backed durable workflow state and canonical events | Distributed server and workers | Restarts, retries, horizontal workers, and remote clients | ## Default: engine-lifetime memory By default, `Engine` owns a volatile session catalog and event log. It retains the immutable Agent snapshot associated with each session and requires no database, server, network connection, credential, or filesystem access. Dropping a `Session` does not immediately discard its committed history. Reopen it by passing its typed `SessionId` to the engine that created it. A separate engine cannot infer the session’s Agent configuration, and process exit loses volatile history. This default fits tests, command-line tools, short-lived workers, and applications that deliberately own a higher-level record elsewhere. ## Local: crash-durable events For local applications, the feature-gated `LocalConfig` adds a crash-durable event log under the configured application data directory. It also supplies a trusted real-disk workspace plus SQLite-backed task and schedule state: ```rust use everruns::{Agent, Engine, LocalConfig, OpenAI}; let local = LocalConfig::new(".everruns-data").workspace("./workspace"); let agent = Agent::builder() .instructions("Work inside the configured workspace.") .provider(OpenAI::from_env()?) .model("gpt-5.6-terra") .local(local) .build()?; let engine = Engine::new(); let session = engine.create(agent); ``` Enable it with `cargo add everruns --features local`. Select both directories from trusted application configuration. After a restart, rebuild the Agent from trusted application configuration, attach it to a new engine, and resume the committed session by ID. For a session created with an explicit Harness, also deserialize its portable definition and call `Engine::attach_with_harness`; `Engine::attach` remains the no-Harness path. The local profile is designed for one embedded process at a time. Coordinate process ownership before handing the directory to another application process. Within one process, every live Engine configured with the same local data directory shares one backend bundle, so concurrent Engine values cannot build divergent JSONL indexes or SQLite handles for that profile. The event-log file format and host backends are not Framework APIs. Do not edit the log or build application writes around its representation. Use `Session::history` for bounded reads and `Engine::resume` to continue a session; see [Session History and Resume](https://docs.everruns.com/framework/session-history/) for the complete lifecycle. Applications remain responsible for filesystem permissions, backups, retention, and selecting a data directory that is not controlled by model or request input. New local state files are created owner-only on Unix, but applications must still protect copied files and backups. Message content is application data and may be sensitive even though provider credentials are not written there by Framework configuration. ## Canonical host persistence Durable conversation truth belongs to canonical events; history and context are projections of that record. Advanced hosts use `EventLog` and `EventHistory` from `everruns-host`, including `JsonlEventLog` when a local append-only event log is appropriate. Framework applications continue sessions with `Engine::resume` and traverse bounded event-derived pages from `Session::history`. A host that needs its own storage implements the public `EventLog`/`EventReader` SPI and supplies it through `HostBackends::with_event_log`; see [Implementing a custom event log](https://docs.everruns.com/framework/canonical-events/#implementing-a-custom-event-log). `JsonlEventLog` bounds startup recovery before indexing: the default accepts at most 128 MiB and 1,000,000 canonical events. Oversize logs fail to open with a typed recovery-limit error instead of allocating or scanning without bound. Do not design new application persistence around a legacy storage representation. ## Platform: distributed durable execution The Everruns Platform uses the same `everruns-engine` turn state machine as the Framework, but adapts it through `everruns-durable`. The server schedules work, workers execute phases and apply effects, and PostgreSQL stores workflow checkpoints and canonical events. A worker can disappear between phases and a later worker can continue from the committed checkpoint. This is a deployment boundary, not another configuration mode on `everruns::Engine`. Remote applications use the Platform API or an SDK; product hosts compose the lower-level durable crates. See [Framework Architecture](https://docs.everruns.com/framework/architecture/) for the layer map. --- # Quickstart > Install everruns and run a real agent against a live model provider in about five minutes. Source: ## Install Add the application-facing crate with a model provider: ```bash cargo add everruns --features openai export OPENAI_API_KEY=sk-... ``` `--features openai` bundles the OpenAI driver. Any other provider is its own crate — see [Supported providers](https://docs.everruns.com/framework/supported-providers/). ## Run one turn ```rust use everruns::{Agent, Engine, OpenAI}; #[tokio::main] async fn main() -> Result<(), Box> { let agent = Agent::builder() .instructions("You are a concise assistant.") .provider(OpenAI::from_env()?) .model("gpt-5.6-terra") .build()?; let engine = Engine::new(); let session = engine.create(agent); let turn = session.send_and_wait("Explain durable execution in one sentence.").await?; println!("{}", turn.response); Ok(()) } ``` The model id stays credential-free: `.model("…")` names the model, and `.provider(…)` supplies the driver, endpoint, and key separately. `OpenAI::from_env` reads `OPENAI_API_KEY` and redacts it from debug output. `send_and_wait` is the request/response convenience; use `send` when the application needs to stream output or steer a turn while it runs. ## Give it a tool An agent becomes useful when it can act. Annotate a function and hand it over: ```rust use everruns::{Agent, OpenAI}; #[everruns::tool] /// Look up the current stock level for a SKU. async fn stock_level(sku: String) -> Result { Ok(inventory_lookup(&sku).await) } let agent = Agent::builder() .instructions("Answer inventory questions. Use the tool rather than guessing.") .provider(OpenAI::from_env()?) .model("gpt-5.6-terra") .tool(stock_level()) .build()?; ``` The macro derives the JSON schema from the signature, so the model sees typed arguments and the code stays ordinary Rust. See [Tools and macros](https://docs.everruns.com/framework/tools-and-macros/). ## Where to go next * [Agents](https://docs.everruns.com/framework/agents/) — instructions, files, workspaces, and MCP. * [Tools and macros](https://docs.everruns.com/framework/tools-and-macros/) — typed function tools. * [Sessions](https://docs.everruns.com/framework/sessions/) — multi-turn state and history. * [Framework architecture](https://docs.everruns.com/framework/architecture/) — how the pieces fit. ## Testing without a provider Once you are building for real, you will want tests that do not call a model. `Model::simulated` replays a canned response through the same model/provider path: ```rust use everruns::{Agent, Engine, Model}; let agent = Agent::builder() .instructions("You are a concise assistant.") .model(Model::simulated("Everruns is ready.")) .build()?; let turn = Engine::new().create(agent).send_and_wait("Are you ready?").await?; assert_eq!(turn.response, "Everruns is ready."); ``` It is a test double, not a local model: it runs no inference, so it proves your wiring rather than any model behavior. Use it in tests and CI, not as a way to run Everruns without a provider. See [Testing and simulation](https://docs.everruns.com/framework/testing-and-simulation/). --- # Session History and Resume > Read bounded conversation history and continue Framework sessions without importing host or runtime storage APIs. Source: Every Framework session has a typed `SessionId`. Keep that value when an application may need to reopen the conversation: ```rust use everruns::{Agent, Engine, OpenAI, SessionId}; let agent = Agent::builder() .instructions("Remember the conversation.") .provider(OpenAI::from_env()?) .model("gpt-5.6-terra") .build()?; let engine = Engine::new(); let session = engine.create(agent); let session_id: SessionId = session.session_id(); session.send_and_wait("My project is Atlas.").await?; drop(session); let resumed = engine.resume(session_id).await?; resumed.send_and_wait("Continue with that project.").await?; ``` `resume` verifies the ID against the engine’s session catalog. It does not infer identity from a non-empty transcript: a valid session can have no messages, and stray events do not create a resumable session. An unknown ID returns a typed not-found error. The resumed session uses the immutable Agent snapshot attached to that engine; it never reconstructs behavior from events. ## Read bounded history `Session::history` creates an owned query. Calling `page` returns at most 100 messages by default in canonical event-sequence order, oldest first: ```rust let page = session.history().page().await?; for message in &page.messages { println!("{:?}: {}", message.role, message.text()); } ``` Set a smaller or larger page size with `limit`. The maximum is 256 messages; an excessive value returns `HistoryError::InvalidLimit` with the allowed maximum. A page never claims to contain the entire transcript. Continue from its opaque cursor: ```rust let first = session.history().limit(25)?.page().await?; if let Some(cursor) = first.next_cursor { let second = session.history().limit(25)?.after(cursor)?.page().await?; // `second` continues the same stable snapshot. } ``` `HistoryCursor` is opaque, session-bound, and safe to store as a string with `Display` and restore with `FromStr`. A cursor fixes the snapshot’s high-water mark: events appended after the first page do not appear midway through that page walk. Start a new query to see them. Passing a malformed, cross-session, expired, or incompatible cursor returns a distinct typed history error. History projection also applies a bounded raw-event replay safety limit; an unusually lifecycle-heavy snapshot that exceeds it returns `HistoryError::HistoryTooLarge` instead of performing an unbounded scan. For callers that intentionally walk the whole snapshot, `pages` is a lazy convenience that still reads one bounded page at a time: ```rust let mut pages = session.history().limit(50)?.pages(); while let Some(page) = pages.next_page().await? { for message in page.messages { println!("{}", message.text()); } } ``` After the final page, `next_page` remains fused and returns `None`. It does not re-read the backend or produce repeated empty terminal pages. ## Choose a persistence lifecycle The default engine retains its Agent snapshots and in-memory session catalog. It needs no database, network, credentials, or filesystem access. Sessions can be dropped and resumed through that engine, but creating a new engine starts a new volatile history store. Process exit loses it. Enable `local` and configure a trusted application data directory when sessions must survive a new Agent or process: ```rust use everruns::{Agent, Engine, LocalConfig, Model}; let build_agent = || Agent::builder() .instructions("Remember the conversation.") .provider(OpenAI::from_env()?) .model("gpt-5.6-terra") .local(LocalConfig::new(".everruns-data")) .build(); let first_engine = Engine::new(); let session = first_engine.create(build_agent()?); session.start().await?; let session_id = session.session_id(); // In a later process, rebuild trusted behavior before resuming persisted state. let restarted_engine = Engine::new(); restarted_engine.attach(session_id, build_agent()?).await?; let resumed = restarted_engine.resume(session_id).await?; ``` The local profile stores a durable session catalog and crash-durable canonical event log alongside its workspace, task, and schedule state. After restarting, build another Agent with the same trusted data directory, call `engine.attach(session_id, agent)`, then `engine.resume(session_id)`. Attachment rejects IDs absent from that Agent’s configured local catalog. A new session is made durable by its first async operation (`run`, `inspect`, or a history page read); merely allocating a synchronous handle does not commit it. For a session created with an explicit Harness, persist its serialized portable definition, deserialize it after restart, and call `engine.attach_with_harness(session_id, agent, harness)` instead. Harness deserialization validates the definition and generates a new process-local runtime identity. The local profile is for one embedded process at a time. Do not write or edit its files as application data: messages are a read-only projection of committed events, and the storage formats are not Framework APIs. History does not contain model credentials or application secrets unless an application deliberately includes them in message content or event metadata. Choose and protect the local data directory accordingly. --- # Sessions > Keep conversation history across turns while isolating independent Framework sessions. Source: An `Agent` is immutable reusable behavior. An `Engine` owns session identity, history, and runtime state. A `Session` is an engine-bound live conversation. ```rust use everruns::{Agent, Engine, OpenAI}; let agent = Agent::builder() .instructions("Remember the conversation.") .provider(OpenAI::from_env()?) .model("gpt-5.6-terra") .build()?; let engine = Engine::new(); let session = engine.create(agent); let first = session.send_and_wait("My project is Atlas.").await?; let second = session.send_and_wait("Continue with that project.").await?; assert!(first.success); assert!(second.success); ``` `Session` is always a live conversation. `send` accepts a message without waiting for a response. If a turn is active, the message steers that turn; if the previous turn has already finished, it starts a follow-up turn. The receipt reports which case occurred, so applications do not need to race on session state themselves: ```rust let session = engine.create(agent); let initial = session.send("Plan my trip.").await?; let latest = session.send("Prefer trains.").await?; match latest.disposition { SendDisposition::Steered => { assert_eq!(latest.turn_id, initial.turn_id); } SendDisposition::Started => { // The first turn completed before the second message was accepted. } _ => {} } let result = latest.wait().await?; ``` Waiting on the latest receipt works in both cases. `send_and_wait` (also available as the shorter `run` alias) is request/response convenience over the same live session, not a separate mode. The first asynchronous operation materializes the in-process host. Later turns reuse it and send accumulated history through the same context-assembly path. Two sessions opened on one engine have different opaque IDs and isolated histories. The engine retains each immutable Agent snapshot, so a session keeps working after the original Agent handle is dropped. Volatile resume is engine-scoped: another `Engine` rejects the id rather than guessing its configuration. A local profile can be attached to another Engine with the trusted Agent snapshot; live Engines for the same profile share its backend bundle. Conversation isolation does not imply filesystem isolation. The concise `engine.create(agent)` path permanently selects the Agent’s default head before its first inspection or turn; call `session.start().await` to make that selection observable earlier. To fix a session to an isolated project view, bind an [`Environment`](https://docs.everruns.com/framework/workspaces-and-environments/) before execution. A session can never switch heads after it starts. `Session::inspect` returns the context assembled for the next model call. Use it for application assertions and debugging rather than reaching into runtime records or backend stores. Keep `Session::session_id()` when the application may need to reopen a conversation. [Session History and Resume](https://docs.everruns.com/framework/session-history/) covers typed resume, bounded transcript pages, and cursor snapshots. See [Workspaces and Environments](https://docs.everruns.com/framework/workspaces-and-environments/) for exact-head resume, isolation, sharing, and lifecycle. See [Persistence](https://docs.everruns.com/framework/persistence/) to choose engine-lifetime memory or a crash-durable local profile, and [Events and cancellation](https://docs.everruns.com/framework/events-and-cancellation/) to observe a turn in flight. --- # Supported Providers > Every model provider driver Everruns ships today, the wire protocol each speaks, and which services it can power. Source: Everruns talks to model vendors through **drivers**. A driver owns one vendor’s wire protocol; a [`Provider`](https://docs.everruns.com/framework/models-and-providers/) pairs a driver with an endpoint and a credential. The set below is what ships today — the boundary is open, so a [custom driver](https://docs.everruns.com/framework/custom-providers/) is a first-class peer of these, not a lesser one. ## Drivers | Driver | Crate | Wire protocol | Services | Model discovery | | ------------------------- | --------------------- | ------------------------------------------ | -------------------------- | --------------- | | OpenAI | `everruns-openai` | OpenAI Responses | chat, embeddings, realtime | yes | | OpenAI (Chat Completions) | `everruns-openai` | OpenAI Chat Completions | chat | yes | | Azure OpenAI | `everruns-openai` | OpenAI Responses | chat | yes | | Anthropic | `everruns-anthropic` | Anthropic Messages | chat | yes | | Google Gemini | `everruns-gemini` | Gemini `generateContent` | chat | yes | | AWS Bedrock | `everruns-bedrock` | Bedrock `ConverseStream` (SigV4) | chat | yes | | OpenRouter | `everruns-openrouter` | OpenAI Responses-compatible | chat | yes | | Microsoft MAI | `everruns-mai` | OpenAI Chat Completions (Azure AI Foundry) | chat | yes | | Fireworks AI | `everruns-fireworks` | OpenAI Chat Completions-compatible | chat | yes | | Meta Model API | `everruns-meta` | OpenAI Responses-compatible | chat | yes | | LLM Simulator | `everruns-llmsim` | none — in-process test double | chat | no | Every chat driver produces an incremental stream — server-sent events for the HTTP protocols, `ConverseStream` for Bedrock — so token-by-token output works everywhere, not just on one vendor. Tool calling and multi-turn tool results work across all of them: each driver normalizes its vendor’s shape into the same typed events, which is why swapping a provider does not change application code. The environment variables each driver reads are in [Credentials](https://docs.everruns.com/framework/credentials/). ## The simulator is not a provider `everruns-llmsim` is listed above because it registers as a driver, but it runs no inference and reaches no network. It replays canned responses so tests and examples can assert on agent behavior without an API key. It is not a local model and not a way to run Everruns without a vendor. ## Beyond chat Most drivers implement chat only. Two capabilities go further, and both are OpenAI-only today: **Embeddings.** The OpenAI driver powers embedding models for knowledge-base retrieval alongside its chat models. **Realtime voice (WebRTC + WebSocket).** A realtime voice session is negotiated by the platform server, not the Framework. The browser posts its SDP offer to `POST /v1/sessions/{session_id}/voice/calls` and the server answers it; a separate route mints a short-lived client secret from the vendor. The organization’s own API key is used only server-side and never reaches the browser. The server then opens a WebSocket sideband (`wss://…/realtime?call_id=…`) to drive the call and collect transcripts, which land in the session as ordinary events — so a voice turn and a typed turn are the same session, readable through the same history and event streams. This needs the platform server; an embedded Framework process does not expose it. ## Interactive connect OpenRouter declares an OAuth connect flow, so an operator can choose “Connect with OpenRouter” instead of pasting a key. Every other driver takes a credential directly, entered in Settings or supplied in code. ## Choosing one Any OpenAI-compatible gateway that speaks Responses or Chat Completions can usually be reached by pointing the matching driver’s `base_url` at it, rather than writing a driver. Write a [custom driver](https://docs.everruns.com/framework/custom-providers/) when the vendor’s protocol genuinely differs, or when it needs authentication that a bearer token cannot express. --- # Testing and Simulation > Test Framework applications deterministically without network access or provider credentials. Source: `Model::simulated` is the default testing tool. It uses the normal provider resolution and execution path while returning a fixed response locally. Its implementation comes from the publishable, production-safe `everruns-llmsim` crate; the Framework does not depend on test-support code. ```rust use everruns::{Agent, Engine, Model}; let agent = Agent::builder() .instructions("Return the configured result.") .model(Model::simulated("approved")) .build()?; let session = Engine::new().create(agent); let turn = session.send_and_wait("Review this.").await?; assert!(turn.success); assert_eq!(turn.response, "approved"); ``` Useful test layers are: 1. Build-time validation tests for agent, tool, model, MCP, and compaction configuration. 2. Offline session tests with `Model::simulated`. 3. Context assertions through `Session::inspect`. 4. Event/cancellation tests through the public session API. 5. A small opt-in live-provider suite for protocol integration. Keep normal tests credential-free and deterministic. Do not make a live model’s wording or tool choice a unit-test oracle. Temporary directories should own workspace and local-state tests so they do not read or modify developer data. The runnable programs in [Framework examples](https://docs.everruns.com/framework/examples/) are also compiled in CI using only the public `everruns` facade. ## Scripted and low-level simulation Use `Model::simulated_with_config` when an application test needs multiple assistant turns, deterministic tool calls, an injected provider error, or request capture: ```rust use everruns::{Agent, LlmSimConfig, Model}; use everruns_llmsim::{SimToolCall, SimTurn}; let simulation = LlmSimConfig::scripted(vec![ SimTurn::ToolCalls(vec![SimToolCall { name: "lookup".into(), arguments: serde_json::json!({"id": 7}), id: Some("call_lookup".into()), }]), SimTurn::Assistant("approved".into()), ]); let agent = Agent::builder() .instructions("Use lookup, then report the result.") .model(Model::simulated_with_config(simulation)) .build()?; ``` Advanced hosts depend on `everruns-llmsim` with its `host` feature for `LlmSimRuntimeExt`. The `.llm_sim(...)` method registers the provider without changing model selection; `.llm_sim_as_default(...)` explicitly selects it when no default was already configured. Use `everruns-test-support` only for testing/demo helpers such as its in-memory agentic loop, writable fixtures, test doubles, and fake capabilities. The test-support simulator re-exports exist only as a 0.18 migration bridge for 0.17 import paths. --- # Tools and Macros > Add typed async Rust functions or explicit JSON-schema handlers as Framework tools. Source: The default-enabled `everruns::tool` macro turns an async Rust function into a typed agent tool. Parameter types produce JSON Schema and call arguments are deserialized before the function runs. ```rust use everruns::{Agent, OpenAI}; #[everruns::tool] /// Add two integers. async fn add(left: i64, right: i64) -> Result { Ok(left + right) } let agent = Agent::builder() .instructions("Use the add tool for arithmetic.") .provider(OpenAI::from_env()?) .model("gpt-5.6-terra") .tool(add()) .build()?; ``` Use `#[everruns::tool(name = "…", description = "…")]` to override metadata, or `#[tool(rename = "…")]` on a parameter to change its model-facing name. Functions must be async, non-generic, and have plain named parameters. The published `everruns-macros` package is an implementation crate. Its source lives at `crates/macros`, but applications should use the re-exported `everruns::tool` macro and should not depend on `everruns-macros` directly. ## Dynamic handlers `FunctionTool::new` is available when a tool schema is determined at runtime: ```rust use everruns::FunctionTool; use serde_json::json; let echo = FunctionTool::new( "echo", "Return the supplied text.", json!({ "type": "object", "properties": { "text": { "type": "string" } }, "required": ["text"] }), |args: serde_json::Value| async move { Ok::<_, String>(args["text"].clone()) }, ); ``` Prefer the macro for normal typed application tools. Use the dynamic form for schemas obtained from configuration or another protocol. --- # Workspace Security > Configure safe read and write scopes for in-process Everruns agents. Source: `WorkspacePolicy` is the portable security boundary for files visible to an in-process agent. Applications configure it through `everruns`; they do not need `RealDiskFileStore`, `HostBackends`, or a runtime-owned blocklist. ```rust use everruns::{Agent, OpenAI, WorkspacePolicy}; fn build(root: &std::path::Path) -> Result> { let policy = WorkspacePolicy::builder() .allow_read("/") .allow_write("generated") .deny_write("generated/locked") .allow_hidden(".github") .build()?; Ok(Agent::builder() .instructions("Work only inside the configured workspace.") .provider(OpenAI::from_env()?) .model("gpt-5.6-terra") .workspace(root) .workspace_policy(policy) .build()?) } ``` ## Defaults `WorkspacePolicy::default()` and `WorkspacePolicy::read_only()` use the same secure baseline: | Operation | Default | | ---------------------------------------- | ------- | | Read ordinary workspace files | Allowed | | Write, create, or delete | Denied | | Read or write hidden paths | Denied | | Read or write common credential paths | Denied | | Read framework-managed `.agents` content | Allowed | | Recursively delete a directory | Denied | `WorkspacePolicy::read_write()` is an explicit opt-in to ordinary writes. It does not expose additional hidden or sensitive paths and does not enable recursive deletion. It also keeps common dependency and build directories such as `node_modules` and `target` non-writable at every depth. For narrower access, start with `WorkspacePolicy::builder()`, which has no readable or writable scopes until you add them. A custom builder does not inherit the default `.agents` exception. If the agent needs workspace-provided instructions or skills, add both a readable scope and a narrow `allow_hidden(".agents")` opt-in. Protected path names are defense in depth, not content-based secret scanning. Keep credentials outside the mounted workspace, add explicit deny scopes for application-specific secret locations, and never place credentials in `.agents` content. ## Matching and precedence Scopes are literal path prefixes, not globs, and compare ASCII letters without case sensitivity so a deny cannot be bypassed on a case-insensitive backend. `generated` therefore includes `generated/report.md` but not `other/generated/report.md`. * A deny scope always wins over an allow scope. * `deny_write_component` rejects an exact directory or file name at every depth; `deny_write` rejects one rooted path subtree. * Hidden paths need `allow_hidden` for the narrow path that should be visible. * Common credential paths need the stronger `allow_sensitive` opt-in, which also permits hidden components inside that specific scope. * `compose` is restrictive: every composed policy must allow the operation. A library can add constraints without accidentally broadening the application’s policy. Trusted starter files are installed before model access is enforced. This lets an application seed a read-only file even under a non-writable policy. Later reads, writes, and deletes of that file still go through the policy, so seeding a hidden file does not automatically expose it. ## Paths and containment Policy paths live in one portable workspace namespace. These spellings identify the same file: ```text src/lib.rs /src/lib.rs /workspace/src/lib.rs ``` Traversal (`..`), NUL bytes, and backslash-separated paths fail closed. Host absolute paths are backend-specific and are not portable policy scopes; use `/workspace/...` in application configuration and model instructions. The policy layer controls visibility and mutation. The selected filesystem backend remains responsible for mapping workspace paths to storage. The local host backend canonicalizes its root, keeps resolved paths contained, and rejects symlinks in existing path components before every operation. An absolute path outside the configured root cannot expose that host file. The policy governs capabilities that use Everruns’ session filesystem. A custom tool that calls `std::fs`, launches a shell, or uses another storage API does not pass through this boundary. Apply equivalent restrictions to those tools or run them in a sandbox. ## Symlinks and races The built-in local backend rejects a symlink introduced after the workspace was configured because it rechecks components on every operation. This blocks normal traversal and symlink-swap attempts between operations. It is not an OS sandbox. A malicious process running as the same operating system user can race a final path check and filesystem syscall. If local processes are mutually untrusted, use an isolated sandbox/filesystem backend or operating-system isolation. Do not use `WorkspacePolicy` as a substitute for that process boundary. ## Backend extension The in-process host applies the policy after resolving the platform’s filesystem factory. In-memory, local-disk, database, and custom backends all receive the same policy checks. Backend authors still own containment, symlink-safe I/O, quotas, durability, and atomic update guarantees for their storage system. Directory listings and grep are enforced at the same boundary as direct reads. Denied files are not opened by policy grep, and denied names, match counts, and byte totals are not returned. Recursive deletes inspect descendants through the backend before deletion, so opting into recursion does not override a deny or protected descendant. Backends with mutable external state must still treat that preflight-to-delete window as a race boundary. `WorkspaceRootSet` additional roots are named mounts inside one selected head; they are not independent heads and carry no fork/reopen lifecycle. Likewise, `WorkspacePolicy` is path authorization, not a compute sandbox. See [Workspaces and Environments](https://docs.everruns.com/framework/workspaces-and-environments/) for the identity and lifecycle model. --- # Workspaces and Environments > Isolate writable project heads, bind them permanently to sessions, and reopen them safely. Source: An `Agent` describes behavior. A `Session` owns conversation continuity. An `Environment` fixes the execution resources for that session, beginning with one `WorkspaceHead`. A `Workspace` is logical project lineage, not a directory alias. Each `WorkspaceHead` is a stable, backend-owned mutable view of that lineage. All heads present the same portable `/workspace` namespace even when a backend implements them as Git worktrees, remote volumes, or another storage system. ## Isolated local Git heads Enable the `local` feature to use the public Git-worktree backend: ```rust use std::sync::Arc; use everruns::{ Agent, Engine, LocalGitWorkspace, OpenAI, Workspace, WorkspacePolicy, }; let backend = Arc::new(LocalGitWorkspace::new(state)?); let workspace = Workspace::open(backend, repository.to_string_lossy()).await?; let head = workspace .head("feature") .from_revision("main") .create() .await?; let agent = Agent::builder() .instructions("Work in the selected project head.") .provider(OpenAI::from_env()?) .model("gpt-5.6-terra") .workspace_policy(WorkspacePolicy::read_write()) .build()?; let engine = Engine::new(); let session = engine.create(agent).workspace(head).start().await?; assert!(session.workspace_head().is_some()); ``` Head creation is isolated by default. The Framework rejects binding the same isolated head to a second session. Opt into a shared mutable head with `workspace.head("shared").shared().create()`. A shared real-disk head does not become isolated: Framework compare-and-set writes report stale-content conflicts within the host process, and backend status reports Git conflict and dirty metadata. Coordinate other writers at the application or backend layer. Use `head.fork("name").await` to create an isolated head from the current checkpoint. `checkpoint`, `status`, `archive`, and `destroy` are explicit lifecycle operations. Dropping a head, session, agent, workspace, or backend never deletes a worktree or branch. The local backend’s explicit `destroy` removes the worktree and retains its Git branch. Archive blocks later reopen; it does not revoke a filesystem handle already owned by a running session. ## Exact resume `start()` persists the backend’s credential-free opaque binding before the session can execute. `Engine::resume` asks the recorded backend to reopen that exact workspace and head. It returns a structured `ResumeError` when the backend is missing, the head is unavailable, the binding is corrupt, or the backend returns a different identity. It never substitutes an empty or different head. After a process restart, the Agent attached to a durable session created from an explicit backend must register that backend with `AgentBuilder::workspace_backend`. The backend used by a live Environment is remembered automatically in that Agent snapshot. The default memory backend and `AgentBuilder::workspace(path)` shorthand backend are registered by the Framework itself. ## Compatibility window Use `WorkspaceBackend`, `WorkspaceBackendId`, `LocalGitWorkspace`, `AgentBuilder::workspace_backend`, and `WorkspaceHead::backend` in new code. The provider-named types and methods remain as deprecated forwarding aliases. Existing error matches keep their behavior during the deprecation window. Framework and built-in backend paths continue to emit `BuildError::DuplicateWorkspaceProvider`, `ResumeError::WorkspaceProviderUnavailable`, `SessionEnvironmentError::ProviderConflict`, `WorkspaceError::ProviderUnavailable`, and `WorkspaceError::Provider`. Their replacements are `BuildError::DuplicateWorkspaceBackend`, `ResumeError::WorkspaceBackendUnavailable`, `SessionEnvironmentError::BackendConflict`, `WorkspaceError::BackendUnavailable`, and `WorkspaceError::Backend`. Match both names while migrating. New `WorkspaceBackend` implementations should return the backend-named `WorkspaceError` variants. Persisted `WorkspaceBinding::provider_id`, SQLite columns, and existing `workspace-provider` state directory names do not change in this migration. ## Workspace, roots, policy, and sandbox These concepts are deliberately separate: | Concept | Meaning | | ----------------- | ----------------------------------------------------------------------- | | `Workspace` | Logical project or lineage | | `WorkspaceHead` | One reopenable mutable view selected for a session | | `/workspace` | Stable model-visible path presented by the head filesystem | | Additional roots | Extra named mounts in `WorkspaceRootSet`; never heads or lineage | | `WorkspacePolicy` | Portable read/write authorization composed over the selected filesystem | | Sandbox | A process/compute isolation boundary; not provided by path policy alone | The Environment carries its head plus an open type-keyed extension boundary for future compute or network resources. Backends implement the async `WorkspaceBackend` trait directly; there is no backend enum or vendor switch. Every head supplies the existing `SessionFileSystem`, so file tools, seeded files, containment checks, mounts, and `WorkspacePolicy` remain one stack. When a compute resource needs the selected filesystem, attach it with `EnvironmentBuilder::workspace_extension`; its constructor receives the exact head that Framework file tools will use. For a simple application, `engine.create(agent)` is the concise path. Its first `send` or `inspect` selects the default head automatically; optional `session.start().await` selects it earlier without running a turn. `AgentBuilder::workspace(path)` is shorthand for one explicitly shared local directory across that Agent’s sessions; it does not create isolated heads. The shorthand is still a first-class shared head and its exact canonical path binding is persisted for resume. Choose an Environment when isolation, forking, or backend-specific lifecycle matters.