This is the full developer documentation for Everruns
---
# 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.
---
# Author an agent blueprint
> Contribute a code-defined specialist agent from a capability, with a typed configuration contract the spawn path enforces
Source:
An **agent blueprint** is a code-defined specialist agent: a baked-in prompt, a set of private tools, a model-selection strategy, an iteration bound, and a narrow configuration surface. A host agent delegates to it through the ordinary [sub-agents](https://docs.everruns.com/capabilities/sub-agents/) tool without gaining access to its internals.
Reach for a blueprint when work needs different tools, instructions, or model economics than the parent agent — repository scouting, catalog benchmarking, any job where the parent should get the answer without carrying the tools that produced it. Blueprints are not persisted user-created agents; they ship with a capability and are available wherever that capability is enabled.
## Contribute the blueprint
A capability contributes blueprints by implementing `agent_blueprints()`. The returned `AgentBlueprint` carries everything the child runtime needs:
```rust
fn agent_blueprints(&self) -> Vec {
vec![AgentBlueprint {
id: "repo_scout",
name: "Repo Scout",
description: "Search repositories for code, files, and issues. \
Read-only agent for codebase exploration.",
model: BlueprintModel::Fixed("claude-haiku-4-5-20251001".to_string()),
system_prompt: REPO_SCOUT_PROMPT,
tools: vec![Box::new(SearchCodeTool), Box::new(ReadFileTool)],
max_turns: Some(15),
config_schema: Some(json_schema_for::()),
}]
}
```
The `description` is what a parent agent reads when deciding whether to delegate, so write it as a routing decision: what the blueprint is for, and when to pick it.
`model` chooses one of three strategies. `Fixed` pins a model the host cannot override, which suits specialist work with a known cost/quality target. `Default` names a model the validated config may override. `Inherit` takes the parent’s model, for work that genuinely needs the parent’s characteristics.
Tools listed here are **private**. They are instantiated only for the blueprint’s own child session and never appear in the host agent’s tool list.
## Define configuration as a type
Derive the config schema from a Rust struct rather than writing JSON by hand. The struct is the single source of truth: field set, bounds, defaults, and descriptions all reach the spawning agent from one place.
```rust
use everruns_capability::json_schema_for;
use everruns_capability::schemars::JsonSchema;
use serde::{Deserialize, Serialize};
/// The same ceiling the search tools apply to their own arguments.
const MAX_REPOS: u32 = 50;
/// Configuration for the repository scout.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields, default)]
#[schemars(crate = "everruns_capability::schemars")]
pub struct RepoScoutConfig {
/// Maximum number of repositories to scan.
#[schemars(range(min = 1, max = MAX_REPOS))]
pub max_repos: u32,
}
impl Default for RepoScoutConfig {
fn default() -> Self {
Self { max_repos: 10 }
}
}
```
Three habits keep the contract honest:
* **Express each bound once.** Write it as a constant shared by the schema attribute and the code that enforces it at runtime, so a schema edit cannot drift from the clamp it describes.
* **Write doc comments for the caller.** They become the schema descriptions the spawning agent reads. Implementation notes belong in ordinary `//` comments.
* **Close the object** with `deny_unknown_fields` unless the blueprint genuinely accepts open configuration.
Requiring configuration is a matter of leaving a field without a default: fields serde cannot default become `required` in the derived schema, and the spawn path rejects a call that omits them.
## What the spawn path enforces
Configuration is validated against the derived schema **before** a child session exists, so a declared bound constrains the host rather than merely advising the model. A spawn is rejected when:
* a value violates the schema — out of range, wrong type, missing a required property;
* the config carries a key the schema does not define (with `deny_unknown_fields`);
* the blueprint declares no schema at all but config was supplied.
The error returned to the calling agent names the violations and includes the schema, so a model can usually correct its own call and retry.
Validation governs what a **host** may configure. It is not a substitute for a tool checking its own arguments: keep the runtime clamps in the blueprint’s tools, so a misbehaving child agent stays inside the same envelope.
Configuration can only select behavior the blueprint intentionally exposes. It cannot replace the system prompt, inject tools, bypass model policy, or expand capability permissions.
## Delegation and lifetime
A spawned blueprint session is a real durable session. It takes part in the same message, event, task, workspace, cancellation, and recovery infrastructure as any other sub-agent; the difference is only how its runtime is assembled. The session persists the blueprint identity and its validated config, so a worker can reconstruct the same runtime after a retry or handoff.
The blueprint itself stays a stateless template. Follow-ups address the durable session, not the blueprint.
## Worked examples
Two blueprints ship in-tree and are worth reading as references:
* [`integrations/github/src/lib.rs`](https://github.com/everruns/everruns/blob/main/integrations/github/src/lib.rs) — the minimal case: one config field, a pattern constant shared with the runtime check that enforces it.
* [`integrations/openrouter/src/model_scout.rs`](https://github.com/everruns/everruns/blob/main/integrations/openrouter/src/model_scout.rs) — the fuller case: numeric bounds tied to runtime constants, a nested config type, and a spend budget.
## Related
* [Sub-agents](https://docs.everruns.com/capabilities/sub-agents/) — the delegation tool blueprints are invoked through
* [GitHub Scout](https://docs.everruns.com/capabilities/github-scout/) — a shipped blueprint from the caller’s side
---
# Budgets
> Cap session spending in dollars, tokens, or custom credits, with soft pause thresholds and automatic enforcement.
Source:
Budgets let you cap how much a session (or agent, user, or organization) can spend. When a session hits its budget, Everruns stops scheduling further LLM calls. This prevents runaway costs from long-running or misbehaving agents.

## How It Works
After every LLM generation, Everruns computes the cost and debits it from any active budgets for that session. In subagent trees, session-scoped budgets are shared at the root session, so child and grandchild turns spend from the same pool. If the balance reaches zero, the session stops.
1. **LLM call completes**: Everruns extracts token counts (input + output).
2. **Compute debit**: Converts tokens to the budget’s currency:
* `usd`, uses per-model pricing (cost per million tokens for input/output)
* `tokens`, raw token count
* `credits`, 1 credit = 1,000 tokens
* Custom currencies fall back to token count
3. **Debit ledger**: Appends an immutable ledger entry and updates the balance.
4. **Evaluate rules**: Checks thresholds:
* Balance at 20% of limit → warning event
* Spending exceeds soft limit → session pauses
* Balance reaches zero → session stops
Enforcement is **post-hoc**: the check runs after each LLM call, not before. This avoids blocking the hot path. The last generation may slightly overshoot the limit, this is expected and by design.
## Currencies
| Currency | Unit | How cost is calculated |
| --------- | ----------------------- | ----------------------------------------------------------------------------- |
| `usd` | US dollars | Per-model pricing from model profiles (input/output rates per million tokens) |
| `tokens` | Raw tokens | Direct count of input + output tokens |
| `credits` | 1 credit = 1,000 tokens | Token count divided by 1,000 |
| Custom | Any string | Falls back to raw token count |
USD budgets use real per-model pricing. A $10 budget on GPT-4o will last much longer than $10 on Claude Opus, because the per-token cost differs.
## Soft Limits and Pausing
A **soft limit** pauses the session before hitting the hard stop. This is useful in interactive sessions where a human can decide to top up or stop.
1. Spending exceeds `soft_limit` → budget status becomes `paused`
2. The worker detects the pause between atoms → stops scheduling the next LLM call
3. Session transitions to `paused` state
4. User can resume by increasing the limit, topping up, or calling the resume endpoint
For headless sessions (no human watching), the hard limit fires at balance zero and terminates the turn.
## Stacked Budgets
Multiple budgets can apply to the same session. The **most restrictive** budget wins. This lets you combine different types of limits:
* A **$10 USD session budget** caps dollar cost
* A **2M token budget** caps total token usage regardless of model pricing
Budget stacking is enforced across the session hierarchy: root session, app channel, app, agent, user, and organization. The most restrictive matching budget wins.
## CLI Usage
Set a budget when creating a session:
```bash
# $10 USD budget (currency defaults to usd)
everruns sessions create --budget-limit 10
# Explicit currency
everruns sessions create --budget-limit usd:10
# With soft limit — pauses at $8, hard stop at $10
everruns sessions create --budget-limit usd:10 --budget-soft-limit usd:8
# Token budget
everruns sessions create --budget-limit tokens:2000000
# Stacked — both limits, whichever hits first
everruns sessions create --budget-limit usd:10 --budget-limit tokens:2000000
```
## MCP Usage
The `agent_run` MCP tool accepts budget parameters directly:
```json
{
"name": "agent_run",
"arguments": {
"message": "Analyze this codebase",
"agent_id": "agent_abc123",
"budget_limit": 10.00,
"budget_currency": "usd",
"budget_soft_limit": 8.00
}
}
```
Budget operations are also available as catalog commands via the `execute` tool:
```bash
# Create a budget for an existing session
create_budget --subject_type session --subject_id ses_xxx \
--currency usd --limit 10 --soft_limit 8
# Check a session's budget status
check_session_budgets --session_id ses_xxx
# Top up an exhausted budget
top_up_budget --budget_id bdg_xxx --amount 5 --description "Extra allowance"
# List all budgets for a session
list_session_budgets --session_id ses_xxx
```
## API
### Budget CRUD
```plaintext
POST /v1/budgets Create budget
GET /v1/budgets List budgets (?subject_type=&subject_id=)
GET /v1/budgets/{id} Get budget with current balance
PATCH /v1/budgets/{id} Update limit / soft_limit / status
DELETE /v1/budgets/{id} Soft-delete (sets status=disabled)
```
### Budget operations
```plaintext
POST /v1/budgets/{id}/top-up Add credits (negative ledger entry)
GET /v1/budgets/{id}/ledger Paginated ledger entries (?limit=&offset=)
GET /v1/budgets/{id}/check Check budget status
```
### Session shortcuts
```plaintext
GET /v1/sessions/{id}/budgets List budgets for this session
GET /v1/sessions/{id}/budget-check Check all budgets (session + hierarchy)
POST /v1/sessions/{id}/resume Resume paused budgets
```
### Create a session budget
```bash
curl -X POST https://your-instance/api/v1/budgets \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"subject_type": "session",
"subject_id": "ses_01abc...",
"currency": "usd",
"limit": 10.00,
"soft_limit": 8.00
}'
```
### Top up an exhausted budget
```bash
curl -X POST https://your-instance/api/v1/budgets/{budget_id}/top-up \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 5.00,
"description": "Emergency top-up"
}'
```
If the budget was `paused` or `exhausted` and now has positive balance, it automatically reactivates.
## Events
Subscribe to budget events via SSE to react in real time:
| Event | When | Key data |
| ------------------ | ------------------------------- | ------------------------------------------- |
| `budget.warning` | Balance at 20% of limit | `budget_id`, `balance`, `limit`, `currency` |
| `budget.paused` | Spending exceeds soft limit | `budget_id`, `balance`, `soft_limit` |
| `budget.exhausted` | Balance reaches zero | `budget_id`, `balance`, `limit` |
| `budget.resumed` | User resumes after pause/top-up | `budget_id`, `balance`, `limit` |
## Agent Awareness
The `budgeting` capability is included in the **Generic harness** by default. Any session using the Generic harness automatically gets budget-aware behavior:
* The agent’s system prompt includes a “Budget Awareness” section with guidelines for efficient output when budget is constrained
* The agent gets a `check_budget` tool to query remaining balance before expensive operations
When budget is running low, a budget-aware agent will prioritize completing current tasks efficiently rather than exploring new directions.
## Budget Lifecycle

---
# Context Compaction
> How Everruns manages LLM context windows through automatic compaction strategies, observation masking, summarization, and hierarchical memory tiers
Source:
Long-running agent sessions accumulate messages until they exceed the model’s context window. When that happens, the LLM rejects the request. **Context compaction** automatically reduces the conversation size so the agent can keep working without losing important information.
Everruns provides multiple compaction strategies that can be combined. The default `auto` strategy cascades through all of them in order, from cheapest (free) to most expensive (LLM call), stopping as soon as the context fits.

## How It Works
Compaction operates at two points:
1. **Proactively**: before each LLM call, Everruns estimates the token count. If it exceeds a configurable budget threshold (default 85% of the model’s context window), compaction runs *before* the call is made. This avoids the latency of a failed request.
2. **Reactively**: if the LLM still returns a `RequestTooLarge` error (estimation can undercount), the compaction cascade runs and the request is retried automatically.
In both cases, the same cascade of strategies executes:

The UI shows a divider between messages whenever compaction happens:
> **Context compacted** · 142 → 38 messages · observation\_masking+summarization
Click the divider to see the cascade details, which strategies ran, how many messages each step produced, and the time taken.
## Strategies
### Auto (default)
Runs all strategies in order. Stops as soon as context fits. This is the recommended setting for most use cases.
### Observation Masking
Replaces old tool outputs with compact summaries while keeping the message structure intact. This is free (no LLM call) and preserves tool call IDs for tracing.
Two summary formats:
| Format | Example | When to use |
| -------------------- | ----------------------------------------------------------- | --------------------------------- |
| `one_line` (default) | `[read_file → 47 lines, 2340 bytes]` | Most cases, minimal footprint |
| `head_tail` | First 3 lines + `... (14 lines omitted) ...` + last 3 lines | When partial output context helps |
The most recent N tool outputs are always kept verbatim (default: 5).
### Native Provider Compaction
Delegates compaction to the LLM provider’s own endpoint. Currently supported by OpenAI’s Responses API (`/responses/compact`). When available, this can be more intelligent than generic strategies since the provider understands its own tokenization. Everruns sends either a stateful response handle or a standalone transcript to the compact endpoint, never both. The returned ordered context is encrypted at rest as a durable checkpoint and reused across later turns and process restarts. Each request combines the latest checkpoint for the exact provider/model with raw messages written after its source boundary; changing provider or model falls back to raw history.
Compaction never deletes or rewrites session events. Public `context.compacted` events contain only counts, timing, strategy, and an optional checkpoint identifier. Provider-native encrypted context remains confined to the internal provider and storage paths.
### Summarization
Uses an LLM to generate a structured summary of older messages. The summary replaces those messages in context and is wrapped in `[CONVERSATION_SUMMARY]` tags so subsequent compactions can re-summarize it.
You can configure:
* Which model to use (default: same as the agent)
* What information to preserve (decisions, files modified, errors, etc.)
* Custom instructions appended to the summarization prompt
### Aggressive Trim
Last resort. Drops the oldest messages to fit within the token budget. The system prompt and the most recent messages are always preserved. This is lossy, dropped messages cannot be recovered unless Infinity Context is enabled.
## Generic Harness Defaults
The built-in **Generic** harness enables both `compaction` and `infinity_context` by default. Together they keep long sessions unbounded without manual configuration.
| Capability | Role | Default in Generic |
| ---------------------- | ------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| **Infinity Context** | Limits how many messages are loaded from the database into the prompt; provides `query_history` for retrieval | `context_budget_tokens: 100000`, `min_recent_messages: 10` |
| **Context Compaction** | Reduces the size of messages that *are* in the prompt, masking tool outputs, summarizing, or trimming | `strategy: auto`, `proactive: true`, `budget_percent: 0.85` |
The flow for a long-running Generic session:

No configuration is needed, creating a session with the Generic harness gives you this behavior out of the box. To customize, override either capability’s config on the agent or session level.
## Configuration
Compaction is a capability configured per agent or harness via `AgentCapabilityConfig`.
### Default (auto strategy, proactive)
```json
{
"capabilities": ["compaction"]
}
```
### Custom strategy and budget
```json
{
"capabilities": [
{
"ref": "compaction",
"config": {
"strategy": "auto",
"proactive": true,
"budget_percent": 0.85
}
}
]
}
```
### Observation masking only (no LLM calls)
```json
{
"capabilities": [
{
"ref": "compaction",
"config": {
"strategy": "observation_masking",
"observation_masking": {
"keep_recent_tool_outputs": 10,
"summary_format": "head_tail"
}
}
}
]
}
```
### Summarization with a cheaper model
```json
{
"capabilities": [
{
"ref": "compaction",
"config": {
"strategy": "summarization",
"summarization": {
"model": "claude-haiku-4-5-20251001",
"preserve": ["decisions", "files_modified", "errors", "api_keys"],
"instructions": "Focus on architecture decisions and API contract changes"
}
}
}
]
}
```
### Full configuration with memory tiers
```json
{
"capabilities": [
{
"ref": "compaction",
"config": {
"strategy": "auto",
"proactive": true,
"budget_percent": 0.80,
"observation_masking": {
"keep_recent_tool_outputs": 5,
"summary_format": "one_line"
},
"summarization": {
"model": null,
"preserve": ["decisions", "files_modified", "errors", "current_plan"],
"instructions": null
},
"memory_tiers": {
"hot_messages": 20,
"warm_messages": 100
}
}
}
]
}
```
## Configuration Reference
### Top-level
| Field | Type | Default | Description |
| ---------------- | ------- | -------- | -------------------------------------------------------------------------------- |
| `strategy` | string | `"auto"` | Compaction strategy: `auto`, `native`, `observation_masking`, or `summarization` |
| `proactive` | boolean | `true` | Compact before hitting context limits (recommended) |
| `budget_percent` | float | `0.85` | Trigger proactive compaction at this fraction of the context window |
For `auto` and `native`, proactive pressure invokes provider-native compaction when the driver supports it and stores the result as a durable checkpoint. The driver’s effective model context window takes precedence over the built-in profile, so external drivers can report their actual limit. Stateful `previous_response_id` requests skip local proactive pressure checks because their request body is only a delta over provider-held context; reactive too-large recovery remains available.
A native result must materially reduce provider-reported tokens, or serialized bytes when token usage is unavailable: at least 5%, with a 32-unit floor for small measurements. Smaller results do not install or replace a checkpoint and do not emit `context.compacted`. A newly installed checkpoint is not proactively replaced again until a meaningful raw-message suffix has accumulated. Failed and no-op native attempts are also held behind a retry watermark until estimated input grows by both 4,096 tokens and 5%, avoiding repeated compact calls against the same source. Branch and rollback selection ignore watermarks from a different transcript lineage. When it is re-armed, the next native compact request preserves the prior opaque checkpoint items in order before appending that suffix, for both proactive and reactive compaction.
### Observation Masking
| Field | Type | Default | Description |
| -------------------------- | ------- | ------------ | ---------------------------------------------------------- |
| `keep_recent_tool_outputs` | integer | `5` | Number of recent tool outputs to keep verbatim |
| `summary_format` | string | `"one_line"` | How to summarize masked outputs: `one_line` or `head_tail` |
### Summarization
| Field | Type | Default | Description |
| -------------- | -------------- | ----------------------------------------------------------- | --------------------------------------------------------- |
| `model` | string \| null | `null` | Model for summarization. Null = same as the agent’s model |
| `preserve` | string\[] | `["decisions", "files_modified", "errors", "current_plan"]` | Information categories to preserve in summaries |
| `instructions` | string \| null | `null` | Custom instructions appended to the summarization prompt |
### Memory Tiers
| Field | Type | Default | Description |
| --------------- | ------- | ------- | --------------------------------------------------------------- |
| `hot_messages` | integer | `20` | Recent messages kept verbatim (full content) |
| `warm_messages` | integer | `100` | Older messages with observation masking applied to tool outputs |
Messages beyond hot + warm are in the **cold tier**: replaced with a conversation summary. If [Infinity Context](https://docs.everruns.com/capabilities/infinity-context/) is enabled, cold-tier messages remain queryable via `query_history`.
## Memory Tier Diagram

## Combining with Infinity Context
Compaction and [Infinity Context](https://docs.everruns.com/capabilities/infinity-context/) are complementary:
* **Infinity Context** limits how many messages are loaded from the database into the prompt, and provides `query_history` for retrieval.
* **Compaction** reduces the size of messages that *are* in the prompt, making tool outputs smaller, summarizing old turns, or trimming when nothing else works.
For long-running sessions, enable both:
```json
{
"capabilities": [
"infinity_context",
{
"ref": "compaction",
"config": {
"strategy": "auto",
"proactive": true
}
}
]
}
```
With both active, the flow is:
1. Infinity Context limits messages loaded (e.g., last 100 messages)
2. Compaction masks old tool outputs in those messages
3. If still over budget, summarization or trim kicks in
4. Cold-tier messages remain accessible via `query_history`
## Events
Compaction emits two SSE events:
| Event | When | Key fields |
| -------------------- | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `context.compacting` | Semantic compaction starts | `reason` (proactive\_budget, request\_too\_large, manual), `strategy`, `messages_before`, optional `tokens_before` / `bytes_before` |
| `context.compacted` | Material semantic reduction completes | `strategy_used`, `messages_before`, `messages_after`, optional before/after token or byte metrics, `duration_ms`, `steps[]`, optional `checkpoint_id` |
Each step in the cascade is recorded with its strategy name, resulting message count, and duration. Provider-encrypted native compact content is never included in these public events. Observation masking alone remains an outbound model-view optimization and does not emit `context.compacted` because it installs no semantic checkpoint.
## Best Practices
* **Start with defaults.** The `auto` strategy with `proactive: true` handles most cases well.
* **Lower `budget_percent`** (e.g., 0.70) if your agents use large tool outputs frequently, this gives more headroom before the context fills.
* **Increase `keep_recent_tool_outputs`** if your agent often references recent tool results across multiple turns.
* **Use a cheaper model for summarization** (e.g., Haiku) to reduce cost and latency when the summarization step runs.
* **Enable Infinity Context** alongside compaction for sessions that run for hours or days.
* **Customize `preserve`** to match your agent’s domain, if your agent tracks database schemas or API contracts, add those to the preserve list.
## See Also
* [Infinity Context](https://docs.everruns.com/capabilities/infinity-context/), Message history windowing and retrieval
* [Capabilities Overview](https://docs.everruns.com/capabilities/), How capabilities are configured
* [Harnesses](https://docs.everruns.com/features/harnesses/), Where capability configs are applied
* [Events](https://docs.everruns.com/features/events/), SSE event streaming reference
---
# Embedding Everruns
> Choose between the application-facing Framework and low-level host composition.
Source:
For an application that runs agents in its own Rust process, start with the [Everruns Framework](https://docs.everruns.com/framework/) and the `everruns` crate. Its offline [quickstart](https://docs.everruns.com/framework/quickstart/) needs no server, worker, database, network, or credentials.
Low-level embedding is for applications that are themselves execution hosts: servers, evaluation harnesses, research runtimes, or specialized systems that must replace backend stores, platform definitions, or orchestration phases. Those hosts compose `everruns` with `everruns-host` and the focused crates they need.
The useful low-level boundary, security obligations, and crate-selection guidance now live in [Custom backends](https://docs.everruns.com/framework/custom-backends/).
---
# ID Schema
> How Everruns formats and validates public resource identifiers, Stripe-style prefixed IDs.
Source:
Every resource in the Everruns API, agents, sessions, skills, knowledge bases, and so on, is identified by a **prefixed public ID**. The prefix tells you at a glance what kind of resource you are looking at; the suffix is an opaque 32-character token.
This pattern was popularized by Stripe (`cus_`, `sub_`, `pi_`). Treat the suffix as a meaningless string, do not parse it, sort by it, or infer information from it. The only guarantees the API makes about an ID are its format, its uniqueness within an organization, and its stability over the lifetime of the resource.
## Format
All resource identifiers use the same shape:
```plaintext
{prefix}_{32-hex-chars}
```
* `{prefix}` is a short lowercase token that identifies the resource type (for example `agent`, `session`, `skill`).
* `_` separates the prefix from the suffix.
* `{32-hex-chars}` is an opaque 32-character lowercase hexadecimal token.
Example:
```plaintext
agent_5c7f3a91b24e48d6a0e91f3b7c4d2e85
```
Identifiers match a fixed pattern: the resource’s prefix, an underscore, then exactly 32 lowercase hexadecimal characters. For an `agent`, the literal regex is `^agent_[0-9a-f]{32}$`. The API rejects malformed values with `400 Bad Request`.
## Treat the suffix as opaque
The suffix carries no public meaning. In particular:
* **Do not sort by it.** Use `created_at` (or whatever timestamp field the resource exposes) for chronological ordering.
* **Do not infer age, ordering, or shard placement from it.** The encoding may change without notice.
* **Do not parse it as a UUID.** The hex format is a transport convenience; future resources may use different internal schemes while keeping the same wire format.
This decoupling is intentional. The internal database key for a resource and its public ID are deliberately separate concepts, clients only ever see the public ID.
## Client-Supplied IDs
For resources that accept client-supplied IDs on create, you can pass your own `id` as long as it matches the format above and uses the correct prefix for the resource type. If you omit `id`, the server assigns one.
A subset of resources additionally expose `PUT /v1/{resource}/{id}` as an upsert: the same call creates the resource if it does not exist (`201 Created`) and updates it in place if it does (`200 OK`). Where supported, this makes idempotent provisioning straightforward, replay the same `PUT` and the end state is identical. The OpenAPI reference is the source of truth for which resources support this; not every resource has a `PUT` route.
## Serialization
IDs are always serialized as JSON strings. The field name is `id` for the resource itself and `{resource}_id` when referenced from another resource:
```json
{
"id": "agent_5c7f3a91b24e48d6a0e91f3b7c4d2e85",
"session_id": "session_2b8a4d12c673491fae058b7d9c1f6a40"
}
```
## Prefix Reference
The prefix is part of the public contract for each resource. The most common ones are listed below; the canonical list lives in the OpenAPI specification.
The prefix in the table below is the token that appears before the `_` separator, an `agent` resource has IDs that start with `agent_`.
| Resource | Prefix |
| -------------- | ---------- |
| Agent | `agent` |
| Agent version | `agentver` |
| Session | `session` |
| Skill | `skill` |
| Knowledge base | `kb` |
| Memory | `mem` |
| MCP server | `mcp` |
| Schedule | `sched` |
| Image | `img` |
| User | `user` |
| Organization | `org` |
## Design Notes
| Question | Answer |
| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Why prefixed IDs? | They make IDs self-describing and prevent accidentally passing, say, an agent ID where a session ID is expected. |
| Why opaque suffixes? | Decoupling the wire format from internal storage gives the platform room to evolve without breaking clients, and prevents callers from leaning on accidental properties (ordering, creation time) that aren’t part of the contract. |
| Why lowercase hex? | Case-insensitive matching, URL-safe, easy to copy and paste. |
| Are IDs unique across organizations? | Each ID is unique within its owning organization. The same `id` value could in principle appear in two different orgs, but you only ever see IDs scoped to orgs you belong to. |
---
# Memory model
> How Workspace and Memory relate, the two-tier model for what an agent can read and write.
Source:
Everruns separates **where the agent works** from **what the agent remembers across runs**. That split is the whole memory model:
* **Workspace**: the active working area for a session. Per-run, singleton, ephemeral by default. Mounted at `/workspace`.
* **Memory**: org-scoped, named stores. Durable, governed, mountable into Workspaces. RO by default.
These two tiers are intentional. Workspace is where an agent does its current task; Memory is where the org persists state it wants reused across tasks.
For the shipped organization, agent, and user ownership tiers, including the private `/memory/user` and automatic `/memory/agent` mounts, see [Agent and user memory](https://docs.everruns.com/features/memory-scopes/).
## The two-axis grid
Scope × surface, with the same surfaces appearing on both sides:
```text
SESSION ORG
(Workspace — per run) (Memory — durable, shared)
┌────────────────────────┬────────────────────────┐
Files │ workdir, scratch │ shared docs, code │
├────────────────────────┼────────────────────────┤
Tables │ session SQL db │ shared datasets (TBD) │
├────────────────────────┼────────────────────────┤
KV │ run state, notes │ facts, prefs (TBD) │
├────────────────────────┼────────────────────────┤
Secrets │ per-run creds │ org credentials (TBD) │
└────────────────────────┴────────────────────────┘
```
Today only the **Files** surface exists on both sides; Tables exist on the Workspace side (session SQL DB). Tabular, KV, secrets, and structured surfaces on Memory are durable design intent, see `knowledge/runtime-resources/memory.md`.
## Org → Session: Mount
A Memory does not enter a Workspace automatically. The `memory` capability declares which Memories are mounted, where, and with what access mode.
```text
ORG MEMORIES (named, many)
┌──────────────┬──────────────┬──────────────┐
│ mem:crm │ mem:legal-kb │ mem:pricing │ ...
└──────┬───────┴──────┬───────┴──────┬───────┘
│ │ │ per-mount RO | RW
▼ ▼ ▼
┌─────────────────────────────────────────┐
│ SESSION WORKSPACE │
│ │
│ /workspace ← native files │
│ /workspace/mnt/... ← Memory mounts │
└─────────────────────────────────────────┘
```
A Memory can be mounted into any number of sessions concurrently. The mount is snapshotted at session creation, so later archival or rename of the Memory does not destabilize a running session.
## Session-eye view
What the running agent actually sees:
```text
SESSION WORKSPACE (one run)
┌────────────────────────────────────────────────────────┐
│ │
│ NATIVE (lives and dies with the session) │
│ ┌──────────┬──────────┐ │
│ │ files │ tables │ │
│ │ /workspace session SQL db │
│ └──────────┴──────────┘ │
│ │
│ MOUNTED (projected from org Memories) │
│ ┌────────────────────────────────────────────────┐ │
│ │ /workspace/mnt/docs ← mem:design [ro] │ │
│ │ /workspace/mnt/orders ← mem:crm [rw] │ │
│ │ /workspace/mnt/legal ← mem:legal-kb [ro] │ │
│ └────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────┘
```
The same tools (`read_file`, `list_directory`, `grep_files`, `bashkit_shell`) traverse native and mounted paths uniformly. Writes to read-only mounts return clear errors; writes to read-write mounts write through to the underlying Memory and are audited.
## Why two tiers, not one
A single “everything is Memory” abstraction was considered and rejected for two reasons:
1. **Lifecycle and governance differ.** Workspace files are intermediate, agents probe, edit, discard. Memory is durable shared state with audit, lifecycle (active/archived/deleted), and trust boundaries. Forcing one set of policies on both was wrong for both.
2. **Naming clarity.** “Memory” anthropomorphizes what the agent recalls across tasks. “Workspace” describes the desk it’s working on. Mixing the two names (“session memory” vs “org memory”) forced every sentence to carry a qualifier.
External validation: Anthropic’s Claude Managed Agents settled on essentially the same split, workspace files (per-run) plus Memory Stores (`/mnt/memory/`, durable, named, RO/RW mountable). The terminology in this doc mirrors that convention deliberately.
## Invariants
* **Scope.** Sessions cannot see other sessions. Memories are the only sharing point.
* **Default access.** Memory mounts default to read-only. Read-write is explicit and audited.
* **Snapshot mounts.** Mount config is captured at session creation. Archiving a Memory after that surfaces an error rather than disappearing files.
* **Source-backed Memories are read-only.** Memories synced from GitHub/Git cannot be mounted read-write; their contents are replaced atomically on sync.
* **Indexes are not surfaces.** Vector search or embeddings are an index *over* a surface, not a new surface. Surfaces are addressable storage.
## What is *not* Memory
These exist in Everruns but live outside this model:
* **Transcripts and events**: the conversation history is the session’s append-only log, not a memory surface.
* **Sandboxes**: managed compute environments (`knowledge/runtime-resources/session-sandbox.md`) are a separate primitive from storage.
* **Knowledge Bases** (`knowledge/runtime-resources/knowledge-bases.md`), curated entries with stable citation IDs, agent reads via `search_knowledge`. Likely folds into a future “structured” surface of Memory; today it stays separate.
## Mapping to other systems
| Concept | Everruns | Claude Managed Agents | Letta / MemGPT |
| ---------------------------- | ------------------------- | --------------------- | --------------- |
| Per-run scratch | Workspace | Memory Tool | Working memory |
| Durable named store | Memory | Memory Store | Archival memory |
| Mount access control | RO / RW per mount | RO / RW per attach | n/a |
| Background consolidation | , | Dreaming | Reflection |
| Multi-surface (files/tables) | Files today; more planned | Files only | Text blocks |
## Further reading
* [Agent and user memory](https://docs.everruns.com/features/memory-scopes/), scoped memory mounts, access, and privacy defaults
* [`knowledge/runtime-resources/memory.md`](https://github.com/everruns/everruns/blob/main/knowledge/runtime-resources/memory.md), durable design intent for the Memory tier
* [`knowledge/runtime-resources/workspace.md`](https://github.com/everruns/everruns/blob/main/knowledge/runtime-resources/workspace.md), Workspace specification (file surface, mount point, git VCS)
* [`knowledge/runtime-resources/knowledge-bases.md`](https://github.com/everruns/everruns/blob/main/knowledge/runtime-resources/knowledge-bases.md), curated org knowledge, the curation-first sibling of Memory
---
# Network Access Control
> Control which hosts and URLs agents can reach using layered allowlists and blocklists on harnesses, agents, and sessions
Source:
By default, agents with the `web_fetch` capability can access any public URL. **Network access lists** let you restrict which hosts and URLs an agent can reach, giving you fine-grained control over outbound network access.
Access lists are configured at three levels, harness, agent, and session, and each layer can only make access *more* restrictive, never less.
## Configuration
A network access list has two fields:
| Field | Purpose | Default |
| --------- | ---------------------------------------------- | --------------------- |
| `allowed` | If non-empty, only matching URLs are permitted | `[]` (no restriction) |
| `blocked` | Always denied, even if matched by `allowed` | `[]` |
### Pattern Format
Patterns support three formats:
| Format | Example | Matches |
| --------------- | ----------------------------- | ------------------------------ |
| Exact domain | `api.example.com` | Any URL on that exact domain |
| Wildcard domain | `*.example.com` | The domain and all subdomains |
| URL prefix | `https://api.example.com/v1/` | URLs starting with that prefix |
Domain matching is case-insensitive. Blocked patterns always take precedence over allowed patterns.
### API Examples
Set a network access list when creating or updating an agent:
```json
POST /v1/agents
{
"name": "Research Agent",
"system_prompt": "You are a research assistant.",
"network_access": {
"allowed": ["*.github.com", "api.openai.com"],
"blocked": ["evil.example.com"]
}
}
```
Restrict a specific session further:
```json
POST /v1/sessions
{
"agent_id": "agent_...",
"network_access": {
"blocked": ["internal.corp"]
}
}
```
To clear a network access list on update, send an empty object:
```json
PATCH /v1/agents/{id}
{
"network_access": {}
}
```
## Layer Merging
Network access lists merge across three layers using restrictive semantics, each layer can only narrow what the parent allows:
```plaintext
Harness (baseline)
∩ Agent (can only restrict further)
∩ Session (can only restrict further)
```
The merge rules:
| Field | Rule | Rationale |
| --------- | ------------------------------------------------------------------------ | ------------------------------------------------- |
| `allowed` | **Intersection**: child entries kept only if covered by a parent pattern | Child cannot grant access the parent didn’t allow |
| `blocked` | **Union**: all blocked patterns combined | Child cannot un-block a parent’s block |
If a child layer doesn’t set `allowed`, it inherits the parent’s list unchanged.
### Example
Given this configuration:
```plaintext
Harness: allowed: ["*.github.com", "*.openai.com"]
Agent: allowed: ["api.github.com"], blocked: ["evil.com"]
Session: blocked: ["malware.github.com"]
```
The effective policy for the session is:
* **Allowed**: `["api.github.com"]`, kept because it’s a subset of `*.github.com`; `*.openai.com` dropped because the agent didn’t include it
* **Blocked**: `["evil.com", "malware.github.com"]`, union of all layers
Only `api.github.com` is reachable, and both `evil.com` and `malware.github.com` are explicitly denied.
### Harness Inheritance
If a harness inherits from a parent harness (via `parent_harness_id`), the network access list is merged through the inheritance chain before any agent or session layer is applied. The same intersection/union rules apply.
## Enforcement
The merged network access list is checked in `web_fetch` before every HTTP request. If a URL doesn’t match the effective policy, the tool returns an error:
```plaintext
URL blocked by network access policy: https://blocked-domain.com/path
```
Standard SSRF protections (blocking private IPs, loopback, cloud metadata endpoints) are always enforced regardless of the network access list.
## Capabilities Affected
| Capability | Enforced | Notes |
| --------------- | -------- | --------------------------------------------- |
| `web_fetch` | Yes | Checked before every HTTP request |
| `bashkit_shell` | N/A | No network builtins (curl/wget not available) |
---
# Physical Architecture
> The physical components that make up an Everruns deployment, control plane, workers, PostgreSQL, NATS JetStream, Valkey, and the reverse proxy, and how they fit together.
Source:
The [getting-started architecture](https://docs.everruns.com/getting-started/architecture/) page describes the *logical* shape of Everruns: a control plane, a worker tier, and a shared database. This page goes one level deeper and describes the *physical* components an operator actually deploys, what each one is for, when it is optional, and how data flows between them.

## Components at a glance
| Component | Role | Required | Default port |
| ---------------------- | -------------------------------------------------------------------------- | --------------------------- | ------------------------ |
| Reverse proxy | TLS termination and route fan-out for `/api`, `/mcp`, `/.well-known/*`, UI | Yes (or equivalent ingress) | 443 |
| Control plane (server) | REST API, SSE event streams, gRPC server for workers, owns all state | Yes | 9301 (HTTP), 9001 (gRPC) |
| Worker pool | Stateless executors of the agentic loop (input → reason → act) | Yes | , (outbound only) |
| PostgreSQL 17 | Durable storage for agents, sessions, events, durable task queue | Yes | 5432 |
| NATS JetStream | Push-based ephemeral event delivery and task notifications | Optional | 4222 |
| Valkey | Distributed sliding-window rate limiting across control-plane instances | Optional | 6379 |
| Management UI | Operator interface for agent and provider configuration | Optional | , (served by proxy) |
Workers never talk to PostgreSQL, NATS, or Valkey directly. Every read and write goes through the control plane’s gRPC service on port 9001. This is what lets workers run with no database credentials, no encryption keys, and no awareness of the data tier.
## PostgreSQL, the only required stateful component
PostgreSQL is the single source of truth for everything Everruns persists. There is no in-memory cache that needs warming, no secondary store that needs syncing, and no analytics database to keep consistent. If you back up PostgreSQL, you back up the entire system.
What lives in PostgreSQL:
* Agents, sessions, messages, and durable events
* The durable task queue used by the worker tier (claimed via `SKIP LOCKED`)
* Encrypted LLM provider credentials, MCP server registrations, capability config
* Per-session virtual filesystems, knowledge bases, and the event log used for SSE replay
Operational requirements:
* **PostgreSQL 17.** UUIDv7 is implemented via a custom SQL function; PG 18’s native `uuidv7()` will be adopted once it is widely available on managed services.
* **Direct connection for `LISTEN/NOTIFY`.** Pooled or proxied endpoints (PgBouncer, Neon `-pooler`, RDS Proxy) interleave notification frames with query traffic. Set `DATABASE_URL` to the pooled endpoint for normal queries and `DATABASE_UNPOOLED_URL` to a direct session-scoped endpoint for listeners. Startup fails fast if the configured listener URL looks pooled.
* **Pool sizing.** With `EXPECTED_INSTANCES=N` set, each instance sizes its pool so that `pool × instances` stays under 80% of `PG_MAX_CONNECTIONS`.
* **Migrations.** Auto-applied on server startup via embedded sqlx migrations, protected by a PostgreSQL advisory lock so multiple control-plane instances can boot together without racing.
See [`docs/sre/environment-variables.md`](https://docs.everruns.com/sre/environment-variables/) for the full list of database-related variables.
## NATS JetStream, optional push delivery
NATS is not required, but turning it on materially reduces PostgreSQL write pressure and SSE tail latency for busy deployments.
Without NATS, Everruns uses PostgreSQL for both storage *and* delivery: ephemeral events persist to PG and SSE clients poll PG with `LISTEN/NOTIFY` wakeups; workers are notified of new tasks the same way. This works, and it is the default. The cost is write amplification, every streaming-token delta lands in PG even though no client will ever re-read it.
With `NATS_URL` set and JetStream enabled, Everruns rewires two hot paths:
* **Ephemeral event delivery.** Delta events (`output.message.delta`, `reason.thinking.delta`, `tool.output.delta`, `llm.generation`) skip PostgreSQL entirely and flow only through NATS JetStream. SSE streams subscribe to per-session subjects with short-term retention. Durable events (`output.message.completed`, `turn.started`, `tool.completed`, etc.) still persist to PG so SSE reconnection via `since_id` continues to work, missed deltas are acceptable because the completed event carries the full content.
* **Task notifications.** `task.available.{activity_type}` subjects replace PG NOTIFY for worker wakeup, dropping notification latency from \~30 ms to \~1 ms.
NATS is fail-graceful: if the connection fails at startup, the control plane logs a warning and falls back to the PG-backed paths. Only the control plane connects to NATS, workers still talk to the server via gRPC.
## Valkey, optional distributed rate limiting
Valkey is a Redis-compatible key-value store (a Linux Foundation fork of Redis). Everruns uses it for exactly one thing: sliding-window rate limiting that is coordinated across control-plane instances.
When `VALKEY_URL` is not set, rate limiting falls back to an in-memory governor, accurate per-instance, but with N instances behind a load balancer a single IP can consume up to N× the intended budget. Set `VALKEY_URL` when you run more than one control-plane instance and need a shared budget.
Connection details:
* Accepts `redis://`, `rediss://` (TLS), `valkey://`, `valkeys://` (TLS) schemes
* Uses atomic Lua scripts for sliding-window counters
* **Fail-open:** if Valkey is unreachable, the rate limiter allows the request rather than rejecting traffic on a side-channel outage
* Only the control plane connects to Valkey; workers do not need access
## Worker pool, no shared state
Workers are the most operationally boring component in the deployment. They have:
* No database connection
* No encryption key
* No NATS or Valkey access
* No durable local state
They claim a task from the control plane over gRPC, fetch the turn context in a single batched call, run the agentic loop (LLM calls, tool execution), and stream events back. If a worker crashes mid-task, the heartbeat stops, the control plane reclaims the task after 30 seconds, and another worker picks it up. Add workers for throughput; remove them to save cost.
See [Worker authentication](https://docs.everruns.com/sre/runbooks/durable-mode-setup/) for the `WORKER_GRPC_AUTH_TOKEN` and optional mTLS setup that secures this internal channel.
## Reverse proxy contract
A reverse proxy (or platform ingress that enforces the same routes) is mandatory in production:
| Route | Destination | Notes |
| ---------------- | ------------- | ----------------------------------- |
| `/api/*` | Control plane | Disable proxy buffering for SSE |
| `/mcp` | Control plane | Do **not** rewrite under `/api` |
| `/.well-known/*` | Control plane | OAuth discovery; do **not** rewrite |
| `/health` | Control plane | Health check target |
| Everything else | UI | If UI is deployed; otherwise 404 |
TLS terminates at the proxy. Worker gRPC traffic stays on the private network, never expose port 9001 publicly. See [`local/Caddyfile`](https://github.com/everruns/everruns/blob/main/local/Caddyfile) and [`examples/docker-compose-full.yaml`](https://github.com/everruns/everruns/blob/main/examples/docker-compose-full.yaml) for working configurations.
## Development modes
The same binaries collapse into smaller deployments for local work:
* **`DEV_MODE=true` (in-memory).** No PostgreSQL, no Docker. Execution runs in-process inside the server binary; the gRPC server is disabled. Data is lost on restart. Useful for UI iteration and API development.
* **`just start-all` (full local).** Brings up PostgreSQL, Valkey, and NATS as local processes (no Docker required) and starts the server + worker against them. Mirrors production wiring on a single machine.
* **Docker Compose.** The production-shaped topology in one machine; see [Docker Compose](https://docs.everruns.com/getting-started/docker-compose/).
## Multi-instance deployment
Multiple control-plane instances can run behind a load balancer with no session affinity:
| Concern | How it stays correct |
| -------------------- | ----------------------------------------------------------------------------------------- |
| Database connections | `EXPECTED_INSTANCES=N` divides the pool so `pool × instances ≤ 80% of PG_MAX_CONNECTIONS` |
| SSE delivery | `LISTEN/NOTIFY` or NATS subjects fan out to every instance; reconnects are idempotent |
| Task claiming | `SKIP LOCKED` on the durable task queue partitions work naturally |
| Migrations | PostgreSQL advisory lock prevents concurrent runs |
| Rate limits | Valkey-backed sliding-window counters are shared; in-memory falls back to per-instance |
Workers do not require coordination, add as many as you need, in as many regions as you need, as long as they can reach the control-plane gRPC port.
## Further reading
* [Architecture (Getting Started)](https://docs.everruns.com/getting-started/architecture/), the logical model
* [Environment Variables](https://docs.everruns.com/sre/environment-variables/), every knob and its default
* [Docker Compose](https://docs.everruns.com/getting-started/docker-compose/), a production-shaped local setup
* [Custom backends](https://docs.everruns.com/framework/custom-backends/), low-level execution-host composition
---
# Read Tools
> Parameters, pagination, content-type defaults, and exec output retrieval for the read-category tools.
Source:
Agents spend most of their context window on tool results. A single `cat` of a large file can consume thousands of tokens that crowd out reasoning space. The read-category tools are built to return the part the agent asked for and nothing else.
Three tools form the read category:
| Tool | Purpose |
| ---------------- | ---------------------------------------------------------------------------------- |
| `read_file` | Read file content with offset/limit pagination and line numbers |
| `grep_files` | Search file contents by regex — returns matching lines with paths and line numbers |
| `list_directory` | List files and directories with metadata (size, type) |
Together they implement a **search → locate → read** workflow: `grep_files` finds where something is, `read_file` reads the relevant section, and `list_directory` provides structural context.
## read\_file
### Parameters
| Parameter | Type | Default | Description |
| --------- | ------- | ------------ | --------------------------------------------------- |
| `path` | string | *(required)* | Absolute file path (e.g., `/workspace/src/main.rs`) |
| `offset` | integer | 0 | Starting line number (0-indexed) |
| `limit` | integer | 2000 | Maximum lines to return |
### Response
```json
{
"path": "/workspace/src/main.rs",
"content": "1|use std::io;\n2|use serde::Serialize;\n3|\n4|fn main() {\n...",
"total_lines": 450,
"lines_shown": { "start": 1, "end": 450 },
"truncated": false,
"content_type": "source",
"read_mode": "offset",
"size_bytes": 12480,
"content_hash": "sha256:a1b2c3..."
}
```
Key response fields:
* **`content`** — Line-numbered output in compact `N|content` format. The `N|` prefix uses minimal bytes while giving agents precise line references for edits.
* **`total_lines`** — Total lines in the file. Use this to know if more content exists beyond the current window.
* **`lines_shown`** — 1-based start/end of the returned window.
* **`truncated`** — `true` if more content exists beyond the returned window.
* **`content_type`** — Detected content type (e.g., `log`, `csv`, `minified`, `source`). Reflects the heuristic applied.
* **`read_mode`** — `tail` when reading log files from the end; `offset` otherwise.
* **`content_hash`** — SHA-256 hash of the file content, used by `edit_file` for compare-and-set safety.
### Content-Type Defaults
When the agent doesn’t specify `offset` or `limit`, `read_file` adjusts defaults based on file extension:
| Content Type | Extensions | Default Limit | Read Mode | Rationale |
| ------------ | --------------------------------- | ------------- | ------------------- | ------------------------------------------------------ |
| Source code | `.rs`, `.ts`, `.js`, `.py`, etc. | 2000 lines | From offset | Standard — agent usually needs context around a region |
| Log files | `.log`, `.out` | 500 lines | **From end (tail)** | Errors cluster at the end — recent output matters most |
| CSV/TSV data | `.csv`, `.tsv` | 100 lines | From offset | Schema + sample rows; header always included |
| Config | `.json`, `.yaml`, `.yml`, `.toml` | 2000 lines | From offset | Usually need full structure |
| Minified | `.min.js`, `.min.css` | 20 lines | From offset | Single-line files would blow token budgets |
| Text | `.md`, `.txt`, `.rst` | 2000 lines | From offset | Standard |
**Explicit parameters override only the defaults they control.** An explicit `limit` overrides the content-type default limit. An explicit `offset` does **not** change the default limit by itself; it only controls where reading starts, and for log files it disables the default tail-biased read mode in favor of reading from that offset. If both `offset` and `limit` are provided, both behaviors are explicitly controlled.
For CSV files, the header row (line 1) is always included in the response, even when reading with an offset. This ensures the agent always has column names for context.
### Image and Binary Handling
* **Images** (`.png`, `.jpg`, `.gif`, `.webp`) are returned as **native image content blocks**, not text. The agent sees the image directly.
* **Binary files** (base64-encoded) are returned with `"encoding": "base64"` and no line formatting.
* A **hard byte cap** (50 KB) acts as a safety net for pathological files like minified bundles.
## Pagination
For files larger than the default window, page through with `offset` and `limit`:
```json
// First read — gets lines 1-2000
{ "path": "/workspace/big_file.rs" }
// Response shows total_lines: 5000, truncated: true
// Continue reading:
{ "path": "/workspace/big_file.rs", "offset": 2000, "limit": 2000 }
// Gets lines 2001-4000. Continue until truncated: false.
```
The 2000-line default covers most source files entirely in a single read. For the rare cases where files are larger, the `total_lines` field tells the agent exactly how much remains.
## Keeping read results small
The system prompt guides agents toward efficient reading patterns:
1. **Search before read** — Use `grep_files` to find relevant lines, then `read_file` with a targeted `offset` around the match. This avoids reading thousands of irrelevant lines.
2. **Don’t re-read** — Files already in conversation context don’t need to be read again. The agent should reference prior reads.
3. **Check `total_lines`** — When a read is truncated, the agent knows how much remains and can decide whether to continue or search within the unread portion.
4. **Use offset to continue** — After a truncated read, use `offset` to pick up where the previous read left off.
## Exec Output as Readable Files
When sandbox tools (`bash`, `daytona_exec`, `e2b_exec`, etc.) produce output, the inline result is truncated based on the `output` verbosity parameter (default: `concise` \~2 KiB). But the **full output is always persisted** to the session filesystem.
### How It Works
The `tool_output_persistence` capability (included in the Generic harness) writes full output before truncation:
* **stdout** → `/.outputs/{tool_call_id}.stdout`
* **stderr** → `/.outputs/{tool_call_id}.stderr`
The truncated inline result includes metadata pointing to the persisted files:
```json
{
"stdout": "[truncated to 2 KiB — full output saved]",
"stderr": "",
"exit_code": 0,
"success": true,
"full_output": "/.outputs/call_abc123.stdout",
"total_lines": 8450,
"output_files": [
"/.outputs/call_abc123.stdout",
"/.outputs/call_abc123.stderr"
]
}
```
The agent can then `read_file` the persisted output selectively:
```json
// Read last 100 lines of build output
{ "path": "/.outputs/call_abc123.stdout", "offset": 8350, "limit": 100 }
```
### Priority-Aware Truncation
When output is truncated, the system preserves error-relevant content. Lines matching error patterns (`error`, `Error`, `FAILED`, `panicked`, stack traces) are prioritized over noise like `Compiling...` or `Downloading...`. This means the inline 2 KiB concise output is more likely to contain the diagnostically useful parts.
### Output Verbosity Modes
All exec tools accept an `output` parameter:
| Mode | Budget | Use case |
| --------- | --------- | ---------------------------------- |
| `silent` | \~200 B | Fire-and-forget commands |
| `concise` | \~2 KiB | Builds, installs (**default**) |
| `normal` | \~8 KiB | General debugging |
| `verbose` | \~16 KiB | Test failures, error investigation |
| `full` | unlimited | When the agent needs every line |
## Content Hash and Edit Safety
Every `read_file` response includes a `content_hash` (SHA-256). The `edit_file` tool requires this hash via its `expected_hash` parameter — if the file has changed since the read, the edit is rejected.
This **compare-and-set** mechanism prevents stale edits in long conversations where the agent may have read a file many turns ago and the file has since been modified (by another tool call or external process).
```json
// read_file returns content_hash
{ "content_hash": "sha256:a1b2c3..." }
// edit_file uses it as expected_hash — rejected if file changed
{
"path": "/workspace/src/main.rs",
"expected_hash": "sha256:a1b2c3...",
"old_text": "fn main() {",
"new_text": "fn main() -> Result<()> {"
}
```
If an edit is rejected due to a stale hash, the agent should re-read the file to get the current content and hash, then retry the edit.
## Structural Outlines
When `read_file` returns a truncated result, the response includes a **structural outline** of the unread portions — function/class/method signatures without bodies. The agent can orient itself in any file with a single partial read.
```plaintext
1|use std::collections::HashMap;
2|use serde::Serialize;
... (lines read normally) ...
100| let result = process(&input);
--- Outline of lines 101-500 (not shown) ---
// L105: fn process(input: &Input) -> Result { ... }
// L180: fn validate(output: &Output) -> bool { ... }
// L220: struct Config { ... }
// L235: impl Config { ... }
// L236: fn load(path: &str) -> Result { ... }
// L270: fn merge(&mut self, other: &Config) { ... }
// L310: #[cfg(test)] mod tests { ... }
```
### Supported Languages
| Language | Extensions |
| ---------- | ----------------------------- |
| Rust | `.rs` |
| TypeScript | `.ts`, `.tsx`, `.mts` |
| JavaScript | `.js`, `.jsx`, `.mjs`, `.cjs` |
| Python | `.py` |
### What Gets Outlined
* **Functions/methods** — name, parameters, return type
* **Structs/classes/enums** — name
* **Impl blocks / trait implementations** — with nested methods
* **Modules** — including `#[cfg(test)]` detection
* **Interfaces and type aliases** (TypeScript)
For unsupported languages, no outline is generated — the response still works, just without structural context for unread portions.
---
# Request Signing
> How Everruns signs outbound HTTP requests with Ed25519 signatures per RFC 9421, enabling target servers to verify bot identity
Source:
When an AI agent fetches web content, the receiving server has no way to distinguish it from an anonymous scraper. **Request signing** solves this by attaching a cryptographic signature to every outbound HTTP request, letting target servers verify who is making the request and choose to grant or deny access based on that identity.
Everruns implements the [Web Bot Authentication Architecture](https://datatracker.ietf.org/doc/html/draft-meunier-web-bot-auth-architecture) (draft-meunier) using Ed25519 signatures over [RFC 9421 HTTP Message Signatures](https://www.rfc-editor.org/rfc/rfc9421).
## Background
### The Problem
Traditional bot identification relies on `User-Agent` strings, which are trivially spoofed. IP-based allow lists are brittle and don’t scale. There is no standard way for a web bot to prove its identity to a server.
### HTTP Message Signatures (RFC 9421)
RFC 9421 defines a general mechanism for signing HTTP messages. A sender selects components of the request (method, authority, specific headers) and signs them with a private key. The signature and a description of what was signed are transmitted as structured headers:
```plaintext
Signature: sig=:base64url-encoded-signature:
Signature-Input: sig=("@authority");created=1735689600;expires=1735689900;
keyid="JWK-thumbprint";alg="ed25519";nonce="random";
tag="web-bot-auth"
```
The receiving server reconstructs the same signature base from the request, fetches the sender’s public key, and verifies the signature. Replay attacks are prevented by the `created`/`expires` window and random `nonce`.
### Web Bot Authentication Architecture
The [draft-meunier-web-bot-auth-architecture](https://datatracker.ietf.org/doc/html/draft-meunier-web-bot-auth-architecture) builds on RFC 9421 specifically for bot identification:
* **Algorithm**: Ed25519 (fast, small keys, no parameter choices)
* **Covered components**: `@authority` (the target domain) at minimum
* **Key identity**: [JWK Thumbprint](https://www.rfc-editor.org/rfc/rfc7638) (SHA-256 hash of the canonical public key representation)
* **Signature tag**: `"web-bot-auth"`, distinguishes bot-auth signatures from other uses of RFC 9421
* **Discovery**: optional `Signature-Agent` header points to a FQDN where the bot’s public keys can be found
### Key Discovery
The companion [draft-meunier-http-message-signatures-directory](https://datatracker.ietf.org/doc/html/draft-meunier-http-message-signatures-directory) defines how target servers find a bot’s public keys:
```plaintext
GET https:///.well-known/http-message-signatures-directory
```
This returns a [JSON Web Key Set (JWKS)](https://www.rfc-editor.org/rfc/rfc7517#section-5) containing the bot’s Ed25519 public keys. The target server uses the `kid` field to match the key against the `keyid` in the incoming signature.

## How It Works in Everruns
Request signing is implemented as a server-wide feature. When enabled, **every outbound HTTP request** made by the `web_fetch` tool is signed.
### Signing (outbound)
The signing pipeline is handled by [fetchkit](https://github.com/everruns/fetchkit), the library powering the `web_fetch` capability:
1. Agent calls `web_fetch` with a URL
2. fetchkit builds the HTTP request
3. If bot-auth is configured, fetchkit signs the request:
* Covers `@authority` and optionally `signature-agent`
* Generates a random nonce
* Sets `created` and `expires` timestamps
* Signs with Ed25519, attaches `Signature` and `Signature-Input` headers
4. If signing fails (clock error, etc.), the request proceeds unsigned with a warning logged, signing never blocks requests
5. The request is sent to the target server
### Key directory (inbound)
Everruns serves the public key at `/.well-known/http-message-signatures-directory`. This endpoint:
* Is **public** (no authentication required)
* Returns a JWKS containing the server’s Ed25519 public key
* Derives the key at startup from the same seed used for signing
Example response:
```json
{
"keys": [
{
"kty": "OKP",
"crv": "Ed25519",
"x": "base64url-encoded-public-key",
"kid": "JWK-thumbprint-matching-keyid-in-signatures"
}
]
}
```
### What target servers see
A signed request arrives with three additional headers:
| Header | Purpose |
| ----------------- | --------------------------------------------------------------------------- |
| `Signature` | The Ed25519 signature over the covered components |
| `Signature-Input` | Describes what was signed: components, timestamps, key ID, algorithm, nonce |
| `Signature-Agent` | FQDN where the bot’s public keys can be discovered (optional) |
Target servers that support web-bot-auth can:
1. Extract the `keyid` from `Signature-Input`
2. Fetch the public key from the `Signature-Agent` FQDN’s well-known endpoint
3. Verify the signature
4. Apply access policies based on the verified identity
Servers that don’t support it simply ignore the extra headers.
## Verifying Signatures (Server Side)
If you operate a server that receives requests from Everruns agents, here’s how to verify them.
### Verification steps
1. **Check the tag**: parse `Signature-Input` and confirm `tag="web-bot-auth"`. Ignore signatures with other tags.
2. **Check timestamps**: reject if `created` is in the future or `expires` is in the past. A 5-minute clock skew tolerance is reasonable.
3. **Fetch the public key**: extract the `Signature-Agent` FQDN and fetch `https:///.well-known/http-message-signatures-directory`. Find the key matching the `keyid` from `Signature-Input`. Cache the JWKS (keys rotate infrequently).
4. **Reconstruct the signature base**: build the canonical representation per [RFC 9421 Section 2.5](https://www.rfc-editor.org/rfc/rfc9421#section-2.5) using the covered components listed in `Signature-Input`.
5. **Verify**: use Ed25519 to verify the signature against the reconstructed base and the fetched public key.
### Python example
```python
import base64
import hashlib
import time
import httpx
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
def verify_bot_auth(request) -> bool:
"""Verify a web-bot-auth signature on an incoming request."""
# 1. Parse Signature-Input header
sig_input = request.headers.get("signature-input", "")
if 'tag="web-bot-auth"' not in sig_input:
return False # not a bot-auth signature
# Extract parameters from sig_input
# sig=("@authority" "signature-agent");created=...;expires=...;keyid="...";...
params = parse_signature_params(sig_input)
# 2. Check timestamps
now = int(time.time())
if params["created"] > now + 300 or params["expires"] < now:
return False # expired or future-dated
# 3. Fetch public key from Signature-Agent FQDN
agent_fqdn = request.headers.get("signature-agent", "")
jwks_url = f"https://{agent_fqdn}/.well-known/http-message-signatures-directory"
jwks = httpx.get(jwks_url).json()
key_data = next(k for k in jwks["keys"] if k.get("kid") == params["keyid"])
public_key_bytes = base64.urlsafe_b64decode(key_data["x"] + "==")
public_key = Ed25519PublicKey.from_public_bytes(public_key_bytes)
# 4. Reconstruct signature base (RFC 9421 Section 2.5)
# Covered components are listed in parentheses in Signature-Input
sig_base = build_signature_base(request, params)
# 5. Verify
signature = base64.b64decode(
request.headers["signature"].split(":")[1] # sig=:base64:
)
try:
public_key.verify(signature, sig_base.encode())
return True
except Exception:
return False
```
### Node.js example
```javascript
import { createPublicKey, verify } from "node:crypto";
async function verifyBotAuth(request) {
const sigInput = request.headers["signature-input"] || "";
if (!sigInput.includes('tag="web-bot-auth"')) return false;
const params = parseSignatureParams(sigInput);
// Check timestamps (5-minute tolerance)
const now = Math.floor(Date.now() / 1000);
if (params.created > now + 300 || params.expires < now) return false;
// Fetch public key
const fqdn = request.headers["signature-agent"];
const res = await fetch(
`https://${fqdn}/.well-known/http-message-signatures-directory`
);
const jwks = await res.json();
const jwk = jwks.keys.find((k) => k.kid === params.keyid);
const key = createPublicKey({ key: jwk, format: "jwk" });
// Reconstruct signature base and verify
const sigBase = buildSignatureBase(request, params);
const signature = Buffer.from(
request.headers["signature"].split(":")[1],
"base64"
);
return verify(null, Buffer.from(sigBase), key, signature);
}
```
> **Note:** The `parseSignatureParams` and `buildSignatureBase` helpers follow the structured fields parsing rules from [RFC 8941](https://www.rfc-editor.org/rfc/rfc8941) and the signature base construction from [RFC 9421 Section 2.5](https://www.rfc-editor.org/rfc/rfc9421#section-2.5). Libraries like [httpbis-message-signatures](https://pypi.org/project/httpbis-message-signatures/) (Python) and [@httpbis/message-signatures](https://www.npmjs.com/package/@httpbis/message-signatures) (Node.js) handle both.
## Configuration
Request signing is configured via environment variables. Set them before starting the server.
### Environment variables
| Variable | Required | Default | Description |
| --------------------------- | -------- | ------- | -------------------------------------- |
| `BOT_AUTH_SIGNING_KEY_SEED` | yes | , | Base64url-encoded 32-byte Ed25519 seed |
| `BOT_AUTH_AGENT_FQDN` | no | , | FQDN for the `Signature-Agent` header |
| `BOT_AUTH_VALIDITY_SECS` | no | `300` | Signature validity window in seconds |
When `BOT_AUTH_SIGNING_KEY_SEED` is not set, signing is disabled and no crypto code runs at request time.
### Generate a signing key
```bash
python3 -c "import os, base64; print(base64.urlsafe_b64encode(os.urandom(32)).rstrip(b'=').decode())"
```
### Enable signing
```bash
export BOT_AUTH_SIGNING_KEY_SEED="your-base64url-seed-here"
export BOT_AUTH_AGENT_FQDN="bot.yourcompany.com"
```
Then start the server. All `web_fetch` requests will be signed, and the public key will be available at `https://bot.yourcompany.com/.well-known/http-message-signatures-directory`.
### Verify it’s working
```bash
# Check the key directory endpoint
curl -s http://localhost:9301/.well-known/http-message-signatures-directory | jq .
```
## Standards
| Standard | Role |
| ---------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| [RFC 9421, HTTP Message Signatures](https://www.rfc-editor.org/rfc/rfc9421) | Core signing mechanism, how to sign and verify HTTP requests |
| [RFC 8941, Structured Field Values](https://www.rfc-editor.org/rfc/rfc8941) | Encoding format for `Signature` and `Signature-Input` headers |
| [RFC 7638, JWK Thumbprint](https://www.rfc-editor.org/rfc/rfc7638) | How the `keyid` is computed from the public key |
| [RFC 7517, JSON Web Key (JWK)](https://www.rfc-editor.org/rfc/rfc7517) | Format of the keys in the JWKS response |
| [RFC 8037, Ed25519 in JOSE](https://www.rfc-editor.org/rfc/rfc8037) | Ed25519 key representation in JWK format |
| [draft-meunier-web-bot-auth-architecture](https://datatracker.ietf.org/doc/html/draft-meunier-web-bot-auth-architecture) | Bot-specific profile of RFC 9421 (algorithm, tag, covered components) |
| [draft-meunier-http-message-signatures-directory](https://datatracker.ietf.org/doc/html/draft-meunier-http-message-signatures-directory) | Well-known endpoint for public key discovery |
## See Also
* [fetchkit](https://github.com/everruns/fetchkit), The library implementing the signing client
---
# Tool Output Pipeline
> How Everruns processes tool results from execution to the model, verbosity budgets, distillation, persistence, hard limits, and where the full output lives
Source:
Agents generate most of their context from **tool output**: shell commands, file reads, web fetches, SQL queries, and MCP tool calls. Left unmanaged, a single verbose command (`git diff`, a 10,000-row query, an installer log) can blow past the model’s context window, drive up cost, and bury the signal the agent actually needs.
Everruns runs every tool result through a **multi-stage pipeline** that shrinks what the model sees while keeping the full original recoverable. This page explains each stage, the order they run in, and, crucially, *where the full output goes* so the agent can get it back.
## The big picture
```plaintext
tool runs
│
▼
raw output ──────────────────────────────────────────────┐
│ │ (full, lossless)
▼ │
1. Verbosity budget (exec/sandbox tools only) │
│ auto/concise/normal/verbose/full │
▼ │
2. Capability hooks │
│ • Tool Output Distillation (non-exec tools) │
▼ ▼
3. Final infrastructure hooks /outputs/
│ • Persist Output (exec tools → VFS) {tool_call_id}.stdout
│ • Output Hard Limit (64 KiB ceiling) {tool_call_id}.stderr
▼ ▲
inline result → stored in the session, │
shown to the model next turn ──────────────────────┘
(read_file recovers the full original)
```
Two ideas run through the whole pipeline:
1. **Storage stays lossless.** Whatever the model sees inline, the full output is written to the session filesystem (the *destination*). The inline view always carries a pointer back to it.
2. **Each stage shrinks, none deletes.** Truncation, distillation, and masking only change the *view*. The agent can always `read_file` the persisted original.
## Stage 1, Verbosity budget (exec tools)
Exec and sandbox tools (`bash`, `*_exec`, sandboxed shells) clean their output (strip ANSI, collapse carriage returns) and apply a **verbosity budget** before returning. The mode is configurable per call; the default is `auto`:
* **Success (`exit_code == 0`)** → collapse to a compact summary (\~512 bytes), because the full log is persisted (Stage 3) and the agent rarely needs it inline.
* **Failure (non-zero exit)** → keep a larger diagnostic window (\~8 KiB) so the error stays debuggable in-loop.
The full pre-truncation output is stashed on the result as `raw_output` for the persistence hook to consume. Non-exec tools (MCP, web fetch, client tools) do **not** have a verbosity budget, that gap is what Stage 2 exists for.
## Stage 2, Tool Output Distillation (non-exec tools)
[Tool Output Distillation](https://docs.everruns.com/capabilities/) targets the tools Stage 1 doesn’t: **MCP tools and `web_fetch`**, whose results otherwise enter history verbatim. It runs as a capability hook, so it executes *before* the final hooks.
For a large non-exec result, distillation produces a compact, **content-aware** inline view:
| Output shape | What you get inline |
| ---------------- | ---------------------------------------------------------------------------- |
| Large JSON array | Schema-preserving sample: the first few rows + `[… N more items elided …]` |
| Long string | Head + tail window (both ends preserved), with a byte-elision marker |
| Unified diff | A diffstat-style summary: file + hunk headers and `+added / -removed` counts |
| Nested object | Each oversized field distilled; small fields untouched |
Before it replaces anything, distillation **persists the full original** to the session filesystem (same destination as Stage 3) and injects a recovery pointer. If persistence fails, or the session has no filesystem, it restores the verbatim output rather than leave a lossy result the agent can’t recover. **Reversibility is never sacrificed.**
Distillation is on by default in the **generic harness**. Every transform is deterministic, so identical output distills identically and the model provider’s prompt cache keeps hitting across turns.
## Stage 3, Persistence and the hard limit
Two infrastructure hooks always run last, in order:
1. **Persist Output**: for tools that declare the `persist_output` hint (exec/sandbox), writes the full `raw_output` to the session VFS. When content is absent from the inline result, it adds a recovery pointer; complete inline results keep the retained file internal and do not invite a redundant read. It **skips** if a result already carries `output_files` (e.g. distillation already persisted it), so the two never double-write.
2. **Output Hard Limit**: a final, unremovable 64 KiB ceiling. By the time it runs, the result has usually already been budgeted or distilled, so it rarely fires; it’s a backstop against pathological cases.
## The destination, where full output lives
Everything the pipeline elides is recoverable from the **session filesystem**:
```plaintext
/outputs/{tool_call_id}.stdout ← full standard output
/outputs/{tool_call_id}.stderr ← full standard error (when present)
```
When the inline result omits persisted content, it carries the recovery pointer in `output_files` and `full_output`, plus a human-readable note telling the model to use `read_file` (with `offset`/`limit`) for the missing detail. Complete inline output has no model-facing pointer. Persisted streams are capped at 1 MiB each. Deleting the session cascades and removes them.
This is the key to aggressive shrinking: because the original is one `read_file` away, the inline view can be small without the agent losing the ability to drill in.
## How this relates to compaction
The pipeline above operates on **individual tool results at capture time**. [Context Compaction](https://docs.everruns.com/advanced/compaction/) operates **later**, across the whole conversation, when it approaches the context window, masking or summarizing older messages at serialization time.
They compose cleanly:
* The pipeline keeps each result lean as it’s produced.
* Compaction further masks older results when the *accumulated* history grows too large.
* [Infinity Context](https://docs.everruns.com/capabilities/) adds a `query_history` tool to retrieve older *messages* that scrolled out of the window.
Together: the pipeline controls per-result size, compaction controls total-history size, and both keep the full record recoverable.
## Summary
| Stage | Applies to | Effect | Destination of full output |
| ----------------- | -------------------------- | -------------------------------------------------------- | --------------------------------------------- |
| Verbosity budget | exec/sandbox | Compact summary on success, diagnostic window on failure | `raw_output` → persisted in Stage 3 |
| Distillation | MCP / web fetch / non-exec | Content-aware compact view | `/outputs/{id}.stdout` |
| Persist Output | `persist_output` tools | Lossless write + pointer | `/outputs/{id}.{stdout,stderr}` |
| Output Hard Limit | all | 64 KiB ceiling backstop | (already persisted) |
The agent always sees a lean view and can always recover the full original with `read_file`.
---
# Built-ins Overview
> Built-in harness types and capabilities that ship with Everruns. Harnesses define session environments; capabilities add tools and behaviors.
Source:
Everruns ships with built-in **harness types** and **capabilities** that provide the foundation for agent sessions.
## Harnesses
A harness defines the base environment for sessions, system prompt, default model, and bundled capabilities. Every session is assigned a harness.
| Harness | Description | Capabilities |
| ----------------------------------------------------------------------------- | ---------------------------------------- | -------------------------------------------------------------- |
| [Base](https://docs.everruns.com/built-ins/harnesses/base/) | Empty harness, full control | None |
| [Generic](https://docs.everruns.com/built-ins/harnesses/generic/) | Recommended default with core tools | 16 configured, including 14 user-facing defaults |
| [Data Analyst](https://docs.everruns.com/built-ins/harnesses/data-analyst/) | SQL databases, charts, persistent memory | Generic + 5 data capabilities; available as a built-in example |
| [Platform Chat](https://docs.everruns.com/built-ins/harnesses/platform-chat/) | Focused global operator chat | Platform + runtime safeguards |
See the [Harnesses feature guide](https://docs.everruns.com/features/harnesses/) for harness selection, API management, and the prompt stack model.
## Harness Examples
Harness examples are adoptable templates. Import them when you want a preconfigured starting point, then customize the resulting org-owned harness.
| Example | Import Name | Description |
| ------------------ | ------------------ | ----------------------------------------------------------------------------------------------------- |
| Coding (Daytona) | `coding-daytona` | Generic + Daytona sandbox execution + GitHub Scout subagents for repository exploration |
| Coding (Container) | `coding-container` | Generic + self-hosted container sandbox execution + GitHub Scout subagents for repository exploration |
| Data Analyst | `data-analyst` | Generic + SQL databases, charts, persistent memory, and curated data knowledge |
## Capabilities
Capabilities are modular units that extend what an agent can do. Each can contribute tools, system prompt additions, and UI features.
Browse the full [Capabilities reference](https://docs.everruns.com/capabilities/) for the complete list organized by category.
---
# Base Harness
> A harness with no capabilities, leaving all session configuration to the agent or session.
Source:
The **Base** harness is a blank-slate starting point with no bundled capabilities.
## When to Use
* Full control over which tools and behaviors are available
* Testing individual capabilities in isolation
* Minimal-overhead sessions where no default tools are needed
## Configuration
| Property | Value |
| ----------------- | ------------------------------------------ |
| **Type** | `base` |
| **Capabilities** | None |
| **System Prompt** | ”You are a helpful assistant.” |
| **Default Model** | None (inherits from agent or organization) |
## Usage
Assign the Base harness when creating an agent or session:
```bash
curl -X POST http://localhost:9300/api/v1/agents \
-H "Content-Type: application/json" \
-d '{
"name": "Minimal Agent",
"harness_id": "",
"capabilities": ["web_fetch"]
}'
```
The agent’s own capabilities are added on top of the empty harness. In this example, only `web_fetch` would be available.
## See Also
* [Generic Harness](https://docs.everruns.com/built-ins/harnesses/generic/), recommended default with core capabilities
* [Harnesses feature guide](https://docs.everruns.com/features/harnesses/), harness selection and API management
---
# Data Analyst Harness
> Data analysis harness with SQL databases, persistent memory, interactive charts, and a structured analysis pipeline inspired by OpenAI's Dash.
Source:
The **Data Analyst** harness extends the [Generic harness](https://docs.everruns.com/built-ins/harnesses/generic/) with capabilities for data analysis: SQL databases, persistent cross-session memory, rich visualization via OpenUI, and a curated knowledge scaffold. Its system prompt implements a structured 6-step analysis pipeline inspired by [OpenAI’s Kepler data agent](https://openai.com/index/inside-our-in-house-data-agent/) and the open-source [Dash](https://github.com/agno-agi/dash) project.
## When to Use
* Natural-language data analysis (ask questions, get SQL + charts)
* Interactive data exploration with visualization
* Agents that learn from corrections and remember them across sessions
* Analytics workflows grounded in curated knowledge bases (table docs, business rules, validated SQL)
## Configuration
| Property | Value |
| ----------------- | ------------------------------------------ |
| **Type** | `data-analyst` |
| **System Prompt** | Structured 6-step analysis pipeline |
| **Default Model** | None (inherits from agent or organization) |
## Analysis Pipeline
The system prompt guides the agent through six steps on every data question:
1. **Recall**: Search persistent memory for corrections, column mappings, and business definitions from earlier sessions
2. **Inspect**: Use `sql_schema` to verify table structure before writing SQL
3. **Plan**: State the query plan: tables, joins, filters, expected grain, and potential pitfalls
4. **Execute & Validate**: Run the query, then validate (zero rows? duplicates? NULL aggregations?). Self-correct if results look wrong
5. **Visualize**: Summarize findings in plain language, then render charts and tables via OpenUI
6. **Learn**: Use `remember` to save corrections and patterns for future sessions
This mirrors the six-layer context pattern described in [OpenAI’s data agent blog post](https://openai.com/index/inside-our-in-house-data-agent/) and implemented by [Dash](https://github.com/agno-agi/dash).
## Bundled Capabilities
All [Generic harness capabilities](https://docs.everruns.com/built-ins/harnesses/generic/#bundled-capabilities) plus:
| Capability | What it provides |
| -------------------- | ------------------------------------------------------------------------------------------------------------ |
| Session SQL Database | `sql_execute`, `sql_query`, `sql_schema`, session-scoped SQLite databases that auto-create on first write |
| Persistent Memory | `remember`, `recall`, `forget`, cross-session memory with passive recall (8 memories auto-injected per turn) |
| OpenUI | Rich interactive charts, tables, dashboards, and KPI cards rendered inline in chat |
| Todo List | `write_todos`, track multi-step analysis tasks |
| Data Knowledge | Mounts `/knowledge/` scaffold with directories for table docs, business rules, and validated SQL patterns |
## Knowledge Files
The harness mounts a `/knowledge/` directory scaffold in every session:
```plaintext
/knowledge/
tables/README.md # Add one .md per table: columns, types, gotchas
business/README.md # Add metric definitions, business rules, domain terms
queries/README.md # Add validated .sql files as reusable templates
```
These files are read-only scaffolds. Populate them with your organization’s curated knowledge to ground the agent’s SQL generation in reality. The agent reads these files before writing any SQL query.
Combined with persistent memory (which accumulates corrections automatically), this implements the layered context pattern:
| Layer | Source |
| ----------------------- | ---------------------------------------- |
| Table usage & schema | `sql_schema` tool + `/knowledge/tables/` |
| Business annotations | `/knowledge/business/` + AGENTS.md |
| Validated queries | `/knowledge/queries/` |
| Institutional knowledge | MCP servers (Slack, Notion, Confluence) |
| Learning memory | `remember` / `recall` tools |
| Runtime context | `sql_query` / `sql_execute` |
## Example Session
```plaintext
User: Load this CSV and tell me which product category has the highest revenue
Agent: [recalls relevant memories] [inspects any existing schema]
[creates table, imports data]
[runs SELECT category, SUM(revenue) ... GROUP BY category]
[validates: 5 categories, no NULLs, totals match]
[renders bar chart via OpenUI]
[remembers: "revenue column is net of refunds"]
```
## See Also
* [Generic Harness](https://docs.everruns.com/built-ins/harnesses/generic/), the parent harness this extends
* [Capabilities overview](https://docs.everruns.com/features/capabilities/), full capability catalog including memory and OpenUI
* [Harnesses feature guide](https://docs.everruns.com/features/harnesses/), harness selection and API management
---
# Generic Harness
> The default harness, bundling core capabilities for general-purpose agent sessions.
Source:
The **Generic** harness is the recommended default for most use cases. It configures 16 capabilities: 14 user-facing defaults plus cross-cutting helpers for tool narration and side questions. Together they cover file operations, command execution, web access, memory, budgeting, context management, and durable tool output.
## When to Use
* General-purpose assistants
* Coding and scripting tasks
* Research workflows
* Any session where you want a solid set of defaults
## Configuration
| Property | Value |
| ----------------- | ------------------------------------------ |
| **Type** | `generic` |
| **System Prompt** | ”You are a helpful assistant.” |
| **Default Model** | None (inherits from agent or organization) |
## Bundled Capabilities
| Capability | What it provides |
| -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| [File System](https://docs.everruns.com/capabilities/file-system/) | Read, write, list, grep, and delete files in the session workspace (`/workspace`) |
| [Bashkit Shell](https://docs.everruns.com/capabilities/bashkit-shell/) | Sandboxed bash shell for running commands, scripts, and text processing |
| [Web Fetch](https://docs.everruns.com/capabilities/web-fetch/) | Fetch web content with file download support |
| [Storage](https://docs.everruns.com/capabilities/session-storage/) | Key/value store for general data and encrypted secret storage |
| [Session](https://docs.everruns.com/capabilities/session/) | Access session metadata and manage session title |
| [Session Schedules](https://docs.everruns.com/capabilities/session-schedules/) | Create and manage cron-style schedules that wake the session |
| [AGENTS.md](https://docs.everruns.com/capabilities/agent-instructions/) | Reads AGENTS.md from workspace and injects project-level instructions |
| [Agent Skills](https://docs.everruns.com/capabilities/agent-skills/) | Discover and activate skills from `/.agents/skills/` |
| [Infinity Context](https://docs.everruns.com/capabilities/infinity-context/) | Trims older messages from the live prompt while exposing earlier history via `query_history` |
| [OpenAI Tool Search](https://docs.everruns.com/capabilities/openai-tool-search/) | Defers tool schema loading on supported models to reduce prompt size |
| [Context Compaction](https://docs.everruns.com/advanced/compaction/) | Auto-compacts context at 85% budget via cascading strategies |
| [Budgeting](https://docs.everruns.com/capabilities/budgeting/) | Token budget enforcement with configurable meters and rules |
| [Self-Budget](https://docs.everruns.com/capabilities/self-budget/) | Prompt-only guidance for reasoning about a user-requested indicative budget using session usage data |
| [Ask User](https://docs.everruns.com/capabilities/ask-user/) | Ask the user 1–4 structured questions, or collect a credential, and wait for the answer |
| Soft Approval | Prompt-level gate asking permission before a destructive, irreversible, or outward-facing action |
| Tool Output Persistence | Persists full tool output to `/.outputs/` before truncation for lossless retrieval |
Infinity Context and Context Compaction work together to keep long sessions unbounded. See [Context Compaction](https://docs.everruns.com/advanced/compaction/#generic-harness-defaults) for details.
## See Also
* [Base Harness](https://docs.everruns.com/built-ins/harnesses/base/), empty harness for full control
* [Platform Chat Harness](https://docs.everruns.com/built-ins/harnesses/platform-chat/), focused operator chat built on Base
* [Harnesses feature guide](https://docs.everruns.com/features/harnesses/), harness selection and API management
---
# Platform Chat Harness
> Catalog-backed platform tools for the global chat interface.
Source:
The **Platform Chat** harness is a focused operator environment built on the empty [Base harness](https://docs.everruns.com/built-ins/harnesses/base/). It powers the global chat interface where users manage Everruns through the authoritative platform catalog.
## When to Use
* Global chat interface sessions
* Agents that need to manage platform resources (agents, harnesses, providers)
* Administrative assistants that interact with the Everruns API
## Configuration
| Property | Value |
| ----------------- | ----------------------------------------------------- |
| **Type** | `platform-chat` |
| **System Prompt** | Extended prompt with platform management instructions |
| **Default Model** | None (inherits from agent or organization) |
## Bundled Capabilities
| Capability | What it provides |
| ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- |
| [Platform](https://docs.everruns.com/capabilities/platform/) | `discover`, read-only `query`, and mutating `execute` over the authoritative Everruns command catalog |
| [Ask User](https://docs.everruns.com/capabilities/ask-user/) | Ask the operator 1–4 structured questions, or collect a credential, and wait for the answer |
| Soft Approval | Prompt-level gate asking permission before a destructive, irreversible, or outward-facing action |
| Loop detection | Stops repeated command/discovery cycles |
| Error disclosure | Returns actionable command failures to the operator |
| Compaction | Bounds long management conversations |
Platform Chat discovers current command names and schemas before acting. It uses `query` for inspection, `execute` only for requested mutations, and then queries the final state. For recurring autonomous work it creates an Agent Trigger rather than scheduling the Platform Chat session.
Generic-purpose tools such as Bash, web fetch, session secrets, and session schedules are intentionally absent. This keeps command selection focused and prevents credentials or schedules from being written into the management session when they belong to the created worker Agent.
When a tool needs a credential, Platform Chat attaches the capability and creates a value-free Agent credential setup requirement. It links to the Agent’s **Credentials** tab, where the user enters the value in a write-only form. Platform Chat never asks for or reuses plaintext from the conversation.
## See Also
* [Base Harness](https://docs.everruns.com/built-ins/harnesses/base/), the minimal parent this harness extends
* [Platform capability](https://docs.everruns.com/capabilities/platform/), the additional capability
* [Harnesses feature guide](https://docs.everruns.com/features/harnesses/), harness selection and API management
---
# Capabilities Overview
> Capabilities give an agent tools, system prompt fragments, and execution features. Index of every built-in capability.
Source:
Capabilities are modular units that extend what an agent can do. Each capability can contribute:
* **Tools**: callable functions the agent can invoke during conversations
* **System prompt additions**: context and instructions prepended to the agent’s prompt
* **Features**: UI elements unlocked when the capability is active (e.g., Workspace tab)
Agents compose capabilities, enable only what you need.
## Capability Reference
### Core
Fundamental capabilities for file operations, command execution, web access, session management, time awareness, task tracking, scheduling, and agent coordination.
| Capability | ID | Tools |
| ---------------------------------------------------------------------------------------------------- | --------------------------- | ----- |
| [File System](https://docs.everruns.com/capabilities/file-system/) | `session_file_system` | 6 |
| [Bashkit Shell](https://docs.everruns.com/capabilities/bashkit-shell/) | `bashkit_shell` | 1 |
| [Host Shell](https://docs.everruns.com/capabilities/host-shell/) | `host_shell` | 1 |
| [Session](https://docs.everruns.com/capabilities/session/) | `session` | 2 |
| [Storage](https://docs.everruns.com/capabilities/session-storage/) | `session_storage` | 2 |
| [Web Fetch](https://docs.everruns.com/capabilities/web-fetch/) | `web_fetch` | 1 |
| [Current Time](https://docs.everruns.com/capabilities/current-time/) | `current_time` | 1 |
| [Message Metadata](https://docs.everruns.com/capabilities/message-metadata/) | `message_metadata` | 0 |
| [Ask User](https://docs.everruns.com/capabilities/ask-user/) | `ask_user` | 1 |
| [Task Management](https://docs.everruns.com/capabilities/task-management/) | `stateless_todo_list` | 1 |
| [Schedules](https://docs.everruns.com/capabilities/session-schedules/) | `session_schedule` | 3 |
| [Auto-Continue After Usage Limit](https://docs.everruns.com/capabilities/usage-limit-auto-continue/) | `usage_limit_auto_continue` | 0 |
| [Sub Agents](https://docs.everruns.com/capabilities/sub-agents/) | `subagents` | 3 |
| [AGENTS.md](https://docs.everruns.com/capabilities/agent-instructions/) | `agent_instructions` | 0 |
| [Agent Skills](https://docs.everruns.com/capabilities/agent-skills/) | `skills` | 2 |
### Sandboxes
Cloud and container sandbox environments for isolated code execution.
| Capability | ID | Tools |
| ------------------------------------------------------------------ | ------------------ | ----- |
| [Daytona](https://docs.everruns.com/capabilities/daytona/) | `daytona` | 10 |
| [E2B](https://docs.everruns.com/capabilities/e2b/) | `e2b` | 6 |
| [Docker Container](https://docs.everruns.com/capabilities/docker/) | `docker_container` | 5 |
### Browser
Browser automation and web interaction capabilities.
| Capability | ID | Tools |
| ------------------------------------------------------------------ | ------------- | ----- |
| [Browserless](https://docs.everruns.com/capabilities/browserless/) | `browserless` | 7 |
### Data
Structured data and knowledge capabilities.
| Capability | ID | Tools |
| -------------------------------------------------------------------------------------- | ----------------------- | ----- |
| [SQL Database](https://docs.everruns.com/capabilities/sql-database/) | `session_sql_database` | 3 |
| [Retrieval Citations](https://docs.everruns.com/capabilities/citation-retrieval/) | `citation_retrieval` | 0 |
| [Citation Verification](https://docs.everruns.com/capabilities/citation-verification/) | `citation_verification` | 0 |
### Media
Image generation and editing workflows.
| Capability | ID | Tools |
| ------------------------------------------------------------------------------------------ | --------------- | ----- |
| [OpenAI Image Generation](https://docs.everruns.com/capabilities/openai-image-generation/) | `gpt_image_gen` | 2 |
### Tools
Provider-executed and built-in tool capabilities.
| Capability | ID | Tools |
| ------------------------------------------------------------------------------------------ | ------------------------- | ----- |
| [OpenRouter Server Tools](https://docs.everruns.com/capabilities/openrouter-server-tools/) | `openrouter_server_tools` | 0 |
### Integrations
External-service capabilities and blueprint-backed workflows.
| Capability | ID | Tools |
| -------------------------------------------------------------------- | -------------- | ----- |
| [GitHub Scout](https://docs.everruns.com/capabilities/github-scout/) | `github_scout` | 0 |
| [Slack](https://docs.everruns.com/capabilities/slack/) | `slack` | 4 |
### Platform
Agent self-management and platform control.
| Capability | ID | Tools |
| ------------------------------------------------------------ | ---------- | ----- |
| [Platform](https://docs.everruns.com/capabilities/platform/) | `platform` | 3 |
### Optimization
Performance and cost optimization for LLM interactions.
| Capability | ID | Tools |
| ---------------------------------------------------------------------------------- | --------------------- | ----- |
| [Infinity Context](https://docs.everruns.com/capabilities/infinity-context/) | `infinity_context` | 1 |
| [Auto Tool Search](https://docs.everruns.com/capabilities/auto-tool-search/) | `auto_tool_search` | 1 |
| [OpenAI Tool Search](https://docs.everruns.com/capabilities/openai-tool-search/) | `openai_tool_search` | 0 |
| [Claude Tool Search](https://docs.everruns.com/capabilities/claude-tool-search/) | `claude_tool_search` | 0 |
| [Tool Search](https://docs.everruns.com/capabilities/tool-search/) | `tool_search` | 1 |
| [Budgeting](https://docs.everruns.com/capabilities/budgeting/) | `budgeting` | 1 |
| [Self-Budget](https://docs.everruns.com/capabilities/self-budget/) | `self_budget` | 0 |
| [Parallel Tool Calls](https://docs.everruns.com/capabilities/parallel-tool-calls/) | `parallel_tool_calls` | 0 |
### Safety
Streaming-output guardrails and runtime safety nets.
| Capability | ID | Tools |
| ------------------------------------------------------------------------------------------ | ------------------------- | ----- |
| [Prompt Canary Guardrail](https://docs.everruns.com/capabilities/prompt-canary-guardrail/) | `prompt_canary_guardrail` | 0 |
| [Tool Call Repair](https://docs.everruns.com/capabilities/tool-call-repair/) | `tool_call_repair` | 0 |
| [Guardrails](https://docs.everruns.com/capabilities/guardrails/) | `guardrails` | 0 |
The [`guardrails`](https://docs.everruns.com/capabilities/guardrails/) capability runs config-driven checks over model output and tool activity, blocking or logging per check. Checks can be deterministic (regex, blocklist, tool-call patterns) or model-backed, an `llm_judge` policy or a `moderation` decisions, plus delegation to an external guardrail over scoped MCP. Each check binds a rule to a stage (`output`, `tool_use`, `tool_output`) with an `on_fail` of `block` or `log`; model-backed and MCP checks send a bounded excerpt off the sync path and fail open. Use advisory mode and the `POST /v1/capabilities/guardrails/dry-run` endpoint to tune against false positives before enforcing. For ready-made starting points, list the gallery at `GET /v1/capabilities/guardrails/examples`, each preset carries a `data_egress` signal (`none` vs. `utility_llm`), and drop a preset’s `config` into the agent’s `guardrails` capability config.
### Automation
Run shell commands at lifecycle and tool events. Block, mutate, or audit agent actions from outside the model.
| Capability | ID | Tools |
| ---------------------------------------------------------------- | ------------ | ----- |
| [User Hooks](https://docs.everruns.com/capabilities/user-hooks/) | `user_hooks` | 0 |
### Demo
Pre-built domain simulations for testing and demonstrations.
| Capability | ID | Tools |
| ------------------------------------------------------------------------ | ---------------- | ----- |
| [Fake Warehouse](https://docs.everruns.com/capabilities/fake-warehouse/) | `fake_warehouse` | 10 |
| [Fake AWS](https://docs.everruns.com/capabilities/fake-aws/) | `fake_aws` | 11 |
| [Fake CRM](https://docs.everruns.com/capabilities/fake-crm/) | `fake_crm` | 8 |
## Quick Start
### Enable via API
```bash
curl -X POST http://localhost:9300/api/v1/agents \
-H "Content-Type: application/json" \
-d '{
"name": "My Agent",
"system_prompt": "You are a helpful assistant.",
"capabilities": ["session_file_system", "bashkit_shell", "web_fetch"]
}'
```
### Enable via UI
1. Navigate to the Agent detail page
2. Open the **Capabilities** section
3. Toggle capabilities on/off
4. Reorder with drag handles (order affects system prompt priority)
5. Save
### List available capabilities
```bash
curl http://localhost:9300/api/v1/capabilities
```
### Create a declarative capability
Declarative capabilities are persisted capability definitions made from data: system prompt text, scoped MCP servers, text file mounts, and skill packages. They use a public resource ID like `cap_...` and a stable capability reference like `declarative:research_pack`.
```bash
curl -X POST http://localhost:9300/api/v1/capabilities \
-H "Content-Type: application/json" \
-d '{
"definition": {
"name": "research_pack",
"display_name": "Research Pack",
"description": "Default research behavior and resources.",
"system_prompt": "Prefer primary sources and cite them clearly.",
"risk_level": "low"
}
}'
```
Agents and harnesses can use the canonical reference:
```json
{ "ref": "declarative:research_pack" }
```
For convenience, agent and harness write APIs also accept the plain unique name when it matches a declarative capability:
```json
{ "ref": "research_pack" }
```
## Key Concepts
### Dependencies
Some capabilities depend on others. Dependencies are resolved automatically at runtime, you don’t need to manually add them.
| Capability | Depends On |
| ---------------------------------------------------------------------- | ------------------------------------------------------------------ |
| [Bashkit Shell](https://docs.everruns.com/capabilities/bashkit-shell/) | [File System](https://docs.everruns.com/capabilities/file-system/) |
| [Host Shell](https://docs.everruns.com/capabilities/host-shell/) | [File System](https://docs.everruns.com/capabilities/file-system/) |
| [Agent Skills](https://docs.everruns.com/capabilities/agent-skills/) | [File System](https://docs.everruns.com/capabilities/file-system/) |
| [GitHub Scout](https://docs.everruns.com/capabilities/github-scout/) | [Sub Agents](https://docs.everruns.com/capabilities/sub-agents/) |
| [E2B](https://docs.everruns.com/capabilities/e2b/) | [Storage](https://docs.everruns.com/capabilities/session-storage/) |
### Features
Capabilities declare UI features they contribute. The session aggregates features from all active capabilities to decide which UI tabs to render.
| Feature | UI Element | Contributed By |
| -------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `file_system` | Workspace tab | [File System](https://docs.everruns.com/capabilities/file-system/), [Bashkit Shell](https://docs.everruns.com/capabilities/bashkit-shell/), [Host Shell](https://docs.everruns.com/capabilities/host-shell/) |
| `secrets` | Storage tab | [Storage](https://docs.everruns.com/capabilities/session-storage/) |
| `key_value` | Storage tab | [Storage](https://docs.everruns.com/capabilities/session-storage/) |
| `schedules` | Schedules tab | [Schedules](https://docs.everruns.com/capabilities/session-schedules/) |
| `sql_database` | Database tab | [SQL Database](https://docs.everruns.com/capabilities/sql-database/) |
| `subagents` | Subagents tab | [Sub Agents](https://docs.everruns.com/capabilities/sub-agents/) |
| `citations` | Inline citation chips + Sources strip | [Retrieval Citations](https://docs.everruns.com/capabilities/citation-retrieval/), [Citation Verification](https://docs.everruns.com/capabilities/citation-verification/) |
### Ordering
Capabilities are applied in the order configured on the agent. Earlier capabilities’ system prompt additions appear first. Place the most important context-setting capabilities first.
## See Also
* [Concepts](https://docs.everruns.com/getting-started/concepts/), how capabilities fit into the Harness → Agent → Session model
* [API Reference](https://docs.everruns.com/api/), full API documentation
* [MCP Servers](https://docs.everruns.com/features/mcp/), external tool servers as virtual capabilities
---
# AGENTS.md
> Project instructions loaded from configured files in the session workspace and injected into every turn.
Source:
| | |
| ---------------- | -------------------- |
| **ID** | `agent_instructions` |
| **Category** | Core |
| **Features** | None |
| **Dependencies** | None |
Reads project instruction files hierarchically from the session workspace and injects them as the leading user message on every turn. By default it reads `AGENTS.md`. Configure `files` when an agent should also resolve another file such as `CLAUDE.md` at every hierarchy level.
## Tools
None, this capability only contributes conversation context (never system prompt).
## How It Works
1. Agent sends a message
2. Before processing, the system resolves configured filenames from the filesystem root down to the working directory
3. Each file is wrapped in `` XML tags, broadest scope first, behind a trust framing header
4. Injected as the leading user-role message — model-visible, re-resolved every turn, below system instructions in precedence
## Config
```json
{
"files": ["AGENTS.md", "CLAUDE.md"]
}
```
`files` is optional. When omitted, Everruns reads only `/workspace/AGENTS.md`.
## Notes
* Default file name: `AGENTS.md` (plain Markdown, max 32 KiB per file, 128 KiB total per turn)
* Hierarchy: root to working directory; deeper files win, siblings out of scope
* Re-resolved every turn, edits take effect immediately
* Missing configured files are ignored (no error)
* Works with [File System](https://docs.everruns.com/capabilities/file-system/) tools to update instructions dynamically
## See Also
* [AGENTS.md feature guide](https://docs.everruns.com/features/agent-instructions/), detailed documentation
* [File System](https://docs.everruns.com/capabilities/file-system/), manage the AGENTS.md file
* [Agent Skills](https://docs.everruns.com/capabilities/agent-skills/), another way to inject specialized instructions
* [Capabilities Overview](https://docs.everruns.com/capabilities/)
---
# Agent Skills
> Discover and activate portable skill packages from the session workspace at runtime.
Source:
| | |
| ---------------- | ---------------------------------------------------------------------------- |
| **ID** | `skills` |
| **Category** | Core |
| **Features** | None |
| **Dependencies** | [`session_file_system`](https://docs.everruns.com/capabilities/file-system/) |
Discover and activate skills from `/.agents/skills/` in the session filesystem. Skills are portable instruction packages following the [Agent Skills](https://agentskills.io/) open specification.
## Tools
### `list_skills`
Scan `/.agents/skills/` for available skills. Returns names and descriptions only (\~100 tokens per skill).
### `activate_skill`
Load a skill’s full instructions by name.
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ---------------------------------------------------- |
| `name` | string | yes | Skill name (directory name under `/.agents/skills/`) |
Returns: full SKILL.md content and list of bundled files.
## How It Works
Skills use progressive disclosure to keep context efficient:
1. **Discovery** (\~100 tokens), `list_skills` returns only names and descriptions
2. **Activation** (<5000 tokens), `activate_skill` loads the full SKILL.md instructions
3. **Resources** (on-demand), bundled files accessible via [File System](https://docs.everruns.com/capabilities/file-system/) tools
## Workspace layout
```plaintext
/.agents/skills/
deploy/
SKILL.md
templates/
k8s-deploy.yaml
code-review/
SKILL.md
```
## Notes
* Skills are per-session (uploaded to session filesystem)
* Path traversal protection on skill names
* Invalid SKILL.md files are reported but don’t block discovery of other skills
* For organization-wide skills, see the [Skills Registry](https://docs.everruns.com/features/skills-registry/)
## See Also
* [Agent Skills feature guide](https://docs.everruns.com/features/skills/), detailed skills documentation
* [Skills Registry](https://docs.everruns.com/features/skills-registry/), API-managed skills
* [AGENTS.md](https://docs.everruns.com/capabilities/agent-instructions/), simpler alternative for project context
* [File System](https://docs.everruns.com/capabilities/file-system/), upload skill files
* [Capabilities Overview](https://docs.everruns.com/capabilities/)
---
# Ask User
> Let an agent ask the user a small batch of structured questions, and wait for the answer, instead of guessing or ending the turn in prose.
Source:
| | |
| ---------------- | ---------- |
| **ID** | `ask_user` |
| **Category** | Core |
| **Features** | None |
| **Dependencies** | None |
Gives the agent one tool for collecting decisions it cannot make on its own. Instead of guessing, or ending the turn with a paragraph of questions and hoping the user answers all of them, it asks 1–4 structured questions and waits.
The user sees a card with the questions, their options, and a short description of each option’s trade-off. Answering resumes the turn.
Enabled by default on the [Generic](https://docs.everruns.com/built-ins/harnesses/generic/) and [Platform Chat](https://docs.everruns.com/built-ins/harnesses/platform-chat/) harnesses. Not on [Base](https://docs.everruns.com/built-ins/harnesses/base/), which has no interactive surface.
## When to enable it
Enable it for agents that work *with* a person: anything where the agent’s first guess about intent, scope, or preference is likely to be wrong and expensive to undo.
Leave it off for agents that run unattended on a schedule or a trigger. It will not hang them — a client that cannot render a question gets the model’s declared defaults immediately, in the same turn — but an agent nobody is watching should be built to decide, not to ask.
## Not a consent gate
This is the boundary worth being clear about before enabling both:
| | `ask_user` | `request_approval` |
| --------- | ------------------------- | --------------------------- |
| For | Decisions and preferences | Permission to act |
| Example | ”Which environment?" | "May I delete this bucket?” |
| No answer | Resolves to a default | Stays unresolved |
`ask_user` **auto-resolves**. A question nobody answers falls back to the option the model marked as recommended. That is correct for a preference and wrong for permission, so a destructive, irreversible, or outward-facing action must go through `request_approval` (the `soft_approval` capability), whose wait does not auto-resolve.
Both are enabled together on the interactive harnesses for exactly this reason: the agent needs somewhere to put a preference so it stops putting permission questions there. The system prompt states the rule, but the capability pairing is what makes it followable.
## Question kinds
**Choice** — 2 to 6 options, single- or multi-select, optionally with a free-text “Something else” path. Options are ordered most-applicable-first, because that is the order a fallback follows.
**Secret** — collects a credential. The value is stored encrypted in [session storage](https://docs.everruns.com/capabilities/session-storage/) and the agent receives a reference (`session:MY_TOKEN`), never the value itself, so it cannot reach the conversation history or the agent’s context. Tools resolve the reference by name. A secret question never auto-resolves: there is no such thing as a default credential, so an unanswered one is declined and proceeding without it becomes the agent’s explicit decision.
Use the secret kind rather than asking for a key in chat. A key typed into an ordinary message stays in the session history in plain text.
## Tools
| Tool | Description |
| ---------- | --------------------------------------------------------------------------------- |
| `ask_user` | Ask 1–4 structured questions, or collect one credential, and wait for the answer. |
## Configuration
None. Limits are fixed by the contract:
| Limit | Value |
| -------------------- | ------------------------------------------------------- |
| Questions per call | 1–4 (a secret question must be alone) |
| Options per question | 2–6 |
| Timeout | 300 seconds, which the agent may shorten but not extend |
## What the agent is told
The result says what was chosen **and who chose it** — a person, a timeout, or an unattended fallback. An agent that reads a fallback as a considered answer will act with more confidence than the answer deserves, so provenance travels with it.
A declined question is a finished decision. The agent must not ask it again.
## See Also
* [Session Storage](https://docs.everruns.com/capabilities/session-storage/), where a collected secret is kept
* [Implementing a responder](https://docs.everruns.com/framework/ask-user/), for embedding applications
* [Capabilities Overview](https://docs.everruns.com/capabilities/)
---
# Auto Tool Search
> Deferred tool loading that uses the provider's hosted tool search where available (OpenAI or Claude) and a client-side fallback everywhere else.
Source:
| | |
| ---------------- | ------------------ |
| **ID** | `auto_tool_search` |
| **Category** | Optimization |
| **Features** | None |
| **Dependencies** | None |
Enables deferred tool loading and automatically picks the best mechanism for the agent’s model. On agents with many tools, full parameter schemas are not sent upfront, only names and descriptions, and schemas are loaded on demand. This reduces prompt token usage for agents with 15+ tools, regardless of provider.
This is the recommended default for harnesses that may run on different models. It is what the [Generic](https://docs.everruns.com/built-ins/harnesses/generic/) harness uses.
## How It Works
`auto_tool_search` resolves to one of three underlying mechanisms based on the model:
* **Models with native OpenAI tool search** (GPT-5.4 and newer) → the hosted mechanism described in [OpenAI Tool Search](https://docs.everruns.com/capabilities/openai-tool-search/): namespaces + `defer_loading` + a `{"type": "tool_search"}` activator. No extra tool is added; the provider handles search server-side.
* **Models with native Claude tool search** (Opus 4, Sonnet 4.5, Haiku 4.5, and Fable 5 and newer) → the hosted mechanism described in [Claude Tool Search](https://docs.everruns.com/capabilities/claude-tool-search/): per-tool `defer_loading` + a `tool_search_tool_bm25_20251119` server tool. No extra tool is added; the provider handles search server-side.
* **All other models** (Gemini, OpenAI Completions, Claude/GPT reached via a gateway that doesn’t implement the hosted format, …) → the client-side mechanism described in [Tool Search](https://docs.everruns.com/capabilities/tool-search/): schemas are stripped to stubs and a `tool_search` tool loads them back on demand.
The choice is made when the agent’s capabilities are assembled, once the model is known. You don’t have to know in advance which provider an agent will use.
The dispatch looks at the **model id** (matched against the first-party OpenAI/Anthropic profiles), not the transport. In practice that handles the common gateway cases: a Claude model served via Amazon Bedrock or OpenRouter carries a distinct id (`anthropic.claude-…`, `anthropic/claude-…`) that doesn’t match the bare first-party profile, so `auto_tool_search` resolves to the client-side mechanism there.
> **Edge case:** if a masked transport presents a *bare* first-party id that does resolve (e.g. a `gpt-5.4` served through an OpenAI-compatible gateway), `auto_tool_search` picks the hosted capability, but the driver then suppresses the hosted wire format for that transport, so full schemas are sent with **no** client-side fallback (a missed optimization, not a failure). If you run a first-party model id through such a gateway, add the [Tool Search](https://docs.everruns.com/capabilities/tool-search/) capability explicitly to force client-side deferral.
## Tools
One, the client-side `tool_search` tool, used only on models without native support. On models with native tool search, no client-side tool is added and the provider’s hosted search is used instead.
## Configuration
### Default (threshold: 15)
```json
{
"capabilities": ["auto_tool_search"]
}
```
### Custom threshold
```json
{
"capabilities": [
{
"capability_ref": "auto_tool_search",
"config": { "threshold": 10 }
}
]
}
```
The threshold (minimum tool count before deferral activates) applies to both mechanisms. Set to `1` to always activate when the capability is present.
## When to Use a Specific Capability Instead
Prefer the single-mechanism capabilities when you know the model and want explicit behavior:
* [OpenAI Tool Search](https://docs.everruns.com/capabilities/openai-tool-search/) (`openai_tool_search`), hosted, OpenAI only; silently disabled on unsupported models (full schemas sent, no fallback).
* [Claude Tool Search](https://docs.everruns.com/capabilities/claude-tool-search/) (`claude_tool_search`), hosted, Claude only; silently disabled on unsupported models (full schemas sent, no fallback).
* [Tool Search](https://docs.everruns.com/capabilities/tool-search/) (`tool_search`), client-side only; works on any model including OpenAI and Claude.
Do not combine `auto_tool_search` with any of the above on the same agent, it already provides all three paths.
## See Also
* [OpenAI Tool Search](https://docs.everruns.com/capabilities/openai-tool-search/), the hosted mechanism for OpenAI
* [Claude Tool Search](https://docs.everruns.com/capabilities/claude-tool-search/), the hosted mechanism for Claude
* [Tool Search](https://docs.everruns.com/capabilities/tool-search/), the client-side mechanism
* [Capabilities Overview](https://docs.everruns.com/capabilities/)
---
# Bashkit Shell
> Run Bash commands in a sandboxed interpreter with process isolation, resource limits, streaming output, and workspace-only filesystem access.
Source:
| | |
| ---------------- | ---------------------------------------------------------------------------- |
| **ID** | `bashkit_shell` (legacy alias: `virtual_bash`) |
| **Category** | Execution |
| **Risk** | High, assignment requires an org **Admin** |
| **Features** | `file_system` (enables the Workspace tab) |
| **Dependencies** | [`session_file_system`](https://docs.everruns.com/capabilities/file-system/) |
Execute bash commands in a sandboxed environment with no access to the host system. The session filesystem is mounted at `/workspace`, so commands read and write the same files as the [File System](https://docs.everruns.com/capabilities/file-system/) tools.
## Powered by Bashkit
This capability runs on [**bashkit**](https://bashkit.sh), an embeddable bash interpreter that executes shell scripts in-process inside a WASM-like sandbox, with no real shell, no subprocess spawning, and no host access. Learn more at [bashkit.sh](https://bashkit.sh) or browse the source on [GitHub](https://github.com/everruns/bashkit).
Because the interpreter is sandboxed by construction, bash here is **not** a shell-out to the host: there is no `/bin/bash` process, no direct network stack, and no filesystem beyond the session workspace. Outbound HTTP for `curl`/`wget` is off by default and can be enabled per agent (see **Outbound HTTP** below).
## Tools
### `bash`
Execute a shell command (or a multi-line script).
| Parameter | Type | Required | Description |
| ------------- | ------- | -------- | -------------------------------------------------------- |
| `commands` | string | yes | Shell command(s) to execute |
| `working_dir` | string | no | Working directory (default: `/workspace`) |
| `timeout_ms` | integer | no | Timeout in milliseconds (default: `30000`, max: `60000`) |
| `output` | string | no | Output verbosity (`auto`, `normal`, …; default: `auto`) |
Returns `stdout`, `stderr`, `exit_code`, and a `success` flag. Output streams live to the UI and CLI via `tool.output.delta` events while the command runs. On timeout, any partial output captured so far is returned alongside the error.
This tool also supports background execution, long scripts can run detached and report progress without blocking the agent loop.
## Filesystem
The interpreter exposes a single mount:
* **`/workspace`** maps to the session file store. Reads and writes are live, files created by bash are immediately visible to the File System tools and vice versa.
* Paths outside `/workspace` (for example `/etc`, `/home/agent`, `/tmp`) do not exist and cannot be written.
* Symlinks are unsupported; `chmod` is a no-op (the session filesystem does not track Unix permissions, and files are executable by default).
Default environment: `HOME=/home/agent`, `SHELL=/bin/bash`, `PATH=/usr/local/bin:/usr/bin:/bin`, `WORKSPACE=/workspace`, user and host `everruns`.
## Resource limits
Every invocation runs under fixed limits to prevent runaway scripts:
| Limit | Value |
| -------------------- | ------------------------------------- |
| Max commands per run | 1,000 |
| Max loop iterations | 10,000 |
| Max function depth | 100 |
| Max script size | 1 MB |
| Max memory | 10 MB |
| Parser timeout | 5 s |
| Wall-clock timeout | `timeout_ms` (default 30 s, max 60 s) |
## Outbound HTTP (optional)
Set the capability config `{"enable_http": true}` to let scripts use `curl` and `wget`. Every request, including each redirect hop, is routed through the platform egress boundary, where the agent/session network access list and the deployment-wide system allowlist are enforced. Policy denials surface as curl’s native `access denied` failure (exit code 7). Without the flag, the interpreter has no network path at all.
## Security
* **Sandboxed**: no direct network access (outbound HTTP is opt-in and egress-routed), no host filesystem, no subprocess spawning.
* **High risk**: because it exposes arbitrary scripted code execution, assigning `bashkit_shell` to an agent requires an org **Admin**. Existing agents that already had it keep working; the gate applies to new assignments only.
* Built-in observability hooks emit structured `tracing` events per builtin and on interpreter errors (tagged with the session ID) without logging argument values or command output.
## Notes
* Commands operate on the same `/workspace` as [File System](https://docs.everruns.com/capabilities/file-system/) tools.
* Built-in commands support ` --help`, and many also support ` --version`.
* Common builtins: `cd`, `ls`, `cat`, `echo`, `grep`, `head`, `tail`, `sed`, `find`, plus shell features like pipes, redirections, and command substitution. `grep` is backed by the session’s indexed search.
## See Also
* [Bashkit project](https://bashkit.sh), the interpreter powering this capability
* [File System](https://docs.everruns.com/capabilities/file-system/), file operations on the same workspace
* [Sub Agents](https://docs.everruns.com/capabilities/sub-agents/), background and parallel execution
* [Capabilities Overview](https://docs.everruns.com/capabilities/)
---
# Browserless
> Headless browser automation through Browserless for screenshots, DOM reading, scraping, and page interaction.
Source:
| | |
| ---------------- | --------------------------------------------------------------- |
| **ID** | `browserless` |
| **Category** | Browser |
| **Features** | None |
| **Dependencies** | None (session\_storage used opportunistically for CDP sessions) |
Cloud browser automation powered by Browserless. Take screenshots, read DOM content, scrape structured data, and interact with web pages using click, type, keyboard, mouse, and touch events.
## Tools
### `browserless_open_browser`
Open a persistent browser session via CDP. The browser stays alive between tool calls.
| Parameter | Type | Required | Description |
| ------------ | ------- | -------- | --------------------------------------------------------------- |
| `url` | string | no | Initial URL to navigate to |
| `timeout_ms` | integer | no | How long the browser stays alive between calls (default: 60000) |
### `browserless_close_browser`
Close the persistent browser session and release resources.
No parameters.
### `browserless_navigate`
Navigate to a URL and return page metadata (title, links, headings, meta tags).
| Parameter | Type | Required | Description |
| ------------------- | ------- | -------- | ------------------------------------------- |
| `url` | string | yes | The URL to navigate to |
| `wait_for_selector` | string | no | Wait for this CSS selector to appear |
| `wait_for_timeout` | integer | no | Wait this many milliseconds after page load |
### `browserless_screenshot`
Take a PNG screenshot of a page. Returns base64-encoded image data.
| Parameter | Type | Required | Description |
| ------------------- | ------- | -------- | ---------------------------------------------------- |
| `url` | string | yes | The URL to screenshot |
| `full_page` | boolean | no | Capture the full scrollable page (default: true) |
| `selector` | string | no | CSS selector to screenshot a specific element |
| `wait_for_selector` | string | no | Wait for this CSS selector before taking screenshot |
| `wait_for_timeout` | integer | no | Wait this many milliseconds before taking screenshot |
### `browserless_content`
Get the fully rendered HTML content (DOM) of a page, including JavaScript-rendered content.
| Parameter | Type | Required | Description |
| ------------------- | ------- | -------- | -------------------------------------------------------------- |
| `url` | string | yes | The URL to read |
| `wait_for_selector` | string | no | Wait for this CSS selector before reading content |
| `wait_for_timeout` | integer | no | Wait this many milliseconds before reading content |
| `best_attempt` | boolean | no | Continue even if async events fail or timeout (default: false) |
### `browserless_scrape`
Extract structured data from a page using CSS selectors. Returns JSON with matched elements.
| Parameter | Type | Required | Description |
| ------------------- | ------- | -------- | ------------------------------------------- |
| `url` | string | yes | The URL to scrape |
| `elements` | array | yes | Array of `{selector}` objects to extract |
| `wait_for_selector` | string | no | Wait for this CSS selector before scraping |
| `wait_for_timeout` | integer | no | Wait this many milliseconds before scraping |
### `browserless_interact`
Multi-step browser interactions. Navigate to a URL, then perform a sequence of actions.
| Parameter | Type | Required | Description |
| ------------------- | ------- | -------- | ------------------------------------------------------------ |
| `url` | string | yes | The initial URL to navigate to |
| `steps` | array | yes | Ordered list of interaction steps |
| `return_screenshot` | boolean | no | Return screenshot after steps (default: false = DOM content) |
**Supported step actions:**
| Action | Key Parameters | Description |
| ------------------- | --------------------- | -------------------------------------- |
| `click` | `selector` or `x`,`y` | Click element or coordinates |
| `type` | `selector`, `value` | Type text into input field |
| `keyboard` | `key` | Press a key (Enter, Tab, Escape, etc.) |
| `mouse_move` | `x`, `y` | Move mouse to coordinates |
| `touch` | `selector` | Tap element (mobile touch simulation) |
| `scroll` | `value` | Scroll page by pixel amount |
| `wait` | `wait_ms` | Wait for milliseconds |
| `wait_for_selector` | `selector`, `wait_ms` | Wait for element to appear |
| `navigate` | `value` | Navigate to a different URL |
## Authentication
Browserless API token is resolved automatically from **Settings > Connections > Browserless**.
## Notes
* Stateless mode: each tool call uses a fresh browser (no cleanup needed)
* CDP mode: browser persists across calls (close when done)
* Large DOM responses are truncated to 100KB
* Session-aware: tools automatically use CDP session when active, REST otherwise
* `browserless_scrape` always uses REST API (no CDP equivalent)
## See Also
* [Browserless integration guide](https://docs.everruns.com/integrations/browserless/), setup and configuration
* [Capabilities Overview](https://docs.everruns.com/capabilities/)
---
# Budgeting
> Expose active budgets to the agent so it can check the remaining balance and adjust its own spending.
Source:
| | |
| ---------------- | ------------------------- |
| **ID** | `budgeting` |
| **Category** | Cost Control |
| **Features** | `budgeting` |
| **Included in** | Generic harness (default) |
| **Dependencies** | None |
Makes an agent aware of its budget constraints. The agent receives budget information in its system prompt and can proactively check remaining balance before expensive operations.
## Tools
### `check_budget`
Query the budget status for the current session.
> **Note:** The current implementation returns a placeholder response indicating whether budgets are configured. Full budget data (balance, limit, status per budget) requires worker-side tool interception, which is planned for a future iteration. In the meantime, use the REST API (`GET /v1/sessions/{id}/budget-check`) for detailed budget status.
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ---------------------- |
| *(none)* | | | No parameters required |
## Behavior
When the `budgeting` capability is enabled:
1. **System prompt injection**: The agent’s system prompt includes a “Budget Awareness” section with the current budget status and guidelines for efficient output.
2. **Self-regulation**: When budget is running low, the agent prioritizes completing current tasks efficiently rather than exploring new directions or generating verbose output.
3. **Proactive checking**: The agent can call `check_budget` before starting expensive operations (large code generation, multi-step tool chains) to decide whether to proceed or ask the user.
## Related
* [Budgets](https://docs.everruns.com/advanced/budgets/), full budgeting system documentation (limits, currencies, API, CLI)
---
# Retrieval Citations
> Attach claim-level citations to an agent's answer from its knowledge retrieval results, so each grounded sentence links back to the source that supports it.
Source:
| | |
| ---------------- | -------------------- |
| **ID** | `citation_retrieval` |
| **Category** | Knowledge |
| **Features** | `citations` |
| **Dependencies** | None |
Turn the sources an agent retrieves into **claim-level provenance** on its answer. When the agent grounds a reply in a knowledge search, `citation_retrieval` links each grounded sentence to the passage that backs it, and the UI renders those links as inline numbered chips with a hover preview and a deduped **Sources** strip.
It contributes **no tools** and **no system prompt**, and it never rewrites the model’s answer. After the agent responds, it inspects the turn’s retrieval results, aligns each retrieved passage to the sentence it best supports, and attaches a citation there. Alignment is deterministic token overlap, no extra model call, so it is model- and provider-agnostic.
## How it looks
Grounded sentences get an inline numbered chip. Hovering a chip previews the source (title, snippet, and link); a deduped **Sources** strip sits below the message. When [Citation Verification](https://docs.everruns.com/capabilities/citation-verification/) is also enabled, each source carries a faithfulness badge.

## Feed
Reads the results of the agent’s knowledge retrieval tools:
| Tool | Source shape |
| ------------------ | ---------------------------------------------------------------------------------------- |
| `search_index` | Knowledge-index chunks (`kchk_…`), `source_uri`, `document_title`, `snippet`, `location` |
| `search_knowledge` | Knowledge-base entries (`kbe_…`), `resource`, `title`, `snippet` |
Both shapes are normalized into the shared citation envelope. A retrieval result with no usable snippet is skipped, there is nothing to align a claim to.
> **This capability is a no-op on its own.** It cites what the agent retrieves, so it only produces citations when the agent also has a retrieval capability (a knowledge index or knowledge base) that surfaces `search_index` / `search_knowledge` results. With no retrieval feed, there is nothing to cite and the answer is unchanged.
## Configuration
| Field | Type | Default | Description |
| ------------- | ------------ | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `min_overlap` | number (0–1) | `0.5` | Minimum token-overlap ratio for a retrieved passage to attach to a sentence. Overlap is `shared tokens / passage tokens`, so `0.5` means at least half the passage’s distinctive words appear in the sentence. |
Raise `min_overlap` for stricter, higher-precision attachment (fewer chips, each more defensible); lower it for broader coverage when the model paraphrases retrieved text.
## Notes
* **No answer rewrite**: the streamed answer is never changed; annotations attach to sentence spans after generation.
* **Alignment is lexical**: a sentence that paraphrases a source heavily enough to fall below `min_overlap` will not be cited. This favors precision over recall.
* **Enabled by default**: `citation_retrieval` is part of the generic (default) harness, so any agent with a retrieval feed gets citations automatically.
* **Persistence**: annotations ride the message in the event log, so they survive reload, forking, and session export, no separate store.
* **Org scoping**: sources are derived only from already-authorized retrieval results, so a citation can never reference a document the requesting org cannot read.
## See Also
* [Citation Verification](https://docs.everruns.com/capabilities/citation-verification/), stamp a faithfulness verdict on each citation
* [Capabilities Overview](https://docs.everruns.com/capabilities/)
---
# Citation Verification
> Verify that each cited source actually supports the claim it is attached to, stamping a faithfulness verdict on every citation produced by any feed.
Source:
| | |
| ---------------- | ----------------------- |
| **ID** | `citation_verification` |
| **Category** | Knowledge |
| **Features** | `citations` |
| **Guardrail** | Yes |
| **Dependencies** | None |
Check that each citation is **faithful**: that the source it points to actually supports the sentence it is attached to, and stamp a verdict on it. It is a guardrail capability, decoupled from the feeds: it consumes the citations collected during a turn (from [Retrieval Citations](https://docs.everruns.com/capabilities/citation-retrieval/) or any future feed) and verifies them uniformly, so any feed can be paired with any verifier.
It contributes **no tools** and **no system prompt**. The verdict renders in the UI as a badge on each source.
## Verdicts
Each citation gets one of three verdicts, shown as a badge on the chip preview and in the **Sources** strip:
| Verdict | Meaning |
| ------------- | ---------------------------------------------------------------------------------- |
| `entailed` | The source supports the claim. Rendered as a green **verified** badge. |
| `unsupported` | The source does not support the claim. Rendered as an amber **unsupported** badge. |
| `uncertain` | Support could not be established. Rendered as an **unverified** badge. |

## Modes
| Mode | Cost | Behavior |
| --------------------- | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `heuristic` (default) | Free | Deterministic lexical entailment, token overlap between the claim span and the source snippet. No model call. A weak but honest baseline, strongest on verbatim citations. |
| `llm` | One utility-model call per citation | A utility-model judgement per claim/source pair (claim = hypothesis, snippet = premise). More accurate; falls back to the heuristic when no utility model is configured. |
## Configuration
| Field | Type | Default | Description |
| ----------- | ------------------------ | ------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `mode` | `"heuristic"` \| `"llm"` | `"heuristic"` | Verification strategy (see above). |
| `threshold` | number (0–1) | `0.5` | Entailment threshold for the heuristic verdict, the fraction of the claim’s distinctive tokens the source must cover to be `entailed`. |
## Notes
* **Feed-agnostic**: verifies citations from any feed via the shared render contract, so evals can hold the feed fixed and vary only the verifier.
* **Enabled by default**: `citation_verification` is part of the generic (default) harness in `heuristic` mode, so citations are verified out of the box with no model cost.
* **`llm` mode needs a utility model**: with none configured it degrades gracefully to the heuristic rather than failing.
* **No new data egress**: the verifier reasons only over text the feed already retrieved.
## See Also
* [Retrieval Citations](https://docs.everruns.com/capabilities/citation-retrieval/), the feed that produces the citations this verifies
* [Capabilities Overview](https://docs.everruns.com/capabilities/)
---
# Claude Tool Search
> Deferred tool loading on supported Claude models. Tools are loaded on demand through Anthropic's hosted tool search.
Source:
| | |
| ---------------- | -------------------- |
| **ID** | `claude_tool_search` |
| **Category** | Optimization |
| **Features** | None |
| **Dependencies** | None |
Enables [Anthropic’s hosted tool search](https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool) for agents with many tools. Instead of sending full parameter schemas for every tool upfront, only tool names and descriptions reach the model initially. The model discovers and loads full schemas on demand by searching the catalog.
This reduces prompt token usage significantly for agents with 15+ tools, without changing how tools are called or how results are returned. It is the Claude counterpart to [OpenAI Tool Search](https://docs.everruns.com/capabilities/openai-tool-search/); for a model-adaptive default that picks the right mechanism automatically, use [Auto Tool Search](https://docs.everruns.com/capabilities/auto-tool-search/).
## Tools
None, this capability configures the LLM driver, it does not provide tools.
## How It Works
1. **Threshold check**: tool search only activates when the total tool count meets or exceeds the threshold (default: 15). Below the threshold, full schemas are sent as usual.
2. **Deferred schemas**: every deferrable tool gets `defer_loading: true`, so only its name and description reach the model upfront. Anthropic defers each tool individually (there is no namespace grouping).
3. **Hosted search tool**: a `tool_search_tool_bm25_20251119` server tool is added to the request. The model issues a natural-language query against the catalog (tool names, descriptions, argument names, and argument descriptions) and Anthropic returns the 3–5 most relevant tools, expanding them into full definitions inline.
4. **Transparent execution**: the model then calls a discovered tool with a normal `tool_use`; tool calls and results work identically. The only difference is how tools are presented to the model.
Because the hosted search tool is itself never deferred, Anthropic’s requirement that *at least one tool be non-deferred* is always satisfied, even when every function tool is deferrable.
### DeferrablePolicy
Each tool has a `deferrable` policy that controls whether its schema can be deferred:
| Policy | Behavior |
| ----------- | ------------------------------------------------------------------------- |
| `never` | Full schema always sent (use for high-frequency tools like `write_todos`) |
| `automatic` | Deferred when tool search is active and above threshold (default) |
| `always` | Always deferred when tool search is active, regardless of threshold |
Keeping the 3–5 most frequently used tools non-deferred (via `never`) avoids a search round-trip before the agent’s first hot-path call.
### Model Support
Tool search requires model-level support. Per Anthropic, it is available on:
| Model family | Supported |
| ----------------------------------------------------------- | ----------------------------------- |
| Opus 5.5 / 5 (`claude-opus-5-5`, `claude-opus-5`) | Yes |
| Opus 4.x (`claude-opus-4*`) | Yes |
| Sonnet 5 (`claude-sonnet-5`) | Yes |
| Sonnet 4.5 / 4.6 (`claude-sonnet-4-5`, `claude-sonnet-4-6`) | Yes |
| Haiku 4.5 (`claude-haiku-4-5`) | Yes |
| Fable 5.1 / 5 (`claude-fable-5-1`, `claude-fable-5`) | Yes |
| Retired pre-4 Claude models | No (capability is silently ignored) |
When the capability is enabled but the model doesn’t support tool search, the feature is **silently skipped, full tool schemas are sent as usual**. This standalone capability does *not* add a client-side fallback: on an unsupported model it simply does nothing (no error, no behavior change). For automatic fallback to client-side deferral, use [Auto Tool Search](https://docs.everruns.com/capabilities/auto-tool-search/) (or add the [Tool Search](https://docs.everruns.com/capabilities/tool-search/) capability explicitly).
Claude models reached through a non–first-party transport don’t get hosted tool search either, because those transports don’t implement the hosted format:
* **Amazon Bedrock**: this integration uses the ConverseStream API; Anthropic’s server-side tool search on Bedrock is only available via the InvokeModel API.
* **OpenRouter**: its stateless OpenAI-compatible endpoint accepts but does not implement Anthropic’s hosted tool search.
With `claude_tool_search` alone, those transports also send full schemas; pair with [Auto Tool Search](https://docs.everruns.com/capabilities/auto-tool-search/) to get client-side deferral there instead.
## Configuration
### Default (threshold: 15)
```json
{
"capabilities": ["claude_tool_search"]
}
```
### Custom threshold
```json
{
"capabilities": [
{
"capability_ref": "claude_tool_search",
"config": { "threshold": 10 }
}
]
}
```
Lower thresholds activate tool search with fewer tools. Set to `1` to always activate when the capability is present.
## Limitations
* **Claude-only**: this is an Anthropic Messages API feature; other providers (OpenAI, Gemini) ignore this capability. Use [Auto Tool Search](https://docs.everruns.com/capabilities/auto-tool-search/) for cross-provider agents.
* **Supported Claude models only**: pre-4 Claude models don’t support hosted tool search.
* **First-party transport only**: Claude models reached via Bedrock (ConverseStream) or OpenRouter don’t get hosted tool search; with this capability alone they send full schemas. Use [Auto Tool Search](https://docs.everruns.com/capabilities/auto-tool-search/) for client-side deferral there.
* **No standalone fallback**: on an unsupported model/transport this capability is a no-op (full schemas), not a switch to client-side deferral. Combine with `auto_tool_search`, or add `tool_search`, if you want a fallback.
## See Also
* [Anthropic Tool search tool documentation](https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool), official Anthropic guide
* [Auto Tool Search](https://docs.everruns.com/capabilities/auto-tool-search/), model-adaptive default (recommended for multi-provider harnesses)
* [OpenAI Tool Search](https://docs.everruns.com/capabilities/openai-tool-search/), the equivalent for OpenAI models
* [Tool Search](https://docs.everruns.com/capabilities/tool-search/), the provider-agnostic client-side fallback
* [Capabilities Overview](https://docs.everruns.com/capabilities/)
---
# Current Time
> Read the current date and time in a chosen format and timezone.
Source:
| | |
| ---------------- | -------------- |
| **ID** | `current_time` |
| **Category** | Core |
| **Features** | None |
| **Dependencies** | None |
Provides a tool to get the current date and time. Supports multiple formats and timezones.
## Tools
### `get_current_time`
Get the current date and time.
| Parameter | Type | Required | Description |
| ---------- | ------ | -------- | --------------------------------------------------------- |
| `timezone` | string | no | IANA timezone (e.g., `America/New_York`, `Europe/London`) |
| `format` | string | no | Output format: `iso8601`, `unix`, `human` |
## See Also
* [Schedules](https://docs.everruns.com/capabilities/session-schedules/), schedule future tasks
* [Capabilities Overview](https://docs.everruns.com/capabilities/)
---
# Daytona
> Run agent code in Daytona cloud sandboxes with command execution, file access, workspace downloads, and session-scoped lifecycle controls.
Source:
| | |
| ---------------- | ---------------------------------------------------------------------------- |
| **ID** | `daytona` |
| **Category** | Sandboxes |
| **Features** | None |
| **Dependencies** | [`session_storage`](https://docs.everruns.com/capabilities/session-storage/) |
Run code in cloud-based sandboxes powered by Daytona. Create multiple isolated Linux environments per session, execute commands, manage files, and download results.
## Tools
### `daytona_create_sandbox`
Create and start a new sandbox.
| Parameter | Type | Required | Description |
| -------------- | ------ | -------- | ------------------------------ |
| `title` | string | no | Sandbox name |
| `image` | string | no | Container image |
| `upload_files` | array | no | Files to upload after creation |
### `daytona_exec`
Run a shell command in a sandbox (synchronous).
| Parameter | Type | Required | Description |
| ------------ | ------- | -------- | ------------------------ |
| `sandbox_id` | string | yes | Target sandbox |
| `command` | string | yes | Shell command to execute |
| `cwd` | string | no | Working directory |
| `timeout_ms` | integer | no | Timeout in milliseconds |
### `daytona_read_file`
Read a file from a sandbox.
| Parameter | Type | Required | Description |
| ------------ | ------ | -------- | -------------- |
| `sandbox_id` | string | yes | Target sandbox |
| `path` | string | yes | File path |
### `daytona_write_file`
Write a file to a sandbox.
| Parameter | Type | Required | Description |
| ------------ | ------ | -------- | -------------- |
| `sandbox_id` | string | yes | Target sandbox |
| `path` | string | yes | File path |
| `content` | string | yes | File content |
### `daytona_download_workspace`
Download sandbox workspace to session storage.
| Parameter | Type | Required | Description |
| ------------ | ------ | -------- | -------------- |
| `sandbox_id` | string | yes | Target sandbox |
### `daytona_list_sandboxes`
List all sandboxes for the current session.
### `daytona_manage_sandbox`
Stop or delete a sandbox.
| Parameter | Type | Required | Description |
| ------------ | ------ | -------- | ------------------ |
| `sandbox_id` | string | yes | Target sandbox |
| `action` | string | yes | `stop` or `delete` |
### `daytona_git_clone`
Clone a git repository into a sandbox. Automatically uses connected GitHub credentials for private repos.
| Parameter | Type | Required | Description |
| ------------ | ------ | -------- | --------------------------------------- |
| `sandbox_id` | string | yes | Target sandbox |
| `url` | string | yes | Repository URL or `user/repo` shorthand |
| `branch` | string | no | Branch to clone |
### `daytona_git_credentials`
Configure git credentials for push/pull/fetch.
| Parameter | Type | Required | Description |
| ------------ | ------ | -------- | -------------- |
| `sandbox_id` | string | yes | Target sandbox |
## Authentication
Daytona API key is resolved automatically from **Settings > Connections > Daytona**.
## Notes
* Each sandbox is a full isolated Linux environment with network access
* Sandboxes auto-stop after 5 minutes of inactivity
* Always delete sandboxes when done to free resources
* All tools except `daytona_create_sandbox` and `daytona_list_sandboxes` require a `sandbox_id`
## See Also
* [Storage](https://docs.everruns.com/capabilities/session-storage/), API key and state persistence
* [Daytona integration guide](https://docs.everruns.com/integrations/daytona/), setup and configuration
* [Capabilities Overview](https://docs.everruns.com/capabilities/)
---
# Docker Container Sandbox
> Run agent commands and manage files in a Docker container tied to the session. Self-hosted alternative to cloud sandbox providers.
Source:
| | |
| ---------------- | ------------------ |
| **ID** | `docker_container` |
| **Category** | Sandboxes |
| **Features** | None |
| **Dependencies** | None |
Run commands and manage files in a Docker container tied to the session. The container is lazily started on first use and persists for the session duration. A self-hosted alternative to cloud sandbox providers like Daytona or E2B.
> **Experimental:** This capability may change significantly in future releases.
## Tools
### `docker_exec`
Execute a command inside the Docker container.
| Parameter | Type | Required | Description |
| ------------ | ------- | -------- | ----------------------- |
| `command` | string | yes | Shell command to run |
| `cwd` | string | no | Working directory |
| `timeout_ms` | integer | no | Timeout in milliseconds |
Returns stdout, stderr, and exit code. Container is started automatically if not already running.
### `docker_read_file`
Read a text file from the container filesystem.
| Parameter | Type | Required | Description |
| --------- | ------- | -------- | --------------------------------- |
| `path` | string | yes | File path |
| `offset` | integer | no | Line offset to start reading from |
| `limit` | integer | no | Maximum number of lines to return |
### `docker_write_file`
Write a text file into the container filesystem.
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ------------ |
| `path` | string | yes | File path |
| `content` | string | yes | File content |
### `docker_logs`
Retrieve recent logs from the Docker container.
| Parameter | Type | Required | Description |
| --------- | ------- | -------- | ----------------------------- |
| `tail` | integer | no | Number of log lines to return |
### `docker_stop`
Stop the Docker container for this session.
## Configuration
Configure the Docker container via the capability settings:
| Setting | Description |
| ------- | ----------------------------------------- |
| `image` | Docker image to use (e.g. `ubuntu:24.04`) |
| `env` | Environment variables to inject |
| `binds` | Host path mounts |
## Notes
* Only one container runs per session
* The container is stopped when the session ends or `docker_stop` is called
* Docker Engine must be accessible from the server running Everruns
## See Also
* [Container Sandbox integration guide](https://docs.everruns.com/integrations/container-sandbox/), setup and configuration
* [Capabilities Overview](https://docs.everruns.com/capabilities/)
---
# E2B Sandboxes
> Run agent code in isolated E2B cloud sandboxes with command execution, file access, and session-scoped lifecycle management.
Source:
| | |
| ---------------- | ---------------------------------------------------------------------------- |
| **ID** | `e2b` |
| **Category** | Sandboxes |
| **Features** | None |
| **Dependencies** | [`session_storage`](https://docs.everruns.com/capabilities/session-storage/) |
Run code in cloud sandboxes powered by E2B. Create isolated Linux environments, execute commands, and manage sandbox files. Sandboxes are scoped to the session and cleaned up automatically.
## Tools
### `e2b_create_sandbox`
Create a new E2B sandbox.
| Parameter | Type | Required | Description |
| ------------ | ------- | -------- | --------------------------- |
| `template` | string | no | Sandbox template name or ID |
| `timeout_ms` | integer | no | Timeout in milliseconds |
Returns `sandbox_id` and connection details.
### `e2b_exec`
Execute a shell command in a sandbox.
| Parameter | Type | Required | Description |
| ------------ | ------ | -------- | -------------------- |
| `sandbox_id` | string | yes | Target sandbox |
| `command` | string | yes | Shell command to run |
| `cwd` | string | no | Working directory |
### `e2b_read_file`
Read a text file from a sandbox filesystem.
| Parameter | Type | Required | Description |
| ------------ | ------- | -------- | --------------------------------- |
| `sandbox_id` | string | yes | Target sandbox |
| `path` | string | yes | File path |
| `offset` | integer | no | Line offset to start reading from |
| `limit` | integer | no | Maximum number of lines to return |
### `e2b_write_file`
Write a text file into a sandbox filesystem.
| Parameter | Type | Required | Description |
| ------------ | ------ | -------- | -------------- |
| `sandbox_id` | string | yes | Target sandbox |
| `path` | string | yes | File path |
| `content` | string | yes | File content |
### `e2b_list_sandboxes`
List all E2B sandboxes created in the current session.
### `e2b_manage_sandbox`
Pause or kill an E2B sandbox.
| Parameter | Type | Required | Description |
| ------------ | ------ | -------- | ----------------- |
| `sandbox_id` | string | yes | Target sandbox |
| `action` | string | yes | `pause` or `kill` |
## Authentication
E2B API key is resolved automatically from **Settings > Connections > E2B**.
## Notes
* Each sandbox is an isolated Linux environment with internet access
* Sandboxes are tracked per session; use `e2b_manage_sandbox` to kill when done
* File operations target the sandbox filesystem, not the session workspace
## See Also
* [Storage](https://docs.everruns.com/capabilities/session-storage/), API key and state persistence
* [Capabilities Overview](https://docs.everruns.com/capabilities/)
---
# Fake AWS
> Simulated AWS tools for EC2, RDS, S3, IAM, security groups, and CloudWatch, for demoing cloud-operations agents without real infrastructure.
Source:
| | |
| ---------------- | ---------- |
| **ID** | `fake_aws` |
| **Category** | Demo |
| **Features** | None |
| **Dependencies** | None |
Simulated AWS infrastructure management tools for testing and demonstrations. Covers EC2, RDS, S3, IAM, security groups, and CloudWatch. State is persisted in the session filesystem under `/aws/`. Simulates realistic API latency (configurable via `FAKE_AWS_LATENCY_MS`).
## Tools
| Tool | Description |
| ---------------------------- | ------------------------- |
| `aws_list_ec2_instances` | List EC2 instances |
| `aws_create_ec2_instance` | Launch a new EC2 instance |
| `aws_stop_ec2_instance` | Stop an EC2 instance |
| `aws_list_rds_databases` | List RDS databases |
| `aws_create_rds_database` | Create an RDS database |
| `aws_list_s3_buckets` | List S3 buckets |
| `aws_create_s3_bucket` | Create an S3 bucket |
| `aws_list_iam_users` | List IAM users |
| `aws_create_iam_user` | Create an IAM user |
| `aws_list_security_groups` | List security groups |
| `aws_get_cloudwatch_metrics` | Get CloudWatch metrics |
## See Also
* [Fake Warehouse](https://docs.everruns.com/capabilities/fake-warehouse/), simulated warehouse operations
* [Fake CRM](https://docs.everruns.com/capabilities/fake-crm/), simulated customer management
* [Capabilities Overview](https://docs.everruns.com/capabilities/)
---
# Fake CRM
> Simulated CRM tools for mock customers, tickets, interactions, and search, with demo data persisted per session.
Source:
| | |
| ---------------- | ---------- |
| **ID** | `fake_crm` |
| **Category** | Demo |
| **Features** | None |
| **Dependencies** | None |
Simulated CRM and customer support tools for testing and demonstrations. Manage customers, support tickets, and interaction history. State is persisted in the session filesystem.
## Tools
| Tool | Description |
| ---------------------- | ------------------------------- |
| `crm_list_customers` | List customers with pagination |
| `crm_get_customer` | Get customer details by ID |
| `crm_create_customer` | Create a new customer |
| `crm_list_tickets` | List support tickets |
| `crm_create_ticket` | Create a support ticket |
| `crm_update_ticket` | Update ticket status |
| `crm_add_interaction` | Add a customer interaction note |
| `crm_search_customers` | Search customers by criteria |
## See Also
* [Fake Warehouse](https://docs.everruns.com/capabilities/fake-warehouse/), simulated warehouse operations
* [Fake AWS](https://docs.everruns.com/capabilities/fake-aws/), simulated cloud infrastructure
* [Capabilities Overview](https://docs.everruns.com/capabilities/)
---
# Fake Warehouse
> Simulated warehouse management tools with mock inventory, orders, and shipping operations.
Source:
| | |
| ---------------- | ---------------- |
| **ID** | `fake_warehouse` |
| **Category** | Demo |
| **Features** | None |
| **Dependencies** | None |
Simulated warehouse management tools for testing and demonstrations. Provides inventory tracking, shipment management, order processing, invoicing, and returns. State is persisted in the session filesystem.
## Tools
| Tool | Description |
| ---------------------------------- | ---------------------------- |
| `warehouse_get_inventory` | Get current inventory levels |
| `warehouse_update_inventory` | Update item quantities |
| `warehouse_create_shipment` | Create a new shipment |
| `warehouse_list_shipments` | List all shipments |
| `warehouse_update_shipment_status` | Update shipment status |
| `warehouse_create_order` | Create a new order |
| `warehouse_list_orders` | List all orders |
| `warehouse_create_invoice` | Generate an invoice |
| `warehouse_process_return` | Process a return |
| `warehouse_inventory_report` | Generate inventory report |
## See Also
* [Fake AWS](https://docs.everruns.com/capabilities/fake-aws/), simulated cloud infrastructure
* [Fake CRM](https://docs.everruns.com/capabilities/fake-crm/), simulated customer management
* [Capabilities Overview](https://docs.everruns.com/capabilities/)
---
# File System
> Read, write, search, and manage files in an isolated per-session workspace, with glob, grep, and directory operations.
Source:
| | |
| ---------------- | ------------------------------------- |
| **ID** | `session_file_system` |
| **Category** | File Operations |
| **Features** | `file_system` (enables Workspace tab) |
| **Dependencies** | None |
Provides tools to access and manipulate files in the session workspace. Each session has an isolated filesystem rooted at `/workspace`. Files persist for the session duration. `read_file` and `write_file` return a `content_hash` (`sha256:...`) so agents can make freshness-checked `edit_file` calls.
## Tools
### `read_file`
Read the contents of a file. Successful responses include `content_hash`.
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ---------------------------------------------- |
| `path` | string | yes | Absolute path (e.g., `/workspace/src/main.py`) |
### `write_file`
Create or overwrite a file. Parent directories are created automatically. Successful responses include `content_hash`.
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ------------- |
| `path` | string | yes | Absolute path |
| `content` | string | yes | File content |
### `edit_file`
Apply one or more exact text replacements to an existing text file. This tool is text-only, requires the current `content_hash` from `read_file` or `write_file`, and uses compare-and-set semantics so concurrent writes fail cleanly instead of clobbering newer content.
| Parameter | Type | Required | Description |
| --------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `path` | string | yes | Absolute path to an existing text file |
| `expected_hash` | string | yes | Current `content_hash` (`sha256:...`) |
| `edits` | array | yes | One or more `{ old_text, new_text }` replacements matched against the original file. Use a single-element array for one replacement. |
Legacy top-level `old_text`/`new_text` are still accepted for backward compatibility, they are folded into `edits[]`, but new callers should always use `edits[]`.
### `list_directory`
List files and directories at a given path.
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | -------------- |
| `path` | string | yes | Directory path |
### `grep_files`
Search file contents with regex patterns.
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ------------------------------------------- |
| `pattern` | string | yes | Regex pattern |
| `path` | string | no | Directory to search (default: `/workspace`) |
### `delete_file`
Delete a file or directory.
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | -------------- |
| `path` | string | yes | Path to delete |
### `stat_file`
Get file metadata (size, type, timestamps).
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ------------ |
| `path` | string | yes | Path to stat |
## `edit_file` request example
```json
{
"path": "/workspace/app.py",
"expected_hash": "sha256:1c4d...",
"edits": [
{
"old_text": "return 'Hello, World!'",
"new_text": "return 'Hello from Everruns!'"
}
]
}
```
## Notes
* All paths must be under `/workspace`
* Files are session-scoped, no cross-session access
* Parent directories are auto-created on write
* `edit_file` only works on text files and rejects binary/base64 content
* `edit_file` applies all replacements against the original file content and rejects ambiguous or overlapping matches
* `edit_file` preserves the file’s existing BOM and newline style (`LF`, `CRLF`, or `CR`)
* `edit_file` returns a unified diff capped to a bounded size; oversized diffs are truncated and marked as such
* Shared filesystem with [Bashkit Shell](https://docs.everruns.com/capabilities/bashkit-shell/) (same `/workspace`)
## See Also
* [Bashkit Shell](https://docs.everruns.com/capabilities/bashkit-shell/), execute commands against these files
* [Storage](https://docs.everruns.com/capabilities/session-storage/), key/value and secret storage
* [Capabilities Overview](https://docs.everruns.com/capabilities/)
---
# GitHub Scout
> Blueprint-only GitHub repository exploration capability that spawns read-only scout subagents.
Source:
| | |
| ---------------- | ----------------------------------------------------------------- |
| **ID** | `github_scout` |
| **Category** | Integrations |
| **Features** | None |
| **Dependencies** | [`subagents`](https://docs.everruns.com/capabilities/sub-agents/) |
GitHub Scout lets an agent spawn a specialist subagent for read-only GitHub repository exploration. The host agent receives `spawn_agent` with `target.type: "subagent"` through the dependency on `subagents`; monitoring and steering are handled by the generic `session_tasks` tools (`list_tasks`, `get_task`, `message_task`, `cancel_task`). The GitHub API tools stay private inside the spawned `github_scout` blueprint session.
## How to Use
Enable the `github_scout` capability on an agent or harness. Then spawn the blueprint:
```json
{
"name": "spawn_agent",
"arguments": {
"name": "Scout",
"instructions": "Find where authentication middleware is implemented.",
"target": { "type": "subagent" },
"blueprint": "github_scout",
"config": {
"repos": ["fastify/fastify"]
}
}
}
```
The optional `repos` config scopes GitHub searches to `owner/repo` repositories. Config is validated against the blueprint’s schema before the child session is created: entries that are not `owner/repo` and unrecognized config keys are rejected with a schema error instead of being silently ignored.
## Private Blueprint Tools
These tools are available only inside the GitHub Scout child session:
| Tool | Description |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `search_github_code` | Search code with GitHub code search qualifiers such as `repo:`, `path:`, `language:`, `symbol:`, and `filename:` |
| `read_github_file` | Read a UTF-8 file from a repository by `repo`, `path`, and optional `ref` |
| `search_github_issues` | Search issues and pull requests with qualifiers such as `repo:`, `is:issue`, `is:pr`, `state:`, `author:`, and `label:` |
## Authentication
GitHub Scout uses the existing GitHub user connection. In local and compatibility flows, tools can also use a `GITHUB_TOKEN` session secret. Missing credentials return a connection prompt instead of asking for tokens in chat.
## Included Examples
The adoptable **Coding (Daytona)** and **Coding (Container)** harness examples include `github_scout`, so coding agents based on those examples can delegate GitHub repository lookup work to Scout.
## See Also
* [Sub Agents](https://docs.everruns.com/capabilities/sub-agents/), lifecycle tools used to spawn and manage Scout
* [Author an agent blueprint](https://docs.everruns.com/advanced/agent-blueprints/), how Scout and blueprints like it are built
* [Capabilities Overview](https://docs.everruns.com/capabilities/), full capability catalog
---
# Guardrails
> Config-driven checks that constrain agent behavior, inspecting model output and tool activity, then blocking or logging when content matches a rule.
Source:
# Guardrails
| | |
| ---------------- | ------------------------------------------ |
| **ID** | `guardrails` |
| **Category** | Safety |
| **Tools** | None |
| **Dependencies** | Utility LLM (only for model-backed checks) |
| **Risk** | Low |
Guardrails are checks that constrain what an agent does. Where most [capabilities](https://docs.everruns.com/capabilities/) *grant* an ability, a guardrail *restricts* one: it inspects model output and tool activity, then **blocks** or **logs** when content matches a rule.
Guardrails are opt-in. An agent with no guardrails is a fully supported configuration, there is no org-mandated enforcement layer. A [harness](https://docs.everruns.com/features/harnesses/) can attach guardrail capabilities as soft defaults that flow to every agent built on it, and an author can still remove them. This is the platform stance: guardrails are a default posture, not a cage.
The `guardrails` capability holds no rules of its own. Its per-agent config is a declarative list of checks plus a mode; the capability compiles that config and contributes the matching runtime hooks. An empty config (or the capability being absent) contributes nothing, with zero added latency.
## Concepts
A **check** binds a **rule** to a **stage** with an **on-fail action**.
### Stages
| Stage | What it sees |
| ------------- | ---------------------------------------------------------------------- |
| `output` | Streamed assistant text |
| `tool_use` | A tool call before it executes, the tool name and serialized arguments |
| `tool_output` | A tool result before it enters model context |
`tool_output` is the trust boundary for untrusted external content (web pages, MCP responses); indirect-injection and secret-leakage checks belong there.
### Rules
| Rule (`type`) | What it matches | Valid stages | Execution |
| -------------- | -------------------------------------------------------------------- | ------------------------- | ------------------- |
| `regex` | Any of the patterns matches the stage text | all | in-process, sync |
| `blocklist` | Any word/phrase appears as a substring (case-insensitive by default) | all | in-process, sync |
| `tool_pattern` | The tool name matches a `*`-wildcard glob | `tool_use` only | in-process, sync |
| `llm_judge` | A natural-language policy, evaluated by a system model | `tool_use`, `tool_output` | async, model-backed |
| `mcp` | Decision delegated to an external guardrail served over scoped MCP | `tool_use`, `tool_output` | async, off-platform |
| `moderation` | The finalized message scored against content categories | `output` only | async, model-backed |
Deterministic rules (`regex`, `blocklist`, `tool_pattern`) run in the streaming and per-tool-call hot path, linear-time, no I/O, with hard limits on check count, entries, and lengths so an authored pattern can never wedge a worker. Model-backed and MCP rules run only in the async hook path (and, for `output`, on a post-generation end-of-message boundary), never on the sync hot path.
### Engines
The two model-backed types, `llm_judge` and `moderation`, choose which system model answers them with `engine`:
* **`utility_llm`** (the default) prompts your org’s utility model for a verdict — `allow`/`block` for a judge, 0-100 scores per category for moderation. One request per check.
* **`jev`** asks [Jev](https://docs.everruns.com/integrations/typesafe/), TypeSafe’s System One model, a typed question and gets a calibrated probability back. The `threshold` you configure (a percentage, default 50) decides the verdict, and every jev check on a stage is answered in a **single** request. It needs `UTILITY_TYPESAFE_API_KEY` on the deployment.
```json
{
"stage": "tool_use",
"type": "llm_judge",
"engine": "jev",
"threshold": 70,
"prompt": "Block any tool call that deletes customer records."
}
```
Two reasons to prefer `jev` once your deployment has a key configured. It is cheaper on latency: four judge checks on a tool call cost one round trip instead of four. And the verdict is yours — the model reports how likely a violation is, your threshold decides what to do about it, and there is no written verdict to misparse. For moderation it also reads the *tail* of the distribution rather than a score: content that is probably fine but 30% likely to be a clear violation trips a 30% threshold, where an averaged score would hide it.
`utility_llm` stays the default, so existing configs are unchanged. Both engines fail open, honor `on_fail` and advisory mode identically, and send the same bounded excerpt. A check set to `jev` in a deployment with no decisions configured is skipped with a warning.
### On-fail
* `block`, suppresses the matched content: an `output`/`tool_output` block replaces the content with a notice; a `tool_use` block refuses the call and feeds the reason back to the model, which can self-correct. The model’s original tokens are never persisted.
* `log`, records the hit and continues.
An optional per-check `replacement` customizes the block notice or user-facing refusal message.
### Mode: active vs. advisory
A config-level `mode` is `active` (default) or `advisory`. **Advisory downgrades every hit to `log`**: checks run and are recorded, but nothing is blocked. Advisory is how you tune a guardrail against false positives before enforcing it. Mode is per attachment, so the same catalog entry can be advisory on one agent and active on another.
## Config shape
Config is a `GuardrailsConfig` stored under the `guardrails` capability in the agent’s config. Field names are `snake_case`; each check names its rule with a `type` tag alongside the shared `stage` / `on_fail` / `replacement` fields:
```json
{
"mode": "active",
"checks": [
{
"id": "no-secrets-in-output",
"stage": "output",
"on_fail": "block",
"replacement": "[Response withheld: appears to contain a credential.]",
"type": "regex",
"patterns": ["AKIA[0-9A-Z]{16}", "ghp_[A-Za-z0-9]{36}"]
},
{
"id": "no-shell",
"stage": "tool_use",
"on_fail": "block",
"type": "tool_pattern",
"tools": ["bash*", "*exec*"]
}
]
}
```
The `id` is optional but recommended, it is surfaced in reason codes and logs.
## Data egress and failure behavior
* **Deterministic checks** (`regex`, `blocklist`, `tool_pattern`) run entirely in-process; no data leaves the platform.
* **`llm_judge` and `moderation`** send a bounded content excerpt to a system model: with `engine: "utility_llm"`, your org’s *own* configured utility LLM, the same provider the agent already uses; with `engine: "jev"`, the deployment’s decisions. Either way it is an operator-configured destination, not a per-agent one.
* **`mcp`** sends a bounded content excerpt to an external, operator-configured MCP guardrail endpoint. Tenant scoping is enforced by the host’s per-session scoped-MCP resolver, so a config can only reach servers scoped to its own session/org.
Every async check is bounded (10 s timeout; at most 4 utility-LLM calls per invocation, and one batched request for the decisions) and **fails open**: a timeout, error, or unparseable verdict defaults to `allow`. A guardrail outage, or a hostile MCP endpoint, can only ever *allow*, never make execution more permissive than the no-guardrail baseline in a way that blocks a healthy turn. Model-backed checks flow through utility-LLM accounting, not the session model budget.
## Tuning: dry-run and advisory
Two surfaces let you tune checks before enforcing them:
* **`POST /v1/capabilities/guardrails/dry-run`** evaluates a config against sample text for a given stage, with no session and nothing persisted. It returns the triggered checks (id, rule type, effective action, reason code, matched excerpt) and whether the content would be blocked. It runs only deterministic checks, it never makes a network call, so it is the fast false-positive tuning loop for `regex`/`blocklist`/`tool_pattern`.
* **Advisory mode** runs the full set (including model-backed checks) against real traffic in `log`-only form, so you can review what *would* have been blocked before switching to `active`.
## The gallery: ready-made presets
Rather than authoring checks from scratch, list the **guardrail gallery**: a read-only catalogue of adoptable presets:
```plaintext
GET /v1/capabilities/guardrails/examples
```
Each listing carries a full `config` plus trust metadata so a picker can show what a preset does before adoption:
| Field | Meaning |
| ------------- | ------------------------------------------------------------------------------------------- |
| `check_types` | The rule-type composition (e.g. `["regex"]`, `["llm_judge"]`) |
| `stages` | Which stages the preset’s checks run in |
| `data_egress` | `none` for deterministic presets; `utility_llm` when a preset contains a model-backed check |
`data_egress` is **derived from the check types**, not hand-authored, so it stays correct as presets mix deterministic and model-backed checks. Adoption is client-side config composition: drop a preset’s `config` into the agent’s `guardrails` capability config (merging or replacing checks). There is no new persisted resource and no import endpoint. Noisy presets (PII, prompt-injection heuristics) ship `log`-only so they are safe to adopt active and tune before switching individual checks to `block`.
Shipped presets include secret detection, a model-backed secret-leak judge, PII detection, a profanity starter, dangerous-shell blocking, shell-access blocking, and prompt-injection heuristics.
### Worked example: deterministic vs. model-backed secret guardrails
Two presets guard the same risk, a secret reaching output or leaving through a tool, by complementary means.
**`secret-detection`** matches known credential *formats* by pattern. It is in-process and reports no egress:
```json
{
"name": "secret-detection",
"check_types": ["regex"],
"stages": ["output", "tool_output"],
"data_egress": "none"
}
```
It blocks well-known formats (AWS, GitHub, Slack, Google keys, PEM private keys) in model output and in tool results before they reach context. High-precision, safe to run active.
**`secret-leak-judge`** catches secrets by *intent*, including an opaque value whose form is unknown at config time (e.g. one freshly read from a secrets manager), which a regex structurally cannot see:
```json
{
"name": "secret-leak-judge",
"check_types": ["llm_judge"],
"stages": ["tool_use", "tool_output"],
"data_egress": "utility_llm"
}
```
Its `llm_judge` policy blocks a tool call (or result) that would print, echo, log, or transmit secret material in cleartext, while allowing comparisons that reveal only a hash, fingerprint, or redacted form. Because it sits on `tool_use` with `on_fail: block`, a blocked call is **recoverable**: the refusal reason is fed back and the model self-corrects to a safe form (comparing a hash instead of printing the secret). It is model-backed, so `data_egress` is `utility_llm`, adopters are correctly warned that a bounded excerpt leaves the generating path for evaluation. Run it advisory first to tune false positives.
The two are meant to be layered: pattern-matching for known formats, the judge for everything else.
## Reason codes
Every block or log carries a stable `guardrail.` code (e.g. `guardrail.regex`, `guardrail.llm_judge`, `guardrail.moderation`). Clients localize copy from the code rather than the human-readable text. (The separate [Prompt Canary Guardrail](https://docs.everruns.com/capabilities/prompt-canary-guardrail/) capability uses its own `system_prompt_leak` code.)
## Endpoints
| Method | Path | Description |
| ------ | -------------------------------------- | -------------------------------------------------------- |
| `GET` | `/v1/capabilities/guardrails/examples` | List adoptable gallery presets with trust metadata |
| `POST` | `/v1/capabilities/guardrails/dry-run` | Evaluate a config against sample text; nothing persisted |
Both are gated by the same `capability.view` policy as other capability reads.
## Related
* [Prompt Canary Guardrail](https://docs.everruns.com/capabilities/prompt-canary-guardrail/), a narrow streaming-output guardrail for naive system-prompt leakage.
* [Agent Checks](https://docs.everruns.com/features/agent-checks/), advisory, config-*time* review of an agent’s setup. Distinct from guardrails, which enforce at *runtime*.
---
# Host Shell
> Run bash commands on the machine hosting the agent, bounded by a kernel policy (Landlock and seccomp on Linux, Seatbelt on macOS).
Source:
| | |
| ---------------- | -------------------------------------------------------------------------------------------------------- |
| **ID** | `host_shell` |
| **Category** | Execution |
| **Risk** | High |
| **Features** | `file_system` (enables the Workspace tab) |
| **Dependencies** | [`session_file_system`](https://docs.everruns.com/capabilities/file-system/), backed by a real directory |
Run bash commands as real child processes on the machine the agent is running on. Unlike [Bashkit Shell](https://docs.everruns.com/capabilities/bashkit-shell/), the toolchain is real: compilers, package managers and test runners work. A kernel policy bounds what those processes may write and whether they may reach the network.
## When to use this instead of Bashkit
Both capabilities contribute a tool named `bash` over the session workspace, so enable one or the other, not both.
| | Bashkit Shell | Host Shell |
| ------------------ | ------------------------------------ | ------------------------------------ |
| Where commands run | In-process interpreter | This machine, as child processes |
| Native binaries | No | Yes |
| Workspace | Session filesystem (virtual or real) | Must be a real directory |
| Boundary | The interpreter, by construction | Landlock + seccomp, or Seatbelt |
| Network | Off, or egress-routed HTTP | Denied unless containment is removed |
Given a virtual session filesystem, `host_shell` refuses and says so rather than inventing a host path.
## Tools
### `bash`
Execute a shell command or a multi-line script.
| Parameter | Type | Required | Description |
| --------------------- | ------ | -------- | ------------------------------------------------------- |
| `command` | string | yes | Shell command(s) to execute |
| `working_dir` | string | no | Directory to run in (default: the workspace root) |
| `sandbox_permissions` | string | no | `use_default` or `require_escalated` |
| `justification` | string | no | User-facing reason for `require_escalated` |
| `output` | string | no | Output verbosity (`auto`, `normal`, …; default: `auto`) |
`commands` is accepted as an alias for `command`, so an agent written against Bashkit Shell keeps working when the backend is swapped.
Returns `stdout`, `stderr`, `exit_code`, `success`, and the `containment` that was in force. A failure that the containment would explain is flagged with `containment_denial: "likely"`. Output streams live to the UI and CLI while the command runs, and long scripts can run detached.
Every call is a fresh non-interactive `bash -lc` (PowerShell on Windows) rooted at the workspace, so no working directory, variable or export survives between calls.
## Containment
Configured per agent, never by the model.
| Mode | Reads | Writes | Network |
| --------------------------- | ---------- | ------------------------------------------------- | ------- |
| `read-only` | the host | private temp only | denied |
| `workspace-write` (default) | the host | workspace, `/tmp`, private temp, configured roots | denied |
| `danger-full-access` | everything | everything | allowed |
Host **reads** are allowed in every contained mode, for toolchain compatibility. The policy stops writes and network exfiltration; it does not stop a command reading unrelated files on the machine. That is the threat model, stated rather than implied.
The environment a command inherits is an allowlist: `PATH`, locale, and toolchain variables survive; `HOME` and `TMPDIR` are replaced with a private per-process directory, and everything else, including every API key and agent socket path, is dropped.
Two platform differences are deliberate:
* `.git` below the workspace is read-only on macOS. Landlock path rules are additive and cannot subtract it, so Linux permits Git metadata writes inside the workspace.
* Windows has no containment implementation. Every mode there runs uncontained, and the capability says so.
On macOS and Linux, containment fails closed: if the OS primitive is unavailable, the command returns a setup error and is not retried on the host.
## Configuration
```json
{
"containment": "workspace-write",
"approval": "never",
"writable_roots": ["/var/cache/agent"],
"foreground_timeout_secs": 120,
"background_timeout_secs": 86400,
"max_output_bytes": 1048576
}
```
An unknown `containment` or `approval` name is rejected rather than defaulted, so a misspelled boundary cannot resolve to a wider one.
`writable_roots` adds directories a build needs to write beyond the workspace, a package cache for example. It is ignored at `read-only`.
## Approvals
| Policy | When a person is asked |
| ----------------- | --------------------------------------------------------------------------------- |
| `never` (default) | never; a request to escalate is refused |
| `on-failure` | when a command fails in a way the containment would explain |
| `on-request` | when the model sets `sandbox_permissions: require_escalated` with a justification |
| `untrusted` | for anything outside a small read-only command set |
Every policy except `never` needs the host to supply an approval gate. Without one, a policy that would ask refuses instead: an unattended worker has nobody to ask, and a refusal is more honest than a silent escalation.
Regardless of policy, a command that visibly signals the agent’s own process is refused before it is spawned.
## Deployment
This is an embedder capability, not a hosted-product one. It ships in `everruns-host` behind the `host-shell` feature (also reachable as `host-shell` on the `everruns` facade) and is deliberately absent from the hosted catalog: handing agents arbitrary host processes is something a CLI host, a CI runner, or an operator’s own box opts into, not something a shared multi-tenant worker should offer.
On Linux the kernel policy is applied by a helper process, selected with the `launcher` config key:
| `launcher` | Meaning |
| ---------------------------- | -------------------------------------------------------------- |
| `"discover"` (default) | find `everruns-sandbox-exec` beside the binary, then on `PATH` |
| `{"helper": ""}` | run that binary |
| `{"reexec_self": [""]}` | re-exec this binary with those leading arguments |
`everruns-host` ships `everruns-sandbox-exec` under the same feature, but cargo does not build a dependency’s binaries, so a single-binary host will not find one beside it. Such a host routes the arguments into `everruns_host::containment::worker::run_from_args` from its own `main` and selects `reexec_self`. See `examples/host-shell-agent`.
## See Also
* [Bashkit Shell](https://docs.everruns.com/capabilities/bashkit-shell/), the sandboxed interpreter
* [File System](https://docs.everruns.com/capabilities/file-system/), file operations on the same workspace
* [Daytona](https://docs.everruns.com/capabilities/daytona/) and [E2B](https://docs.everruns.com/capabilities/e2b/), real binaries on someone else’s machine
* [Capabilities Overview](https://docs.everruns.com/capabilities/)
---
# Infinity Context
> Trim live prompt history and query older messages on demand, so conversation length is not bounded by the context window.
Source:
| | |
| ---------------- | ------------------ |
| **ID** | `infinity_context` |
| **Category** | Optimization |
| **Features** | None |
| **Dependencies** | None |
Limits the live prompt to recent conversation history while keeping older messages accessible through `query_history`.
This is useful for long-running sessions where the agent should stay responsive without losing access to earlier decisions, identifiers, or requirements.
## Tools
| Tool | Purpose |
| --------------- | ------------------------------------------------------------ |
| `query_history` | Search or retrieve earlier messages from the current session |
## How It Works
1. A message filter caps the number of recent messages sent to the model.
2. If older messages are excluded, the model sees a system notice telling it to use `query_history`.
3. The `query_history` tool can keyword-search history or fetch a specific absolute message range.
## Configuration
Default configuration:
```json
{
"capabilities": ["infinity_context"]
}
```
Custom budget:
```json
{
"capabilities": [
{
"ref": "infinity_context",
"config": {
"context_budget_tokens": 80000,
"min_recent_messages": 12
}
}
]
}
```
| Field | Type | Default | Description |
| ----------------------- | ------- | -------- | ------------------------------------------------------------- |
| `context_budget_tokens` | integer | `100000` | Approximate token budget reserved for message history |
| `min_recent_messages` | integer | `10` | Minimum recent messages to keep even when the budget is tight |
## Limitations
* Search is keyword-based, not semantic
* The tool reads full session history; it does not currently restrict itself to only the trimmed portion
* Budgeting uses a heuristic message-count estimate, not model-specific tokenization
## See Also
* [Context Compaction](https://docs.everruns.com/advanced/compaction/), Complementary capability that reduces the size of messages in the prompt; see [Generic Harness Defaults](https://docs.everruns.com/advanced/compaction/#generic-harness-defaults) for how they work together
* [Capabilities Overview](https://docs.everruns.com/capabilities/)
* [Harnesses](https://docs.everruns.com/features/harnesses/)
---
# Message Metadata
> Annotate user and agent messages with metadata such as their timestamp when they are sent to the LLM, so agents can reason about timing and gaps between messages.
Source:
| | |
| ---------------- | ------------------ |
| **ID** | `message_metadata` |
| **Category** | Core |
| **Features** | None |
| **Dependencies** | None |
Annotates user and agent messages with metadata, currently each message’s timestamp (UTC), when building the LLM request. The model sees each message prefixed with an annotation like:
```plaintext
[time 2026-06-11T09:15:42Z] What changed since yesterday?
```
For user messages the timestamp is when the message was received; for agent messages, when the reply was generated.
This lets agents reason about timing: how long ago something was said, gaps between messages, and whether earlier statements are stale.
Enabled by default on the Generic (default) harness.
Annotations are applied only to the prompt-facing view of the conversation. Stored messages are never modified, and timestamps are stable across turns so prompt caching is unaffected. A short system prompt addition explains the annotation format to the model and instructs it not to emit annotations in its replies.
## Tools
None.
## Configuration
| Field | Type | Default | Description |
| -------- | ----- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `fields` | array | `["timestamp"]` | Metadata fields to render, in order. Supported: `timestamp`. An empty array disables annotations. More fields (e.g. the LLM model) will be added over time. |
User and agent messages are always annotated; system and tool-result messages never are.
## See Also
* [Current Time](https://docs.everruns.com/capabilities/current-time/), tool to get the current wall-clock time
* [Capabilities Overview](https://docs.everruns.com/capabilities/)
---
# OpenAI Image Generation
> Generate and edit raster images with OpenAI's GPT Image API, persist artifacts, and save outputs into the session workspace.
Source:
| | |
| ---------------- | ---------------------------------------------------------------------------- |
| **ID** | `gpt_image_gen` |
| **Category** | Media |
| **Features** | None |
| **Dependencies** | [`session_file_system`](https://docs.everruns.com/capabilities/file-system/) |
Generate new raster images and edit existing ones with OpenAI’s ChatGPT Images 2.0 API model, `gpt-image-2`, by default. The capability also supports Meta’s Muse image model (`muse-image-1.0`) through Meta or OpenRouter providers.
Capability config supports both model selection and a default quality used when the tool call does not specify one:
```json
{
"model": "gpt-image-2",
"default_quality": "medium",
"partial_images": 1,
"fallback": "auto"
}
```
If you need the previous generation model for compatibility, set `"model": "gpt-image-1"`. To use the Muse image model instead, set `"model": "muse-image-1.0"` and configure a Meta provider (served as `muse-image-1.0`) or an OpenRouter provider (served as `meta/muse-image`).
When no OpenAI or Azure OpenAI credentials are configured but a Meta or OpenRouter provider is available, the capability falls back to the Muse image model if `fallback` is `"auto"` (the default). Set `fallback` to `"off"` to require OpenAI or Azure OpenAI credentials for GPT image models instead.
The default quality is `medium`. That keeps latency and reliability reasonable for `gpt-image-2` while still producing polished outputs.
The default `partial_images` value is `1`. For single-image requests, the capability emits `tool.progress` status updates while waiting for the final image. Set it to `0` to disable progress updates, or up to `3` for more feedback at higher token cost.
This capability resolves credentials server-side, persists durable image artifacts, and can also write generated outputs into the session filesystem under `/workspace/.outputs/images/`.
## Credential Resolution
The capability never reads provider credentials from session secrets or environment variables. Resolution order:
1. Default OpenAI provider credentials from the control plane
2. Default Azure OpenAI provider credentials from the control plane
3. Default Meta or OpenRouter provider credentials from the control plane (Muse image model)
## Tools
### `generate_image`
Generate one or more images from a prompt.
| Parameter | Type | Required | Description |
| -------------------- | ------- | -------- | ----------------------------------------------------------------------------------------------------- |
| `prompt` | string | yes | Image generation prompt |
| `size` | enum | no | `1024x1024`, `1536x1024`, `1024x1536`, `auto` |
| `quality` | enum | no | `low`, `medium`, `high`, `auto`. Defaults to capability `default_quality`, which defaults to `medium` |
| `background` | enum | no | `transparent`, `opaque`, `auto` |
| `format` | enum | no | `png`, `jpeg`, `webp` |
| `count` | integer | no | Number of images to generate (1-10) |
| `save_to_session_fs` | boolean | no | Save images into the session filesystem |
| `output_dir` | string | no | Filesystem output directory (default `/workspace/.outputs/images`) |
| `filename_prefix` | string | no | Prefix for artifact and file names |
| `persist_artifact` | boolean | no | Persist into durable image storage (default `true`) |
### `edit_image`
Edit one or more existing images using a prompt.
| Parameter | Type | Required | Description |
| -------------------- | ------- | ----------- | ----------------------------------------------------------------------------------------------------- |
| `prompt` | string | yes | Editing prompt |
| `image_id` | string | conditional | Durable image artifact ID to use as an edit source |
| `path` | string | conditional | Session filesystem path to use as an edit source |
| `size` | enum | no | `1024x1024`, `1536x1024`, `1024x1536`, `auto` |
| `quality` | enum | no | `low`, `medium`, `high`, `auto`. Defaults to capability `default_quality`, which defaults to `medium` |
| `background` | enum | no | `transparent`, `opaque`, `auto` |
| `format` | enum | no | `png`, `jpeg`, `webp` |
| `count` | integer | no | Number of images to produce (1-10) |
| `save_to_session_fs` | boolean | no | Save outputs into the session filesystem |
| `output_dir` | string | no | Filesystem output directory (default `/workspace/.outputs/images`) |
| `filename_prefix` | string | no | Prefix for artifact and file names |
| `persist_artifact` | boolean | no | Persist into durable image storage (default `true`) |
At least one of `image_id` or `path` is required. When both are present, both source images are sent to the edit request.
## Result Shape
Both tools return:
* Native image blocks for direct model consumption
* Structured JSON with:
* `artifact_id` when durable storage is enabled
* `session_file` when workspace save is enabled
* `media_type`, `filename`, `size_bytes`
* `revised_prompt` when OpenAI returns one
## Notes
* Transparent background requires `png` or `webp` output
* High quality can take substantially longer than medium or low on `gpt-image-2`
* Single-image requests emit progress updates by default; multi-image batches still wait for the final response
* Each additional streamed update adds extra image output tokens on the OpenAI side, so higher `partial_images` values trade cost for better perceived latency
* `generate_image` and `edit_image` stay fully exposed even when OpenAI `tool_search` is enabled, so large tool lists do not defer their schemas
* Session file edits must be `png`, `jpg`, `jpeg`, or `webp`
* Edit sources larger than 50 MB are rejected before the API call
* Saved workspace files are written as base64-encoded binary files
## See Also
* [File System](https://docs.everruns.com/capabilities/file-system/), read and reuse workspace images
* [Storage](https://docs.everruns.com/capabilities/session-storage/), store per-session OpenAI overrides
* [Capabilities Overview](https://docs.everruns.com/capabilities/)
---
# OpenAI Tool Search
> Deferred tool loading on supported OpenAI models. Tools are loaded on demand through semantic search.
Source:
| | |
| ---------------- | -------------------- |
| **ID** | `openai_tool_search` |
| **Category** | Optimization |
| **Features** | None |
| **Dependencies** | None |
Enables [OpenAI’s tool\_search](https://platform.openai.com/docs/guides/tool-search) for agents with many tools. Instead of sending full parameter schemas for every tool upfront, only tool names and descriptions are sent initially. The model loads full schemas on-demand when it decides to call a tool.
This reduces prompt token usage significantly for agents with 15+ tools, without changing how tools are called or how results are returned.
## Tools
None, this capability configures the LLM driver, it does not provide tools.
## How It Works
1. **Threshold check**: tool\_search only activates when the total tool count meets or exceeds the threshold (default: 15)
2. **Namespace grouping**: tools are grouped by their capability’s category into [namespace](https://platform.openai.com/docs/api-reference/responses/create#responses-create-tools) entries, giving the model semantic structure for discovery
3. **Deferred schemas**: tools marked as deferrable have `defer_loading: true` set, meaning only name + description are sent upfront
4. **`tool_search` entry**: a `{"type": "tool_search"}` activator is appended to the tools array, enabling the model’s built-in tool search index
5. **Transparent execution**: tool calls and results work identically; the only difference is how tools are presented to the model
### DeferrablePolicy
Each tool has a `deferrable` policy that controls whether its schema can be deferred:
| Policy | Behavior |
| ----------- | ------------------------------------------------------------------------- |
| `never` | Full schema always sent (use for high-frequency tools like `write_todos`) |
| `automatic` | Deferred when tool\_search is active and above threshold (default) |
| `always` | Always deferred when tool\_search is active, regardless of threshold |
### Model Support
Tool search requires model-level support. Currently supported:
| Model family | Supported |
| ---------------- | ----------------------------------- |
| `gpt-5.4*` | Yes |
| `gpt-5.5*` | Yes |
| All other models | No (capability is silently ignored) |
When the capability is enabled but the model doesn’t support tool\_search, the feature is silently skipped, no errors, no behavior change.
## Configuration
### Default (threshold: 15)
```json
{
"capabilities": ["openai_tool_search"]
}
```
### Custom threshold
```json
{
"capabilities": [
{
"capability_ref": "openai_tool_search",
"config": { "threshold": 10 }
}
]
}
```
Lower thresholds activate tool\_search with fewer tools. Set to `1` to always activate when the capability is present.
## Limitations
* **OpenAI-only**: tool\_search here is an OpenAI Responses API feature; other providers ignore this capability. For Claude, use [Claude Tool Search](https://docs.everruns.com/capabilities/claude-tool-search/); for a provider-adaptive default, use [Auto Tool Search](https://docs.everruns.com/capabilities/auto-tool-search/).
* **Supported OpenAI reasoning models only**: earlier OpenAI models don’t support tool\_search
* **No client-side tools**: currently only applies to built-in (server-executed) tools
## See Also
* [OpenAI Tool Search documentation](https://platform.openai.com/docs/guides/tool-search), official OpenAI guide
* [OpenAI Responses API: tools parameter](https://platform.openai.com/docs/api-reference/responses/create#responses-create-tools), API reference for namespace and tool\_search types
* [Claude Tool Search](https://docs.everruns.com/capabilities/claude-tool-search/), the equivalent for Claude models
* [Auto Tool Search](https://docs.everruns.com/capabilities/auto-tool-search/), model-adaptive default
* [Capabilities Overview](https://docs.everruns.com/capabilities/)
---
# OpenRouter Server Tools
> Enable OpenRouter's provider-executed server tools, web search, web fetch, datetime, image generation, and more. OpenRouter runs them server-side and returns the final answer; non-OpenRouter providers ignore the setting.
Source:
| | |
| ---------------- | ----------------------------------------- |
| **ID** | `openrouter_server_tools` |
| **Category** | Tools |
| **Features** | None |
| **Dependencies** | None |
| **Risk** | High (grants provider-executed web reach) |
Enables [OpenRouter’s provider-executed “server tools”](https://openrouter.ai/docs/guides/features/server-tools) (beta). Unlike normal [function tools](https://docs.everruns.com/features/capabilities/), these run **server-side by OpenRouter**: it loops internally and returns the final answer, so the agent loop never dispatches them. This capability contributes *request intent*, not executable tools, the selected tools are compiled into the OpenRouter request’s `tools` array as provider-executed entries. The concrete implementation lives in the focused `everruns-integrations-openrouter` crate; core carries only the provider-neutral routing contract.
This is the OpenRouter counterpart to client-executed web access like [Web Fetch](https://docs.everruns.com/capabilities/web-fetch/): the difference is *who runs the tool*. With server tools, OpenRouter performs the search or fetch and folds the results into the same generation, no extra round-trip through Everruns. Use it when your agents run on the [OpenRouter provider](https://docs.everruns.com/providers/openrouter/) and you want built-in web reach without wiring up a separate search [integration](https://docs.everruns.com/integrations/).
## Tools
None, this capability configures the OpenRouter request, it does not provide client-side tools. The model invokes server tools during the generation and OpenRouter executes them; the only client-visible artifact is the final answer.
## Available server tools
OpenRouter exposes these server tools. Enable any subset:
| Tool | Name | What it does |
| ---------------- | ------------------ | ------------------------------------------------------------------------------------------ |
| Web Search | `web_search` | Searches the web and grounds the answer in results. Accepts an optional `max_results` cap. |
| Web Fetch | `web_fetch` | Fetches and reads a URL the model chooses. |
| Date & Time | `datetime` | Gives the model the current date and time. |
| Image Generation | `image_generation` | Generates images inline. |
| Apply Patch | `apply_patch` | Applies code patches. |
| Fusion | `fusion` | OpenRouter’s Fusion tool. |
| Advisor | `advisor` | OpenRouter’s Advisor tool. |
| Subagent | `subagent` | Delegates to an OpenRouter-run subagent. |
`web_search` is the only server tool that takes parameters today (`web_search_max_results`). Availability of each tool depends on the upstream model and OpenRouter’s beta rollout, see [OpenRouter’s server-tools docs](https://openrouter.ai/docs/guides/features/server-tools) for the current list.
## How it works
1. **Capability config → request intent**: the tools you enable are compiled into the OpenRouter routing config and serialized by the OpenRouter driver into the request’s `tools` array as `{"type":"openrouter:…"}` entries.
2. **OpenRouter executes server-side**: when the model decides to call a server tool, OpenRouter runs it, loops internally, and returns the final answer. The agent loop never sees an intermediate tool call.
3. **No-op off OpenRouter**: non-OpenRouter providers ignore the routing config entirely. Enabling this capability on a non-OpenRouter agent is a harmless no-op, so it is safe to leave on for agents that may switch providers.
## Configuration
### Enable web search
```json
{
"capabilities": [
{
"capability_ref": "openrouter_server_tools",
"config": { "tools": ["web_search"] }
}
]
}
```
### Enable several tools and cap web-search results
```json
{
"capabilities": [
{
"capability_ref": "openrouter_server_tools",
"config": {
"tools": ["web_search", "web_fetch", "datetime"],
"web_search_max_results": 5
}
}
]
}
```
Config rules:
* `tools`, array of server-tool names from the table above. Unknown names are rejected on write. Duplicates are de-duplicated.
* `web_search_max_results`, positive integer; only decorates `web_search`. It is ignored for every other tool and rejected when `< 1`.
## Security
Enabling server tools grants the model **provider-executed web reach** (`web_search` / `web_fetch`). OpenRouter performs these requests, so Everruns’ own egress controls do not apply, the same data-exfiltration class as client-side [Web Fetch](https://docs.everruns.com/capabilities/web-fetch/). The capability is therefore rated **High risk** and gated behind the same admin-only trust check as other outbound-web capabilities. Grant it only to agents you trust with outbound web access.
## Limitations
* **OpenRouter only**: this is an OpenRouter request extension. Other providers ignore it (no error, no behavior change).
* **Beta**: server tools are an OpenRouter beta; tool availability varies by upstream model and may change.
* **Provider-side execution**: because OpenRouter runs the tools, their activity does not appear as Everruns tool calls. Inspect them in OpenRouter’s [dashboard logs](https://docs.everruns.com/providers/openrouter/#logs-traces-and-observability) instead.
## See Also
* [OpenRouter provider](https://docs.everruns.com/providers/openrouter/), configure the provider these tools run on, plus OAuth, actual-cost reporting, and logs
* [OpenRouter server-tools docs](https://openrouter.ai/docs/guides/features/server-tools), official OpenRouter guide
* [Web Fetch](https://docs.everruns.com/capabilities/web-fetch/), the client-executed equivalent
* [Integrations overview](https://docs.everruns.com/integrations/), search and web integrations as an alternative
* [Capabilities Overview](https://docs.everruns.com/capabilities/)
---
# Parallel Tool Calls
> Controls whether the agent requests multiple tool calls per turn and runs them concurrently, prefer parallel, avoid (serialize), or leave the provider default.
Source:
| | |
| ---------------- | --------------------- |
| **ID** | `parallel_tool_calls` |
| **Category** | Optimization |
| **Features** | None |
| **Dependencies** | None |
| **Risk** | Low |
Controls the agent’s request-level preference for parallel (multiple per turn) tool calls, and whether the local tool scheduler runs a batch concurrently.
Most providers emit several tool calls in a single turn by default, and Everruns runs independent tool calls concurrently. This capability makes that behavior explicit and configurable: turn it up to actively request batching of independent reads and searches, or turn it down to force strictly one tool call at a time.
## Tools
None, this capability only configures the outbound LLM request and the local tool scheduler.
## How It Works
The capability resolves a `mode` into a request-level preference that threads through two places:
1. **The LLM request.** On providers that expose a wire control, the preference is sent on the request:
* **OpenAI** (Chat Completions and Responses), and the OpenAI-compatible **MAI** and **Fireworks** providers, the top-level `parallel_tool_calls` boolean.
* **OpenRouter**: forwarded on the Responses body; ignored by routed providers that do not support it.
* **Anthropic**: `tool_choice.disable_parallel_tool_use` (sent only when the request carries tools).
* **Gemini** and **Bedrock** have no equivalent request control, so nothing is sent. The local scheduler (below) still honors the preference.
2. **The local tool scheduler.** `avoid` forces the scheduler to run the turn’s tool calls strictly sequentially. This applies to **every** provider, so `avoid` is honored even where there is no wire control.
Whether the preference is sent on the wire is gated per provider/model: a driver that cannot express it omits the field rather than risking an API error.
## Config
```json
{
"capabilities": [
{
"ref": "parallel_tool_calls",
"config": { "mode": "prefer" }
}
]
}
```
### Modes
| Mode | Provider request | Local scheduler |
| ------------------ | ---------------------------------------------- | ------------------------------------- |
| `prefer` (default) | Request parallel tool calls where supported | Concurrent (class-aware, the default) |
| `avoid` | Ask for one tool call per turn where supported | Serialized |
| `none` | Omit, provider default | Concurrent (class-aware, the default) |
When the capability is enabled without an explicit `mode`, the default is `prefer`. `none` is equivalent to not enabling the capability; it is useful to neutralize a preference inherited from a parent harness.
The **Generic** harness and the built-in **coding** harnesses enable this capability with `mode: "prefer"` by default.
## Precedence
An explicit `parallel_tool_calls` field set directly on a harness, agent, or session is a lower-level escape hatch and takes precedence over this capability.
## When To Use
* **`prefer`**: workloads that issue many independent reads or searches per turn benefit from batching (faster turns, fewer round-trips).
* **`avoid`**: when tool calls must be observed and applied one at a time, or when a model produces lower-quality parallel batches for your workload.
## Limitations
* **Provider gating.** `prefer` only changes the wire request on providers with a control for it (OpenAI/Anthropic families). Elsewhere providers already parallelize by default, so `prefer` is a no-op on the wire.
* **Durable mode.** A harness/agent-level `mode` other than the default (`prefer`) is applied with full fidelity in the in-process runtime; in durable worker mode, harness/agent capability config falls back to the default, set the mode at the session level, or use the explicit `parallel_tool_calls` field, to override durably. (This matches other config-bearing capabilities.)
## See Also
* [Capabilities](https://docs.everruns.com/features/capabilities/), the extension model this capability plugs into
* [Agentic Loop](https://docs.everruns.com/explanation/agentic-loop/), how the runtime schedules a batch of tool calls
---
# Platform
> Discover, inspect, and manage Everruns resources through the command catalog.
Source:
| | |
| ---------------- | ---------------------------------------------------------------------------------------- |
| **ID** | `platform` |
| **Category** | Platform |
| **Risk** | High |
| **Tools** | `discover`, `query`, `execute` |
| **Dependencies** | `session_file_system` when embedded docs are enabled |
| **Mounts** | `/workspace/docs`, the Everruns documentation, read-only, when embedded docs are enabled |
The Platform capability gives an agent the same catalog-backed command surface as Everruns’ `/mcp` endpoint. Operations come from the server’s registered command inventory, so models can discover current names and schemas instead of guessing them or relying on a separate handwritten API.
Platform Chat includes this capability by default. Other agents and harnesses must be assigned it explicitly. Because `execute` can mutate platform resources, the capability is high-risk and follows the normal admin-only assignment rule.
## Tools
### `discover`
Search for operations by name, category, description, or schema terms. Results include command metadata, read-only decision, and output-shape hints. Searches with multiple matches omit schemas and return a refinement hint to keep the result compact. A query that exactly matches a command name returns only that command with its schemas and `bash_usage`, a copyable invocation with the exact supported flags. It also includes bounded `output_fields` paths for building `jq` filters without guessing field names. If expanded schemas would make the result too large, the response omits them with a notice while retaining the authoritative scripting summaries. Use `all: true` only when you truly need to list the entire scriptable catalog, not for a task-specific lookup.
```json
{ "query": "models" }
```
Once you find a command, discover its exact name before invoking it:
```json
{ "query": "create_agent" }
```
Platform builtins do not implement `--help`. Use `bash_usage` and the returned schema instead of probing with `--help` or guessing flag names. Pass array and object values as JSON text, for example `--capabilities '[{"ref":"mcp:..."}]'`.
Unknown flags are rejected before a command runs. This prevents misspelled security-sensitive options, such as an authentication flag, from being silently ignored during a mutation.
### `query`
Run a bounded Bashkit script with only read-only Everruns commands available as builtins. It supports pipes, variables, loops, conditionals, and `jq`.
```json
{ "commands": "list_models | jq '.data[] | {id, model_id, display_name}'" }
```
Commands with mutations or open-world side effects are not available in `query`. Use it to inspect current state and validate changes.
### `execute`
Run a bounded Bashkit script with the full scriptable command catalog. Use it for requested create, update, delete, and other mutating operations.
```json
{
"commands": "create_agent --name 'support-agent' --system_prompt 'Help users.' --default_model_id 'model_...'"
}
```
`execute` is not transactional. If a later command in a script fails, earlier commands may already have succeeded. Inspect the resulting state with `query` before retrying.
MCP server command results include both their public resource `id` and a derived `capability_ref` in the `mcp:` form accepted by Agent capability configuration. Capture JSON results and use `jq` to pass dependent IDs or capability references to later commands in the same script. No separate MCP attachment operation is needed.
## Scope and authorization
Platform tools are always bound to the current session’s organization. Their schemas do not accept `organization_id`, and an injected override is rejected. The server resolves the session’s human owner for every distributed call and applies that caller’s normal command permissions. Attaching this capability does not grant authority the owner does not already have.
## Autonomous workflows
For recurring autonomous work, create an Agent and an Agent Trigger. Do not use a schedule on the Platform Chat session: that would wake the management chat, not provision an independently owned worker workflow.
Credentials are not transferred from Platform Chat session secrets into a new Agent. Configure integrations through their supported Agent-scoped credential or connection flow; do not paste credentials into command scripts.
## Platform documentation
When the build embeds the product documentation, this capability mounts it at `/workspace/docs` as a read-only virtual filesystem, served from memory with no database writes per session. Agents browse it with the standard file tools (`read_file`, `list_directory`, `grep`) or with `cat`, `ls`, and `grep` through Bashkit Shell.
Key sections:
* `/workspace/docs/getting-started/`, introduction, concepts, architecture
* `/workspace/docs/features/`, SDK, CLI, UI, events, harnesses, capabilities
* `/workspace/docs/capabilities/`, per-capability reference
* `/workspace/docs/integrations/`, external integrations (Slack, Daytona, etc.)
* `/workspace/docs/advanced/`, budgets, compaction, embedding, network access
* `/workspace/docs/sre/`, environment variables, runbooks
## See also
* [Platform Chat harness](https://docs.everruns.com/built-ins/harnesses/platform-chat/)
* [MCP](https://docs.everruns.com/features/mcp/)
* [Agent Triggers](https://docs.everruns.com/features/agent-triggers/)
* [Platform Management](https://docs.everruns.com/capabilities/platform-management/), the removed predecessor
---
# Platform Management (removed)
> Removed capability. Its management tools are superseded by the catalog-backed Platform capability.
Source:
> **This capability has been removed.** Use the [Platform capability](https://docs.everruns.com/capabilities/platform/) instead. Agents and harnesses that still reference `platform_management` keep running, but the capability contributes no tools and no system prompt.
| | |
| --------------- | -------------------------------------------------------------- |
| **ID** | `platform_management` |
| **Category** | Platform |
| **Status** | Retired |
| **Tools** | None |
| **Replacement** | [`platform`](https://docs.everruns.com/capabilities/platform/) |
## Why it was removed
Its tools were hand-written alongside the API rather than derived from it, so they covered only harnesses, agents, apps, and sessions, and their schemas drifted as the platform grew. The `platform` capability exposes the same surface through `discover`, `query`, and `execute` over the server’s registered command catalog, which is the same inventory behind Everruns MCP, so it cannot drift.
## What to do
Replace the capability on any agent or harness that still lists it:
1. Open the agent or harness. A removed capability is flagged in its capability list.
2. Remove `platform_management` and add `platform`. Both are high-risk, so an admin performs the change.
3. Prompts that named the old tools should name the new flow instead: find the command with `discover`, read state with `query`, mutate with `execute`.
## Tool mapping
| Removed tool | Replacement |
| ------------------------ | -------------------------------------------------------------------------------- |
| `read_capabilities` | `list_capabilities` / `get_capability` |
| `read_harnesses` | `list_harnesses` / `get_harness` |
| `manage_harnesses` | `create_harness`, `update_harness`, `delete_harness`, `copy_harness` |
| `read_agents` | `list_agents` / `get_agent` |
| `manage_agents` | `create_agent`, `update_agent`, `delete_agent` |
| `read_apps` | `list_apps` / `get_app` / `list_app_channels` |
| `manage_apps` | `create_app`, `update_app`, `delete_app`, `publish_app`, `unpublish_app` |
| `manage_app_channels` | `add_*_app_channel`, `update_app_channel`, `delete_app_channel` |
| `read_sessions` | `list_sessions` / `get_session` |
| `manage_sessions` | `create_session`, `delete_session` |
| `session_send_message` | `create_message` |
| `session_read_messages` | `list_messages` |
| `session_context_report` | `get_session_context_report` |
| `session_read_response` | No equivalent. It blocked until the turn finished; poll `list_messages` instead. |
Run `discover` for the exact current schema of any command in that table rather than assuming the flags.
## Platform documentation
The embedded documentation mount at `/workspace/docs` moved to the [Platform capability](https://docs.everruns.com/capabilities/platform/) unchanged.
## See also
* [Platform](https://docs.everruns.com/capabilities/platform/), the replacement
* [Capabilities Overview](https://docs.everruns.com/capabilities/)
---
# Prompt Canary Guardrail
> Streaming output guardrail that withholds the assistant message when the model echoes the first sentence of its system prompt back to the user.
Source:
| | |
| ---------------- | ------------------------- |
| **ID** | `prompt_canary_guardrail` |
| **Category** | Safety |
| **Features** | None |
| **Dependencies** | None |
| **Risk** | Low |
Detects naive system-prompt leakage during streaming. At the start of each assistant message, the capability extracts the first qualifying sentence of the assembled system prompt and uses it as a canary needle. If the model’s accumulated output ever contains that needle, streaming aborts, the client is told to discard everything it accumulated, and a canned refusal becomes the persisted assistant message. The original tokens are never stored or replayed on subsequent turns.
This is intentionally narrow: a single substring match against one normalized needle. It catches obvious prompt-extraction attempts (“repeat your instructions”, “what are you told to do?”) without trying to be a general-purpose data-loss-prevention layer.
## Tools
None, this capability hooks the streaming output via the capability framework’s output-guardrail extension point.
## How It Works
1. **Arming**: At the start of each assistant message stream, the capability walks sentence boundaries in the assembled system prompt and picks the first sentence whose normalized form is **≥ 30 characters**. This skips short generic openers like “You are a helpful assistant.” in favor of an agent-specific identifying sentence
2. **Normalization**: Both sides of the comparison are lowercased, and runs of whitespace are collapsed to a single space, so the canary survives reformatting (extra spaces, capitalization drift, line wrapping)
3. **Streaming check**: After every text delta, the canary runs a substring scan over the accumulated assistant text. The check is synchronous and cheap, no I/O, no allocations beyond the normalized buffer
4. **Block on match**: When the needle appears in the accumulated output, the stream is aborted, the offending pending delta is suppressed, and `output.message.replaced` is emitted with `reason_code: "system_prompt_leak"`. The replacement text becomes the persisted assistant message
When the system prompt is too short or too generic to produce a needle ≥ 30 characters, the capability declines to arm for that stream and is a no-op.
## Streaming Timeline With a Trip
```plaintext
output.message.started
│
▼
output.message.delta ← model text accumulating ("Sure, my instructions are: …")
│
▼ (canary trips on the next delta — pending text is suppressed)
output.message.replaced
│ (UI discards what it accumulated, shows replacement)
▼
output.message.completed ← persisted message body = replacement
```
## Configuration
### Default
```json
{
"capabilities": ["prompt_canary_guardrail"]
}
```
Replacement text defaults to:
> \[Response withheld: the model attempted to reveal protected instructions.]
### Custom replacement
```json
{
"capabilities": [
{
"ref": "prompt_canary_guardrail",
"config": { "replacement": "I can't share my system instructions." }
}
]
}
```
## When To Enable
Use this capability when:
* You ship agents with proprietary, brand-specific, or compliance-relevant system prompts that should not be revealed verbatim to end users
* You want a cheap, deterministic defense against the most common prompt-extraction prompts
* You can tolerate a generic refusal in place of the model’s response when the canary trips
Do **not** rely on this for:
* General-purpose data-loss prevention (PII, secrets in tool output, etc.), those need their own surfaces
* Defense against paraphrased or summarized prompt leaks, the canary only catches verbatim or near-verbatim copies of the first sentence
* Tool output or extended-thinking surfaces, the canary only inspects assistant text
## Limitations
* **Verbatim-only**: a model that paraphrases (“My role is to act as an internal pricing oracle…”) will not trip the canary
* **First-sentence-only**: if the model leaks a *later* sentence of the system prompt, the canary won’t catch it. Consider rewriting prompts so the most identifying claim is the opening sentence
* **No partial matching**: the substring must appear in full. Truncated leaks (cut off mid-sentence) pass through
## See Also
* [Events](https://docs.everruns.com/features/events/), the streaming event protocol that carries `output.message.replaced`
* [Capabilities](https://docs.everruns.com/features/capabilities/), the extension model these guardrails plug into
---
# Self-Budget
> Prompt-only guidance for agents to reason about a user-requested indicative budget using session usage data. Distinct from the platform-enforced `budgeting` capability.
Source:
| | |
| ---------------- | ------------------------- |
| **ID** | `self_budget` |
| **Category** | System |
| **Features** | *(none)* |
| **Tools** | *(none)* |
| **Included in** | Generic harness (default) |
| **Dependencies** | None |
Teaches the agent how to self-manage an **indicative** budget that the user mentions in conversation, for example, “you have $7” or “keep this under 20k tokens”. The capability contributes prompt text only; it adds no tools and performs no enforcement.
For platform-enforced budgets (authoritative limits that pause or stop sessions automatically), use the separate [`budgeting`](https://docs.everruns.com/capabilities/budgeting/) capability.
## How It Works
`self_budget` is prompt-only. When the capability is enabled the agent’s system prompt gets a “Self-Managed Budget” section that explains:
* The self-budget is an **agent-managed soft target**, not a hard limit.
* Session usage metadata (exposed via `get_session_info`) is the source of truth for current spend.
* The agent decides when to start tracking, when to re-check, and when to stop.
* As the target tightens, the agent should adapt, shorter outputs, fewer retries, narrower exploration, fewer redundant tool calls.
* The agent avoids claiming exact cost certainty when only token counts or partial pricing are available.
* The agent distinguishes between platform-enforced budgets and user-requested indicative budgets when reporting progress.
There is no `self_budget` tool. Usage data comes from `get_session_info`, which is provided by the [`session`](https://docs.everruns.com/capabilities/session/) capability (bundled by default in the Generic harness).
## Self-Budget vs Budgeting
| Aspect | `self_budget` | `budgeting` |
| ----------- | ----------------------------------- | --------------------------------------------------- |
| What it is | Agent-managed soft target | Platform-enforced limit |
| Tools | None | `check_budget` |
| Enforcement | None (prompt guidance only) | Session is paused/stopped automatically |
| Data source | `get_session_info` cumulative usage | Budgets table / ledger |
| Use case | User says “you have $7” in chat | Org/session has a configured budget in the platform |
The two capabilities are non-conflicting and can run together. The Generic harness includes both.
## Related
* [Budgeting](https://docs.everruns.com/capabilities/budgeting/), platform-enforced budgets with the `check_budget` tool
* [Session](https://docs.everruns.com/capabilities/session/), provides `get_session_info`, the usage data source
* [Budgets](https://docs.everruns.com/advanced/budgets/), full budgeting system documentation
---
# Session
> Inspect and update the current session's metadata, including its ID, title, and agent name.
Source:
| | |
| ---------------- | --------- |
| **ID** | `session` |
| **Category** | Session |
| **Features** | None |
| **Dependencies** | None |
Tools to read and update session metadata like title and agent information.
## Automatic titles
Automatic title maintenance is opt-in. Set `auto_title` to `true` in the capability configuration to have the agent create a concise 3–7 word title before handling the first substantive request. The title is a required pre-work update. The agent updates it later, also before other work or a response, only when the conversation’s primary theme materially changes, not for minor follow-ups or subtopics. Title writes update session metadata and do not count as project or workspace file changes.
Title changes emit `session.title.updated` with the previous and new title. A repeated write of the current title is a no-op and emits no event.
## Tools
### `get_session_info`
Get current session metadata.
Returns: session ID, title, agent name.
### `write_session_title`
Update the session title.
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ----------------- |
| `title` | string | yes | New session title |
## See Also
* [Storage](https://docs.everruns.com/capabilities/session-storage/), persist data within the session
* [Schedules](https://docs.everruns.com/capabilities/session-schedules/), schedule future tasks
* [Capabilities Overview](https://docs.everruns.com/capabilities/)
---
# Schedules
> Schedule one-shot and recurring cron-based tasks within a session.
Source:
| | |
| ---------------- | ----------------------------------- |
| **ID** | `session_schedule` |
| **Category** | Core |
| **Features** | `schedules` (enables Schedules tab) |
| **Dependencies** | None |
Schedule future tasks within the current session. Supports one-shot (run once at a specific time) and recurring (cron expression) schedules.
## Tools
### `create_schedule`
Create a new scheduled task.
| Parameter | Type | Required | Description |
| ----------------- | ------ | ----------- | ---------------------------------------- |
| `message` | string | yes | The message/task to execute |
| `scheduled_at` | string | conditional | ISO 8601 datetime for one-shot schedules |
| `cron_expression` | string | conditional | Cron expression for recurring schedules |
Provide either `scheduled_at` or `cron_expression`, not both.
### `cancel_schedule`
Cancel an active schedule.
| Parameter | Type | Required | Description |
| ------------- | ------ | -------- | ---------------------------- |
| `schedule_id` | string | yes | ID of the schedule to cancel |
### `list_schedules`
List all schedules for the current session.
## Recurring background tasks
Recurrence is built on schedules, there is no separate “recurring task” object to configure. A recurring (`cron_expression`) schedule either delivers a scheduled turn to the session, or, when paired with a background **monitor** task, runs a probe on each fire and records the result on the task’s thread. This composition (recurring schedule + monitor) is the supported way to run periodic background work, there is no separate recurring-task primitive to configure.
## Notes
* Maximum 5 active schedules per session
* Cron uses standard 5-field format (minute, hour, day, month, weekday)
* Scheduled messages are sent to the session as if the user sent them
* Use [Current Time](https://docs.everruns.com/capabilities/current-time/) to determine “now” before scheduling
## See Also
* [Current Time](https://docs.everruns.com/capabilities/current-time/), get current time for scheduling context
* [Session](https://docs.everruns.com/capabilities/session/), session metadata
* [Capabilities Overview](https://docs.everruns.com/capabilities/)
---
# Session Storage
> Session-scoped key/value storage and encrypted secret storage.
Source:
| | |
| ---------------- | -------------------------------------------- |
| **ID** | `session_storage` |
| **Category** | Storage |
| **Features** | `secrets`, `key_value` (enables Storage tab) |
| **Dependencies** | None |
Two storage mechanisms scoped to the current session:
* **Key/Value store**: plain-text storage for general data
* **Secret store**: AES-256-GCM encrypted storage for sensitive data
## Tools
### `kv_store`
Manage plain-text key/value pairs.
| Parameter | Type | Required | Description |
| ----------- | ------ | ----------- | ----------------------------------- |
| `operation` | enum | yes | `set`, `get`, `delete`, or `list` |
| `key` | string | conditional | Required for `set`, `get`, `delete` |
| `value` | string | conditional | Required for `set` |
### `secret_store`
Manage encrypted secrets. Same interface as `kv_store` but values are encrypted at rest.
| Parameter | Type | Required | Description |
| ----------- | ------ | ----------- | ----------------------------------- |
| `operation` | enum | yes | `set`, `get`, `delete`, or `list` |
| `name` | string | conditional | Required for `set`, `get`, `delete` |
| `value` | string | conditional | Required for `set` |
## Notes
* Data is session-scoped, no cross-session access
* `set` uses upsert semantics (overwrites existing keys)
* Secret operations require `SECRETS_ENCRYPTION_KEY` to be configured
* `list` returns keys/names only (not values) for secrets
* The Storage tab can create, replace, and delete values, but never reads a value back
* A session secret is available only to that session. It does not follow an Agent Trigger that creates a session per invocation.
* `secret_store get` exposes the decrypted value to the running model. Do not use session secrets for MCP tool-parameter credentials that must stay out of model context; configure those on the Agent’s **Credentials** tab instead.
## See Also
* [Session](https://docs.everruns.com/capabilities/session/), session metadata
* [File System](https://docs.everruns.com/capabilities/file-system/), file-based storage alternative
* [Capabilities Overview](https://docs.everruns.com/capabilities/)
---
# Slack
> Act in the Slack conversation as the bot the workspace already invited: reactions, message updates, file uploads, and user lookups.
Source:
| | |
| ---------------- | --------------- |
| **ID** | `slack` |
| **Category** | Integrations |
| **Features** | `slack_actions` |
| **Dependencies** | None |
An agent published to a [Slack endpoint](https://docs.everruns.com/integrations/slack/) can reply in its thread. This capability lets it do the rest — react to a message, rewrite one it posted, share a file, resolve a user ID to a name — as the same bot the workspace invited.
No second credential. The tools resolve the endpoint’s own bot token server-side, so there is nothing extra to provision, scope, or rotate.
## Requirements
The tools only work in a session a Slack message created. An agent that has the capability enabled but is running from the API, a schedule, or another channel has no Slack endpoint to act as, and every tool returns an error saying so rather than acting as some other endpoint’s bot.
Where an agent carries two Slack endpoints, each with its own bot, the tools act as the endpoint that created the session.
Your Slack app needs the scope for each action you use: `reactions:write` for reactions, `chat:write` for updates, `files:write` for uploads, and `users:read` for lookups. Slack answers a missing scope with an error the agent sees.
## Tools
### `slack_add_reaction`
Add an emoji reaction to a message. The cheapest acknowledgement available — prefer it over posting “working on it”.
| Parameter | Type | Required | Description |
| ----------- | ------ | -------- | --------------------------------------- |
| `channel` | string | yes | Channel ID the message is in |
| `timestamp` | string | yes | The message’s `ts` |
| `name` | string | yes | Emoji name without colons (e.g. `eyes`) |
Reacting with an emoji that is already there succeeds; the result says `already_reacted`.
### `slack_update_message`
Rewrite a message this bot posted. Use it to turn a status message into its result instead of posting a second message.
| Parameter | Type | Required | Description |
| ----------- | ------ | -------- | -------------------------------------- |
| `channel` | string | yes | Channel ID the message is in |
| `timestamp` | string | yes | The `ts` of the bot message to rewrite |
| `text` | string | yes | Replacement text; Markdown is rendered |
Only messages this bot posted can be updated.
### `slack_lookup_user`
Resolve a Slack user ID to that person’s display name, real name, timezone, and whether they are a bot.
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ---------------------------------------------------- |
| `user_id` | string | yes | Slack user ID (the `<@U…>` mention form is accepted) |
Returns only those addressing fields. Email, phone, and title are not exposed to the agent.
### `slack_upload_file`
Share a file into the conversation. Use it for reports, diffs, and logs too long to read in a message.
| Parameter | Type | Required | Description |
| ----------------- | ------ | -------- | --------------------------------------------------- |
| `channel` | string | yes | Channel ID to share into |
| `filename` | string | yes | Filename shown in Slack, including its extension |
| `content` | string | yes | The file’s text content |
| `thread_ts` | string | no | Thread to share into; omit to post at channel level |
| `initial_comment` | string | no | Message posted alongside the file |
Content is capped at 8 MiB.
## Notes
* Posting to an arbitrary channel is deliberately not offered. The blast radius of “anywhere the bot is” is wider than “the thread that asked”, and the reply path already answers in the thread.
* A retired or disabled endpoint stops acting immediately, even for a session it created earlier.
* Slack rate limits reach the agent with Slack’s own retry advice rather than as a generic failure.
* The [Slack MCP server](https://docs.everruns.com/features/mcp/) stays supported for anything this does not cover. This removes the second credential for the common cases; it does not replace MCP.
## See Also
* [Slack Integration](https://docs.everruns.com/integrations/slack/), publishing an agent to a Slack workspace
---
# SQL Database
> Session-scoped SQLite databases: create tables, run queries, and persist relational data per session.
Source:
| | |
| ---------------- | ---------------------- |
| **ID** | `session_sql_database` |
| **Category** | Data |
| **Features** | `sql_database` |
| **Dependencies** | None |
Session-scoped SQLite databases for structured data storage. Create tables, insert data, and run queries, all isolated to the current session.
## Tools
### `sql_execute`
Run DDL/DML statements (CREATE TABLE, INSERT, UPDATE, DELETE).
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ------------------------ |
| `sql` | string | yes | SQL statement to execute |
### `sql_query`
Run SELECT queries. Results limited to 1000 rows.
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ------------ |
| `sql` | string | yes | SELECT query |
### `sql_schema`
Introspect the database schema, list tables, columns, and types.
## Notes
* Database is session-scoped, destroyed when the session ends
* SELECT queries return at most 1000 rows
* Standard SQLite SQL syntax
## See Also
* [Storage](https://docs.everruns.com/capabilities/session-storage/), simpler key/value alternative
* [File System](https://docs.everruns.com/capabilities/file-system/), file-based data storage
* [Capabilities Overview](https://docs.everruns.com/capabilities/)
---
# Sub Agents
> Spawn subagents that run tasks in isolated context windows, through the generic session task tools.
Source:
| | |
| ---------------- | ----------- |
| **ID** | `subagents` |
| **Category** | Core |
| **Features** | `subagents` |
| **Dependencies** | None |
Spawn subagents for parallel task execution. Each subagent runs in its own isolated context window, allowing the parent agent to delegate verbose or independent tasks without cluttering the main conversation. Subagents inherit the parent’s harness and agent configuration but operate with their own message history.
## Tools
### `spawn_agent`
Sessions with `subagents` expose `target.type: "subagent"` in the shared `spawn_agent` dispatcher. If first-party handoffs or external A2A delegation are also active, the same tool advertises those target types too. The dispatcher returns a `task_id` for the generic session task tools and moves Everruns toward one delegation surface across subagents, first-party agent handoffs, and external A2A agents.
Create and start a new subagent by calling `spawn_agent` with `target.type: "subagent"`. By default the subagent runs in the background: the tool returns immediately with a `task_id`, the parent agent keeps working, and the session is notified when the subagent finishes. Use the `task_id` with the generic session task tools to monitor, message, or cancel the subagent.
| Parameter | Type | Required | Description |
| -------------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | string | yes | Human-readable name for the subagent. Must be unique within the session. |
| `instructions` | string | yes | Instructions for what the subagent should do. This becomes the subagent’s initial prompt. |
| `target.type` | string | yes | Must be `subagent`. |
| `mode` | string | no | `background` (default) returns immediately with a `task_id`; `foreground` blocks until the subagent completes and returns its result inline. |
| `blueprint` | string | no | Optional specialist blueprint ID, such as `github_scout`, that supplies its own prompt, model, and private tools. |
| `config` | object | no | Blueprint-specific configuration, validated against the blueprint’s schema before the child session is created. Only valid when `blueprint` is set. |
## Managing subagents after spawn
Use the generic `session_tasks` tools to monitor and steer subagents after spawning. The `task_id` is returned by `spawn_agent`.
* `list_tasks` with `kind: "subagent"`, list all subagent tasks and their status
* `get_task` with the task ID, get detailed status and result for a specific subagent
* `message_task`, send a steering message or additional context to a running subagent
* `cancel_task`, request cooperative cancellation of a subagent
* `wait_task`, block until a subagent reaches a terminal or interrupted state
## Notes
* **Governed spawning**: subagents can spawn nested subagents up to `max_subagent_depth` (default 2); set it to 0 to block subagent spawning. Each root session also has `max_active_descendant_tasks` (default 16) and `max_total_descendant_tasks` (default 200) caps to bound wide fan-out and repeated spawn loops.
* **Shared budget pool**: nested subagents spend from the root session’s session-scoped budget.
* **Background mode (default)**: spawning returns immediately with a `task_id`. The final result lands on the task record (`summary` via `get_task`), and the parent session is woken when the subagent reaches a terminal state. Background runs are capped at 6 hours.
* **Foreground mode**: `mode: "foreground"` blocks until the subagent completes and returns its result inline. Foreground execution has a 5-minute timeout.
* **Inherited configuration**: subagents inherit the parent’s harness and agent configuration.
* **Blueprints**: specialist blueprints can run with their own prompt, model, and private tools while still using the same subagent lifecycle.
## See Also
* [`knowledge/runtime-resources/session-tasks.md`](https://github.com/everruns/everruns/blob/main/knowledge/runtime-resources/session-tasks.md), generic task monitoring and control (`list_tasks`, `get_task`, `message_task`, `cancel_task`, `wait_task`)
* [Author an agent blueprint](https://docs.everruns.com/advanced/agent-blueprints/), contributing a specialist agent with a typed configuration contract
* [GitHub Scout](https://docs.everruns.com/capabilities/github-scout/), blueprint-only GitHub repository exploration
* [Session](https://docs.everruns.com/capabilities/session/), session metadata and lifecycle
* [Platform](https://docs.everruns.com/capabilities/platform/), agent and platform configuration
* [Capabilities Overview](https://docs.everruns.com/capabilities/), full list of available capabilities
---
# Task Management
> Structured task lists for tracking multi-step work within a session.
Source:
| | |
| ---------------- | --------------------- |
| **ID** | `stateless_todo_list` |
| **Category** | Core |
| **Features** | None |
| **Dependencies** | None |
Enables agents to create and manage structured task lists. State is maintained in conversation history, each tool call sends the complete list.
## Tools
### `write_todos`
Create or update the complete task list. Each call replaces the entire list.
| Parameter | Type | Required | Description |
| --------- | ----- | -------- | -------------------------------------------------- |
| `todos` | array | yes | Array of `{ content, status, activeForm }` objects |
Task statuses: `pending`, `in_progress`, `completed`.
## Notes
* **Stateless**: no database table; state lives in conversation history
* Each `write_todos` call must include the **complete** list (not incremental updates)
* Best practice: exactly one task `in_progress` at a time
* Only mark a task `completed` when fully done (tests pass, no errors)
* `activeForm` is the present-continuous label shown during execution (e.g., “Running tests”)
## See Also
* [Session](https://docs.everruns.com/capabilities/session/), session metadata management
* [Capabilities Overview](https://docs.everruns.com/capabilities/)
---
# Tool Call Repair
> Detects and repairs malformed tool-call arguments from the model, recovering the turn instead of surfacing a raw parse error.
Source:
| | |
| ---------------- | ------------------ |
| **ID** | `tool_call_repair` |
| **Category** | Safety |
| **Features** | None |
| **Dependencies** | None |
| **Risk** | Low |
Opt-in recovery net for malformed tool calls. Models occasionally emit tool-call arguments that are not clean JSON, wrapped in a Markdown code fence, surrounded by prose, with trailing commas or single quotes, or with values typed as strings where the schema wants numbers. Without repair, such a call either surfaces a parse error or silently collapses to empty arguments and fails downstream. This capability salvages the call so the turn proceeds.
**Disabled by default.** The capability is registered so agents can enable it, but contributes nothing unless explicitly selected. With it off, behavior is byte-for-byte unchanged.
## Tools
None, the capability intercepts inside the `reason` step, after the model’s tool calls are finalized and before the assistant message is built.
## How It Works
1. **Deterministic local salvage**: A pure function runs over each call’s `arguments`: it unwraps fenced code blocks and strips surrounding prose, removes trailing commas, normalizes single quotes to double quotes, and coerces string-typed known keys to the type declared by the tool’s JSON schema (e.g. `"42"` → `42` for an integer property). An already-valid call is a no-op.
2. **Bounded corrective re-prompt**: When local salvage cannot recover a usable object, the capability allows up to `max_reprompts` attempts per call (default
1. before falling through to the normal error path. The re-prompt is realized by the agent loop: the unrepaired call proceeds to today’s error path and the model retries on the next iteration. The per-call cap guarantees there is no infinite repair loop.
3. **Observability**: Each malformed call emits one `tool.call_repaired` event carrying an outcome label: `local-salvage`, `re-prompt`, or `gave-up`.
## Configuration
### Default
```json
{
"capabilities": ["tool_call_repair"]
}
```
### Custom re-prompt cap
```json
{
"capabilities": [
{
"ref": "tool_call_repair",
"config": { "max_reprompts": 2 }
}
]
}
```
`max_reprompts` accepts `0`–`5`. `0` means “salvage locally or fall straight through to the error path with no re-prompt”.
## When To Enable
Use this capability when:
* You run models or providers that occasionally wrap tool arguments in prose or code fences, or emit lenient JSON (single quotes, trailing commas)
* You want a malformed call to recover the turn rather than waste an iteration on a raw parse error
## Limitations
* **Verbatim JSON only**: salvage extracts an embedded JSON object; it does not invent missing required fields or guess intent from natural language
* **Bounded input**: argument blobs larger than 256 KiB are treated as un-salvageable without parsing (a denial-of-service guard against runaway model output)
* **No deep type checking**: coercion handles top-level `integer` / `number` / `boolean` string values; full schema validation remains the tool’s job
## See Also
* [Events](https://docs.everruns.com/features/events/), the streaming event protocol that carries `tool.call_repaired`
* [Capabilities](https://docs.everruns.com/features/capabilities/), the extension model this capability plugs into
---
# Tool Search
> Provider-agnostic deferred tool loading. Tool parameter schemas stay hidden until the model loads them on demand.
Source:
| | |
| ---------------- | ------------- |
| **ID** | `tool_search` |
| **Category** | Optimization |
| **Features** | None |
| **Dependencies** | None |
Enables deferred tool loading for agents with many tools, on **any** model. Instead of sending full parameter schemas for every tool upfront, only tool names and descriptions are sent initially. The model loads full schemas on demand by calling the `tool_search` tool.
Unlike the hosted [OpenAI Tool Search](https://docs.everruns.com/capabilities/openai-tool-search/) and [Claude Tool Search](https://docs.everruns.com/capabilities/claude-tool-search/), which rely on the provider’s server-side `tool_search` feature, this capability implements tool search entirely client-side. It therefore works with Gemini, OpenAI Completions, models reached through gateways that don’t implement hosted search, and any other provider, not just GPT-5.4+ or Claude 4. For a default that automatically picks hosted search where available and this client-side path everywhere else, use [Auto Tool Search](https://docs.everruns.com/capabilities/auto-tool-search/).
## Tools
* **`tool_search`**: search the available tools by keyword and load their full parameter schemas.
## How It Works
The diagram below traces one deferred tool through a full round-trip, from schema stripping at context-assembly time, through the `tool_search` call, to calling the real tool with its restored parameters.

1. **Threshold check**: deferral only activates when the total tool count meets or exceeds the threshold (default: 15). Below it, full schemas are sent unchanged.
2. **Schema stripping**: a tool-definition hook replaces the parameter schema of every deferrable tool with a minimal open-object stub (name + description survive). The shared prompt carries the search instruction once instead of repeating it in every stub. This runs when the runtime agent is built, so the model never receives the full schemas upfront.
3. **`tool_search` tool**: a real tool is added to the agent. When the model calls it with a query, the tool inspects its sibling tools and returns the full JSON parameter schemas of the matches.
4. **Progressive disclosure**: `tool_search` also records the matched tools as *revealed*. The hook re-runs on every reasoning iteration, so on the next step the revealed tools are advertised with their full, authoritative schema on the *registered* definition. This is what lets a structured tool caller actually pass arguments to a previously deferred tool, rather than only reading its schema as text.
5. **System-prompt guidance**: a short note instructs the model to call `tool_search` before using a tool whose parameters it has not yet loaded.
6. **Transparent execution**: the underlying tools stay registered and executable. Tool calls and results work identically; only how schemas reach the model changes.
### DeferrablePolicy
Each tool has a `deferrable` policy that controls whether its schema can be deferred:
| Policy | Behavior |
| ----------- | ------------------------------------------------------------------------- |
| `never` | Full schema always sent (use for high-frequency tools like `write_todos`) |
| `automatic` | Deferred when tool\_search is active and above threshold (default) |
| `always` | Always deferred when tool\_search is active |
The `tool_search` tool itself is never deferred.
### Never-defer allowlist
`DeferrablePolicy::Never` is set by the tool’s *owner*. An embedder that composes tools it does not own (for example file/shell tools from another crate) can instead keep specific tools fully loaded by name:
* Programmatically: `ToolSearchCapability::with_never_defer(["read_file", "bash", ...])`.
* By configuration: a `never_defer` array (merged with any programmatic list).
Allowlisted tools behave exactly like `DeferrablePolicy::Never` tools, their full schema is always sent, so the agent is never forced through a `tool_search` round-trip before its first read/edit/shell call.
### Search ranking and result bounding
Because there is no hosted semantic index, `tool_search` ranks matches client-side with a deliberately simple, predictable scheme:
* **Field-weighted keyword overlap**: each whitespace-separated query term scores **3** if it appears in a tool’s *name* and **1** if it only appears in the *description*. A name hit is a far stronger signal of intent than an incidental word in prose, so it dominates.
* **Exact-name bonus**: a query that is exactly a tool name gets a large bonus (**+100**), so “load this specific tool” always ranks that tool first. The deferred stub tells the model to query the exact tool name, so this is the common path. Wrapping punctuation is stripped first, so a quoted or backticked name (`"read_file"`, `` `read_file` ``) still matches.
* **Top-band cutoff**: only results scoring at least **half the top score** are returned, trimming weak tail matches so a loose query does not drag in loosely related tools.
* **Result cap**: at most **8** tools are returned per call. Every returned tool is also *revealed* (its full schema is un-deferred for the rest of the session), so the cap bounds both the response payload and how much of the catalogue a single search can permanently un-defer.
* **Visible-tool scoping**: the search only considers tools visible in the current turn (the turn-scoped allowlist), so it never reveals a tool the agent could not otherwise call.
* **No-match fallback**: if nothing matches, the tool returns the catalogue of available tool *names* (not schemas) so the model can refine its query instead of dead-ending. An empty query lists tools so the model can browse.
The session reveal set that drives progressive disclosure is itself bounded: it is keyed per session and evicts the oldest sessions past a fixed cap, so reveals never leak across sessions or grow without limit (an evicted session simply re-runs `tool_search`).
### Model Support
None required. Because deferral and search are implemented client-side, every model works the same way. For GPT-5.4+ you may prefer [OpenAI Tool Search](https://docs.everruns.com/capabilities/openai-tool-search/), which uses the provider’s hosted index; use this capability for all other models.
## Configuration
```json
{
"capabilities": ["tool_search"]
}
```
The activation threshold defaults to 15 tools (`DEFAULT_TOOL_SEARCH_THRESHOLD`). Both the threshold and a never-defer allowlist can be set via capability config:
```json
{
"capabilities": {
"tool_search": {
"threshold": 20,
"never_defer": ["read_file", "write_file", "edit_file", "list_directory", "grep_files", "bash"]
}
}
}
```
## Benchmarks
Deferral only touches how tool *parameter schemas* reach the model, names and descriptions still go out in full, so the savings scale with how many tools an agent carries and how rich their schemas are.
Measured on a representative 19-tool generic-agent surface (file, shell, web-fetch, session, storage, todo, time, scheduling, and subagent tools, plus `tool_search` itself), comparing the serialized tool list the driver sends to the model **with and without** deferral on the first turn:
| Metric | Full schemas | Deferred (first turn) | Saving |
| --------------------------- | ------------------------- | ----------------------- | --------------- |
| Tool list sent to model | \~9.1 KB (\~2,270 tokens) | \~3.0 KB (\~740 tokens) | **67% smaller** |
| Parameter-schema bytes only | \~7.2 KB | \~1.0 KB | **86% smaller** |
Token figures use the \~4-chars-per-token rule of thumb for JSON. 18 of the 19 tools were deferred (`tool_search` keeps its schema). Net savings grow with tool count: an agent with dozens of MCP tools defers proportionally more.
These numbers come from the `benchmark_prompt_size_reduction` test in `crates/builtins/src/tool_search.rs`, which also guards the reduction against regressions. Reproduce them with:
```bash
cargo test -p everruns-builtins --lib benchmark_prompt_size_reduction -- --nocapture
```
The trade-off is one extra `tool_search` round-trip per deferred tool before its first use; for many-tool agents the upfront token savings dominate.
## Limitations
* **Server-executed tools**: the search reads schemas from the worker-side tool registry. This includes built-in tools and MCP server tools (MCP tools are registered as first-class registry tools). Client-side tools that are not registered worker-side are not returned by `tool_search` (their stripped definition is still sent so the model knows they exist).
* **Extra round-trip**: loading a schema costs one `tool_search` call before the first use of a deferred tool. The token savings outweigh this for agents with many tools.
## See Also
* [Auto Tool Search](https://docs.everruns.com/capabilities/auto-tool-search/), model-adaptive default (hosted where available, this client-side path elsewhere)
* [OpenAI Tool Search](https://docs.everruns.com/capabilities/openai-tool-search/), hosted deferred loading for GPT-5.4+
* [Claude Tool Search](https://docs.everruns.com/capabilities/claude-tool-search/), hosted deferred loading for Claude 4+
* [Capabilities Overview](https://docs.everruns.com/capabilities/)
---
# Auto-Continue After Usage Limit
> When an LLM subscription/plan usage limit is reached, automatically resume the interrupted work shortly after the limit resets.
Source:
| | |
| ---------------- | --------------------------- |
| **ID** | `usage_limit_auto_continue` |
| **Category** | Core |
| **Features** | None |
| **Dependencies** | None |
| **Risk** | Low |
Some providers cap usage per subscription window rather than per minute. When a plan usage limit is hit, for example the ChatGPT/Codex `429` `usage_limit_reached` response, the turn fails and the session goes idle until the limit resets, often hours later. This capability makes the session resume on its own once the window clears, so long-running work is not silently stranded.
It contributes **no tools**. The behavior is encapsulated behind a reusable platform boundary, the capability supplies an *LLM error hook* (an in-process capability hook, the same family as tool-call hooks and message filters) that the agent runtime invokes generically when a turn fails with a terminal error. The runtime has no special-casing for usage limits; any capability can provide the same kind of error-recovery hook.
## How It Works
1. **Decision**: The provider error is classified as `provider_usage_limit_reached`, which captures the absolute reset time (`resets_at`, unix seconds) reported by the provider. This is driver-agnostic: any driver whose error body carries the `usage_limit_reached` wording is covered.
2. **Scheduling**: When the capability is enabled and a reset time is present, a one-shot session schedule is created to fire `delay_seconds` after the reset. When it fires, the configured `prompt` is injected as a user message and the interrupted work resumes.
3. **Message copy**: The user-facing error reads *“You’re out of LLM usage limits. Your usage limit resets at \.”* When (and only when) a continuation was actually scheduled, it appends *“We’ll continue work automatically once it resets.”*, so the copy never promises a resumption that will not happen. Without the capability, the same error stays generic and no continuation is scheduled.
## Configuration
### Default
```json
{
"capabilities": ["usage_limit_auto_continue"]
}
```
Defaults: continuation fires **120 seconds** after the reported reset with the prompt **“Continue tasks”**.
### Custom delay and prompt
```json
{
"capabilities": [
{
"ref": "usage_limit_auto_continue",
"config": {
"delay_seconds": 300,
"prompt": "Resume the migration where you left off."
}
}
]
}
```
| Field | Type | Default | Description |
| --------------- | ----------------- | ------------------ | ---------------------------------------------------------------------------------------------------------- |
| `delay_seconds` | integer (0–86400) | `120` | How long to wait after the reset before resuming. A small buffer avoids racing the provider’s reset clock. |
| `prompt` | string | `"Continue tasks"` | Message injected to resume work when the limit resets. |
## When To Enable
Use this capability for agents running long, autonomous, or scheduled work on provider plans that enforce usage-window limits, where a multi-hour stall would otherwise require a human to manually restart the session.
## Limitations
* Requires a provider whose error reports a concrete reset time; without one the error copy stays generic and no continuation is scheduled.
* The continuation is delivered through the session schedule machinery, so it requires both a session schedule store and a schedule poller in the deployment. The hosted platform provides both. The default embedded runtime provides neither, so this capability is **not** part of the runtime-safe preset, an embedder must wire a `SessionScheduleStore` (e.g. via a schedule-store factory) and run a poller for it to take effect; otherwise its error hook is a no-op and the error copy stays generic.
## See Also
* [Schedules](https://docs.everruns.com/capabilities/session-schedules/), the scheduling machinery the continuation reuses
* [Capabilities](https://docs.everruns.com/features/capabilities/), the capability model
---
# User Hooks
> Run user-authored shell commands at lifecycle and tool events. Block, mutate, or audit agent actions from outside the model.
Source:
| | |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| **ID** | `user_hooks` |
| **Category** | Automation |
| **Features** | None |
| **Dependencies** | None |
| **Risk** | High |
| **Spec** | [knowledge/runtime-resources/user-hooks.md](https://github.com/everruns/everruns/blob/main/knowledge/runtime-resources/user-hooks.md) |
User Hooks let you inject shell commands at six well-defined points in the agent execution lifecycle. The runtime hands each hook a structured JSON payload through environment variables (`$EVERRUNS_HOOK_PAYLOAD_JSON` plus a copy at `$EVERRUNS_HOOK_PAYLOAD_PATH` on the session VFS) and reads a structured decision back from stdout. (Delivery is via env vars rather than process stdin because the `bashkit_shell` interpreter runs the script in-process and exposes no process stdin.) Hooks can run silently (logging only), mutate the inputs they observe, or block the action outright.
This is the same pattern you’ll recognize from Claude Code hooks, Git hooks, and pre/post-tool middleware in agent SDKs, applied as a first-class capability so any agent can adopt it, any hook bundle can be shared across an organization, and every invocation lands in the audit log.
## When to enable
* **Security gates**: block any `bash` call matching `rm -rf /` before the sandbox sees it.
* **Format-on-write**: run `cargo fmt` or `prettier` after every `edit_file`.
* **Audit / observability**: POST every tool call to your SIEM.
* **Project bootstrapping**: at `session_start`, clone a repo, install deps, or seed a workspace.
* **CI-style validation**: at `turn_end`, run tests and surface failures back to the model on the next turn.
## Risk
High. The capability accepts arbitrary shell commands from config. Even though the commands run inside the session’s `bashkit_shell` sandbox (no host filesystem, no host network beyond the session’s egress policy), the assignment gate is admin-only, anyone who can configure this capability can run arbitrary code in the session sandbox on every agent action.
## Events
Six events. Two (`pre_tool_use`, `user_prompt_submit`) can block; the rest are advisory-only. **`pre_tool_use` and `post_tool_use` fire today**; the other four events ship their schema/validation here but their runtime wire-in lands in follow-up changes (see “What’s not yet wired” below).
| Event | Fires at | Can block? | Mutation surface |
| -------------------- | ------------------------------------ | ---------- | -------------------- |
| `session_start` | New session created | no | none |
| `user_prompt_submit` | User message accepted, before reason | yes | user message text |
| `pre_tool_use` | Each tool call, before execution | yes | `ToolCall.arguments` |
| `post_tool_use` | Each tool call, after execution | no | `ToolResult` |
| `turn_end` | Turn finishes | no | none |
| `session_end` | Session close/archive | no | none |
See [`knowledge/runtime-resources/user-hooks.md`](https://github.com/everruns/everruns/blob/main/knowledge/runtime-resources/user-hooks.md) for the per-event JSON payload shape and full block / mutate semantics.
## Configuration
The capability accepts two top-level fields:
```json
{
"capabilities": [
{
"ref": "user_hooks",
"config": {
"hooks": [ /* UserHookSpec entries */ ],
"disabled_contributions": [ /* HookId strings to mute */ ]
}
}
]
}
```
### `UserHookSpec`
```jsonc
{
"id": "fmt_after_edit", // optional; defaults to "{event}_{idx}"
"event": "post_tool_use",
"matcher": { "tool_name": "edit_file" }, // tool events only
"executor": {
"type": "bash",
"command": "scripts/fmt.sh",
"env": { "FMT_PROFILE": "ci" }
},
"timeout_ms": 5000, // 100..30_000
"on_error": "warn", // "block" | "allow" | "warn"
"description": "Run formatter after edit_file"
}
```
#### Matcher
The `matcher` block applies only to `pre_tool_use` and `post_tool_use`. Setting it on lifecycle events is rejected at validation time.
| Field | Meaning |
| ---------------- | ------------------------------------------------------ |
| `tool_name` | Exact tool name match |
| `tool_name_glob` | Restricted glob: `a\|b\|c` alternation or trailing `*` |
| `args_jsonpath` | Dot-path into `ToolCall.arguments` (e.g. `$.command`) |
| `match_regex` | Fires when extracted value matches this regex |
| `deny_regex` | Inverse, fires when extracted value matches this regex |
`match_regex` and `deny_regex` are mutually exclusive. Regex flavor is the Rust `regex` crate (no look-around, no backreferences).
#### `on_error`
What to do when the executor itself fails (timeout, non-JSON output, sandbox error):
* `block`, treat as `{"decision": "block", "reason": "hook failed"}`. Use for security-critical hooks.
* `allow`, log + continue.
* `warn`, log + emit `hook.warning` event + continue. **Default.**
## Bash hook contract
The bash executor is modeled after Claude Code hooks with one departure: the payload is delivered via env vars (and a session-VFS file) rather than stdin, because the bashkit interpreter doesn’t expose process-level stdin to user scripts.
### Input, env vars + VFS file
Every hook invocation sets these env vars:
| Var | Meaning |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `EVERRUNS_HOOK_PAYLOAD_JSON` | Full payload, JSON-encoded as one string. Read with `jq -n 'env.EVERRUNS_HOOK_PAYLOAD_JSON \| fromjson'`. |
| `EVERRUNS_HOOK_PAYLOAD_PATH` | Path on the session VFS to a file containing the same JSON. Cleaned up after the hook returns. Use for `cat "$EVERRUNS_HOOK_PAYLOAD_PATH" \| jq` workflows. |
| `EVERRUNS_HOOK_EVENT` | One of `session_start`, `user_prompt_submit`, `pre_tool_use`, `post_tool_use`, `turn_end`, `session_end`. |
| `EVERRUNS_HOOK_ID` | Stable hook id (`{capability_id}:{name}` or `user:{name}`). |
| `EVERRUNS_HOOK_SESSION_ID` | Current session id. |
| `EVERRUNS_HOOK_TURN_ID` | Set when a turn is in flight. |
| `EVERRUNS_HOOK_TOOL_NAME` | Set for `pre_tool_use` / `post_tool_use`. |
| `EVERRUNS_HOOK_TOOL_CALL_ID` | Set for `pre_tool_use` / `post_tool_use`. |
Payload envelope:
```json
{
"event": "pre_tool_use",
"hook_id": "user:guard_rm",
"session_id": "ses_…",
"turn_id": "trn_…",
"org_id": "org_…",
"agent_id": "agt_…",
"ts": "2026-05-28T12:34:56.789Z",
"data": {
"tool_name": "bash",
"tool_call_id": "call_…",
"arguments": { "command": "ls -la" }
}
}
```
### Output, stdout
Three accepted shapes, tried in this order:
1. **Empty stdout**: exit 0 = allow; non-zero = block (stderr surfaced as the block reason). This is the Git-hook escape hatch.
2. **JSON decision**: stdout starts with `{`:
```json
{
"decision": "allow" | "mutate" | "block",
"reason": "string shown in audit/UI",
"user_message": "string surfaced to the user when blocking",
"patch": { /* event-specific mutation */ }
}
```
3. **Anything else** → executor error → `on_error` policy applies.
### Limits
* **Timeout**: configurable per hook; default 5 s, max 30 s.
* **Output size**: 64 KiB total (stdout + stderr).
* **Sandbox**: runs through `bashkit_shell` against the session VFS. No host shell. Inherits the session’s egress policy.
* **stderr**: captured into the audit log; never shown to the model unless `decision == "block"` with no `reason`.
## Composition
Hooks chain in capability-declaration order, then array order within each capability. The first `block` decision wins; mutations from earlier hooks survive even if a later hook blocks.
Capabilities other than `user_hooks` can ship hook bundles, see [Hook bundles from other capabilities](#hook-bundles-from-other-capabilities). To mute a bundled hook, list its `HookId` under `disabled_contributions`:
```json
{
"capabilities": [
{ "ref": "rust_quality_pack" },
{ "ref": "user_hooks", "config": {
"disabled_contributions": ["rust_quality_pack:fmt_after_edit"]
} }
]
}
```
`HookId` format:
* Capability contribution: `{capability_id}:{name}`
* User config: `user:{name}`
## Hook bundles from other capabilities
Any built-in capability can contribute hook specs to your agent by overriding `Capability::user_hooks_with_config` and returning a list of `UserHookSpec`s. The `guarded-bash-demo` seed agent is a live example: its `user_hooks` capability config (in [`crates/server/src/seed.rs`](https://github.com/everruns/everruns/blob/main/crates/server/src/seed.rs)) ships a `pre_tool_use` hook that refuses destructive `rm -rf` invocations before the bash tool ever runs, with no extra setup.
Capability-contributed hooks ride the trust gate of enabling the contributing capability, so admin assignment rules still apply. Operators can mute any individual contribution via `disabled_contributions` on a sibling `user_hooks` capability config.
> Declarative-capability hook bundles (one POST to register, many agents to reuse) are on the roadmap and are not yet wired into the declarative capability schema. See [`knowledge/runtime-resources/user-hooks.md`](https://github.com/everruns/everruns/blob/main/knowledge/runtime-resources/user-hooks.md) for the deferred contract.
See [`examples/hook-bundles/`](https://github.com/everruns/everruns/tree/main/examples/hook-bundles) for ready-to-paste user-config bundle JSON.
## Examples
### Block `rm -rf` from the bash tool
```json
{
"capabilities": [
{
"ref": "bashkit_shell"
},
{
"ref": "user_hooks",
"config": {
"hooks": [
{
"id": "guard_rm",
"event": "pre_tool_use",
"matcher": {
"tool_name": "bash",
"args_jsonpath": "$.commands",
"deny_regex": "(?:^|;|&&|\\|)\\s*rm\\s+-rf\\b"
},
"executor": {
"type": "bash",
"command": "echo '{\"decision\":\"block\",\"reason\":\"rm -rf is not allowed\",\"user_message\":\"That command is blocked by policy.\"}'"
},
"on_error": "block",
"description": "Reject destructive rm invocations"
}
]
}
}
]
}
```
### Format Rust files after every edit
```json
{
"capabilities": [
{ "ref": "session_file_system" },
{ "ref": "bashkit_shell" },
{
"ref": "user_hooks",
"config": {
"hooks": [
{
"id": "fmt_rs",
"event": "post_tool_use",
"matcher": {
"tool_name": "edit_file",
"args_jsonpath": "$.path",
"match_regex": "\\.rs$"
},
"executor": {
"type": "bash",
"command": "cargo fmt --check 2>&1 || cargo fmt"
},
"on_error": "warn"
}
]
}
}
]
}
```
### Seed a workspace on session start
```json
{
"capabilities": [
{ "ref": "session_file_system" },
{ "ref": "bashkit_shell" },
{
"ref": "user_hooks",
"config": {
"hooks": [
{
"id": "init",
"event": "session_start",
"executor": {
"type": "bash",
"command": "echo 'session bootstrapped' > /workspace/.bootstrap && echo '{}'"
},
"on_error": "warn",
"description": "Drop a sentinel file the agent can read"
}
]
}
}
]
}
```
### Block a user prompt that pastes a secret
`user_prompt_submit` is the only lifecycle event that can **block**: a `block` decision aborts the turn before the LLM runs. The original prompt text arrives in `data.message`. Here the hook reads it, and if it looks like a pasted private key, rejects the turn with a message shown to the user. `on_error: "block"` makes the hook fail closed.
```json
{
"capabilities": [
{ "ref": "bashkit_shell" },
{
"ref": "user_hooks",
"config": {
"hooks": [
{
"id": "block_secrets_in_prompt",
"event": "user_prompt_submit",
"executor": {
"type": "bash",
"command": "msg=$(echo \"$EVERRUNS_HOOK_PAYLOAD_JSON\" | jq -r '.data.message'); if printf '%s' \"$msg\" | grep -qiE 'BEGIN (RSA|OPENSSH|EC|DSA) PRIVATE KEY'; then echo '{\"decision\":\"block\",\"reason\":\"prompt contains a private key\",\"user_message\":\"Your message looks like it contains a private key and was blocked. Remove the secret and resend.\"}'; else echo '{}'; fi"
},
"timeout_ms": 5000,
"on_error": "block",
"description": "Reject prompts that paste a private key"
}
]
}
}
]
}
```
`user_prompt_submit` can also **mutate** the prompt instead of blocking, emit `{"decision":"mutate","patch":{"message":""}}` and the turn proceeds with the rewritten text. For example, to prepend a house style reminder:
```jsonc
{
"id": "prepend_style_note",
"event": "user_prompt_submit",
"executor": {
"type": "bash",
"command": "echo \"$EVERRUNS_HOOK_PAYLOAD_JSON\" | jq -c '{decision:\"mutate\",patch:{message:(\"[reminder: follow the house style guide]\\n\" + .data.message)}}'"
},
"on_error": "warn"
}
```
### Log every completed turn
`turn_end` is advisory, its decision is ignored, so use it for side effects like metrics or audit trails. The payload’s `data.success` reports whether the turn finished cleanly.
```json
{
"capabilities": [
{ "ref": "session_file_system" },
{ "ref": "bashkit_shell" },
{
"ref": "user_hooks",
"config": {
"hooks": [
{
"id": "log_turn_end",
"event": "turn_end",
"executor": {
"type": "bash",
"command": "echo \"$EVERRUNS_HOOK_PAYLOAD_JSON\" | jq -r '.ts + \" turn \" + .turn_id + \" success=\" + (.data.success | tostring)' >> /workspace/.turn-log; echo '{}'"
},
"timeout_ms": 3000,
"on_error": "warn",
"description": "Append one line per completed turn to /workspace/.turn-log"
}
]
}
}
]
}
```
## Observability
Today, hook decisions and errors are recorded in the server logs via `tracing`: blocks, ignored post-hook blocks, mutations, `on_error` outcomes, and `disabled_contributions` mutings are logged with the resolved `hook_id` and the `tool_call_id`.
**Planned (deferred):** structured `hook.invoked` / `hook.completed` / `hook.blocked` / `hook.warning` events emitted through the same observability pipeline as tool events (Braintrust, OTel). These are not emitted yet, see the spec’s observability section.
## Event firing
All six events fire:
* **`pre_tool_use`**: before each tool call; can block or mutate the call.
* **`post_tool_use`**: after each tool call; can mutate the result.
* **`session_start`**: after a session is created (mounts + initial files in place); advisory.
* **`session_end`**: when a session is deleted, before VFS eviction; advisory.
* **`user_prompt_submit`**: on the first reason iteration, before the LLM is consulted; can block (aborts the turn with a user-facing message) or mutate the user message text.
* **`turn_end`**: when a turn reaches a terminal outcome; advisory.
Note on `user_prompt_submit` blocking: the API persists the user message and runs the turn asynchronously, so a block does not reject the HTTP request, it aborts the turn. The session shows a `turn.failed` outcome carrying the hook’s `user_message`.
## See also
* [`knowledge/runtime-resources/user-hooks.md`](https://github.com/everruns/everruns/blob/main/knowledge/runtime-resources/user-hooks.md), full contract
* [`knowledge/execution/capabilities.md`](https://github.com/everruns/everruns/blob/main/knowledge/execution/capabilities.md), capability framework
* [`knowledge/security/threat-model.md`](https://github.com/everruns/everruns/blob/main/knowledge/security/threat-model.md), TM-HOOK entries
* [Bashkit Shell](https://docs.everruns.com/capabilities/bashkit-shell/), the sandbox that runs hook commands
---
# Web Fetch
> Fetch a URL and convert its HTML to markdown.
Source:
| | |
| ---------------- | ----------- |
| **ID** | `web_fetch` |
| **Category** | Network |
| **Features** | None |
| **Dependencies** | None |
Fetch content from URLs and convert HTML to markdown or plain text. Powered by [FetchKit](https://github.com/everruns/fetchkit) with built-in SSRF protection, low-noise extraction, bounded crawl discovery, and structured fetchers for common developer and research sources.
## Tools
### `web_fetch`
Fetch a URL and return its content.
| Parameter | Type | Required | Description |
| ------------------- | ------- | -------- | ---------------------------------------------------------------------- |
| `url` | string | yes | URL to fetch |
| `method` | string | no | HTTP method (default: `GET`) |
| `as_markdown` | boolean | no | Convert HTML to markdown |
| `as_text` | boolean | no | Convert HTML to plain text |
| `content_focus` | string | no | Extraction mode: `full`, `main`, `readable`, or `agent` |
| `crawl` | boolean | no | Discover and fetch a bounded set of same-origin pages |
| `max_pages` | integer | no | Maximum crawl pages, including the seed (default: 5, maximum: 20) |
| `if_none_match` | string | no | ETag for a conditional request |
| `if_modified_since` | string | no | Last-Modified value for a conditional request |
| `save_to_file` | string | no | Workspace destination when file download is enabled for the capability |
Returns: content body, status code, metadata, quality signals, redirect history, and crawl summaries when requested.
## Notes
* **Timeouts**: 1s for first byte, 30s for body. Partial content returned on body timeout.
* **Binary content**: Images, PDFs, etc. return metadata only (content type, size), not the binary data.
* **Focused extraction**: Use `content_focus: "agent"` for FetchKit’s lowest-noise extraction strategy.
* **No JavaScript rendering**: Web Fetch returns the server’s response as delivered. Pages that build their content client-side need a browser capability such as [Browserless](https://docs.everruns.com/capabilities/browserless/).
* **Crawl scope**: Crawl discovery stays on the seed URL’s origin and enforces FetchKit’s page limit.
* **SSRF protection**: Private IPs (loopback, RFC1918, link-local, CGNAT) are blocked by default with DNS pinning to prevent rebinding attacks.
* **Excessive newlines**: Automatically filtered from converted content.
## See Also
* [File System](https://docs.everruns.com/capabilities/file-system/), save fetched content to workspace
* [Capabilities Overview](https://docs.everruns.com/capabilities/)
---
# Bashkit
> Run agent shell commands inside a virtual Bash interpreter with sandboxed filesystems, resource limits, network controls, and async execution.
Source:
[Bashkit](https://github.com/everruns/bashkit) is a virtual Bash interpreter written in Rust. It provides sandboxed, in-process execution with no real filesystem access by default, purpose-built for running untrusted bash scripts in multi-tenant agent environments.
## Why Bashkit?
Agents need shell access to be effective, installing packages, running builds, inspecting files. But spawning real bash processes in a shared environment creates isolation, security, and resource control problems. Bashkit solves this by interpreting bash in-process against a virtual filesystem, giving agents a full shell experience without host access.
## Core Capabilities
* **POSIX-compliant shell language**: variables, parameter expansion, command substitution, arithmetic, pipelines, redirections, control flow, functions, arrays, globs, here-documents
* **85 built-in commands**: core I/O (`echo`, `cat`, `printf`), navigation (`cd`, `ls`, `find`), text processing (`grep`, `sed`, `awk`, `jq`, `sort`), file operations (`mkdir`, `rm`, `cp`, `mv`), archives (`tar`, `gzip`), network (`curl`, `wget` with domain allowlist, optional `http_client` feature), and more
* **Virtual filesystem**: pluggable backends: `InMemoryFs`, `OverlayFs`, `MountableFs`
* **Resource limits**: configurable caps on command count, loop iterations, and function call depth
* **Network allowlist**: HTTP requests via `curl`/`wget` require explicit per-domain authorization (optional `http_client` feature)
* **Async-native**: built on tokio
### Experimental Features
* **Git**: virtual git operations within the VFS (no host access)
* **Python**: embedded Monty interpreter (pure Rust, Python 3.12 compatible) with VFS bridging
## How Everruns Uses Bashkit
Everruns integrates bashkit as the execution backend for the **Bashkit Shell** agent capability. When an agent runs shell commands, they execute inside bashkit rather than a real shell.
Everruns compiles bashkit **without** the `http_client` feature, so the `curl`/`wget` network builtins listed above are not available inside sessions, the Bashkit Shell capability has no network access. Use the Web Fetch capability for HTTP.
### Session Filesystem Bridge
Bashkit’s pluggable filesystem trait lets Everruns bridge the interpreter directly to the session file store. Files created by other tools are immediately visible inside bash, and vice versa, no pre/post sync needed. Session files are mounted at `/workspace` in the bash environment.
* **Live file visibility**: files written by other tools during bash execution are immediately visible
* **No sync overhead**: eliminates pre/post execution sync of the entire filesystem
* **Memory efficiency**: files read on-demand instead of loading all into memory
* **Single source of truth**: consistent file state across all agent capabilities
### Resource Controls
Bashkit’s execution limits map directly to Everruns’ per-session resource constraints, preventing runaway scripts from consuming shared infrastructure. The network allowlist ensures agents can only reach explicitly authorized domains.
## Links
* [GitHub repository](https://github.com/everruns/bashkit)
---
# Event Reference
> Every Everruns event type with its schema and an SSE example: input, output, tool, lifecycle, and error events.
Source:
This page documents all event types in the Everruns event protocol.
## Input Events
### input.message
Emitted when a user message is submitted to the session.
| Field | Type | Description |
| --------- | ------- | ----------------------- |
| `message` | Message | The user message object |
```json
{
"type": "input.message",
"data": {
"message": {
"id": "message_...",
"role": "user",
"content": [{"type": "text", "text": "Hello!"}],
"created_at": "2024-01-15T10:30:00.000Z"
}
}
}
```
## Output Events
### output.message.started
Emitted when the LLM starts generating a response. This marks the start of generation, not model reasoning: reasoning has its own events (see [Reasoning Events](#reasoning-events)) and its own channel.
| Field | Type | Description |
| --------- | ------- | ----------------------------------------------------------------------------------------------------- |
| `turn_id` | string | Turn ID this output belongs to |
| `model` | string? | Optional model name being used |
| `phase` | string? | Best-effort phase hint: `commentary` or `final_answer`. Absent means unclassified, never “reasoning”. |
```json
{
"type": "output.message.started",
"data": {
"turn_id": "turn_...",
"model": "gpt-5.2"
}
}
```
### output.message.delta
Incremental text update during LLM generation. Events are batched (\~100ms).
| Field | Type | Description |
| ------------- | ------ | ----------------------------- |
| `turn_id` | string | Turn ID this delta belongs to |
| `delta` | string | New text since last delta |
| `accumulated` | string | Total text so far |
```json
{
"type": "output.message.delta",
"data": {
"turn_id": "turn_...",
"delta": "Hello",
"accumulated": "Hello"
}
}
```
### output.message.completed
Emitted when the agent response is complete.
| Field | Type | Description |
| ---------- | -------------- | -------------------------- |
| `message` | Message | The complete agent message |
| `metadata` | ModelMetadata? | Model information |
| `usage` | TokenUsage? | Token usage statistics |
`message.phase` is authoritative for whether this message is intermediate `commentary` or the turn’s `final_answer`, and `message.phase_source` says whether the provider reported that phase (`provider`) or the runtime inferred it from tool-call presence (`derived`). Reasoning artifacts appear as `reasoning` content parts inside `message.content`, in the order the provider emitted them.
```json
{
"type": "output.message.completed",
"data": {
"message": {
"id": "message_...",
"role": "assistant",
"content": [{"type": "text", "text": "Hello! How can I help?"}]
},
"usage": {
"input_tokens": 50,
"output_tokens": 25
}
}
}
```
## Turn Lifecycle Events
### turn.started
Emitted when a turn begins execution.
| Field | Type | Description |
| ------------------ | ------- | -------------------------------- |
| `turn_id` | string | Turn identifier |
| `input_message_id` | string | Message that triggered this turn |
| `input_content` | string? | Optional input content preview |
```json
{
"type": "turn.started",
"data": {
"turn_id": "turn_...",
"input_message_id": "message_...",
"input_content": "Hello!"
}
}
```
### turn.completed
Emitted when a turn completes successfully.
| Field | Type | Description |
| ------------------------ | ----------- | ------------------------------------ |
| `turn_id` | string | Turn identifier |
| `iterations` | integer | Number of reason-act iterations |
| `duration_ms` | integer? | Duration in milliseconds |
| `usage` | TokenUsage? | Aggregated token usage |
| `input_content` | string? | Optional input content |
| `final_message_id` | string? | Canonical final assistant message ID |
| `final_answer_preview` | string? | Bounded final answer preview |
| `time_to_first_token_ms` | integer? | First-token latency |
| `tool_call_count` | integer? | Completed tool-call count |
| `llm_call_count` | integer? | LLM generation count |
| `status` | string? | Optional completion status |
```json
{
"type": "turn.completed",
"data": {
"turn_id": "turn_...",
"iterations": 3,
"duration_ms": 1500,
"usage": {
"input_tokens": 500,
"output_tokens": 200
},
"final_message_id": "message_...",
"final_answer_preview": "Done.",
"time_to_first_token_ms": 120,
"tool_call_count": 2,
"llm_call_count": 3,
"status": "completed"
}
}
```
### turn.failed
Emitted when a turn fails with an error.
| Field | Type | Description |
| ------------ | ------- | ------------------- |
| `turn_id` | string | Turn identifier |
| `error` | string | Error message |
| `error_code` | string? | Optional error code |
```json
{
"type": "turn.failed",
"data": {
"turn_id": "turn_...",
"error": "Rate limit exceeded",
"error_code": "RATE_LIMIT"
}
}
```
### turn.cancelled
Emitted when a turn is cancelled by the user.
| Field | Type | Description |
| --------- | ----------- | ------------------------- |
| `turn_id` | string | Turn identifier |
| `reason` | string? | Cancellation reason |
| `usage` | TokenUsage? | Usage before cancellation |
```json
{
"type": "turn.cancelled",
"data": {
"turn_id": "turn_...",
"reason": "User requested",
"usage": {
"input_tokens": 100,
"output_tokens": 50
}
}
}
```
## Reasoning Events
Model reasoning, as distinct from the `reason.started` / `reason.completed` lifecycle events further down, which mark an LLM *inference step* in the reason/act loop and are unrelated to whether the model reasoned.
These events are emitted by models that expose reasoning (Anthropic Claude with thinking enabled, OpenAI GPT-5.x and o-series with reasoning effort configured, Gemini with a thinking budget, and Chat Completions models that return `reasoning_content`). Everything here belongs to the reasoning channel and must never be rendered as assistant text.
### reason.thinking.started
Emitted when extended thinking begins.
| Field | Type | Description |
| --------- | ------- | ----------- |
| `turn_id` | string | Turn ID |
| `model` | string? | Model name |
```json
{
"type": "reason.thinking.started",
"data": {
"turn_id": "turn_...",
"model": "claude-4-opus"
}
}
```
### reason.thinking.delta
Streams incremental thinking content.
| Field | Type | Description |
| ------------- | ------ | --------------------- |
| `turn_id` | string | Turn ID |
| `delta` | string | New thinking text |
| `accumulated` | string | Total thinking so far |
```json
{
"type": "reason.thinking.delta",
"data": {
"turn_id": "turn_...",
"delta": "Let me think about this...",
"accumulated": "Let me think about this..."
}
}
```
### reason.thinking.completed
Emitted when extended thinking completes.
| Field | Type | Description |
| ---------- | ------ | ------------------------- |
| `turn_id` | string | Turn ID |
| `thinking` | string | Complete thinking content |
```json
{
"type": "reason.thinking.completed",
"data": {
"turn_id": "turn_...",
"thinking": "I need to consider the user's request carefully..."
}
}
```
### reason.item
Emitted when one reasoning artifact completes. One event per provider reasoning block, in emission order.
Carries identity and safe summary text only. The opaque payloads that make the artifact replayable — provider signatures and encrypted reasoning context — are deliberately excluded: they are replay state, not content, and never appear in events or on any API surface.
| Field | Type | Description |
| ------------- | --------- | -------------------------------------------------------------- |
| `turn_id` | string | Turn ID this artifact belongs to |
| `provider` | string | Provider that produced it (`anthropic`, `openai`, `google`) |
| `model` | string? | Model reported by the provider |
| `item_id` | string | Provider-assigned identifier, when the provider issues one |
| `summary` | string\[] | Provider-curated summary segments. Never raw chain-of-thought. |
| `token_count` | integer? | Reasoning tokens attributed to this artifact |
```json
{
"type": "reason.item",
"data": {
"turn_id": "turn_...",
"provider": "openai",
"model": "gpt-5.2",
"item_id": "rs_68a1f...",
"summary": ["Checking the build logs before answering."],
"token_count": 412
}
}
```
## Atom Lifecycle Events
These events mark steps of the reason/act execution loop. `reason.*` here means “the LLM inference step”, not model reasoning — for that see [Reasoning Events](#reasoning-events).
### reason.started
Emitted when LLM inference begins.
| Field | Type | Description |
| ---------- | -------------- | ----------------- |
| `agent_id` | string | Agent ID |
| `metadata` | ModelMetadata? | Model information |
```json
{
"type": "reason.started",
"data": {
"agent_id": "agent_...",
"metadata": {
"model": "gpt-5.2"
}
}
}
```
### reason.completed
Emitted when LLM inference completes.
| Field | Type | Description |
| ----------------- | ----------- | ---------------------------- |
| `success` | boolean | Whether the call succeeded |
| `text_preview` | string? | First 200 chars of response |
| `has_tool_calls` | boolean | Whether tools were requested |
| `tool_call_count` | integer | Number of tool calls |
| `error` | string? | Error if failed |
| `duration_ms` | integer? | Duration |
| `usage` | TokenUsage? | Token usage |
```json
{
"type": "reason.completed",
"data": {
"success": true,
"text_preview": "Hello! I can help you with...",
"has_tool_calls": false,
"tool_call_count": 0,
"duration_ms": 1200,
"usage": {
"input_tokens": 100,
"output_tokens": 50
}
}
}
```
### act.started
Emitted when tool execution batch begins.
| Field | Type | Description |
| ------------ | ------------------ | -------------------- |
| `tool_calls` | ToolCallSummary\[] | Tools to be executed |
```json
{
"type": "act.started",
"data": {
"tool_calls": [
{"id": "tc_1", "name": "get_weather"},
{"id": "tc_2", "name": "search_web"}
]
}
}
```
### act.completed
Emitted when tool execution batch completes.
| Field | Type | Description |
| --------------- | -------- | --------------------- |
| `completed` | boolean | All tools completed |
| `success_count` | integer | Successful tool calls |
| `error_count` | integer | Failed tool calls |
| `duration_ms` | integer? | Total duration |
```json
{
"type": "act.completed",
"data": {
"completed": true,
"success_count": 2,
"error_count": 0,
"duration_ms": 500
}
}
```
### tool.started
Emitted when individual tool execution begins.
| Field | Type | Description |
| ----------- | -------- | ----------------------------- |
| `tool_call` | ToolCall | Full tool call with arguments |
```json
{
"type": "tool.started",
"data": {
"tool_call": {
"id": "tc_1",
"name": "get_weather",
"arguments": {"city": "London"}
}
}
}
```
### tool.completed
Emitted when individual tool execution completes.
| Field | Type | Description |
| -------------- | --------------- | ------------------------------------------ |
| `tool_call_id` | string | Tool call ID |
| `tool_name` | string | Tool name |
| `success` | boolean | Whether it succeeded |
| `status` | string | ”success”, “error”, “timeout”, “cancelled” |
| `result` | ContentPart\[]? | Result content |
| `error` | string? | Error message |
| `duration_ms` | integer? | Duration |
```json
{
"type": "tool.completed",
"data": {
"tool_call_id": "tc_1",
"tool_name": "get_weather",
"success": true,
"status": "success",
"result": [{"type": "text", "text": "Sunny, 22°C"}],
"duration_ms": 250
}
}
```
## LLM Events
### llm.generation
Full visibility into LLM API calls. Emitted after each call.
| Field | Type | Description |
| ---------- | ------------------------ | -------------------- |
| `messages` | Message\[] | Messages sent to LLM |
| `tools` | ToolDefinitionSummary\[] | Available tools |
| `output` | LlmGenerationOutput | LLM response |
| `metadata` | LlmGenerationMetadata | Call metadata |
```json
{
"type": "llm.generation",
"data": {
"messages": [...],
"tools": [{"name": "get_weather", "description": "..."}],
"output": {
"text": "Hello!",
"tool_calls": []
},
"metadata": {
"model": "gpt-5.2",
"provider": "openai",
"usage": {"input_tokens": 100, "output_tokens": 50},
"duration_ms": 1200,
"time_to_first_token_ms": 150,
"success": true,
"finish_reasons": ["stop"]
}
}
}
```
## Session Events
### session.started
Emitted when a session begins.
| Field | Type | Description |
| ---------- | ------- | --------------------- |
| `agent_id` | string | Agent ID |
| `model_id` | string? | Model ID if specified |
```json
{
"type": "session.started",
"data": {
"agent_id": "agent_..."
}
}
```
### session.activated
Emitted when a session becomes active (turn started).
| Field | Type | Description |
| ------------------ | ------ | --------------------------- |
| `turn_id` | string | Turn that activated session |
| `input_message_id` | string | Triggering message |
```json
{
"type": "session.activated",
"data": {
"turn_id": "turn_...",
"input_message_id": "message_..."
}
}
```
### session.idled
Emitted when a session becomes idle (turn completed).
| Field | Type | Description |
| ------------ | ----------- | ------------------------ |
| `turn_id` | string | Completed turn |
| `iterations` | integer? | Iterations in turn |
| `usage` | TokenUsage? | Cumulative session usage |
```json
{
"type": "session.idled",
"data": {
"turn_id": "turn_...",
"iterations": 3,
"usage": {
"input_tokens": 1500,
"output_tokens": 800
}
}
}
```
## Subagent Events (retired)
The `subagent.spawned`, `subagent.completed`, `subagent.failed`, and `subagent.cancelled` events have been **retired**. The subagent flow is now modeled as Session Tasks, which emit `task.*` lifecycle events (`task.created`, `task.updated`, `task.message.sent`, `task.message.received`) on the parent session instead. New sessions never emit `subagent.*`.
These event types are no longer produced or part of the supported contract. Historical `subagent.*` events recorded in older session logs remain in storage, but are filtered out of the events and SSE APIs like any unsupported type, they are not returned to consumers (aggregate counters such as `error_count` still include them). Consumers should read `task.*` events going forward.
## Supporting Types
### TokenUsage
Token consumption statistics.
| Field | Type | Description |
| ----------------------- | -------- | ----------------------------------- |
| `input_tokens` | integer | Input/prompt tokens |
| `output_tokens` | integer | Output/completion tokens |
| `cache_read_tokens` | integer? | Tokens read from cache |
| `cache_creation_tokens` | integer? | Tokens written to cache (Anthropic) |
### ModelMetadata
Information about the model used.
| Field | Type | Description |
| ------------- | ------- | ---------------------------- |
| `model` | string | Model name (e.g., “gpt-5.2”) |
| `model_id` | string? | Internal model ID |
| `provider_id` | string? | Internal provider ID |
### ToolCallSummary
Compact tool call representation.
| Field | Type | Description |
| ------ | ------ | ------------ |
| `id` | string | Tool call ID |
| `name` | string | Tool name |
### ToolCall
Full tool call with arguments.
| Field | Type | Description |
| ----------- | ------ | --------------------- |
| `id` | string | Tool call ID |
| `name` | string | Tool name |
| `arguments` | object | Tool arguments (JSON) |
---
# Explanation
> Background on Everruns, why durable execution, why events are the primary store, how the agentic loop and capability layering fit together.
Source:
These pages discuss *why* Everruns is shaped the way it is. They don’t tell you how to do anything (see [How-to guides](https://docs.everruns.com/how-to/)) and they aren’t lookup tables (see [Reference](https://docs.everruns.com/api/)). They’re here to give you a mental model so the other docs make sense.
Read these when:
* You’re evaluating Everruns and want to understand the design.
* A reference page tells you *what* something is but you want to know *why* it exists.
* You’re about to make an architectural decision and want to know which guarantees you can rely on.
## Topics
* [Core concepts](https://docs.everruns.com/explanation/concepts/), the entity model: harnesses, agents, sessions, capabilities, and how they compose into a runtime.
* [The agentic loop](https://docs.everruns.com/explanation/agentic-loop/), the reason–act cycle, execution phases, and why turns are bounded.
* [Architecture](https://docs.everruns.com/explanation/architecture/), control plane, workers, and the API-first design.
* [Durable execution](https://docs.everruns.com/explanation/durable-execution/), why agents survive crashes, and the trade-offs of a PostgreSQL-backed engine.
* [Events as the primary store](https://docs.everruns.com/explanation/events/), why an append-only event log is the source of truth, not a side-channel.
---
# The agentic loop
> Why agent execution is a bounded reason–act cycle, what execution phases mean, and how multi-step tool flows stay coherent across turns.
Source:
An “agent” is a loop, not a function call. This page explains the loop Everruns runs and why it’s shaped the way it is.
## Reason, then act
Each **turn** is one iteration of:
1. **Reason.** Send the full conversation history (system prompt + messages + previous tool results) to the LLM. The model either produces text or requests tool calls.
2. **Act.** If the model requested tool calls, execute them in parallel. The results become new messages in the conversation.
3. **Loop.** If there were tool calls, go back to step 1. If the model produced a final text response, the turn completes.
A turn is capped at **10 iterations** by default. That cap exists for one reason: a misbehaving prompt can drive the model into an infinite tool-calling spiral, and an unbounded loop costs real money in tokens. The cap is configurable per session.
## Why parallel act?
The model often emits several tool calls in one response, “read these three files” or “fetch these two URLs”. Executing them sequentially would serialize independent work for no reason. Everruns runs all tool calls from a single reason step concurrently and only re-enters reason once every call has completed (or failed).
This means tool implementations cannot assume ordering between calls within a single act phase. If two tools must run in order, the model has to emit them across two turns.
## Execution phases
When an assistant message includes tool calls, the model hasn’t given a final answer yet, it’s just narrating its plan. When the message has no tool calls, that’s the final answer.
Everruns labels each assistant message with an **execution phase**: `Commentary` (intermediate, before/between tool calls) or `FinalAnswer` (completed response). Two consumers care:
* **The model.** Some providers (OpenAI Responses API on the GPT-5.4 and GPT-5.5 families) accept and return phase annotations on replayed history. Without them, models can mistake earlier commentary for completed answers and stop early on long flows.
* **The UI.** Phase tells the chat surface whether to keep the “thinking” indicator on or render the message as a final response.
Phases are derived from message state (presence of tool calls) and stored on the message. Providers that don’t accept phases on the wire still get accurate internal tracking.
## Turn lifecycle
Every turn emits these events in order:
```plaintext
turn.started
reason.started → reason.completed (one LLM call)
act.started → act.completed (zero or more tool calls)
... (repeat reason/act until the model produces a final answer or max iterations) ...
turn.completed | turn.failed | turn.cancelled
```
Streaming text and tool calls produce `output.message.delta` and `tool.started` / `tool.completed` events in between. The full event catalog is in the [Event Reference](https://docs.everruns.com/event-reference/).
## Why turns are durable, not in-memory
A naïve implementation of the loop would hold all turn state in worker memory. Everruns doesn’t, because a worker crash mid-turn would lose work the user already paid for.
Instead each step (reason, each tool call) is a separate durable task. The worker persists state after every step. If a worker crashes between steps, the control plane detects the missed heartbeat and re-queues the next task on a different worker. From the application’s perspective: a brief delay, then the stream continues. No retry button required.
This trade-off, paying for a database write on every step, is what makes Everruns a *durable* agentic harness rather than a thin LLM wrapper. See [Durable execution](https://docs.everruns.com/explanation/durable-execution/).
## What happens when the loop “gets stuck”
Three failure modes show up in practice:
* **Runaway tool calls.** The model keeps calling tools without converging. Mitigated by the iteration cap; the turn fails with `turn.failed` once the cap is hit.
* **A tool that hangs.** Tool calls have configurable timeouts. The act phase reports the failure as a tool result so the model can recover on the next reason step.
* **The model rejects the prompt as too large.** Context [compaction](https://docs.everruns.com/advanced/compaction/) runs reactively and the request is retried. The conversation continues with older messages compressed.
In all three cases the session stays usable. You don’t lose the conversation; you lose at most one turn.
---
# Architecture
> How Everruns is structured, control plane, workers, REST API, and the design choices behind the API-first, horizontally-scalable, headless approach.
Source:
Everruns is **API-first** and **headless**. The web UI is optional, the SDKs are optional, and the entire platform is reachable through a documented REST API. This page explains why.
## Two processes, one database
A running Everruns deployment is two kinds of process plus PostgreSQL:
* The **control plane** exposes the REST API, owns auth, serves SSE event streams, and persists all state in PostgreSQL.
* **Workers** execute the agentic loop. They claim durable tasks, call LLMs, run tools, and report results back. They hold no long-lived state, restart any worker at any time and nothing breaks.

The control plane scales vertically and is fronted by a load balancer. Workers scale horizontally, add more for throughput, remove some to save cost. PostgreSQL is the only piece of stateful infrastructure.
## Why a separate worker tier?
You could run the agentic loop in the API server process and avoid the extra hop. Everruns doesn’t, for three reasons:
1. **LLM calls are long.** A single turn easily spends 10–60 seconds in network I/O. Tying that to the request thread caps API throughput and makes deploys painful.
2. **Failure isolation.** A misbehaving tool that hangs the worker process should not take down the API. They’re separate concerns and they get separate processes.
3. **Durability.** Workers can crash and the task gets reclaimed by another worker. Tying execution to a request would lose the work the moment the connection drops.
The control plane and workers talk over gRPC inside your VPC. End users only ever see the REST API.
## Why headless
Most agent platforms ship a chat UI and treat the API as a side-channel. Everruns inverts this: the API is the product, and the UI is one client among many (the SDK, the CLI, customer-built apps, and the bundled management UI all consume the same endpoints).
The consequences:
* Every feature has to land in the API before it lands in the UI. There are no “UI-only” features.
* You can ship Everruns as backend infrastructure for a product whose UI looks nothing like ours.
* Multitenancy, auth, and quotas are enforced at the API layer, so you cannot accidentally bypass them by using a different client.
The management UI exists for operators, configuring providers, browsing sessions, debugging events, not as the primary interaction surface.
## Why REST + SSE, not WebSockets
Sessions need a bidirectional flow: the client posts messages, the server streams events. WebSockets would be a natural fit, but Everruns uses **REST for writes and SSE for reads**.
* REST is cacheable, debuggable with `curl`, and survives every reverse proxy on the planet.
* SSE is a one-way streaming protocol that works through HTTP/1.1, HTTP/2, and HTTP/3 with no special infrastructure.
* The events you’d want to push to the server (cancel, new message) are infrequent enough that a `POST` is the right shape.
Combined with `since_id` resumption and 5-minute connection cycling, SSE gives you reconnection-as-a-feature instead of reconnection-as-a-bug.
## Multitenancy and isolation
Everruns is organization-scoped end-to-end:
* All resources (agents, sessions, capabilities, providers) belong to exactly one organization.
* API keys carry an org membership; the API enforces the boundary on every request.
* Sessions are isolated at the database row level, there is no cross-session filesystem or key-value access.
* Secrets are encrypted at rest with an organization-scoped key chain.
## Where the boundaries are
Things the control plane owns: auth, durable task queue, event log, virtual filesystem, key-value storage, capability registry, MCP catalog.
Things workers own: nothing persistent. They borrow the database, run their tasks, and report back.
Things your application owns: the agent definitions, the prompt design, and the channel-specific glue (Slack apps, webhooks, schedules). The platform is intentionally neutral about *what* you build with it.
---
# Core concepts
> Understand the entity model behind Everruns, harnesses, agents, sessions, capabilities, and how they merge into a runtime.
Source:
Everruns has five entities that you’ll meet over and over: **harness**, **agent**, **session**, **capability**, and **event**. This page explains how they relate and why each exists separately.
For field-by-field schemas, see the [API reference](https://docs.everruns.com/api/) and the [Concepts cheat-sheet](https://docs.everruns.com/getting-started/concepts/).
## Configuration vs. runtime
The single most useful distinction in Everruns:
| Layer | Entities | Lifetime |
| ----------------- | --------------------------- | ------------------------------------------------ |
| **Configuration** | Harness, Agent, Capability | Long-lived. You author these. |
| **Runtime** | Session, Turn, RuntimeAgent | Created per conversation. The server owns these. |
| **Data** | Event, Message | Append-only log produced during runtime. |
Your application creates **configuration**, starts **runtime**, and consumes **data**. Reference pages that mix these layers, and there are some, read as if everything was one bag of “stuff to set”. The three roles never collapse into each other.
## Why three configuration layers (harness, agent, session)?
You could imagine flattening everything into one “agent config” object. Everruns deliberately doesn’t.
* A **harness** answers *“what environment am I running in?”*, model defaults, baseline tools, network access. The same harness is reused across many agents, and each agent holds a reference to the one it runs on.
* An **agent** answers *“what role am I playing?”*, system prompt, domain capabilities, the agent’s voice.
* A **session** answers *“what’s true for this one conversation?”*, extra tools the user just unlocked, an overridden model, a tighter network policy.
When a session starts, all three layers merge into a single `RuntimeAgent` via an associative fold of overlays. Earlier layers form the base; later layers override or add. System prompts concatenate; network policies can only narrow (allow lists intersect, blocklists union); capabilities are deduplicated by ID.
The motivation: operators control harnesses, app authors control agents, end users (or the runtime) control sessions. Each layer has a different blast radius, and the merge rules reflect that.
None of the three is the agent loop. The loop, the thing that assembles context, calls the model, and dispatches tools, is the runtime, and it is not configured as an entity. This is worth stating because “agent harness” names that loop in most other projects, and Everruns uses the word that way itself when it calls the product a durable agentic harness engine. The `Harness` entity is a different thing with the same name: the configuration a session runs on top of.
## Why capabilities are first-class
Tools could just be free-floating function declarations attached to an agent. Capabilities exist because three concerns travel together:
1. The **tool definition** (name, schema, handler).
2. The **system prompt addition** that teaches the model when to use it.
3. The **session state** the tool needs (mount points in the filesystem, secrets, dependencies on other capabilities).
A capability bundles those three. Enabling `web_fetch` on an agent gives you not just the tool but also the prompt fragment explaining how to use it. Enabling `bashkit_shell` automatically pulls in `session_file_system` because of the declared dependency. Capability ordering is meaningful, earlier capabilities’ prompt fragments appear first in the merged system prompt.
MCP servers and skills are also capabilities. They participate in the same merge, dependency resolution, and tool-name prefixing. This keeps the model surface uniform regardless of where a tool came from.
## Sessions are stateful; turns are bounded
A session is a long-lived conversation: it owns an isolated virtual filesystem, a key/value store, and the full event log. Sessions don’t terminate, they go `idle` waiting for the next input.
Inside a session, work happens in **turns**. A turn is one cycle of *reason* (call the LLM) and *act* (execute tools), repeated until the model produces a final answer. Turns are bounded, by default capped at 10 iterations, to make runaway loops impossible.
The reason–act loop is described in more detail in [The agentic loop](https://docs.everruns.com/explanation/agentic-loop/).
## Messages are derived, events are stored
There is no `messages` table in Everruns. The primary store is the **event log**: an immutable, append-only sequence of records per session. Messages are *reconstructed* from those events when needed.
This sounds backwards until you remember what the application actually wants:
* The UI wants a stream of *deltas* and *tool calls* and *state transitions*, not just finished messages.
* Observability wants a trace of every LLM call and every tool invocation.
* Replay and durability want a deterministic record of what happened.
A message log can’t carry that. An event log can, and you can derive a message log from it cheaply. So events come first.
See [Events as the primary store](https://docs.everruns.com/explanation/events/) for the consequences.
## Endpoints connect Agents to the outside world
A bare Agent has no way to receive messages from external users. An **Endpoint** belongs to one Agent and exposes it through a transport such as Slack, AG-UI, A2A, FCP, or Public Chat.
Each endpoint owns its inbound authentication, session-routing strategy, transport configuration, version policy, and publish state. One Agent can have several endpoints, and each endpoint can be published or revoked independently. Create and manage them from the Agent’s **Integrations** tab.
For proactive scheduled work, use [Agent triggers](https://docs.everruns.com/features/agent-triggers/) instead of an endpoint.
---
# Durable execution
> Why Everruns persists every step of agent execution to PostgreSQL, what guarantees that provides, and the trade-offs of avoiding Temporal-style infrastructure.
Source:
The word “durable” in “durable agentic harness engine” means a specific thing: **every step of an agent’s execution survives a process restart**. This page explains why that matters, how it works, and what it costs.
## The problem
An agent turn looks simple from the outside, send a message, get a streamed response. Internally it’s a chain of network calls that each take seconds:
1. Call the LLM provider.
2. Parse tool calls, dispatch them.
3. Wait for each tool to return.
4. Call the LLM again with the results.
5. …repeat…
Anywhere in that chain, the worker process can crash, the container can be reaped, the network can blip. A naïve implementation loses all the work done so far and forces the user to retry. For a 30-second turn that already burned tokens, this is unacceptable.
## The mechanism
Everruns runs every step as a **durable task**. Each task:
* Has a typed input and output.
* Persists its result to PostgreSQL before acknowledging completion.
* Has retry and timeout policies.
* Heartbeats while running, so the control plane can detect a crashed worker.
A turn is a small state machine over those tasks. The state lives in `durable_workflow_events`, an append-only event log just for the workflow engine. To replay a workflow, you load its events and feed them back into the state machine, same input, same decisions, same output.
When a worker crashes mid-turn, the control plane sees the missed heartbeats, marks the in-flight task as failed, and re-queues it. Another worker picks it up. The application sees a momentary stall in the SSE stream, then it continues.
## Why a custom engine
The obvious alternative is Temporal (or Cadence, or Restate). Everruns deliberately built its own minimal durable engine, `everruns-durable`, instead. The reasoning:
1. **Single dependency.** PostgreSQL is the only stateful infrastructure. Operators don’t need to run a second cluster with its own ops story.
2. **Co-located with the rest of the platform.** Workflow events and session events live in the same database, in the same transaction when needed. There’s no eventual consistency between “what happened” and “what was reported.”
3. **Tight scope.** Everruns runs agentic workflows specifically, limited fan-out, short-to-medium duration, well-understood failure modes. We don’t need the full Temporal feature set, and the operational surface area of a tightly-scoped engine is much smaller.
The trade-off: no multi-region replication beyond what PostgreSQL itself offers, no language-agnostic SDK (the engine is Rust-only inside the platform), no visual workflow designer. For agent execution these are not missed.
## Guarantees
What durable execution gives you:
* **No work lost on crash.** If a worker dies, another worker resumes from the last persisted step. Tokens already paid for are not paid for again.
* **Exactly-once tool execution.** Tool calls are persisted by their result, not their attempt. A tool that completed but failed to ack will not be re-run.
* **Deterministic replay.** Reloading a session reproduces the same message sequence, which makes traces and exports authoritative.
What it doesn’t give you:
* **Idempotence of side effects.** If your tool POSTs to an external API, the external API will see one call per *successful* execution but a retried-task scenario can still cause duplicates if a tool completes externally and crashes before persisting. Tools that have external side effects must include their own idempotency keys.
* **Real-time latency guarantees.** Persisting every step adds tens of milliseconds per task. For agent workloads (already dominated by LLM latency) this is invisible; for hot-loop workloads it would be costly.
## When the database becomes the bottleneck
Everruns is designed to run on a single PostgreSQL primary. Read replicas help for reporting; write-heavy session loads are handled by partitioning the durable workflow tables by workflow ID hash and by keeping event payloads compact.
For deployments that outgrow a single primary, the migration path is to a sharded PostgreSQL setup keyed by organization, but in practice, LLM provider rate limits cap throughput long before the database does.
## Further reading
* [The agentic loop](https://docs.everruns.com/explanation/agentic-loop/), what each step inside a turn looks like.
* [Architecture](https://docs.everruns.com/explanation/architecture/), how the control plane and workers interact.
---
# Events as the primary store
> Why Everruns uses an append-only event log as the source of truth for sessions instead of a conventional message table.
Source:
Most chat systems store messages in a `messages` table and emit events as a side-channel. Everruns inverts this: the **event log is the source of truth**, and messages are derived from events. This page explains why.
## What an event is
An **event** is an immutable record of something that happened in a session. Examples:
* A user submitted a message.
* The LLM produced a token of streaming output.
* A tool was called.
* A tool completed.
* A turn started, completed, failed, or was cancelled.
Every event has a session-local monotonic sequence number, a stable ID (UUID v7), a type in dot notation (`turn.completed`, `output.message.delta`), and a typed payload. Events are appended to the log and never modified or deleted.
The full catalog is in the [Event Reference](https://docs.everruns.com/event-reference/).
## Why not a `messages` table?
A conventional design has:
* A `messages` table for the conversation.
* A separate change-feed (Kafka, NATS, a `pg_notify` channel) for real-time updates.
* A traces table for observability.
This gives you three sources of truth that can disagree. When the SSE stream and the messages table disagree, which one is right? When a message is edited mid-stream, what does the change-feed say? When a tool call is observed but no resulting message is persisted, did it happen?
Everruns avoids the question by making the event log authoritative for all three concerns:
* **Conversation history** is reconstructed from events. A `Message` is a projection of a contiguous range of `output.*` and `input.*` events.
* **The real-time stream** is just a tail of the same log. SSE clients receive events as they’re appended; resumption with `since_id` is a database read against the same table.
* **Tracing and replay** consume the log directly. Every LLM call, tool execution, and state transition is there.
One log, one ordering, one source of truth.
## Consequences
This design has visible consequences in the API:
* **You can’t UPDATE a message.** What you can do is append a new event that supersedes part of the projection. Messages can’t be edited because they don’t exist as rows.
* **Ordering is by sequence number, not timestamp.** Two events with the same wall-clock time still have a strict order. Timestamps are informational.
* **Resumption is cheap.** `since_id` is a primary-key scan. You can disconnect and reconnect to an SSE stream millions of events later and get exactly the missing tail.
* **Audit is free.** Everything that happened in a session is in one table, in order, immutably.
## Compatibility guarantees
Because events are the API contract, Everruns treats them like a public protocol. The compatibility rules are:
| Change | Allowed |
| ------------------------------------------ | ------------------------------------------- |
| Add a new event type | yes |
| Add an optional field to an existing event | yes |
| Add a new enum value to an existing field | yes |
| Remove a field | no, breaking change |
| Change the type of a field | no, breaking change |
| Reuse a sequence number | no, sequence numbers are atomic per session |
Consumers must follow the dual: ignore unknown fields, ignore unknown event types, and treat optional fields as optional. The SDK clients do this automatically.
## What about volume?
A long agent session can produce thousands of events, streaming deltas alone can be hundreds per turn. Two things keep this manageable:
* **Delta events are batched** at \~100ms. The model produces tokens faster than that, but consumers don’t need every token as a separate event.
* **Sessions are bounded by intent, not duration.** Even busy sessions rarely exceed five-figure event counts. PostgreSQL handles that easily.
For very long-running sessions, [context compaction](https://docs.everruns.com/advanced/compaction/) reduces the *prompt* size but does not modify the event log. The full history remains available via [Infinity Context](https://docs.everruns.com/capabilities/infinity-context/) and the event API.
## Further reading
* [Event Reference](https://docs.everruns.com/event-reference/), every event type and payload.
* [The agentic loop](https://docs.everruns.com/explanation/agentic-loop/), what produces events during a turn.
* [How-to: stream events](https://docs.everruns.com/how-to/stream-events/), practical patterns.
---
# Agent Checks
> Advisory quality checks for agent configurations, structural problems, completeness gaps, and cost warnings surfaced while you build.
Source:
# Agent Checks
Agent checks review an agent configuration and surface advisory findings while you build: structural problems (duplicated instructions, conflicting style guidance), completeness gaps (tool references that do not exist), and cost warnings (oversized prompts).
Checks are advisory only. Findings never block saving, publishing, or version creation.
## Where Findings Appear
* **Agent editor → Preview tab**: a Checks card lists findings for the current draft, updating as you edit.
* **API**: `POST /v1/agents/preview` returns a `findings` array alongside the resolved system prompt and tools.
* **MCP / platform commands**: the `preview_agent` and `analyze_agent` commands return the same findings, so agents and automations can review configurations programmatically.
## Findings
Each finding includes:
| Field | Description |
| ---------- | --------------------------------------------------------------------------- |
| `rule_id` | Stable rule identifier, e.g. `prompt.duplicate_paragraphs` |
| `severity` | `warning`, `info`, or `suggestion`, there is no `error`; checks never block |
| `category` | `structure`, `completeness`, `effectiveness`, `safety`, or `cost` |
| `message` | Human-readable explanation |
| `location` | The config field (and byte span, when applicable) the finding points at |
## AI Analysis
The **Analyze** button on the Checks card runs a deeper on-demand review using the platform’s internal utility LLM (requires `UTILITY_OPENAI_API_KEY` or `UTILITY_OPENROUTER_API_KEY` on the deployment). Three scoped checkers run in parallel:
| Rule | What it catches |
| ------------------- | -------------------------------------------------------------------------------------------------------------- |
| `llm.contradiction` | Instructions that cannot both be followed, including conflicts between the prompt and capability contributions |
| `llm.structure` | Redundancy, verbosity, vague instructions, and structure that buries critical rules |
| `llm.tool_guidance` | Prompt guidance that misdescribes available tools or assumes functionality no tool provides |
LLM findings can carry a suggested replacement for the offending text; when the finding is anchored to a span of your prompt, an **Apply fix** button replaces it in place. Analysis is available via `POST /v1/agents/analyze`, which returns built-in and LLM findings merged.
The reviewed prompt is treated strictly as data: checker outputs are bounded, severities are clamped, and findings are advisory text only.
## Health Checks
A **health check** runs the agent for real. It generates a handful of smoke-test cases from the agent’s description, system prompt, and capabilities, runs each as an actual session against the agent’s configured model, and scores the result two ways:
* **Deterministic**: the agent produced a non-empty answer and finished within a turn budget.
* **AI judge**: the platform’s utility LLM grades the agent’s final response against a rubric generated for that case.
A case passes only when both checks pass. The run surfaces a score card (pass rate, passed count, average score, average turns) and a per-case list, each case links to the real session so you can inspect the full conversation, tool calls, and events.
Health checks are asynchronous (they run several real sessions and take a minute or two). Trigger one and poll for the result:
| Method | Path | Description |
| ------ | ---------------------------------------------- | -------------------------------------------------------------------- |
| `POST` | `/v1/agents/{agent_id}/health-checks` | Start a run; returns a pending run with an `id` |
| `GET` | `/v1/agents/{agent_id}/health-checks/{run_id}` | Poll the run; `status` goes `pending → running → completed`/`failed` |
| `GET` | `/v1/agents/{agent_id}/health-checks` | List recent runs for the agent |
Runs are stored per agent and keyed by the resolved config hash. Health checks require the utility LLM (`UTILITY_OPENAI_API_KEY` or `UTILITY_OPENROUTER_API_KEY`) to generate and judge cases, and the agent’s own model must be usable. They are advisory: a low score never blocks anything.
## Built-in Rules
Checks run against the *resolved* configuration, after harness and capability contributions are merged, so they can catch issues that span layers.
| Rule | Severity | What it catches |
| ------------------------------ | -------- | ------------------------------------------------------------------------- |
| `prompt.empty` | info | Agent has no system prompt of its own |
| `prompt.very_long` | warning | Authored prompt over 32 KiB, sent on every model turn |
| `prompt.resolved_very_long` | info | Full prompt over 96 KiB after harness/capability contributions |
| `prompt.template_variables` | warning | `{{placeholder}}` text that would reach the model literally |
| `prompt.duplicate_paragraphs` | warning | The same paragraph appears more than once |
| `prompt.restates_contribution` | info | Prompt duplicates text already contributed by the harness or a capability |
| `prompt.conflicting_style` | info | Asks for both brevity and detail without stating conditions |
| `tools.unknown_reference` | info | Prompt references a tool that no enabled tool or capability provides |
| `tools.duplicate_names` | warning | Two tools share a name, so the model cannot distinguish them |
High-cardinality rules (`prompt.duplicate_paragraphs`, `tools.unknown_reference`, `tools.duplicate_names`) cap how many findings they emit. When the cap is exceeded they add a single companion `info` finding with the rule ID suffixed `.summary` (e.g. `prompt.duplicate_paragraphs.summary`) noting that only the first N were shown, so a large prompt cannot amplify into an unbounded response.
## Roadmap
A later phase adds org-configurable rules: per-rule enable/severity settings for the built-ins plus custom declarative and natural-language-rubric rules.
---
# AGENTS.md
> The AGENTS.md capability resolves project-level instructions from workspace files hierarchically and injects them as the leading user message on every turn.
Source:
The **AGENTS.md** capability resolves project instruction files hierarchically — from the session filesystem root down to the working directory — and injects them as the leading user-role message on every turn. By default it reads `AGENTS.md`, Everruns’ implementation of the [`AGENTS.md`](https://agents.md/) open standard, an emerging convention backed by OpenAI, Google, Cursor, Sourcegraph, and the Linux Foundation.
Workspace files are untrusted third-party content, so they never enter the system prompt: harness safety instructions always take precedence, and file edits never invalidate the cache-stable system prefix.
When the capability is enabled:
* The agent resolves `AGENTS.md` from the filesystem root down to the working directory on every turn by default.
* A `docs/AGENTS.md` applies to work under `docs/`; deeper files override shallower ones on conflict. Files in sibling subtrees are never loaded.
* Agents can configure `files` to resolve additional filenames such as `CLAUDE.md` at every hierarchy level.
* Edits during a session apply on the next turn (no restart).
* The assembled block opens with a trust framing header and renders each file in `` sections as the first user message — never as system prompt.
* If a configured file doesn’t exist, the agent operates normally.
## Prompt order
Every turn the model sees, top-to-bottom:
1. **System prompt**: harness safety instructions, tool guidance, role (cache-stable).
2. **Conversation context**: the resolved `AGENTS.md` hierarchy, broadest scope first — model-visible, re-resolved every turn, but below system instructions in precedence.
3. **Conversation history and the latest user message**.
Explicit user instructions win over project files on conflict; system instructions always win over both.
## Limits
* Content cap: **32 KiB per file** (excess truncated with a warning), plus a **128 KiB total budget** per turn binding hierarchy depth — deeper files past the budget are omitted with an explicit note.
* Plain Markdown, no required sections.
* At most 16 instruction files resolved per turn.
## Compatibility
Everruns keeps `AGENTS.md` as the default. Configure the capability with `files` to read additional tool-specific files:
```json
{
"files": ["AGENTS.md", "CLAUDE.md"]
}
```
## Do something
* [Use AGENTS.md for project instructions](https://docs.everruns.com/how-to/use-agents-md/), full guide with examples.
* [Equip an agent with tools](https://docs.everruns.com/how-to/equip-agents-with-tools/), adding capabilities generally.
## See also
* [AGENTS.md capability reference](https://docs.everruns.com/capabilities/agent-instructions/), config schema.
---
# Agent Triggers
> Run an agent proactively on a recurring schedule, choose session reuse, test it immediately, and inspect recent outcomes.
Source:
Agent triggers let an Agent start work on its own schedule. A trigger belongs to one Agent, runs on that Agent’s Harness, and sends a configured message when it fires. It does not require an inbound endpoint.
Schedule triggers are the only trigger type currently available.
## Create a trigger in the UI
1. Open the agent.
2. Select the **Integrations** tab.
3. Select **Add trigger**.
4. Choose a preset or enter a 5-field cron expression (or a 7-field expression whose year is `*`), then enter an IANA timezone. The editor shows the schedule in human-readable form and previews the next eight runs. The default timezone is `UTC`.
5. Choose a session mode:
* **Shared session** reuses one durable session for this trigger. Use it when later runs should see the trigger’s previous conversation.
* **New session per run** creates a fresh session every time. Use it when each run should be isolated.
6. Enter the message that starts the run. Messages may use `{{...}}` template values from the agent, trigger, and invocation context.
7. Leave **Enabled** on to activate the schedule, then select **Create trigger**.
The trigger card shows the schedule in human-readable form, its timezone, message, session mode, enabled state, and recent run outcomes.
## Operate and test a trigger
From the **Triggers** section of the **Integrations** tab you can:
* enable or disable the recurring schedule;
* edit its schedule, timezone, session mode, message, or enabled state;
* select **Run now** to start one invocation immediately;
* inspect the most recent durable execution outcomes; or
* delete the trigger.
**Run now** uses the same execution path as a scheduled run. The trigger must be enabled.
## API
Agent triggers are managed below `/v1/agents/{agent_id}/triggers`:
| Method | Path | Purpose |
| -------- | ----------------------------------------------------- | ------------------------- |
| `GET` | `/v1/agents/{agent_id}/triggers` | List triggers |
| `POST` | `/v1/agents/{agent_id}/triggers` | Create a schedule trigger |
| `GET` | `/v1/agents/{agent_id}/triggers/{trigger_id}` | Get one trigger |
| `PATCH` | `/v1/agents/{agent_id}/triggers/{trigger_id}` | Update provided fields |
| `DELETE` | `/v1/agents/{agent_id}/triggers/{trigger_id}` | Delete a trigger |
| `POST` | `/v1/agents/{agent_id}/triggers/{trigger_id}/trigger` | Run it now |
| `GET` | `/v1/agents/{agent_id}/triggers/{trigger_id}/runs` | List recent outcomes |
Create requests accept `cron_expression`, `timezone`, `session_mode`, `message`, and `enabled`. See the [API reference](https://docs.everruns.com/api/) for current request and response schemas.
## Migrated App Schedules
The retired App model allowed `schedule` channels. Everruns migrated Agent-bound schedules to Agent triggers and preserved their cron expression, timezone, session mode, message, execution identity, and history.
Agent triggers, webhook triggers, and session schedules solve different problems:
* use an **agent trigger** when an agent should wake itself on a recurring schedule;
* use a **webhook trigger** when an external HTTP request should invoke an Agent; and
* use a **session schedule** when the current conversation should continue later.
## See also
* [Slack Integration](https://docs.everruns.com/integrations/slack/), publish an Agent to an inbound messaging endpoint.
* [Session participants](https://docs.everruns.com/features/session-participants/), understand the host agent used by a trigger-created session.
* [API reference](https://docs.everruns.com/api/), exact trigger schemas and responses.
---
# Agent Versions
> Save immutable Agent snapshots, compare changes, roll back, and bind endpoints to a default, latest, or pinned version.
Source:
# Agent Versions
Agent versions are saved snapshots of an agent configuration. They let you preserve a known-good prompt, tool, and capability setup while continuing to edit the draft agent.
This feature is gated by `FEATURE_AGENT_VERSIONS`.
## What You Can Do
* Save the current agent draft as a new version.
* Set a default version for normal use.
* Compare authored and resolved configuration between two versions.
* Roll back the editable draft to a previous version.
* Fork a version into a new agent.
* Configure each endpoint to use the Agent default, latest version, or a pinned version.
## Runtime Behavior
When a session is created, Everruns records the resolved `agent_version_id` on the session. Events emitted during that session include version metadata so logs, traces, and exports can identify the exact agent configuration that ran.
Endpoints can use:
* `default`: follow the agent default version.
* `latest`: always use the newest saved version.
* `pinned`: keep using a specific version until changed.
## Notes
Versions are immutable. Rollback updates the editable draft and saves a new rollback version so history remains append-only.
---
# Apps Compatibility
> Understand the retired App model, permanent route compatibility, and the Agent-owned endpoint model that replaces it.
Source:
Apps are retired from Everruns management. New integrations belong directly to an Agent as **endpoints** or **triggers**.
* Use an **endpoint** when an external peer sends a request and waits for a reply. Slack, AG-UI, A2A, FCP, and Public Chat use endpoints.
* Use a **trigger** when a schedule or event starts Agent work without a reply channel.
Create and manage both from the Agent’s **Integrations** tab.

## Existing Apps
Everruns keeps existing App records for historical attribution and compatibility. Existing installs continue to serve traffic, but the App list, detail page, create flow, and management API are retired.
The old `/v1/apps/{app_id}/…` ingress paths remain permanent aliases. They resolve to the migrated endpoint and continue to work. Do not rewrite a working existing installation only to change its URL.
New integrations use endpoint-scoped canonical paths:
```text
/v1/e/{endpoint_id}/slack/events
/v1/e/{endpoint_id}/ag-ui
/v1/e/{endpoint_id}/a2a
/v1/e/{endpoint_id}/fcp
```
## Endpoint Lifecycle
Each endpoint has its own lifecycle:
```text
Draft ⇄ Live
Draft → Disabled
Live → Disabled
Disabled → Draft
```
* **Draft**: Configured but does not accept ingress traffic.
* **Live**: Published and able to accept traffic while its Agent is active and exposures are not suspended.
* **Disabled**: Kept for configuration but rejects ingress traffic and does not invoke the Agent.
Publishing or unpublishing one endpoint does not change another endpoint on the same Agent.
## Where to Go
* [Slack Integration](https://docs.everruns.com/integrations/slack/), create and publish a Slack endpoint.
* [Agent Triggers](https://docs.everruns.com/features/agent-triggers/), configure proactive scheduled work.
* [Agent Versions](https://docs.everruns.com/features/agent-versions/), select which Agent version an endpoint uses.
---
# Capabilities
> Capabilities give an agent tools, system prompt fragments, and session state. Overview with links to the reference.
Source:
A **capability** is a self-contained unit that extends an agent. Each capability can contribute three kinds of thing:
1. **Tools**: functions the agent can invoke during a turn.
2. **System prompt additions**: text prepended to the agent’s prompt that teaches the model when and how to use those tools.
3. **Mount points**: files, directories, or session state the tools need to operate.
Agents *compose* capabilities. Enable as many as you need; leave the rest disabled. The runtime resolves capabilities in topological order (dependencies first) and concatenates their system prompt fragments in the order you configured them on the agent.
## Why capabilities, not just tools
A bare tool registration is a function with a JSON schema. Capabilities exist because real tools need more than that. To use `bashkit_shell` effectively, the agent needs both the `bash` tool *and* the prompt fragment explaining the sandbox model *and* the session filesystem (a dependency). Capabilities bundle those concerns into one enable/disable unit.
See [Why capabilities are first-class](https://docs.everruns.com/explanation/concepts/#why-capabilities-are-first-class) for the full rationale.
## Where capabilities come from
| Source | ID format | Example |
| -------------- | -------------------- | ---------------------------------- |
| Built-in | `snake_case` | `web_fetch`, `session_file_system` |
| MCP server | `mcp:{uuid}` | `mcp:550e8400-...` |
| Registry skill | `skill:{uuid}` | `skill:550e8400-...` |
| Declarative | `declarative:{name}` | `declarative:research_pack` |
All four kinds participate in the same merge, dependency resolution, and tool-name prefixing. The agent doesn’t care where a tool came from.
## Where to attach them
Capabilities can be attached at three layers, and the layers stack additively:
* **Harness**: defaults for every session that uses this harness.
* **Agent**: capabilities for this specific role.
* **Session**: extras for this one conversation.
See [Why three configuration layers](https://docs.everruns.com/explanation/concepts/#why-three-configuration-layers-harness-agent-session) for how the merge works.
## Browse the catalog
The full list of built-in capabilities, organised by category, lives in the reference:
* **[Capabilities reference](https://docs.everruns.com/capabilities/)**: every capability with ID, tools, parameters, and dependencies.
## Do something
* [Equip an agent with tools](https://docs.everruns.com/how-to/equip-agents-with-tools/), the practical recipe.
* [Give an agent web access](https://docs.everruns.com/how-to/give-an-agent-web-access/), narrower task with network policies.
* [Customize a harness](https://docs.everruns.com/how-to/customize-a-harness/), bundle capabilities at the harness layer.
## See also
* [Concepts](https://docs.everruns.com/explanation/concepts/), the entity model in full.
---
# CLI
> Manage agents, sessions, and conversations from the command line.
Source:
The `everruns` CLI is a command-line client for the Everruns API. It covers the same surface as the SDK, agents, sessions, messages, capabilities, and is designed to compose well with shell pipelines.
This page covers installation, configuration, and the command surface. For scripting patterns and `jq` examples, see [Automate with the CLI](https://docs.everruns.com/how-to/automate-with-the-cli/).
## Install
### Homebrew (macOS / Linux)
```bash
brew tap everruns/tap
brew install everruns
```
### Cargo
From the Git repository:
```bash
cargo install --git https://github.com/everruns/everruns everruns-cli
```
Or clone and build:
```bash
git clone https://github.com/everruns/everruns.git
cd everruns
cargo install --path crates/cli
```
### Verify
```bash
everruns --version
```
## Configure
The CLI defaults to the hosted API at `https://app.everruns.com/api`. Override for local or self-hosted deployments:
```bash
# Per command
everruns --api-url http://localhost:9300/api agents list
# Per shell
export EVERRUNS_API_URL=http://localhost:9300/api
export EVERRUNS_API_KEY=dev
```
## Command surface
| Group | Subcommands |
| -------------- | ------------------------------------------- |
| `agents` | `create`, `list`, `get`, `update`, `delete` |
| `sessions` | `create`, `list`, `get`, `cancel`, `delete` |
| `capabilities` | (list, no subcommand) |
| `chat` | Send a message and stream the response |
### Agents
```bash
# Inline
everruns agents create \
--name "my-agent" \
--system-prompt "You are a helpful assistant." \
--tag production
# From a file (TOML, YAML, JSON, or Markdown front matter)
everruns agents create -f agent.toml
everruns agents create -f agent.yaml
everruns agents create -f agent.md
```
If `./agent.toml` exists and you don’t pass inline flags, `everruns agents create` picks it up automatically. The file formats are documented in [Define agents as files](https://docs.everruns.com/how-to/define-agents-as-files/).
```bash
everruns agents list
everruns agents get agt_xxx
everruns agents delete agt_xxx
```
### Sessions
```bash
everruns sessions create --agent agt_xxx
everruns sessions create --agent agt_xxx --title "Debug session"
# With session-level overrides
everruns sessions create \
--agent agt_xxx \
--harness generic \
--capability 'web_fetch={"timeout":10}' \
--hint setup_connection=true \
--network-allow api.example.com \
--max-iterations 8
```
Also accepts: `--locale`, repeatable `--tag`, `--system-prompt`, `--hints-json`, repeatable `--network-block`, repeatable `--secret KEY=VALUE`, and budget flags.
```bash
everruns sessions list
everruns sessions get ses_xxx
```
### Chat
```bash
everruns chat "Tell me a joke!" --session ses_xxx
```
Options: `--timeout ` (default 300), `--no-stream` to queue without waiting.
## Output formats
Every command accepts `-o` / `--output`:
```bash
everruns agents list -o json
everruns agents list -o yaml
```
`--quiet` suppresses headers and prints only the essential identifier, useful for capturing IDs in shell variables.
## See also
* [Automate with the CLI](https://docs.everruns.com/how-to/automate-with-the-cli/), `jq`, quiet mode, scripting patterns.
* [Define agents as files](https://docs.everruns.com/how-to/define-agents-as-files/), file formats for `-f`.
* [SDK](https://docs.everruns.com/features/sdk/), the programmatic equivalent.
## Agent composition
Discover and manage the plugins, skills, and knowledge bases available to agents:
```bash
# Plugins
everruns plugins list
everruns plugins get
everruns plugins install
everruns plugins uninstall
# Skills
everruns skills list
everruns skills get
everruns skills create ./SKILL.md
everruns skills delete
# Knowledge bases
everruns knowledge-bases list
everruns knowledge-bases get
everruns knowledge-bases create "Product docs" --description "Published product documentation"
everruns knowledge-bases delete
```
Resource identifiers are URL-encoded before requests are sent. Use the global `--output json` or `--output yaml` option for machine-readable discovery output.
Skill creation reads the supplied Markdown file and sends its contents to Everruns. Knowledge document ingestion and assigning composition resources to an agent are not yet exposed by the CLI.
---
# Evals
> Define, run, and track behavioral tests for Agents. Each case runs a real session and is scored.
Source:
Evals let you define, run, and track **behavioral tests** for your Agents. An Eval is a named collection of cases; each case sends messages to a fresh session and scores the result. Use them to compare runs across models and catch regressions after a prompt change.
Because every case runs a **real session** against the target agent and harness, failures are fully debuggable, click into the session to see the conversation, tool calls, and events exactly as they happened.
## Concepts
| Entity | What it is |
| -------------- | -------------------------------------------------------------------------------------------------------- |
| **Eval** | Top-level, org-scoped collection of cases. Follows the standard `active → archived → deleted` lifecycle. |
| **EvalCase** | A single test: input messages, scoring rules, execution bounds, and optional artifact collection. |
| **EvalRun** | One execution of an eval’s cases against a target, producing per-case results. |
| **EvalTarget** | How a session is instantiated from a Harness, Agent, model, and system prompt. |
Targets resolve in order `EvalRun → EvalCase → Eval → org default harness`, so you can run the same cases against different models per run or override per case.
## How a case runs
1. A fresh session is created from the resolved target.
2. The case’s `conversation` messages are delivered sequentially (multi-turn supported).
3. Optional `post` messages run after the session idles, for example, executing a test script.
4. Optional `artifacts` capture named session files.
5. `scorers` grade the result, returning `0.0`–`1.0` for nuanced pass/fail.
Eval runs are durable workflows: they reuse the same execution engine as production sessions, so a worker restart mid-run does not lose progress.
## SWE-bench Lite
Beyond user-facing behavioral evals, Everruns ships a **SWE-bench Lite** harness for measuring coding-agent performance against the standard benchmark, using the same eval machinery (`post` verification messages run the test scripts that determine pass/fail).
## Related
* [Harnesses](https://docs.everruns.com/features/harnesses/), what an eval target runs
* [Agent Versions](https://docs.everruns.com/features/agent-versions/), immutable configurations that evals can compare
---
# Events
> SSE streaming of session events: categories, structure, and the wire protocol.
Source:
Every action during a session, user input, LLM responses, tool calls, lifecycle transitions, emits an **event**. The event log is the source of truth for session state; the SSE stream is a live tail of that log.
For *why* the platform is shaped this way, see [Events as the primary store](https://docs.everruns.com/explanation/events/). For the full catalog of event types and payloads, see the [Event Reference](https://docs.everruns.com/event-reference/).
## Event categories
| Category | Examples | Description |
| ------------ | -------------------------------------------------------------------------------- | ----------------------------------------------------- |
| **Input** | `input.message` | User messages submitted to the session |
| **Output** | `output.message.started`, `output.message.delta`, `output.message.completed` | Agent response lifecycle |
| **Turn** | `turn.started`, `turn.completed`, `turn.failed`, `turn.cancelled` | Turn lifecycle |
| **Thinking** | `reason.thinking.*` | Extended thinking content (Claude, GPT-5.x, o-series) |
| **Atom** | `reason.*`, `act.*`, `tool.*` | Internal execution phases |
| **LLM** | `llm.generation` | Full LLM API call details |
| **Session** | `session.started`, `session.activated`, `session.idled`, `session.model.changed` | Session state changes |
| **Subagent** | `subagent.*` | Subagent lifecycle |
## Event structure
```json
{
"id": "event_01933b5a00007000800000000000001",
"type": "turn.completed",
"ts": "2024-01-15T10:30:00.000Z",
"session_id": "session_01933b5a00007000800000000000002",
"sequence": 42,
"context": {
"turn_id": "turn_...",
"input_message_id": "message_...",
"trace_id": "turn_...",
"span_id": "abc123",
"parent_span_id": "def456"
},
"data": { /* type-specific payload */ }
}
```
| Field | Type | Description |
| ------------ | ------- | --------------------------------------------------------- |
| `id` | string | Unique event ID (UUIDv7) |
| `type` | string | Event type in dot notation |
| `ts` | string | ISO 8601 with millisecond precision |
| `session_id` | string | Session this event belongs to |
| `sequence` | integer | Monotonic per-session sequence (ordering source of truth) |
| `context` | object | Correlation IDs for tracing |
| `data` | object | Event-specific payload |
Ordering is by `sequence`, not `ts`. Two events with the same wall-clock time still have a strict order.
## Common patterns
### Started → completed → failed
Long-running operations follow a lifecycle:
```plaintext
turn.started → turn.completed
↘ turn.failed
↘ turn.cancelled
```
These boundaries are what your UI uses to manage state and surface errors.
### Delta streaming
Streaming content uses delta events with accumulated state:
```json
{
"type": "output.message.delta",
"data": {
"turn_id": "turn_...",
"delta": "Hello",
"accumulated": "Hello"
}
}
```
Deltas are batched at \~100ms to reduce volume.
## Forward-compatibility
Events follow a defined contract. Consumers must tolerate evolution:
| Change | Allowed |
| --------------------------- | ------- |
| New event types | yes |
| New optional fields | yes |
| New enum values | yes |
| Removing or retyping fields | no |
Your deserializer should ignore unknown fields, ignore unknown event types, and treat optional fields as optional.
## Consume the stream
* [Stream events with the SDK](https://docs.everruns.com/how-to/stream-events/), the convenient path (Python, Rust, TypeScript).
* [Consume events via raw SSE](https://docs.everruns.com/how-to/consume-events-via-sse/), protocol-level, when the SDK isn’t available.
## See also
* [Event Reference](https://docs.everruns.com/event-reference/), complete event type catalog with payloads.
* [Events as the primary store](https://docs.everruns.com/explanation/events/), design rationale.
---
# Harnesses
> A harness is what an agent runs on, the execution environment, default model, and bundled capabilities that agents and sessions extend.
Source:
A **harness** is what an agent *runs on*. It answers “what environment am I working in, and what is available to me?”, the execution environment, the default model, and a bundle of capabilities. Every session is assigned exactly one harness. Agents and sessions then layer their own configuration on top.
Harness here does not mean the agent loop
Elsewhere in the industry, “agent harness” usually names the loop that drives the model, the thing that assembles context, calls the LLM, and dispatches tools. Everruns describes itself as a *durable agentic harness engine* in that sense.
A **Harness** (the entity on this page) is not that loop. The loop is the runtime, and you never configure it directly. A Harness is the reusable configuration a session runs on top of.
The split that matters is **world versus behavior**:
| | Answers | Owns |
| ----------- | ----------------------------------------- | -------------------------------------------------------------------------------------- |
| **Harness** | ”What am I running in?” | Execution environment, network access, capability bundle, default model, starter files |
| **Agent** | ”What role am I playing?” | Instructions, domain capabilities, the agent’s voice |
| **Session** | ”What is true for this one conversation?” | Per-conversation extras, overrides, a tighter network policy |
A harness exists before any agent uses it, and many agents share one.
### Who points at a harness
Both an agent and a session carry a harness reference:
* Every **agent** holds exactly one `harness_id`. It is the harness that agent’s sessions run on by default. On create, an agent inherits the organization’s default harness unless you pass `harness_id` or `harness_name`; an explicit choice stays pinned.
* A **session** may name its own harness and override the agent’s.
Precedence when a session starts, first match wins:
1. The harness named on the session request
2. The agent’s harness
3. The organization default
4. The built-in fallback
So the same agent can be run on a different harness for one session without editing the agent, while changing it for good means updating the agent.
For the design rationale (why three configuration layers exist), see [Concepts](https://docs.everruns.com/explanation/concepts/#why-three-configuration-layers-harness-agent-session).
## Built-in harnesses
| Harness | What it provides | Best for |
| ----------------------------------------------------------------------------- | ---------------------------------- | -------------------------------------------------- |
| [Base](https://docs.everruns.com/built-ins/harnesses/base/) | Empty, no capabilities | Minimal agents, custom tool composition, testing |
| [Generic](https://docs.everruns.com/built-ins/harnesses/generic/) | Core capabilities most agents need | General-purpose assistants, coding tasks, research |
| [Data Analyst](https://docs.everruns.com/built-ins/harnesses/data-analyst/) | Generic plus SQL, charts, memory | Data workflows |
| [Platform Chat](https://docs.everruns.com/built-ins/harnesses/platform-chat/) | Focused platform command surface | Operator chat |
The Generic harness is the recommended default. See the [Built-in harnesses reference](https://docs.everruns.com/built-ins/harnesses/base/) for the exact capability bundle each one ships with.
## Naming
Every harness has two names:
* **`name`**: stable URL-friendly slug (`generic`, `deep-research`). Unique per org. Use this in API calls, CLI, code.
* **`display_name`**: human label shown in the UI.
`name` format: `[a-z0-9]+(-[a-z0-9]+)*`, max 64 chars, no consecutive hyphens.
## How harnesses combine with agents and sessions
The system prompt is built from three layers, each wrapped in XML tags:
1. Harness capabilities (foundation)
2. Agent capabilities (role)
3. Session capabilities (per-conversation extras)

The merge is associative: a chain of inherited harnesses produces the same `RuntimeAgent` as a single pre-merged harness.
### The base system prompt is optional
A harness bundles more than a prompt, capabilities, MCP servers, a default model, network access, and starter files. Because of that, the base `system_prompt` is **optional**. Omit it (or leave it empty) when a harness exists only to add capabilities or MCP servers on top of a parent: the effective prompt is then composed entirely from the parent harness, the agent, the session, and capability contributions. Empty or whitespace-only prompts contribute nothing, and if no layer contributes a prompt the agent runs with no base system prompt at all.
## Do something
* [Customize a harness](https://docs.everruns.com/how-to/customize-a-harness/), build your own as a base for many agents.
* [Equip an agent with tools](https://docs.everruns.com/how-to/equip-agents-with-tools/), add capabilities at the agent layer.
## See also
* [Built-in harnesses](https://docs.everruns.com/built-ins/harnesses/base/), reference for the shipped harnesses.
* [Concepts](https://docs.everruns.com/explanation/concepts/), entity model.
---
# Model Context Protocol (MCP)
> Everruns is both an MCP server, exposing its agents and tools to external clients with OAuth 2.1, and an MCP client that registers remote servers as virtual capabilities.
Source:
Everruns speaks the [Model Context Protocol](https://spec.modelcontextprotocol.io) on **both sides**: it exposes its own agents and tools as an MCP server, and it consumes remote MCP servers as agent capabilities.
## Everruns as an MCP server
Every Everruns deployment exposes an authenticated MCP endpoint at `/mcp` so external clients, Claude Desktop, Cursor, VS Code, or another agent, can discover and call your agents and tools over JSON-RPC. It is a standard product surface, with no deployment variable or organization feature toggle.
* **Transport**: JSON-RPC 2.0 over Streamable HTTP (`POST /mcp`). Protocol versions `2025-06-18` and `2025-03-26` are supported.
* **Methods**: `initialize`, `ping`, `tools/list`, `tools/call`, `resources/list`, `resources/read`.
* **Auth**: MCP requests authenticate and resolve an organization before dispatch. OAuth 2.1 uses mandatory PKCE, and MCP access tokens are bound to the exact `/mcp` resource so they cannot be reused against the REST API. Unauthenticated requests fail with `401` and protected-resource discovery metadata is served at `/.well-known/oauth-protected-resource/mcp`.
* **Entity cards**: under protocol `2025-06-18`, tools like `agent_get_card` return a sandboxed `text/html` MCP App resource at the `ui://` scheme alongside a text summary, so MCP-Apps-aware hosts render rich cards while others fall back to text.
Routing is intentionally split: REST under `/api/*`, MCP OAuth under `/oauth/*`, and MCP JSON-RPC at `/mcp`.
## Everruns as an MCP client
Register a remote MCP server and its tools appear as a **virtual capability**: auto-discovered, namespaced, and executed alongside built-in capabilities. No code changes are needed to give an agent new tools.
* **Org-managed servers**: organization-scoped `McpServer` records connect over remote HTTP (Streamable HTTP). `stdio` is rejected by the hosted control plane and is only available to single-tenant runtime/CLI hosts.
* **Scoped `mcpServers`**: harnesses, agents, and sessions can embed remote MCP config directly (the remote-server subset of `.mcp.json`) for session-local or agent-local wiring without creating an org-global record.
* **Tool naming**: discovered tools are namespaced per server so they never collide with built-in capabilities.
* **Protocol compatibility**: the client negotiates the MCP protocol era per server. By default (`auto`) it issues a session-less `2026-07-28` request and transparently falls back to the stateful `initialize` handshake (`2025-06-18` / `2025-03-26`) for servers that require it, caching the verdict per server. Set the protocol mode to `legacy`, `stable`, or `rc` to pin a specific era and skip negotiation. No setting is needed for the common case.
## When a tool needs a person
Some MCP tools cannot finish without a human: a payment to authorize, an API key to paste, a consent screen to click. Under protocol `2026-07-28` the server hands back a URL instead of asking for the value, and Everruns holds the turn until someone answers. See [URL mode elicitation](https://docs.everruns.com/features/mcp-url-elicitation/).
## Use Everruns from your AI tools
To connect Claude Code, Codex, or Cursor to a deployment via the `everruns` plugin, see [Use in AI tools](https://docs.everruns.com/getting-started/use-in-ai-tools/).
## Related
* [URL mode elicitation](https://docs.everruns.com/features/mcp-url-elicitation/), tool calls that need a person
* [Capabilities](https://docs.everruns.com/features/capabilities/), how virtual capabilities fit the capability system
* [Slack Integration](https://docs.everruns.com/integrations/slack/), publishing an Agent through a messaging endpoint
---
# URL mode elicitation
> When an MCP server needs a secret, an authorization, or a payment, Everruns holds the turn and asks a person to finish it in their browser — the value never passes through the client or the model.
Source:
Some tool calls cannot be completed by an agent alone. A billing server needs the customer to authorize a charge with their bank; an analytics server needs the user’s own API key; a provider needs an OAuth consent screen clicked. The value involved must never reach the agent: not the MCP client, not the model’s context, not the event log.
MCP’s answer is **URL mode elicitation** (protocol `2026-07-28`). Instead of asking the client for the value, the server answers `tools/call` with a URL and waits. Everruns supports it on both sides.
## As an MCP client: the turn holds
When a tool call comes back with a URL elicitation, Everruns pauses the turn and puts the URL in front of the person, with the domain highlighted:
[](https://docs.everruns.com/videos/mcp-url-elicitation-consent.mp4)
The user asks for a charge, the server needs their bank’s authorization, the turn holds on a consent card, and the tool runs once they come back and confirm.
Consent is collected in **two steps** — *Open link*, then *I’ve finished — continue* — because only the person knows when the interaction on the other side actually finished. Answering the server the moment the tab opens resumes the turn too early, and the server simply asks again.
### Entering a secret
The same flow carries values the agent must never see. Here the server needs the user’s own API key and collects it on its own page:
[](https://docs.everruns.com/videos/mcp-url-elicitation-enter-a-secret.mp4)
The key goes from the user’s browser straight to the provider. The Everruns transcript carries the report, never the key.
### What the client guarantees
* **The capability is declared only when a human can answer.** A host with no way to reach a person declares no `elicitation` capability at all, so a compliant server cannot ask.
* **The URL is validated before anyone sees it**: `https` only (loopback `http` for local development), so a consent surface is never handed a `javascript:` or `file:` URL. The client never fetches it.
* **The domain is shown, and Punycode is flagged.** Internationalized domains are legitimate but can impersonate; the card says so.
* **Consent is single use and bound to one domain.** A server that elicits `pay.example.com`, waits for the click, then elicits somewhere else on the retry gets no reuse of that consent — the user is asked again.
* **A refusal is final.** Declining ends the call and tells the agent to continue without the tool.
### Refusing
[](https://docs.everruns.com/videos/mcp-url-elicitation-decline.mp4)
The user declines, and the agent carries on without the tool instead of asking again.
### One consent, one domain
[](https://docs.everruns.com/videos/mcp-url-elicitation-domain-swap.mp4)
Consent was given for one host. The server elicits a different one on the retry, so the consent is not reused: the user is asked afresh, for the new domain.
## As an MCP server: Everruns serves the form
Everruns’ own `/mcp` endpoint uses the same mechanism when a client asks it to store a secret or connect a provider. `session_set_secret` never accepts a value as a parameter: it answers with a URL to a form Everruns serves, the user types the value there, and the retry confirms it is stored. The MCP client that started the call only ever holds the URL.
The page requires the visitor’s own session on top of the signed link, and refuses anyone but the user the elicitation was minted for — the link alone grants nothing, which is what closes the phishing case the spec warns about.
[](https://docs.everruns.com/videos/mcp-url-elicitation-as-server.mp4)
An MCP client asks Everruns to store a secret, gets a URL back, the user fills in the form Everruns serves, and the retry confirms it is stored.
## Clients that cannot render a card
Pausing is a client capability, declared per session:
```json
{ "hints": { "url_elicitation": true } }
```
The Chat UI declares it automatically. A client that does not gets the older behaviour: the turn continues and the elicitation reaches the user through the tool result, as an actionable `url_elicitation_required` payload with the URL and the server’s reason.
To complete such a call from your own client, see [Complete a URL elicitation over the API](https://docs.everruns.com/how-to/complete-a-url-elicitation/).
## Protocol support
URL mode elicitation is `2026-07-28` only. In earlier eras elicitation is a server-initiated request over a server-to-client stream this transport does not open, so Everruns declares nothing regardless of what the host can do.
## Try it
`examples/mcp-url-elicitation/` in the repository has a dependency-free MCP server that elicits, and a script that drives the whole flow over the API.
## Related
* [MCP](https://docs.everruns.com/features/mcp/), Everruns on both sides of the protocol
* [Capabilities](https://docs.everruns.com/features/capabilities/), how MCP servers become agent tools
---
# Agent and User Memory
> Understand organization, agent, and user memory scopes, their automatic mount paths, and the privacy and access rules for each.
Source:
Memory keeps files available across sessions. Everruns provides three ownership scopes so shared organization knowledge, an agent’s learned context, and a user’s preferences do not have to share the same access boundary.
## The three scopes
| Scope | Lifetime and owner | How it enters a session | Access |
| ------------ | --------------------------------------------- | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| Organization | A named store managed within one organization | Select it in the `memory` capability and choose a path under `/workspace` | Read-only by default; read-write must be selected explicitly |
| Agent | One server-managed store per agent | Automatically mounted for sessions hosted by that agent at `/memory/agent` | Read-write in the hosted session |
| User | One server-managed store per user | Automatically mounted in the owning user’s private, default session workspace at `/memory/user` | Read-write for the owner and the agent working in that session |
Organization memories are appropriate for shared references such as runbooks or product documentation. Agent memory follows one host agent across its sessions. User memory follows one user so preferences and personal context can persist across their sessions.
## Privacy defaults
**User memory is private by default.** Only the session owner may access `/memory/user` through user-facing session file APIs. Other users cannot read or mutate the subtree, and file searches redact matches from it. Internal runtime access lets the agent working for the owner read and update that memory.
User memory is not mounted into caller-attached shared workspaces. Everruns waits to expose it there until mounts can be participant-local instead of visible to the entire workspace.
Agent memory is separate from user memory. It is shared by sessions hosted by the same agent, so do not use `/memory/agent` for user-private data. A guest agent’s private memory is not merged into a shared session’s workspace-wide `/memory/agent` path.
## How automatic mounts work
When a session starts, Everruns lazily creates any missing scoped stores and mounts their current files:
* `/memory/agent` contains the host agent’s persistent files.
* `/memory/user` contains the session owner’s persistent files when the session uses the owner’s private default workspace.
Both automatic mounts are read-write. The regular file tools and Workspace UI traverse them alongside session files, and writes persist to the scoped memory for later sessions.
The `/memory/*` namespace is reserved. You cannot use it for caller-supplied initial files or for mounts configured through the public `memory` capability.
## Organization memory access
Organization memories are created and managed through the Memory UI and `/v1/memories` API. Add the `memory` capability to select an active organization memory, its mount path, and its access mode.
* Omitted access mode defaults to `readonly`.
* A `readwrite` mount writes through to the durable organization memory.
* Source-backed GitHub or Git memories are always read-only.
* Agent- and user-scoped memories are server-managed and cannot be selected through `memory.mounts`.
See [Memory model](https://docs.everruns.com/advanced/memory-model/) for the relationship between durable Memory and a session Workspace, or the [API reference](https://docs.everruns.com/api/) for current organization-memory schemas.
## See also
* [Memory model](https://docs.everruns.com/advanced/memory-model/), Workspace and durable Memory architecture.
* [Session participants](https://docs.everruns.com/features/session-participants/), host and guest agent behavior in shared sessions.
* [File System capability](https://docs.everruns.com/capabilities/file-system/), tools that access session and mounted files.
---
# SDKs
> Official client libraries for Rust, Python, and TypeScript — typed clients, async APIs, SSE streaming with automatic reconnection.
Source:
The Everruns SDKs are official client libraries for building agent applications. Rust, Python, and TypeScript with a consistent API across all three.
What the SDKs handle for you:
* **Consistent API** across languages
* **Async/await patterns** throughout
* **SSE streaming** with automatic reconnection, heartbeat-based stale detection, and `since_id` resumption
* **Typed models** generated from the OpenAPI spec
* **Sub-client organization** (`agents`, `sessions`, `messages`, `events`, `filesystem`, …)
For a hands-on lesson, see [Build your first agent](https://docs.everruns.com/tutorials/building-agents-using-sdk/) or the minimal [Run an Agent](https://docs.everruns.com/tutorials/run-an-agent/) notebook.
## Install
* Rust
Requires Rust 1.70+
```bash
cargo add everruns-sdk
```
* Python
Requires Python 3.10+
```bash
pip install everruns-sdk
```
* TypeScript
Requires Node.js 18+
```bash
npm install @everruns/sdk
```
## Authenticate
All SDKs read `EVERRUNS_API_KEY` from the environment by default. You can also pass it explicitly.
* Rust
```rust
use everruns_sdk::Client;
let client = Client::from_env()?; // from env
let client = Client::new("your-api-key"); // explicit
```
* Python
```python
from everruns_sdk import Client
client = Client() # from env
client = Client(api_key="your-api-key") # explicit
```
* TypeScript
```typescript
import { Client } from "@everruns/sdk";
const client = new Client(); // from env
const client = new Client({ apiKey: "your-api-key" }); // explicit
```
## API coverage
| Resource | Operations |
| --------------------- | ------------------------------------------------------------------- |
| **Agents** | Create, list, get, update, upsert, archive, import, export, preview |
| **Sessions** | Create, list, get, update, delete, cancel |
| **Messages** | Create, list |
| **Events** | Poll, stream (SSE) |
| **Capabilities** | List, get |
| **LLM Providers** | Create, list, get, update, delete, sync models |
| **LLM Models** | Create, list, get, update, delete |
| **MCP Servers** | Create, list, get, update, delete |
| **Filesystem** | List, read, create, update, delete, move, copy, grep, stat |
| **Session Databases** | Create, list, get, delete, schema |
| **Images** | Upload, list, get, thumbnail, delete |
| **Organizations** | Create, list, get, update |
| **Scheduled Tasks** | Create, list, get, update, delete, pause, resume, trigger |
## Error handling
| Error type | Description |
| --------------------- | -------------------------- |
| `AuthenticationError` | Invalid or missing API key |
| `NotFoundError` | Resource not found |
| `RateLimitError` | Rate limit exceeded |
| `ApiError` | General API error |
* Rust
```rust
use everruns_sdk::Error;
match client.agents().get("invalid-id").await {
Ok(agent) => println!("Found: {}", agent.name),
Err(Error::NotFound(msg)) => println!("Not found: {}", msg),
Err(Error::Authentication(msg)) => println!("Auth error: {}", msg),
Err(e) => println!("Other error: {}", e),
}
```
* Python
```python
from everruns_sdk import NotFoundError, AuthenticationError
try:
agent = await client.agents.get("invalid-id")
except NotFoundError as e:
print(f"Not found: {e}")
except AuthenticationError as e:
print(f"Auth error: {e}")
```
* TypeScript
```typescript
import { NotFoundError, AuthenticationError } from "@everruns/sdk";
try {
const agent = await client.agents.get("invalid-id");
} catch (e) {
if (e instanceof NotFoundError) {
console.log(`Not found: ${e.message}`);
} else if (e instanceof AuthenticationError) {
console.log(`Auth error: ${e.message}`);
}
}
```
## Do something
* [Build your first agent](https://docs.everruns.com/tutorials/building-agents-using-sdk/) — guided tutorial.
* [Stream events with the SDK](https://docs.everruns.com/how-to/stream-events/) — common streaming patterns.
* [Handle errors and cancel turns](https://docs.everruns.com/how-to/handle-errors-and-cancellation/) — graceful failure paths.
* [Orchestrate multi-agent pipelines](https://docs.everruns.com/how-to/orchestrate-multi-agent-pipelines/) — chain agents together.
## See also
* [API reference](https://docs.everruns.com/api/) — every endpoint, generated from OpenAPI.
* [Event Reference](https://docs.everruns.com/event-reference/) — every event type.
* [GitHub Repository](https://github.com/everruns/sdk) — source code and examples.
## Overview video
[Everruns SDK Overview](https://www.youtube.com/embed/1FfGKUTzBzA)
---
# Session Participants
> Invite agents into a shared session, address a specific agent for a turn, and understand host, member, user, join, and leave behavior.
Source:
A session can include more than one agent and more than one user. Session participants record who is present, whether they are the host or a member, and when they joined or left.
## Host and member roles
An agent-backed session has one active host agent. The host supplies the session’s harness and answers user turns by default.
Other agents and users join as members:
* an **agent member** can be addressed for an individual turn;
* a **user member** records a person participating in the session, but cannot be addressed as an agent responder; and
* a member that leaves remains in participant history with a leave time.
Inviting a member agent does not replace the host or the host’s harness. The invited agent contributes its behavior, model defaults, capabilities, and client tools only when a turn is addressed to it.
## Invite an agent in the UI
The session view shows multi-party membership in the **In this session** rail on desktop. It labels each participant as **Host** or **Member** and as **Agent** or **User**.
To add an agent:
1. Open **In this session**.
2. Select **Invite agent**.
3. Choose an agent that is not already active in the session.
The agent joins as a member. The rail also shows participants who have left. Use the remove action on an active member to make it leave; the host cannot leave through the ordinary participant action. If a user who left sends another message, that user rejoins automatically with a new participation interval.
Agents can perform the same operation with invite-mode handoff. An agent with the `agent_handoff` capability calls `spawn_agent` with `mode = invite`. Unlike foreground or background handoff, invite mode joins the target agent to the current session instead of creating a child session.
## Address an agent for one turn
When at least one active member agent is available, the composer shows an **Address** selector:
* **Session host (default)** sends the turn to the host agent.
* Selecting a member agent sends that turn to the selected participant.
Addressing is per turn. It does not change the session’s host or default responder. A participant must still be active and must be an agent; requests that address a user or a participant who already left are rejected.
## API
Participant membership is managed below a session:
| Method | Path | Purpose |
| -------- | --------------------------------------------------------- | ----------------------------------------------- |
| `GET` | `/v1/sessions/{session_id}/participants` | List active and past participants in join order |
| `POST` | `/v1/sessions/{session_id}/participants` | Add an agent or user member |
| `DELETE` | `/v1/sessions/{session_id}/participants/{participant_id}` | Mark a member as having left |
To address an agent, set `addressed_participant_id` when creating a message with `POST /v1/sessions/{session_id}/messages`. Omit it to use the host.
The participant list is the source of truth for join and leave history. Join and leave do not emit dedicated participant events on the session SSE stream. See the [API reference](https://docs.everruns.com/api/) for current schemas and authorization requirements.
## See also
* [Agent triggers](https://docs.everruns.com/features/agent-triggers/), proactive runs start sessions with the trigger’s agent as host.
* [Sub Agents capability](https://docs.everruns.com/capabilities/sub-agents/), foreground, background, and invite handoff modes.
* [Core concepts](https://docs.everruns.com/explanation/concepts/), sessions, turns, and agents.
---
# Agent Skills
> Portable instruction packages following the agentskills.io open spec — progressive disclosure, bundled scripts, on-demand activation.
Source:
**Agent Skills** are portable instruction packages following the [Agent Skills](https://agentskills.io/) open specification. They keep the agent’s context efficient through *progressive disclosure*: the agent sees only short names and descriptions until it activates a skill, at which point the full instructions and bundled resources load.
## Overview video
[Agent Skills Overview](https://www.youtube.com/embed/mNqXP4NA_8U)
## Three-stage disclosure
1. **Discovery** (\~100 tokens per skill) — names and descriptions appear in the system prompt under ``.
2. **Activation** (under 5,000 tokens) — full SKILL.md instructions load when the agent calls `activate_skill`.
3. **Resources** — bundled files mount at `/skills/{name}/` and are read with the normal filesystem tools.
This keeps thousands of skills available to an agent without burning context on the ones it doesn’t need.
## Two ways to use skills
| Source | Where they live | When to use |
| ---------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------- |
| Workspace skills | `/.agents/skills/` in the session VFS | Project-specific skills; per-session experimentation |
| Registry skills | Organization-wide via the [Skills Registry](https://docs.everruns.com/features/skills-registry/) | Shared across agents; managed via API |
The built-in `skills` capability handles workspace discovery. Registry skills appear in the capability system as virtual capabilities (`skill:{uuid}`).
## Do something
* [Package an agent skill](https://docs.everruns.com/how-to/package-a-skill/) — author a SKILL.md, bundle scripts.
* [Publish a skill to the registry](https://docs.everruns.com/how-to/publish-a-skill-to-the-registry/) — share it.
## See also
* [Agent Skills capability reference](https://docs.everruns.com/capabilities/agent-skills/) — tools, dependencies, config.
* [Skills Registry](https://docs.everruns.com/features/skills-registry/) — the org-wide registry.
* [SKILL.md format spec](https://agentskills.io/) — upstream open spec.
---
# Skills Registry
> API endpoints for managing organization-wide skill packages, create from SKILL.md or ZIP, validate, assign to agents as virtual capabilities.
Source:
The **Skills Registry** stores skills at the organization level. Registry skills persist across sessions and appear in the capability system as virtual capabilities with ID `skill:{uuid}`. They can be assigned to any agent like any other capability.
For workspace-scoped skills (drop a SKILL.md into a session’s `/.agents/skills/`), see [Agent Skills](https://docs.everruns.com/features/skills/).
## API surface
| Method | Path | Description |
| ------ | ------------------------- | ------------------------- |
| POST | `/v1/skills` | Create from SKILL.md |
| POST | `/v1/skills/upload` | Create from ZIP archive |
| GET | `/v1/skills` | List |
| GET | `/v1/skills/{id}` | Get metadata |
| GET | `/v1/skills/{id}/content` | Get full content |
| PATCH | `/v1/skills/{id}` | Update |
| DELETE | `/v1/skills/{id}` | Delete |
| POST | `/v1/skills/validate` | Validate without creating |
## ID formats
| Source | ID | Example |
| --------- | ------------------------------------ | -------------------------------------------- |
| Registry | `skill:{uuid}` | `skill:550e8400-e29b-41d4-a716-446655440000` |
| Workspace | Aggregated under `skills` capability | , |
## Dependencies
Registry skills automatically depend on `session_file_system` so bundled resources can be read after activation. You don’t need to add it explicitly.
## Security
* Archive uploads are validated against path traversal, ZIP bombs, and size limits.
* Skill instructions are returned as tool results, not injected into the system prompt, they cannot bypass capability isolation.
* Skill names are unique per organization.
* Disabled skills are hidden from listings.
## Do something
* [Publish a skill to the registry](https://docs.everruns.com/how-to/publish-a-skill-to-the-registry/), full upload, validate, assign flow.
* [Package an agent skill](https://docs.everruns.com/how-to/package-a-skill/), author the SKILL.md.
## See also
* [Agent Skills](https://docs.everruns.com/features/skills/), the concept and workspace flow.
* [API reference](https://docs.everruns.com/api/), full request/response schemas.
---
# Management UI
> Manage agents, sessions, capabilities, settings, files, and event streams through the optional web interface.
Source:
While Everruns is a headless agent platform designed for API-first integration, it provides an optional management UI for administrative tasks and session monitoring.
## Overview
The management UI is a Next.js application that provides:
* Agent management (create, edit, delete)
* Session monitoring and chat interface
* Capabilities browser
* Settings management (LLM providers, personal access tokens, team members)
* Dashboard with system statistics
Access the UI at `http://localhost:9300` when running locally.
## Navigation
The sidebar provides access to main sections:
| Section | Description |
| ------------ | ------------------------------------------------------------- |
| Dashboard | Overview statistics and quick actions |
| Agents | List, create, and manage agents |
| Capabilities | Browse available capabilities |
| Settings | Configure providers, personal access tokens, and team members |
## Dashboard
The dashboard provides an at-a-glance view of your system:
* **Stats Cards**: Total agents, active sessions, and other metrics
* **Recent Agents**: Quick access to recently created or updated agents
* **Quick Actions**: Shortcuts to create agents or browse the agent list
## Agents
### Agent List
The agents page displays all agents in a card grid layout.

Each card shows:
* Agent name and status badge (active/inactive)
* Truncated ID
* Description preview
* Enabled capabilities with icons
* Tags
* Creation date
* Edit button
Click a card to view the agent details, or click the edit icon to modify the agent.
### Agent Detail
The agent detail page shows:
* **System Prompt**: Full system prompt with markdown rendering
* **Sessions List**: All sessions for this agent with status indicators
* **Capabilities**: Enabled capabilities with descriptions
* **Configuration**: Default model, description, tags, timestamps
Actions available:
* **Edit**: Modify agent configuration
* **New Session**: Create a new conversation session
### Create/Edit Agent
The agent form allows you to configure:
* **Name**: Display name for the agent
* **Description**: Optional description
* **System Prompt**: Instructions for the agent (supports markdown)
* **Default Model**: LLM model to use for conversations
* **Capabilities**: Enable/disable available capabilities
* **Tags**: Organizational tags
## Sessions
### Session View
Each session has three tabs:
#### Chat Tab
The primary interface for viewing and participating in conversations:
* Message history with user messages (dark bubbles) and agent responses
* Tool call visualization with expandable details
* Tool results displayed inline
* Message input with keyboard shortcuts (Enter to send, Shift+Enter for newline)
* Reasoning effort selector (for models that support extended thinking: Anthropic Claude, OpenAI GPT-5.x, o-series)
#### File System Tab
Browse and manage files associated with the session’s sandboxed environment.
#### Events Tab
View raw session events for debugging:
* Sequence number
* Event type (input.message, output.message.completed, tool.completed, etc.)
* Timestamp
* JSON data payload
### Session Status
Sessions display their current status:
| Status | Badge | Description |
| ------- | --------- | ------------------------------ |
| started | Outline | Newly created, no messages yet |
| idle | Secondary | Ready for input |
| active | Primary | Currently processing |
## Capabilities
The capabilities page lists all available functionality modules:
* **Summary Panel**: Counts by status (available, coming soon, deprecated)
* **Category Tags**: Filter by capability type
* **Capability Cards**: Click to view details including tools and configuration
Each capability card shows:
* Icon and name
* Identifier (for API use)
* Status badge
* Description
* Category tag
## Settings
### LLM Providers
Configure language model providers:
* Add provider credentials (API keys)
* Enable/disable specific models
* Set default models for agents
### Personal access tokens
Manage personal access tokens for programmatic access. Tokens are tied to your user account (not an organization) and inherit access to every organization available to your account:
* Create new personal access tokens
* View existing tokens (values hidden)
* Revoke tokens
### Members
View and manage team members (when authentication is enabled).
---
# 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.

## 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.

## 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

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.

## 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.

## 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

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

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.

## 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.
---
# Architecture
> The Everruns system architecture: control plane, worker nodes, durable execution engine, and SSE event streaming.
Source:
Everruns is a **headless durable agentic harness engine** built for reliability and scale. It provides a REST API for managing agents, sessions, and runs with real-time event streaming via SSE.
## Platform Overview

## Key Design Principles
| Principle | Description |
| ------------------------ | --------------------------------------------------------- |
| **Headless / API-First** | Integrate via REST API. No UI required for production. |
| **Agentic Loop** | Core pattern: Reason → Act, repeated until task complete. |
| **Durable Execution** | Agent state survives restarts. Never lose progress. |
| **Horizontal Scaling** | Add workers to increase throughput. |
| **Provider Agnostic** | OpenAI, Anthropic, Gemini, or custom LLM providers. |
## Components
For the deployable shape of these components, which run as separate processes, which are required, and which are optional infrastructure like NATS and Valkey, see [Physical Architecture](https://docs.everruns.com/advanced/physical-architecture/).
### Control Plane
Central coordinator that exposes the REST API and manages all state in PostgreSQL:
* **Agents** - AI assistant configurations
* **Sessions** - Conversation state and history
* **Events** - Real-time event streaming via SSE
### Workers
Stateless executors that run agentic loops composed from atoms (Input → Reason → Act). Workers are:
* **Scalable** - Add more to handle concurrent sessions
* **Fault-tolerant** - Failed tasks automatically recovered
* **Stateless** - All state lives in PostgreSQL
### Management UI (Optional)
Administrative interface for platform operators. **Not required for production use.**
* Agent configuration
* Session monitoring
* LLM provider settings
See [Management UI](https://docs.everruns.com/features/ui/) for details.
## Further Reading
* [Physical Architecture](https://docs.everruns.com/advanced/physical-architecture/) - The deployable components: PostgreSQL, NATS, Valkey, workers, and how they connect
* [Introduction](https://docs.everruns.com/getting-started/introduction/) - Getting started
* [API Reference](https://docs.everruns.com/api/) - Full API documentation
* [Capabilities](https://docs.everruns.com/features/capabilities/) - Extend agent functionality
---
# Concepts
> How harnesses, agents, endpoints, sessions, turns, events, capabilities, tools, and files fit together in the execution model.
Source:
This page is the **concept cheat-sheet**: short definitions of every entity, organised into three layers (high-level execution model, session internals, and settings). For the design rationale behind each entity, read [Core concepts](https://docs.everruns.com/explanation/concepts/) under Explanation.
## High Level
Harness and Agent are **configuration containers**: they hold capabilities and define behavior. At runtime, their configuration merges into a **RuntimeAgent** which executes inside a Session.

* **Solid arrows**: configuration ownership: Harness has Agents and Capabilities, Agent has Capabilities
* **Dashed arrows**: runtime assembly: config merges into RuntimeAgent, which executes in a Session
### Harness
A Harness is what an agent runs on. It defines the execution environment, defaults, and constraints for sessions: which capabilities are available by default, the default model, network access, and starter files.
* There can be many harnesses in the system
* Each agent holds exactly one harness reference
* Each session runs on exactly one harness, the agent’s unless the session names another
* A harness can have capabilities attached to it
A Harness is not the agent loop. “Agent harness” commonly means that loop elsewhere, and Everruns uses the word that way when describing itself as a durable agentic harness engine. The loop is the runtime; a Harness is configuration the runtime reads. See [Harnesses](https://docs.everruns.com/features/harnesses/).
### Agent
An Agent is a domain-specific or task-specific configuration for the agentic loop. It defines the system prompt, the default LLM model, and which capabilities are enabled.
* There can be many agents in the system
* A session may or may not have an agent assigned
* Agents can be assigned or changed during the lifetime of a session
* Each agent has capabilities with position ordering
* Each agent references a default LLM model
### Session
A Session is a working instance of an agentic loop. It is configured by its harness and, optionally, by an agent. Sessions are the primary execution context where conversations happen.
* There can be many sessions in the system
* Each session has an assigned harness
* The agent is optional and can change over the session’s lifetime
* Sessions can have their own capabilities, which are additive to the agent’s capabilities
* Sessions can override the LLM model
* Status flow: `started` → `active` → `idle` (sessions work indefinitely)
### Capability
A Capability is a modular, reusable configuration unit that extends the behavior of a harness, agent, or session. Each capability can contribute:
1. **System prompt additions**: text prepended to the agent’s prompt
2. **Tools**: functions the agent can invoke
3. **Mount points**: files and directories populated in the session filesystem
* Can be attached to a harness, an agent, or a session
* Session capabilities are additive to agent capabilities
* Built-in capabilities use `snake_case` IDs (e.g., `current_time`, `web_fetch`)
* MCP servers appear as virtual capabilities with `mcp:{uuid}` IDs
* Capabilities can depend on other capabilities, resolved in topological order
See [Capabilities](https://docs.everruns.com/features/capabilities/) for a full list and configuration details.
### Tool
A Tool is a function the agent can invoke during execution. Tools are provided by capabilities.
* Built-in tools have no name prefix
* MCP tools are prefixed: `mcp_{server_name}__{tool_name}`
* Executed during the act phase of a turn
### Endpoint
An Endpoint is an Agent-owned way for an external caller to reach that Agent. Slack, AG-UI, A2A, FCP, and Public Chat each use an endpoint with transport-specific configuration.
* Each endpoint belongs to exactly one Agent.
* Each endpoint has its own publish state, credentials, identity, and version policy.
* Lifecycle: `draft` → `live` → `draft`.
* Incoming messages route to sessions by the endpoint’s session strategy.
* An Agent can own multiple independently published endpoints.
Open an Agent’s **Integrations** tab to create and manage endpoints. See [Slack Integration](https://docs.everruns.com/integrations/slack/) for a complete example.
***
## Session Internals
Each session contains turns, messages, events, an isolated filesystem, and key-value storage.

### Turn
A Turn is one iteration of the agent loop: reason (call the LLM) then act (execute tools).
* Each turn belongs to a session
* A turn produces messages and emits events
* Lifecycle: `turn.started` → reason → act → `turn.completed` (or `turn.failed`)
#### The Agentic Loop
Understanding the reason-act loop is key to building effective agents. Here’s what happens inside each turn:

Each iteration:
1. **Reason**: The LLM receives the full conversation history (system prompt + messages + tool results) and produces either a text response or tool calls
2. **Act**: All tool calls from the LLM are executed in parallel. Results are added to the conversation history
3. **Loop**: If there were tool calls, go back to Reason. If the LLM produced a final text response, the turn is complete
The loop runs for a maximum of **10 iterations** per turn to prevent runaway execution.
#### Durable Execution
In production mode (PostgreSQL-backed), each step is a separate durable task:

If a worker crashes mid-turn, the control plane detects the missed heartbeat and re-queues the task for another worker. Your application sees a brief delay, not a failure.
### Message
A Message is a conversation entry reconstructed from the event log. Messages are not stored in a separate table.
* Roles: `user`, `agent`, `tool_result`
* Content is an array of parts: text, image, tool\_call, tool\_result
* Agent messages may include extended thinking content from reasoning models (Anthropic Claude, OpenAI GPT-5.x and o-series)
* Supports per-message controls such as model override and reasoning effort
### Event
An Event is an immutable, append-only record. Events are the primary data store for conversations and SSE notifications.
* Atomic per-session sequence numbering
* Types: input, output, turn, atom, tool, LLM, session lifecycle
* Cannot be updated or deleted
* Carries correlation context: turn ID, input message ID, execution ID
See [Events](https://docs.everruns.com/features/events/) for the full event reference.
### File System
Each session has an isolated virtual filesystem stored in PostgreSQL.
* Paths are relative to `/workspace`
* Capabilities can mount initial files and directories
* Shared between the FileSystem and BashkitShell capabilities
* Files support an optional read-only flag
### Key-Value Store
Each session has scoped storage with two tiers:
* **Key/Value**: plain text storage for general data such as state, preferences, or intermediate results
* **Secrets**: AES-256-GCM encrypted at rest for API keys, tokens, and credentials
* Storage is session-isolated and cannot be accessed across sessions
***
## Settings
System-wide configuration for LLM providers, models, and MCP servers.

### LLM Provider
An LLM Provider is a configured API provider such as OpenAI or Anthropic. Providers store encrypted API keys and contain models.
* Provider types include `openai`, `openrouter`, `openai_completions`, `anthropic`, `gemini`, and `bedrock`
* Each provider contains many models
* Default providers (OpenAI, Anthropic) are seeded on startup
### LLM Model
An LLM Model is a specific model within a provider (e.g., `gpt-5.2`, `claude-sonnet-5`).
* Each model belongs to one provider
* Sources: predefined, discovered from the provider API, or manually added
* Model resolution priority: message controls → session override → agent default → system default
### MCP Server
An MCP Server is a remote server that exposes tools via the Model Context Protocol. MCP servers are integrated as virtual capabilities.
* Each server becomes a capability with ID `mcp:{server_uuid}`
* Tools are discovered at runtime and cached with a 24-hour TTL
* Tool names are prefixed to avoid conflicts: `mcp_{server}__{tool}`
* Execution happens via HTTP JSON-RPC
---
# Docker Compose
> Deploy the Everruns platform with Docker Compose: control plane, workers, UI, and PostgreSQL.
Source:
Deploy the complete Everruns platform using Docker Compose. This guide sets up the control plane, workers, UI, and database in a single command.
## Prerequisites
* Docker Engine 20.10+
* Docker Compose v2.0+
* 4GB available RAM
## Quick Start
### 1. Download Docker Compose File
```bash
# Create directory and download docker-compose file
mkdir everruns && cd everruns
curl -o docker-compose.yaml https://raw.githubusercontent.com/everruns/everruns/main/examples/docker-compose-full.yaml
```
### 2. Generate Encryption Key
Everruns encrypts LLM API keys at rest. Generate a key:
```bash
python3 -c "import os, base64; print('kek-v1:' + base64.b64encode(os.urandom(32)).decode())"
```
### 3. Create Environment File
Create a `.env` file with your encryption key and optional LLM API keys:
.env
```bash
SECRETS_ENCRYPTION_KEY=kek-v1:
# Optional: Add API keys here to skip UI configuration
DEFAULT_OPENAI_API_KEY=sk-...
DEFAULT_ANTHROPIC_API_KEY=sk-ant-...
DEFAULT_GEMINI_API_KEY=AIza...
```
### 4. Start Services
```bash
docker compose pull # Fetch latest images
docker compose up -d
```
The published compose file defaults to app entry point on `9300`. If that port is busy, override before startup:
```bash
EXAMPLE_PROXY_PORT=10300 docker compose up -d
```
This starts:
* PostgreSQL database
* Control plane API
* 3 worker instances
* Next.js UI
* Caddy reverse proxy
### 5. Access the Platform
| Service | URL |
| ------------------ | -------------------------------------------------------------- |
| **Web UI** | |
| **API** | … |
| **OAuth** | … |
| **MCP** | |
| **OAuth Metadata** | |
| **Health Check** | |
## Configuration
### Run Multiple Copies
If you want multiple Everruns compose stacks on the same machine, set both a Compose project name and host-port overrides:
```bash
COMPOSE_PROJECT_NAME=everruns-demo-2 \
EXAMPLE_PROXY_PORT=10300 \
docker compose up -d
```
### Configure LLM Provider
If you didn’t set LLM API keys (`DEFAULT_OPENAI_API_KEY`, `DEFAULT_ANTHROPIC_API_KEY`, or `DEFAULT_GEMINI_API_KEY`) in your `.env` file, configure via UI:
1. Open
2. Navigate to **Settings** > **Providers**
3. Add your OpenAI or Anthropic API key
4. Save and verify connection
### Create Your First Agent
1. Go to **Agents** in the UI
2. Click **Create Agent**
3. Set a name and system prompt
4. Select your configured LLM provider
5. Save the agent
### Start a Session
```bash
# Create a session (agent_id in request body)
curl -X POST http://localhost:9300/api/v1/sessions \
-H "Content-Type: application/json" \
-d '{"agent_id": "{agent_id}"}'
# Send a message
curl -X POST http://localhost:9300/api/v1/sessions/{session_id}/messages \
-H "Content-Type: application/json" \
-d '{"message": {"role": "user", "content": [{"type": "text", "text": "Hello!"}]}}'
```
## Scaling Workers
Add more workers by scaling the worker services:
```bash
# Scale to 5 workers
docker compose up -d --scale worker-1=1 --scale worker-2=1 --scale worker-3=3
```
Or modify `docker-compose.yaml` to add more worker services.
## Monitoring
### View Logs
```bash
# All services
docker compose logs -f
# Specific service
docker compose logs -f server
docker compose logs -f worker-1
```
### Distributed Tracing
Set `OTEL_EXPORTER_OTLP_ENDPOINT` to export traces to any OTLP-compatible backend (Grafana Tempo, Datadog, etc.).
## Stopping Services
```bash
# Stop all services
docker compose down
# Stop and remove volumes (deletes data)
docker compose down -v
```
## Troubleshooting
### Database Connection Issues
If services fail to connect to PostgreSQL:
```bash
# Check postgres health
docker compose ps postgres
# View postgres logs
docker compose logs postgres
```
### Migration Failures
Migrations are auto-applied when the API server starts. If migrations fail:
```bash
# Check API logs for migration errors
docker compose logs api
# Restart API to retry migrations
docker compose restart api
```
To check migration status before deployment:
```bash
docker compose exec api everruns-admin migrate-info
```
### Worker Not Processing
Verify workers can reach the control plane:
```bash
# Check worker logs
docker compose logs worker-1
# Verify gRPC connection
docker compose exec worker-1 /bin/sh -c "echo" || echo "Cannot exec (distroless image)"
```
## Next Steps
* [API Reference](https://docs.everruns.com/api/) - Full API documentation
* [Capabilities](https://docs.everruns.com/features/capabilities/) - Extend agent functionality
* [Environment Variables](https://docs.everruns.com/sre/environment-variables/) - Advanced configuration
---
# Introduction
> What Everruns is, what it provides, and where to go next.
Source:
Everruns is a durable agentic harness engine built on Rust. It provides APIs for managing agents, sessions, and runs, streams events over SSE, and persists execution state in PostgreSQL so a long-running task survives a worker restart.
## Key Concepts
### Agents
An agent is a configuration the runtime executes. Each one carries:
* A system prompt that defines its behavior
* A set of capabilities that provide tools
* Model configuration for the underlying LLM
### Sessions
Sessions represent conversations with an agent. Each session maintains:
* Conversation history
* Current execution state
* Configuration overrides
### Capabilities
A capability is a unit of agent behavior. Each one can:
* Add instructions to the system prompt
* Provide tools for the agent to use
* Modify execution behavior
See [Capabilities](https://docs.everruns.com/features/capabilities/) for more details.
## Getting Started
### Ways to run Everruns
* **[Everruns Cloud](https://app.everruns.com)**: the hosted Platform, open in early access. We run the server, database, and workers. Free for now, and you bring your own model provider keys.
* **[Docker Compose](https://docs.everruns.com/getting-started/docker-compose/)**: run the full Platform on infrastructure you control.
* **[Framework](https://docs.everruns.com/framework/)**: embed durable agents in a Rust process, with no separate Platform to operate.
### Quick Start
1. Deploy Everruns using the provided Docker images, or create an account on [Everruns Cloud](https://app.everruns.com) and skip this step
2. Configure your LLM providers via the Settings UI
3. Create an agent
4. Start sessions and interact through the API or UI
### API Access
The API is available at your deployment URL:
* **API Base**: `https://your-domain.com/api/v1/`
* **OpenAPI Spec**: `https://your-domain.com/api-doc/openapi.json`
## Architecture
Everruns uses a layered architecture:
* **API Layer**: HTTP endpoints (axum), SSE streaming
* **Core Layer**: Agent abstractions, capabilities, tools
* **Worker Layer**: Durable workflows for reliable execution
* **Storage Layer**: PostgreSQL with encrypted secrets and durable execution state
See [Architecture](https://docs.everruns.com/getting-started/architecture/) for how these layers interact.
---
# Use in AI Tools
> Set up Everruns in AI tools through the Everruns plugin.
Source:
# Use in AI Tools (Everruns Plugin)
The `everruns` plugin connects Claude Code, Codex, and Cursor to any Everruns deployment over MCP. It ships skills, slash commands, and agents; Codex setup additionally uses the plugin’s `defaultPrompt` and `description`.
## Quickstart (local Everruns)
```bash
just up
just agent-auth PROVIDER=cursor # or vscode, claude, codex, gemini, droid, opencode
```
This scaffolds a plugin that talks to your local deployment. Point the plugin at a different deployment by setting `EVERUNS_MCP_URL` (defaults to `https://app.everruns.com/mcp`).
## Codex (ChatGPT + CLI)
Use the plugin’s `defaultPrompt` and `description` so you do not have to type OAuth scopes and MCP labels by hand:
```jsonc
{
"title": "Everruns",
"text": "...",
"images": ["docs/getting-started/codex-everruns-plugin.png"],
"skill": "everruns",
"commands": ["commands"],
"mcp": "everruns",
"defaultPrompt": "You are using Everruns at ${EVERUNS_MCP_URL:-https://app.everruns.com/mcp}. Use the everruns skill...",
"description": "Connects Codex to Everruns over MCP with skills, slash commands, and agents."
}
```

To install the published plugin, open the `everruns` plugin page in Codex and choose **Add to Codex**.
## Plugin layout
The portable plugin lives in `plugins/everruns/`:
* `plugin.json` / `mcp.json` — marketplace registration (name `everruns`, version, MCP server URL).
* `.claude-plugin/plugin.json` — Claude Code manifest.
* `.codex-plugin/plugin.json` — Codex manifest (`defaultPrompt`, `description`).
* `.cursor-plugin/plugin.json` — Cursor manifest.
* `skills/everruns/SKILL.md` — the agent skill (frontmatter `name: everruns`).
* `commands/` — slash commands.
`EVERUNS_MCP_URL` selects the deployment the plugin talks to; it defaults to `https://app.everruns.com/mcp`.
## Read the docs as text
The documentation site publishes itself as plain Markdown for agents and other tools that would rather read text than HTML.
* [`/llms.txt`](https://docs.everruns.com/llms.txt) — the index: the three ways to run Everruns, every documentation set, and the machine-readable surfaces. Start here.
* [`/llms-full.txt`](https://docs.everruns.com/llms-full.txt) — every prose page in one file (roughly 250k tokens).
* [`/llms-small.txt`](https://docs.everruns.com/llms-small.txt) — the same corpus without the vendor- and operator-specific long tails.
* `/_llms-txt/.txt` — one topic at a time, mirroring the sidebar: [framework](https://docs.everruns.com/_llms-txt/framework.txt), [getting-started](https://docs.everruns.com/_llms-txt/getting-started.txt), [built-ins](https://docs.everruns.com/_llms-txt/built-ins.txt), [guides](https://docs.everruns.com/_llms-txt/guides.txt), [integrations](https://docs.everruns.com/_llms-txt/integrations.txt), [explanation](https://docs.everruns.com/_llms-txt/explanation.txt), [reference](https://docs.everruns.com/_llms-txt/reference.txt), [operations](https://docs.everruns.com/_llms-txt/operations.txt). Prefer a set over the complete text.
* [`/api/openapi.json`](https://docs.everruns.com/api/openapi.json) — the REST API as OpenAPI 3.0. The text sets carry prose only, so take endpoint shapes from here.
Every page in those files begins with its title, its description, and a `Source:` line holding the canonical URL. Cite that URL, not the text file.
---
# How-to guides
> Task-oriented recipes for common Everruns workflows, equipping agents with tools, streaming events, deploying to channels, and operating production agents.
Source:
Each how-to here solves one concrete problem. They assume you already understand the basics (read the [Tutorials](https://docs.everruns.com/tutorials/run-an-agent/) first) and they don’t try to teach concepts (see [Explanation](https://docs.everruns.com/explanation/) for that).
## Building agents
* [Equip an agent with tools](https://docs.everruns.com/how-to/equip-agents-with-tools/), pick capabilities and assign them.
* [Give an agent web access](https://docs.everruns.com/how-to/give-an-agent-web-access/), `web_fetch`, network policies, allowlists.
* [Define agents as files](https://docs.everruns.com/how-to/define-agents-as-files/), version-controllable agent definitions in Markdown, TOML, or YAML.
* [Use AGENTS.md for project instructions](https://docs.everruns.com/how-to/use-agents-md/), inject project-level context into the system prompt.
* [Customize a harness](https://docs.everruns.com/how-to/customize-a-harness/), create your own harness as a starting point for many agents.
* [Share knowledge with OKF](https://docs.everruns.com/how-to/share-knowledge-with-okf/), import/export Knowledge Bases as Open Knowledge Format bundles, managed like code.
* [Migrate between LLM providers](https://docs.everruns.com/how-to/migrate-providers/), swap OpenAI ↔ Anthropic ↔ Gemini without rewriting agents.
## Running agents
* [Stream events with the SDK](https://docs.everruns.com/how-to/stream-events/), consume the SSE stream from Python, with reconnection and event filtering.
* [Consume events via raw SSE](https://docs.everruns.com/how-to/consume-events-via-sse/), when you don’t want the SDK: curl, EventSource, or any HTTP client.
* [Handle errors and cancel turns](https://docs.everruns.com/how-to/handle-errors-and-cancellation/), graceful failure paths, turn cancellation, retries.
* [Orchestrate multi-agent pipelines](https://docs.everruns.com/how-to/orchestrate-multi-agent-pipelines/), chain sessions together.
* [Build a foreman agent](https://docs.everruns.com/how-to/build-a-foreman-agent/), put one agent in front of a team of specialists and let it triage and delegate.
## Packaging and distribution
* [Package an agent skill](https://docs.everruns.com/how-to/package-a-skill/), author a SKILL.md, bundle scripts and references.
* [Publish a skill to the registry](https://docs.everruns.com/how-to/publish-a-skill-to-the-registry/), share skills across agents.
* [Publish an agent as a Slack app](https://docs.everruns.com/how-to/publish-to-slack/), deploy an agent to a Slack workspace.
## Upgrading
* [Migrate to 0.18](https://docs.everruns.com/how-to/migrate-to-0-18/), move Rust code off the `everruns-core` paths that changed, with a symbol-by-symbol table of where each type now lives.
## Operating
* [Automate with the CLI](https://docs.everruns.com/how-to/automate-with-the-cli/), scripting against the CLI with `jq`.
* [Deploy with Docker Compose](https://docs.everruns.com/getting-started/docker-compose/), bring up the full platform.
* [Enforce a budget](https://docs.everruns.com/how-to/enforce-a-budget/), cap token spend per agent, session, or organization.
---
# Automate with the CLI
> Script the Everruns CLI with structured output, jq, quiet mode, and shell pipelines for CI, cron jobs, and integration with other tools.
Source:
The CLI emits structured output (JSON, YAML) for scripting. Combined with `jq` and `--quiet` mode, it composes naturally with shell pipelines.
For a command reference, see [CLI](https://docs.everruns.com/features/cli/).
## Capture IDs
```bash
AGENT_ID=$(everruns agents create \
--name "assistant" \
--system-prompt "You are a helpful assistant." \
-o json | jq -r '.id')
SESSION_ID=$(everruns sessions create --agent "$AGENT_ID" -o json | jq -r '.id')
everruns chat "What time is it?" --session "$SESSION_ID"
```
## Quiet mode
`--quiet` suppresses headers and tables, printing only the essential identifier:
```bash
everruns agents create -f agent.toml --quiet
# Output: agt_550e8400e29b41d4a716446655440000
```
Useful inside `$(...)` substitution when JSON parsing is overkill.
## Filter listings
```bash
# Active agents only
everruns agents list --output json | jq '.data[] | select(.status == "active")'
# Just the names of agents tagged "production"
everruns agents list -o json \
| jq -r '.data[] | select(.tags[]? == "production") | .name'
# Agents created in the last 24h
everruns agents list -o json \
| jq --arg cutoff "$(date -u -d '24 hours ago' +%FT%TZ)" \
'.data[] | select(.created_at > $cutoff)'
```
## Configure the API URL
```bash
# Per-command
everruns --api-url http://localhost:9300/api agents list
# For the whole shell
export EVERRUNS_API_URL=http://localhost:9300/api
export EVERRUNS_API_KEY=dev
```
In CI, set both via secrets and the CLI will pick them up automatically.
## Drive sessions from a file-defined agent
```bash
cat > agent.md <<'EOF'
---
name: "code-reviewer"
capabilities:
- ref: current_time
- ref: filesystem
config:
allowed_paths: ["/workspace"]
tags: [development]
---
You are an expert code reviewer.
When reviewing code:
1. Check for bugs and edge cases
2. Suggest performance improvements
3. Ensure code follows best practices
EOF
AGENT_ID=$(everruns agents create -f agent.md -o json | jq -r '.id')
SESSION_ID=$(everruns sessions create --agent "$AGENT_ID" -o json | jq -r '.id')
everruns chat "Review the diff at HEAD~1..HEAD" --session "$SESSION_ID"
```
## Send-and-exit (no streaming)
`--no-stream` queues the message and returns immediately. Useful when a downstream system polls for results.
```bash
everruns chat "Process the queue" --session "$SESSION_ID" --no-stream
```
## See also
* [CLI reference](https://docs.everruns.com/features/cli/), full command list and flags.
* [Define agents as files](https://docs.everruns.com/how-to/define-agents-as-files/), the file formats accepted by `-f`.
---
# Build a foreman agent
> Put one agent in front of a team of specialists, so a Slack mention is triaged and delegated to the right worker instead of answered by a generalist.
Source:
A **foreman** is an agent whose job is routing, not answering. It receives an ambiguous request, decides which specialist should handle it, delegates, and reports back. The specialists are ordinary Agents with their own prompts, tools, and models.
This is server-side delegation. If you want the *application* to chain agents in sequence, see [Orchestrate multi-agent pipelines](https://docs.everruns.com/how-to/orchestrate-multi-agent-pipelines/) instead — that pattern keeps control in your code. A foreman keeps control in the agent, which is what you want when the trigger is a human asking for something in Slack.
## Prerequisites
* Two or more Agents to delegate to, each already working on its own.
* An agent to act as the foreman.
* For the Slack front door: a Slack workspace where you can create apps.
## Step 1 — Get the workers right first
Build and test each specialist on its own before wiring any delegation. A foreman that routes correctly to a broken worker looks like a broken foreman, and you will debug the wrong layer.
Give each one a narrow prompt and only the capabilities it needs. “Reviews Rust diffs for correctness bugs” routes better than “helps with code”, because the foreman picks targets from their descriptions.
Note their agent ids (`agent_...`).
## Step 2 — Give the foreman the `agent_handoff` capability
Delegation targets are an explicit allowlist on the foreman. Configure the `agent_handoff` capability with one entry per worker:
```json
{
"targets": [
{
"id": "code_reviewer",
"name": "Code Reviewer",
"description": "Reviews diffs and pull requests for correctness bugs",
"agent_id": "agent_...",
"required_connections": [],
"required_scopes": []
},
{
"id": "incident_responder",
"name": "Incident Responder",
"description": "Investigates alerts and production incidents",
"agent_id": "agent_..."
}
]
}
```
`description` is the field the model reads when choosing a target, so write it for that purpose. `id` is the stable key the foreman passes back in tool calls; keep it short.
Two properties worth knowing, because they shape what the foreman can do:
* The foreman **does not inherit the target’s tools**, and never receives the target’s provider credentials. It can ask a worker to act; it cannot act as the worker.
* `required_connections` gates a handoff on a provider connection existing before the run starts, which turns a mid-run credential failure into an up-front refusal.
`required_scopes` are audit labels in the current implementation, not enforced grants — a tool that needs hard authorization checks its own scopes before acting.
## Step 3 — Write the foreman’s prompt
This is the part that decides whether the thing works, and the part no configuration can do for you. A foreman’s prompt needs four things:
1. **When to delegate and when to answer.** Without this, a foreman either delegates trivia or answers things it should have routed. Be concrete: “If the request names a file, a diff, or a PR, hand off to `code_reviewer`.”
2. **How to choose between overlapping targets**, and what to do when none fits — usually ask a clarifying question rather than guessing.
3. **What to do while work is running.** Background handoffs return immediately; the foreman must know to report that it has dispatched, not to invent a result.
4. **How to report back.** A foreman that delegates silently is worse than no foreman, because the requester cannot tell whether anything is happening.
Keep it short. A long routing prompt tends to produce a foreman that reasons about routing out loud in your Slack channel.
## Step 4 — Understand what `spawn_agent` gives you
The foreman delegates with `spawn_agent`, using `target.type = "agent"` and the `target.id` from your config. The parameters that matter for a foreman:
| Parameter | Why a foreman cares |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mode` | `background` (default) returns a `task_id` immediately so the foreman can dispatch several workers and stay responsive. `foreground` blocks until the child finishes. `invite` joins the target into the *current* session instead of a child one. |
| `result_schema` | A JSON Schema the child must satisfy. Turns “whatever the worker said” into a structured result the foreman can act on rather than re-parse. |
| `public_context` | Non-secret context appended to the child task. Use it for the Slack thread reference so the worker knows where the request came from. |
| `instructions` | The work request. **Must not contain credentials** — the child has its own. |
Background handoffs create a task with `wake_policy = on_terminal`, so the foreman is woken when a worker finishes. It does not poll, and you should not prompt it to.
While work is in flight the foreman manages it with the generic task tools: `list_tasks`, `get_task`, `message_task` to steer a worker mid-run, and `cancel_task`. These work identically for subagents, so a foreman can mix both kinds of delegation.
### Subagent or handoff?
Both go through `spawn_agent`; the difference is what the child *is*.
| | `target.type = "subagent"` | `target.type = "agent"` |
| ------------ | ------------------------------------------------------ | ------------------------------------------------------------------------- |
| Child config | Inherits the foreman’s harness and agent configuration | The target Agent’s own prompt, capabilities, MCP servers, model |
| Set-up cost | None — spawn by name | Build and allowlist the Agent first |
| Use when | The work is the same kind of work, just parallel | The work needs different tools, a different model, or different authority |
A foreman that fans out “review these six files” wants subagents. A foreman that routes “is this a code question or an incident?” wants handoff. Most real ones use both.
Subagent fan-out needs the separate `subagents` capability on the foreman — see [Sub Agents](https://docs.everruns.com/capabilities/sub-agents/). Nesting is depth-governed (`max_subagent_depth`) with root-tree caps on live and total descendant tasks, so a foreman cannot fork-bomb your org by accident.
## Step 5 — Put it in front of Slack
Expose the **foreman** — and only the foreman — on Slack. The workers stay internal; they are reached through delegation, not by being mentioned.
Follow [Publish an agent as a Slack app](https://docs.everruns.com/how-to/publish-to-slack/) for the mechanics. Two choices matter for a foreman:
* **`session_strategy: per_thread`** (the default). Each Slack thread becomes one foreman session, which is what you want: the thread is the unit of work, and the foreman keeps its delegation state for the life of that thread.
* **Enable the agent surface** (`agent_surface_enabled`) if you want the foreman available in Slack’s assistant pane as well as in channels. The pane streams replies token-by-token and shows a status line while tools run.
## Step 6 — Let the foreman act on Slack (optional)
Out of the box an agent can reply in its own thread and nothing else. It cannot add a reaction, send a DM, look someone up, or post to another channel.
If your foreman needs those — acknowledging a request with an emoji while work runs is the common one — attach a Slack MCP server as a capability on the foreman. MCP servers appear as virtual capabilities alongside built-in ones.
Be aware this means a second Slack token, separate from the channel’s bot token, with its own scopes to manage and rotate.
## Step 7 — Test the routing, not the workers
Send the foreman requests that are deliberately near the boundary between two targets, and requests that match none. Those are where routing fails. A request that obviously belongs to one worker will pass whether or not your prompt is any good.
Check that a dispatched request reports back into the thread that asked. A foreman that accepts work and reports somewhere else trains people to stop using it.
## Limits worth knowing before you commit
* **No approval buttons.** Slack interactivity is not wired up, so a foreman cannot ask “approve this?” with a button and act on the click. It can only ask in prose and read the reply. For a foreman that dispatches consequential work, this is the real constraint.
* **Progress is per-turn, not per-task.** The Slack status line reflects the foreman’s current turn. “3 of 5 workers finished” is available to the foreman via `list_tasks` but is not rendered into Slack for you; the foreman has to say it.
* **Two identities** if you use a Slack MCP server, as above.
## See also
* [Sub Agents](https://docs.everruns.com/capabilities/sub-agents/) — the `subagents` capability and the shared `spawn_agent` dispatcher
* [Publish an agent as a Slack app](https://docs.everruns.com/how-to/publish-to-slack/)
* [Orchestrate multi-agent pipelines](https://docs.everruns.com/how-to/orchestrate-multi-agent-pipelines/) — the client-side alternative
---
# Complete a URL elicitation over the API
> Drive the pause-and-consent flow from your own client — declare the hint, read the confirm_url_elicitation event, and post the user's decision.
Source:
When an MCP server asks that a person finish something in their browser, Everruns pauses the turn and waits for a decision (see [URL mode elicitation](https://docs.everruns.com/features/mcp-url-elicitation/)). The Chat UI renders a card for this. Any client can do the same over the REST API and the SSE stream.
## 1. Declare that you can ask
The pause only happens for clients that say they can answer it:
```bash
curl -X POST "$EVERRUNS/api/v1/sessions" \
-H 'content-type: application/json' \
-d '{
"agent_id": "agent_01a063c3b55f79f2b5de55fb002e0ae3",
"hints": { "url_elicitation": true }
}'
```
Without the hint the turn never pauses and the flow cannot be completed — see [Clients that cannot pause](#clients-that-cannot-pause).
## 2. Watch for the pause
Send a message as usual. When a tool call hits an elicitation, the session moves to `waiting_for_tool_results` and a `tool.call_requested` event arrives on `GET /api/v1/sessions/{session_id}/sse`:
```json
{
"type": "tool.call_requested",
"data": {
"tool_calls": [
{
"id": "url_elicitation_01a063cf-c0bb-7891-926e-fd83aeb24d88",
"name": "confirm_url_elicitation",
"arguments": {
"server": "acme_analytics",
"tool": "run_revenue_report",
"retry_tool": "mcp_acme_analytics__run_revenue_report",
"message": "Acme Analytics needs your API key before it can run this report.",
"url": "https://acme-analytics.example/connect?ref=rev-2026-08",
"url_host": "acme-analytics.example",
"url_is_punycode": false
}
}
]
}
}
```
Everything needed to render your own surface is in `arguments`: the full URL, the host to emphasise, whether that host is Punycode, and which server is asking.
Show the URL in full and open it only on an explicit action. Never fetch it on the user’s behalf.
## 3. Post the decision
Post **when the user says they have finished**, not when they open the link. The server checks whether the out-of-band interaction completed, so consenting at open time just makes it ask again.
```bash
curl -X POST "$EVERRUNS/api/v1/sessions/$SESSION_ID/mcp-elicitation-consent" \
-H 'content-type: application/json' \
-d '{
"tool_call_id": "url_elicitation_01a063cf-c0bb-7891-926e-fd83aeb24d88",
"action": "accept"
}'
```
```json
{ "host": "acme-analytics.example", "status": "active" }
```
`"action": "decline"` records nothing and lets the agent continue without the tool. Errors worth handling: `404` when the tool call is not a pending elicitation, `409` when the session is not paused (already answered, or timed out).
The request body carries only the decision. The server, tool and domain the consent applies to are read from the event Everruns emitted, so a client cannot record consent for something the user was never shown.
## 4. Nothing else to do
Everruns records the consent, adds the decision to the conversation as a user turn, and resumes. Your stream then shows the tool being called again and the result arriving — the retry answers the MCP server `accept` on your behalf.
## Timing and reuse
* **You have about five minutes.** A session left in `waiting_for_tool_results` is swept (`TOOL_RESULT_TIMEOUT_SECS`, default `300`), the pending call is completed as a timeout, and the turn resumes without consent.
* **One consent authorises one retry.** It is deleted when used, so a second elicitation asks again.
* **Consent is bound to the domain the user saw.** If the server elicits a different host on the retry, the consent is not reused and a new `confirm_url_elicitation` event arrives.
## Clients that cannot pause
Without the `url_elicitation` hint the turn continues and the model relays the link, carrying this payload as the tool result:
```json
{
"code": "url_elicitation_required",
"url": "https://acme-analytics.example/connect?ref=rev-2026-08",
"url_host": "acme-analytics.example",
"url_is_punycode": false,
"server": "acme_analytics",
"tool": "run_revenue_report",
"retry_tool": "mcp_acme_analytics__run_revenue_report",
"message": "Acme Analytics needs your API key before it can run this report.",
"declined": false
}
```
That is informational only: with no pause there is no pending call to answer, the consent endpoint returns `409`, and the tool elicits again on every retry. Declare the hint for any client that needs these tools to complete.
## Calling Everruns as an MCP server
The reverse direction is plain MCP. Declare the capability in `_meta`:
```json
{
"_meta": {
"io.modelcontextprotocol/clientCapabilities": { "elicitation": { "url": {} } }
}
}
```
`session_set_secret` then answers with an `input_required` result instead of taking a value:
```json
{
"resultType": "input_required",
"requestState": "eyJ1c2VyX2lkIjoi…",
"inputRequests": {
"secret": {
"method": "elicitation/create",
"params": {
"mode": "url",
"url": "https://app.example.com/api/mcp/elicitations/secret?token=eyJ1c2Vy…",
"message": "Everruns needs the value of 'STRIPE_API_KEY' for session session_…"
}
}
}
}
```
Send the user to that URL, then retry the same call with the state echoed and the answer under the server’s own key:
```json
{
"name": "session_set_secret",
"arguments": { "session_id": "session_…", "name": "STRIPE_API_KEY" },
"requestState": "eyJ1c2VyX2lkIjoi…",
"inputResponses": { "secret": { "action": "accept" } }
}
```
```json
{ "resultType": "complete", "structuredContent": { "name": "STRIPE_API_KEY", "stored": true } }
```
A client that never declared `elicitation.url` gets `-32021` with the missing capability named, rather than being asked for the value.
## A runnable version
`examples/mcp-url-elicitation/` in the repository has both halves: a dependency-free MCP server that elicits, and a script that walks the flow above end to end against your deployment.
## Related
* [URL mode elicitation](https://docs.everruns.com/features/mcp-url-elicitation/)
* [Consume events via SSE](https://docs.everruns.com/how-to/consume-events-via-sse/)
---
# Consume events via raw SSE
> Subscribe to the Everruns event stream from any HTTP client using Server-Sent Events, with reconnection via since_id.
Source:
When you can’t use the SDK, a non-Python service, a browser client, a Postman test, the SSE protocol is available directly. This guide covers the protocol details you need.
For the SDK convenience layer, see [Stream events](https://docs.everruns.com/how-to/stream-events/).
## Subscribe
```bash
curl -N "https://your-host/api/v1/sessions/$SESSION_ID/sse" \
-H "Authorization: Bearer $EVERRUNS_API_KEY"
```
Each event arrives as:
```plaintext
event: turn.completed
id: event_01933b5a00007000800000000000001
data: {"id":"event_...","type":"turn.completed","data":{...}}
```
(Blank line terminates each event, per the SSE spec.)
## Resume after disconnect
Pass `since_id` to pick up where you left off:
```bash
curl -N "https://your-host/api/v1/sessions/$SESSION_ID/sse?since_id=event_..." \
-H "Authorization: Bearer $EVERRUNS_API_KEY"
```
Event IDs are UUIDv7 and the server orders them by an atomic per-session sequence number. Resumption is gap-free and duplicate-free.
## Heartbeats
The server sends a heartbeat every 30 seconds as a comment line:
```plaintext
: heartbeat
```
Comments are invisible to SSE event parsers, they don’t appear as events. Their only purpose is to keep the TCP connection alive and let your client distinguish “idle” from “dead.”
**Client requirement:** treat the connection as stale if no data (event or heartbeat) arrives within 45 seconds. Reconnect with the last received event ID.
## Connection cycling
To avoid stale connections through proxies, the server gracefully cycles SSE connections every 5 minutes. Before closing, it sends:
```plaintext
event: disconnecting
data: {"reason":"connection_cycle","retry_ms":100}
```
Clients should reconnect immediately using `since_id` of the last event received. No events are dropped during the transition.
## Browser EventSource
```javascript
function connect(sessionId, lastEventId) {
const url = new URL(`/api/v1/sessions/${sessionId}/sse`, API_BASE);
if (lastEventId) url.searchParams.set("since_id", lastEventId);
const es = new EventSource(url, { withCredentials: true });
es.addEventListener("connected", () => console.log("SSE connected"));
es.addEventListener("disconnecting", (e) => {
const { retry_ms } = JSON.parse(e.data);
es.close();
setTimeout(() => connect(sessionId, lastEventId), retry_ms);
});
["input.message", "output.message.delta", "turn.completed"].forEach((t) => {
es.addEventListener(t, (e) => {
const data = JSON.parse(e.data);
lastEventId = data.id;
// handle event...
});
});
es.onerror = () => {
es.close();
setTimeout(() => connect(sessionId, lastEventId), 2000);
};
}
```
The native `EventSource` API uses the `retry:` field that every event includes (100ms during active streaming, up to 500ms while idle). You don’t need to set retry yourself.
## Poll as a fallback
If your environment can’t hold long-lived connections (some serverless runtimes), poll instead:
```bash
curl "https://your-host/api/v1/sessions/$SESSION_ID/events?since_id=$LAST_ID" \
-H "Authorization: Bearer $EVERRUNS_API_KEY"
```
The same `since_id` resumption works; latency increases by the polling interval.
## See also
* [Event Reference](https://docs.everruns.com/event-reference/), every event type and payload.
* [Events as the primary store](https://docs.everruns.com/explanation/events/), why the protocol is shaped this way.
* [Stream events with the SDK](https://docs.everruns.com/how-to/stream-events/), the convenient path.
---
# Customize a harness
> Create a custom harness that bundles your preferred capabilities, system prompt baseline, and default model, then use it as the starting point for many agents.
Source:
A harness is the base environment for sessions, system prompt baseline, default model, and pre-bundled capabilities. Create a custom one when you have a set of defaults you want to share across many agents.
For the design rationale, see [Why three configuration layers](https://docs.everruns.com/explanation/concepts/#why-three-configuration-layers-harness-agent-session).
## Create a harness via API
```bash
curl -X POST http://localhost:9300/api/v1/harnesses \
-H "Content-Type: application/json" \
-d '{
"name": "research-assistant",
"display_name": "Research Assistant",
"description": "Harness with research capabilities",
"system_prompt": "You are a research assistant. Cite primary sources.",
"capabilities": [
{"ref": "session_file_system"},
{"ref": "web_fetch"},
{"ref": "stateless_todo_list"}
]
}'
```
Names follow `[a-z0-9]+(-[a-z0-9]+)*` (up to 64 characters, no consecutive hyphens) and are unique per organization. Use `name` in API calls; `display_name` is for the UI only.
`system_prompt` is optional. Omit it when a harness exists only to bundle capabilities or MCP servers on top of a parent, the effective prompt is then composed from the parent harness, agent, session, and capabilities. For example, a capability-only harness that inherits its prompt from `generic`:
```bash
curl -X POST http://localhost:9300/api/v1/harnesses \
-H "Content-Type: application/json" \
-d '{
"name": "research-tools",
"display_name": "Research Tools",
"parent_harness_id": "harness_...",
"capabilities": [
{"ref": "web_fetch"},
{"ref": "stateless_todo_list"}
]
}'
```
## Preview before creating
```bash
curl -X POST http://localhost:9300/api/v1/harnesses/preview \
-H "Content-Type: application/json" \
-d '{
"system_prompt": "You are a helpful assistant.",
"capabilities": [
{"ref": "session_file_system"},
{"ref": "bashkit_shell"}
]
}'
```
Preview returns the merged system prompt and the tool list. Useful when capability ordering matters or when you’re not sure which capability adds which prompt fragment.
## Use the harness for sessions
```bash
# By name
curl -X POST http://localhost:9300/api/v1/sessions \
-H "Content-Type: application/json" \
-d '{
"harness_name": "research-assistant",
"agent_id": "agent_..."
}'
# Or via the CLI
everruns sessions create --harness research-assistant --agent agent_...
```
## Inheritance
Harnesses support single-parent inheritance, a child harness can extend another, layering on extra capabilities or a longer system prompt. The merge is associative: a chain of N harnesses produces the same `RuntimeAgent` as a single pre-merged harness.
This is useful when one team owns a base harness and other teams add their own specialisation on top.
## When to use a harness vs. an agent
* **Harness**: defaults shared across *many agents* (e.g., “all our internal agents have file access, web fetch, and the company AGENTS.md”).
* **Agent**: per-role configuration (system prompt, voice, role-specific tools).
* **Session**: per-conversation tweaks (extra capability for this user, narrower network policy).
Don’t pack agent-specific behaviour into a harness, it makes the harness a god-object and erodes the layering benefit.
## See also
* [Harnesses feature page](https://docs.everruns.com/features/harnesses/)
* [Built-in harnesses](https://docs.everruns.com/built-ins/harnesses/base/), the shipped baselines you can extend.
* [Equip an agent with tools](https://docs.everruns.com/how-to/equip-agents-with-tools/), at the agent layer.
---
# Define agents as files
> Author agent definitions in Markdown, TOML, YAML, or JSON so they can be version-controlled, reviewed in pull requests, and imported via the SDK or CLI.
Source:
Agents can be defined as files with structured metadata and a system prompt. This makes them shareable, reviewable, and version-controllable, useful for teams that want agents in git rather than only in the API.
## Markdown with front matter
The most readable format. The YAML front matter holds metadata; the body becomes the system prompt.
```markdown
---
name: "hackernews-reader"
description: "An agent that browses HackerNews autonomously"
tags:
- demo
- hackernews
capabilities:
- web_fetch
- current_time
- session_file_system
---
You are a HackerNews reader agent. You autonomously browse
Hacker News to find interesting stories, read discussions,
and research authors.
```
Import via the SDK:
```python
with open("hackernews-reader.md") as f:
agent = await client.agents.import_agent(f.read())
```
Or via the CLI:
```bash
everruns agents create -f hackernews-reader.md
```
## TOML
```toml
name = "research-assistant"
description = "Helps with research tasks"
# Base execution harness for this agent (id or name). Omit to default to the
# org's built-in `generic` harness. Sessions started from the agent inherit it.
harness_name = "generic"
system_prompt = """
You are a helpful research assistant.
Always cite your sources.
"""
tags = ["research", "assistant"]
[[capabilities]]
ref = "current_time"
[[capabilities]]
ref = "web_fetch"
```
If `./agent.toml` exists in the current directory and you don’t pass inline flags, `everruns agents create` picks it up automatically.
## YAML
```yaml
name: "research-assistant"
description: "Helps with research tasks"
# Base execution harness (id or name); omit to default to the built-in `generic`.
harness_name: "generic"
system_prompt: |
You are a helpful research assistant.
Always cite your sources.
capabilities:
- ref: current_time
config: {}
- ref: web_fetch
config: {}
tags:
- research
```
Shorthand form (capability IDs only):
```yaml
capabilities:
- current_time
- web_fetch
```
The long form (`ref` + `config`) is required for per-agent capability configuration.
## JSON
JSON is supported for tooling that generates agent definitions programmatically. It has no special features over TOML/YAML, pick the format your team prefers.
```bash
everruns agents create -f agent.json
```
## Seed sessions with initial files
To pre-populate the session workspace, either pass `--initial-files-dir` on the CLI or use the `initial_files` front matter field:
```markdown
---
name: "a11y-audit"
capabilities:
- daytona
initial_files:
- .
- .agents/*
---
Run axe-core audits...
```
Entries can be:
* `.`, the entire current directory (non-hidden files plus `.agents/`).
* A subdirectory, walked recursively. Glob suffixes like `/*` are stripped.
* A single file path.
Hidden files outside `.agents/` are skipped; symlinks outside the base directory are rejected; binary files are ignored.
## Update vs. create
`everruns agents update` accepts the same file formats. Passing an explicit `` positional disables implicit `agent.toml` selection so you can update a different agent from the same directory.
## See also
* [CLI reference](https://docs.everruns.com/features/cli/), full command surface.
* [Equip an agent with tools](https://docs.everruns.com/how-to/equip-agents-with-tools/), choosing capabilities.
* [Use AGENTS.md for project instructions](https://docs.everruns.com/how-to/use-agents-md/), adding per-project guidance.
---
# Enforce a budget
> Cap LLM spend per session or agent with USD, token, or credit-denominated budgets, soft pause thresholds, and stacked limits.
Source:
Budgets cap how much a session can spend on LLM calls. After every generation, Everruns debits the cost from any active budgets; when the balance reaches zero, the session stops. This guide creates and applies a budget.
For the design and enforcement semantics, see [Budgets](https://docs.everruns.com/advanced/budgets/).
## Create a USD budget for a session
```bash
curl -X POST http://localhost:9300/api/v1/budgets \
-H "Content-Type: application/json" \
-d '{
"scope": "session",
"scope_id": "session_...",
"currency": "usd",
"limit": 10.00,
"soft_limit": 8.00
}'
```
When session spend exceeds `soft_limit`, the session pauses (status becomes `paused`) so a human can decide to top up or stop. When it hits `limit`, the session terminates.
## Token-denominated budget
```bash
curl -X POST http://localhost:9300/api/v1/budgets \
-H "Content-Type: application/json" \
-d '{
"scope": "agent",
"scope_id": "agent_...",
"currency": "tokens",
"limit": 2000000
}'
```
Token budgets are model-agnostic, they cap raw token usage regardless of which provider the session uses.
## Currencies at a glance
| Currency | Unit | Cost basis |
| --------- | ----------------------- | -------------------------------------------------------- |
| `usd` | US dollars | Per-model pricing (input/output cost per million tokens) |
| `tokens` | Raw tokens | Direct count of input + output tokens |
| `credits` | 1 credit = 1,000 tokens | Token count ÷ 1,000 |
| Custom | Any string | Falls back to raw token count |
USD budgets reflect real costs: $10 lasts much longer on GPT-4o than on Claude Opus.
## Stack budgets for layered limits
You can apply multiple budgets to a session at once. The **most restrictive** wins. A common pattern:
* `$10 USD` session budget, caps dollar cost.
* `2,000,000 tokens` agent budget, caps total tokens regardless of pricing.
Both apply; whichever runs out first stops the session.
## Listen for budget events
Budget thresholds emit events you can subscribe to:
```python
async for event in client.events.stream(session.id):
if event.type == "budget.warning":
print(f"Budget warning: {event.data}")
elif event.type == "budget.paused":
print(f"Session paused at soft limit")
elif event.type == "budget.exhausted":
print(f"Session stopped — budget exhausted")
```
A warning fires at 20% remaining; pause fires when crossing the soft limit; exhaustion fires at zero balance.
## Resume a paused session
After a `budget.paused` event, you can:
* Increase `limit` to give the session more headroom:
```bash
curl -X PATCH http://localhost:9300/api/v1/budgets/$BUDGET_ID \
-H "Content-Type: application/json" \
-d '{ "limit": 20.00, "soft_limit": 16.00 }'
```
* Or call the resume endpoint to continue against the existing limit (the next LLM call may push the budget over).
## A note on enforcement
Budget checks run **after** each LLM call, not before, to avoid latency on the hot path. The last generation can slightly overshoot the limit, this is expected and by design. Treat budgets as cost caps, not hard cutoffs measured in single tokens.
## See also
* [Budgets](https://docs.everruns.com/advanced/budgets/), full design, ledger semantics, custom currencies.
* [Self-Budget capability](https://docs.everruns.com/capabilities/self-budget/), let agents inspect their own budget at runtime.
---
# Equip an agent with tools
> Assign capabilities to an agent so it can read files, run shell commands, fetch URLs, and track tasks.
Source:
This guide assigns common capabilities to an agent so it can interact with files, run commands, and fetch URLs. For the full catalog see the [Capabilities reference](https://docs.everruns.com/capabilities/).
## Common capabilities
| Capability ID | Tools provided | What it’s for |
| --------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------ |
| `web_fetch` | `web_fetch` | Fetch URLs, convert HTML to markdown |
| `session_file_system` | `read_file`, `write_file`, `edit_file`, `list_directory`, `grep_files`, `delete_file`, `stat_file` | Per-session virtual filesystem |
| `bashkit_shell` | `bash` | Sandboxed bash shell |
| `stateless_todo_list` | `write_todos` | Structured task tracking |
| `current_time` | `get_current_time` | Current date/time awareness |
| `session_storage` | `kv_store`, `secret_store` | Key/value and encrypted secrets |
## Assign capabilities at creation
```python
agent = await client.agents.create(
name="Researcher",
system_prompt="You research topics and save notes to /workspace.",
capabilities=["web_fetch", "session_file_system", "stateless_todo_list"],
)
```
## Update an existing agent
```python
await client.agents.update(
agent.id,
capabilities=["web_fetch", "session_file_system", "bashkit_shell"],
)
```
## Configure a capability
Some capabilities accept per-agent configuration. Use the long form:
```python
await client.agents.update(
agent.id,
capabilities=[
{"ref": "web_fetch", "config": {"enable_file_download": True}},
{"ref": "session_file_system"},
],
)
```
## Verify the agent has the tools
```python
agent = await client.agents.get(agent.id)
for cap in agent.capabilities:
print(cap.ref, cap.config or "")
```
## Notes on ordering
Capability order matters, capabilities earlier in the list contribute their system prompt fragments first. Put high-priority context (project conventions, AGENTS.md) before tool-specific guidance.
## See also
* [Capabilities reference](https://docs.everruns.com/capabilities/), all available capabilities.
* [Why capabilities are first-class](https://docs.everruns.com/explanation/concepts/#why-capabilities-are-first-class), the design rationale.
* [Give an agent web access](https://docs.everruns.com/how-to/give-an-agent-web-access/), narrower task with network policies.
---
# Give an agent web access
> Enable the web_fetch capability, restrict outbound network access with allow/block lists, and verify the agent reaches only intended hosts.
Source:
`web_fetch` gives an agent the `web_fetch` tool, fetch any URL, optionally convert HTML to markdown. By default it can reach any public host, with built-in SSRF protection blocking private IPs. To restrict it further, layer **network access lists** on the harness, agent, or session.
## Enable the capability
```bash
curl -X PATCH http://localhost:9300/api/v1/agents/$AGENT_ID \
-H "Content-Type: application/json" \
-d '{
"capabilities": [
{ "ref": "web_fetch" }
]
}'
```
Or in an agent definition file:
```yaml
capabilities:
- ref: web_fetch
- ref: session_file_system # so the agent can save fetched content
```
## Restrict to specific hosts
Pass `network_access` when creating or updating the agent. Patterns can be exact domains, wildcard domains, or URL prefixes:
```bash
curl -X POST http://localhost:9300/api/v1/agents \
-H "Content-Type: application/json" \
-d '{
"name": "Research Agent",
"system_prompt": "You are a research assistant.",
"capabilities": [{ "ref": "web_fetch" }],
"network_access": {
"allowed": ["*.github.com", "api.openai.com", "https://docs.python.org/3/"],
"blocked": ["evil.example.com"]
}
}'
```
| Pattern | Matches |
| ----------------------------- | ----------------------- |
| `api.example.com` | Exact domain |
| `*.example.com` | Domain + all subdomains |
| `https://api.example.com/v1/` | URL prefix |
Domain matching is case-insensitive. **Blocked patterns always win** over allowed patterns.
## Layered policies
The three layers (harness → agent → session) can only narrow access, never expand it:
* `allowed` lists intersect across layers.
* `blocked` lists union across layers.
So a session can tighten what its agent allows but cannot punch a hole through the agent’s `blocked` list.
## Tighten further per-session
When you don’t trust a particular session’s input, restrict more:
```bash
curl -X POST http://localhost:9300/api/v1/sessions \
-H "Content-Type: application/json" \
-d '{
"agent_id": "agent_...",
"network_access": {
"blocked": ["internal.corp", "*.staging.example.com"]
}
}'
```
## SSRF protection
Even without an explicit policy, `web_fetch` blocks private IP ranges by default, loopback, RFC1918, link-local, and CGNAT, with DNS pinning to prevent rebinding attacks. To explicitly allow a private host, set it in `allowed` and disable SSRF protection at the harness level (see [Network access control](https://docs.everruns.com/advanced/network-access/)).
## Verify
Try a URL the agent should reach, then one it shouldn’t, and check the tool result events:
```python
await client.messages.create(session.id, "Fetch https://api.github.com/zen")
# Should succeed.
await client.messages.create(session.id, "Fetch https://evil.example.com/")
# Should fail with a network policy error in the tool result.
```
## See also
* [Network access control](https://docs.everruns.com/advanced/network-access/), full pattern semantics and layering rules.
* [Web Fetch capability](https://docs.everruns.com/capabilities/web-fetch/), tool reference.
* [Equip an agent with tools](https://docs.everruns.com/how-to/equip-agents-with-tools/)
---
# Handle errors and cancel turns
> Detect and recover from failed turns, cancel a long-running turn, and react to common error events from the SSE stream.
Source:
Turns can fail (the LLM rejected the request, a tool errored repeatedly) or be cancelled by the user. The event stream tells you which. This guide covers both paths.
## Detect a failed turn
Failures surface as `turn.failed` events:
```python
async for event in client.events.stream(session.id):
if event.type == "turn.completed":
break
if event.type == "turn.failed":
err = event.data.get("error", "unknown")
print(f"Turn failed: {err}")
break
```
The `error` field is a structured object, type, message, optional cause. Inspect it to decide whether to retry, surface to the user, or escalate.
## Cancel a long-running turn
To cancel a turn that’s already running:
```python
import asyncio
await client.messages.create(session.id, "Analyse every Python package on PyPI")
await asyncio.sleep(2)
await client.sessions.cancel(session.id)
```
Cancellation emits a `turn.cancelled` event, appends a user message noting the cancellation, and the worker emits a final agent message confirming the work was stopped. The session itself stays open and accepts new messages.
## React to all three terminal states
```python
TERMINAL = {"turn.completed", "turn.failed", "turn.cancelled"}
async for event in client.events.stream(session.id):
if event.type in TERMINAL:
print(f"[{event.type}]")
if event.type == "turn.failed":
print(event.data.get("error"))
break
```
## Retry a failed turn
Failed turns don’t auto-retry from the application’s perspective (durable execution retries individual steps inside a turn, not the whole turn). To retry, send the message again:
```python
async def send_with_retry(client, session_id, content, attempts=2):
for attempt in range(attempts):
await client.messages.create(session_id, content)
async for event in client.events.stream(session_id):
if event.type == "turn.completed":
return True
if event.type == "turn.failed":
if attempt == attempts - 1:
return False
break
return False
```
Don’t retry indefinitely, a turn that fails twice usually fails for a reason (rate limit, malformed prompt, missing capability). Surface to the user.
## Common error patterns
| Event payload | Cause | Action |
| ----------------------------- | ---------------------------------------- | --------------------------------------------------------------- |
| `rate_limit_exceeded` | LLM provider rate-limited the worker | Wait, retry with backoff |
| `request_too_large` | Context overflowed even after compaction | Trim the conversation, start a fresh session |
| `tool_call_failed` (terminal) | A tool errored repeatedly | Inspect tool result events, fix the agent prompt or tool config |
| `cancelled` | User or app called `sessions.cancel` | No retry, user intent |
## See also
* [Stream events](https://docs.everruns.com/how-to/stream-events/)
* [Event Reference](https://docs.everruns.com/event-reference/), all event types and payloads.
* [The agentic loop](https://docs.everruns.com/explanation/agentic-loop/#what-happens-when-the-loop-gets-stuck), failure modes.
---
# Migrate between LLM providers
> Switch agents from OpenAI to Anthropic to Gemini (or any OpenAI-compatible provider) without rewriting prompts or capabilities.
Source:
Everruns abstracts the LLM behind a uniform interface, so the same agent can run on OpenAI, Anthropic, Gemini, or any OpenAI-compatible provider. This guide swaps providers cleanly without losing sessions or rewriting agents.
## Concepts
Two pieces decide which model runs:
* **LLM Provider**: a configured API provider with encrypted credentials (e.g., `openai`, `openrouter`, `anthropic`, `gemini`, `openai_completions`).
* **LLM Model**: a specific model on a provider (e.g., `gpt-5.6-sol`, `claude-sonnet-5`, `gemini-3.5-flash`).
Model resolution priority on each turn:
1. Message-level `model` control (if present on the incoming message).
2. Session override (`session.default_model_id`).
3. Agent default (`agent.default_model_id`).
4. System default.
## Add a new provider
```bash
curl -X POST http://localhost:9300/api/v1/providers \
-H "Content-Type: application/json" \
-d '{
"name": "anthropic",
"provider_type": "anthropic",
"api_key": "sk-ant-..."
}'
```
The API key is encrypted at rest. The provider’s models are discovered on creation; manually-added models are also supported.
## Switch an agent’s default model
```bash
curl -X PATCH http://localhost:9300/api/v1/agents/$AGENT_ID \
-H "Content-Type: application/json" \
-d '{ "default_model_id": "model_claude_sonnet_4" }'
```
New sessions inherit the new default. **Existing sessions keep running on the model they started with** unless you override per-session or per-message.
## Override per session
For an A/B comparison, override at the session level:
```bash
curl -X POST http://localhost:9300/api/v1/sessions \
-H "Content-Type: application/json" \
-d '{
"agent_id": "agent_...",
"default_model_id": "model_claude_sonnet_4"
}'
```
## Override per message
The most targeted form, run a single turn on a different model:
```python
await client.messages.create(
session.id,
"Re-analyse the above with extra rigour.",
model="model_claude_opus",
)
```
## Compatibility caveats
Most behaviour ports across providers, but a few things differ:
* **Extended thinking / reasoning effort.** Supported on Anthropic Claude, OpenAI GPT-5.x, and o-series. Other models silently ignore the `reasoning_effort` control.
* **Execution phases on the wire.** OpenAI Responses API on the GPT-5.4 and GPT-5.5 families accepts `phase` annotations on replayed messages; other providers (and earlier OpenAI models) ignore them. Internal tracking continues regardless. The authoritative list is the `supports_phases` flag in `crates/core/src/llm_model_profiles.rs`. See [Execution phases](https://docs.everruns.com/explanation/agentic-loop/#execution-phases).
* **Tool call format differences.** The platform handles translation, but very large tool schemas may compress better on one provider than another.
* **Per-token cost.** USD budgets adjust automatically since they use per-model pricing. Token budgets are model-agnostic.
## Verify before flipping production
A safe migration sequence:
1. Add the new provider and verify discovered models.
2. Create a test agent that mirrors production, with `default_model_id` set to a model on the new provider.
3. Run an evaluation suite against the test agent.
4. Once happy, update the production agent’s `default_model_id`, new sessions migrate over.
5. Leave old sessions on the old model; they age out naturally.
## See also
* [Concepts: LLM Provider and Model](https://docs.everruns.com/explanation/concepts/), entity model.
* [Observability with Braintrust](https://docs.everruns.com/observability/braintrust/), evaluate cross-provider quality.
---
# Migrate to 0.18
> Move Rust code off `everruns-core` paths that changed in 0.18, with a symbol-by-symbol table of where each type now lives.
Source:
0.18 narrows `everruns-core` to the neutral execution kernel. Types that were persisted control-plane records, hosted service contracts, product composition or concrete integrations moved to the crate that owns them. The behaviour, the wire formats and the stored schema are unchanged, only the import paths.
## Retain an Engine for sessions
The Framework exposes a concrete application execution owner. The 0.18 API removes `agent.session()` and `agent.resume(id)`; applications retain the engine that owns session identity and resume authority:
```diff
let session = agent.session();
use everruns::Engine;
let engine = Engine::new();
let session = engine.create(agent);
let id = session.session_id();
let resumed = agent.resume(id).await?;
let resumed = engine.resume(id).await?;
```
`Engine` volatile resume is deliberately process-local and engine-scoped. The old `InMemoryEngine` name remains a type alias. Engine retains the immutable Agent snapshot and exact Environment/WorkspaceHead; it does not serialize a Scale-compatible Agent definition.
For a locally persisted session after process restart, rebuild the Agent from trusted application configuration, call `engine.attach(id, agent).await?`, and then `engine.resume(id).await?`. Attachment verifies the persisted session catalog before accepting the behavior snapshot; credentials and closures are never serialized.
This affects you if your Rust code imports from `everruns_core` directly. If you use the `everruns` facade, most of this is invisible: the facade re-exports what applications need, and where a moved type is part of that surface it is re-exported from its new home under the same name.
## The quickest path
Most migrations are a find-and-replace of a crate prefix. Compile, read the unresolved-import errors, and look each symbol up in the tables below.
```bash
cargo build 2>&1 | grep -E "unresolved import|no .* in"
```
Add whichever crates the table points you at:
```toml
everruns-platform = "0.18" # persisted records, hosted service contracts
everruns-host = "0.18" # execution composition and host wiring
everruns-provider = "0.18" # provider SPI, typed IDs, sqlx impls
everruns-capability = "0.18" # capability identity/configuration contract
everruns-mcp = "0.18" # MCP adapter and the OAuth protocol client
everruns-llmsim = "0.18" # deterministic production-safe simulator
```
The earlier `everruns-session-services` preview package was consolidated into `everruns-host`; use `everruns_host::session_services` for its namespaced API.
## Composition
The single biggest change for embedders. `PlatformDefinition` no longer exists.
| 0.17 | 0.18 |
| ------------------------------------------------------ | --------------------------------------------------- |
| `everruns_core::PlatformDefinition` | `everruns_host::HostComposition` |
| `everruns_core::PlatformDefinitionBuilder` | `everruns_host::HostCompositionBuilder` |
| `everruns_server::oss_platform_definition()` | `everruns_server::oss_host_composition()` |
| `everruns_server::oss_platform_definition_for_grade()` | `everruns_server::oss_host_composition_for_grade()` |
| `everruns_worker::default_platform_definition()` | `everruns_worker::default_host_composition()` |
| `ServerAppBuilder::platform_definition(..)` | `ServerAppBuilder::host_composition(..)` |
| `WorkerAppBuilder::platform_definition(..)` | `WorkerAppBuilder::host_composition(..)` |
```diff
use everruns_core::PlatformDefinition;
use everruns_host::HostComposition;
let platform = PlatformDefinition::builder()
let composition = HostComposition::builder()
.capability_registry(capabilities)
.driver_registry(drivers)
.build();
ServerAppBuilder::new().platform_definition(platform)
ServerAppBuilder::new().host_composition(composition)
```
The type is otherwise identical, same fields, same builder methods. It moved to the layer that executes a turn, because selecting a deployment’s capabilities and drivers is composition rather than kernel configuration.
### Input/Reason/Act execution kernel
Concrete phase execution moved out of `everruns-core`. Import phase algorithms and their I/O values from `everruns-engine`; keep neutral effect contracts in core.
| 0.17 | 0.18 |
| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------- |
| `everruns_core::atoms::{InputAtom, InputAtomInput, InputAtomResult}` | `everruns_engine::{InputAtom, InputAtomInput, InputAtomResult}` |
| `everruns_core::atoms::{ReasonAtom, ReasonInput, ReasonResult}` | `everruns_engine::{ReasonAtom, ReasonInput, ReasonResult}` |
| `everruns_core::atoms::{ActAtom, ActInput, ActResult, ToolCallResult}` | `everruns_engine::{ActAtom, ActInput, ActResult, ToolCallResult}` |
| `everruns_core::atoms::AtomContext` | `everruns_core::ExecutionContext` (also re-exported by `everruns-engine`) |
| `everruns_core::atoms::{PreToolUseHook, PostToolExecHook, PreToolUseDecision}` | `everruns_core::tool_hooks::*` |
The generic `Atom` trait was removed; it had no production dynamic-dispatch use. Call the concrete executor’s inherent async `execute` method. There is no core compatibility module in 0.18. Serialized phase payloads retain the same fields, so durable records remain readable even though the Rust ownership path changed.
### Turn context and command completion
Store-backed turn preparation now belongs to `everruns-host`. Core keeps the secret-free execution snapshot, pure context transformations, and narrow effects used by custom hosts.
| 0.17 | 0.18 |
| ---------------------------------------------------- | ---------------------------------------------------- |
| `everruns_core::assemble_turn_context` | `everruns_host::assemble_turn_context` |
| `everruns_core::inspect_turn_context` | `everruns_host::inspect_turn_context` |
| `everruns_core::load_execution_snapshot` | `everruns_host::load_execution_snapshot` |
| `everruns_core::load_execution_snapshot_for_session` | `everruns_host::load_execution_snapshot_for_session` |
| `everruns_core::StoreCommandHost` | `everruns_host::StoreCommandHost` |
`everruns_engine::ReasonAtom::new` no longer accepts harness, agent, session, and provider stores or a driver registry. Construct an `everruns_host::StoreTurnContextResolver` from those host services, then pass that resolver plus the narrow message, capability, and event effects to the atom. Hosts that already loaded a `ResolvedExecutionSnapshot` should call `everruns_host::assemble_turn_context_from_snapshot` and execute the atom with the resulting `AssembledTurnContext`; this avoids a second store load.
For a fully custom host, implement the neutral `everruns_core::TurnContextResolver`, or provide already-resolved `ResolvedTurnContextInput` to `everruns_core::assemble_resolved_turn_context`. That input contains a secret-free model/provider identity and an opaque ready driver; provider keys and endpoints are never serializable kernel values.
`CommandTurnContext` now exposes `session_id` directly instead of an `ExecutionSession`. Commands retain the same filtered messages, effective prompt, locale, model, streaming, and error-decision behavior without receiving a session record.
## Persisted records
These are database and API records. Execution consumes a portable projection of each; the stored row is control-plane state.
| 0.17 (`everruns_core::`) | 0.18 |
| ----------------------------------------------------------------------------------------- | -------------------------------- |
| `Agent`, `AgentVersion`, `AgentStatus`, `AgentVersionChangeKind` | `everruns_platform::` |
| `Harness`, `HarnessStatus`, `BuiltInHarnessDefinition`, `BuiltInHarnessRole` | `everruns_platform::` |
| `Session`, `SessionStatus`, `SessionSource`, `SessionActivity`, `SessionParticipant` | `everruns_platform::` |
| `Workspace`, `WorkspaceStatus` | `everruns_platform::workspace::` |
| `Eval`, `EvalCase`, `EvalRun`, `EvalCaseResult`, `EvalRunDataset`, `EvalTarget`, `Scorer` | `everruns_platform::` |
| `Observer`, `ObserverMatch`, `LlmJudgeConfig`, `TraceScore` | `everruns_platform::` |
| `FeatureFlags`, `FeatureFlagMap`, `FeatureFlagDefinition` | `everruns_platform::` |
| `Budget`, `LedgerEntry` | `everruns_platform::` |
If you were reading a stored record to run a turn, you probably want the portable projection instead, `AgentDefinition`, `HarnessDefinition` and `ExecutionSession` all stay in `everruns_core`, produced at the platform loading boundary by `Agent::execution_definition`, `Harness::execution_definition` and `Session::execution_session`.
## Hosted service contracts
| 0.17 (`everruns_core::`) | 0.18 |
| ------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| `session_sqldb::*`, `SessionSqlDbStore`, `DatabaseInfo`, `SqlQueryResult`, `SqlExecuteResult`, `TableSchema`, `ColumnSchema`, `SessionSqlDbError` | `everruns_platform::session_sqldb::` |
| `traits::SessionMutator` | `everruns_host::SessionMutator` (also re-exported by platform) |
| `session_sandbox::*`, config, state, instance, exec/file payloads, `SessionSandboxProvider`, `SessionSandboxProviderPlugin` | `everruns_platform::session_sandbox::` |
| `Connector`, `ConnectorRegistry`, `ConnectorPlugin` | `everruns_platform::connector::` |
| `EmailSender`, `EmailMessage`, `SystemEmailConfig`, `ResendEmailSender` | `everruns_platform::email::` |
| `OAuthClient`, `TokenSet`, `PkcePair` | `everruns_mcp::oauth::protocol::` |
Neutral per-turn contracts remain in `everruns-core`, but the catch-all `everruns_core::traits` module is gone. Import from the owning concern instead, for example `everruns_core::tool_context::ToolContext`, `everruns_core::session_files::SessionFileSystem`, or `everruns_core::provider_resolution::ProviderStore`. The deployment-owned `SessionFileSystemFactory` and its context now come from `everruns-host`.
Two of these also changed how a capability *reaches* the service. `sqldb_store` and `session_mutator` are no longer fields on `ToolContext`; they resolve from the type-keyed extension bag:
```diff
let Some(store) = &context.sqldb_store else { ... };
let Some(store) = context.extensions.get::() else { ... };
let store = &store.0;
```
If you implement a custom host, install them the way `everruns-host` does:
```rust
extensions.insert(Arc::new(SessionSqlDbStoreExt(store)));
extensions.insert(Arc::new(SessionMutatorExt(mutator)));
```
## Capabilities and implementations
| what | 0.18 home |
| ------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| Knowledge Bases and Indexes, Memories, delegation, subagents, background and scheduled work, user hooks, citations, model scouting, platform management | `everruns_platform::capabilities::` |
| Session info and session storage | `everruns_host::session_services::capabilities::` (also re-exported by platform) |
| Session SQL database and session sandbox | `everruns_platform::capabilities::` |
| `spawn_background` and its runtime, event sink, admission permits, reattach | `everruns_platform::background_run::` |
| Portable built-ins, human intent, infinity context, skills, UI prompts, compaction, tool search | `everruns_builtins::` |
| `everruns_openui::{PromptOptions, default_library, generate_prompt}` | `everruns_builtins::openui::{PromptOptions, default_library, generate_prompt}` |
| `everruns_a2ui::{PromptOptions, default_catalog, generate_prompt}` | `everruns_builtins::a2ui::{PromptOptions, default_catalog, generate_prompt}` |
| OpenRouter workspace, model scout, and provider-executed server tools | `everruns_integrations_openrouter::` |
| Filesystem, shell, web fetch, Lua | `everruns_integrations_*` |
| MCP adapter | `everruns_mcp::` |
| In-process HTTP egress transport | `everruns_host::DirectEgressService` |
| Telemetry init, exporter event listeners, `CompositeEventListener` | `everruns_host::observability::` (feature `observability`) |
| `llmsim` driver, configs, scripted turns, registry helpers, host-builder extension | `everruns_llmsim::` |
| in-memory agentic loop, writable test doubles, fixture capabilities | `everruns_test_support::` |
| `everruns_core::in_memory::{InMemoryAgentStore, InMemoryHarnessStore, InMemorySessionStore, InMemoryProviderStore}` | `everruns_host::{InMemoryAgentStore, InMemoryHarnessStore, InMemorySessionStore, InMemoryProviderStore}` |
| `everruns_core::in_memory::{InMemoryMessageRetriever, InMemoryEventEmitter}` | `everruns_test_support::{InMemoryMessageRetriever, InMemoryEventEmitter}` for isolated deterministic tests |
Product presets compose these explicitly. Core registries are now empty by default: use `everruns_host::runtime_capability_registry()` for the Framework preset or `everruns_platform::capabilities::hosted_capability_registry()` for the hosted product catalog.
Hosted conversation history has no writable message-store replacement. Append canonical events through `everruns_host::EventLog` / `HostEventEmitter` and read messages through `EventHistory`. This avoids message/event dual writes and keeps resume and replay behavior identical across in-memory and durable hosts.
`everruns-test-support` continues to re-export its 0.17 simulator paths during the 0.18 migration, so existing test suites can upgrade without an immediate import rewrite. Treat that as a migration bridge: production code, new tests, and low-level hosts should depend on `everruns-llmsim` directly. The application-facing `everruns::Model::simulated` and `Model::simulated_with_config` APIs are unchanged.
## Features
| 0.17 | 0.18 |
| -------------------------------------- | ------------------------------------------------------------------------- |
| `everruns-core/sqlx` | removed, use `everruns-provider` with `features = ["sqlx"]` |
| `everruns-core/embedded-platform-docs` | removed, it gated nothing; use `everruns-platform/embedded-platform-docs` |
| `everruns-platform/sqlx` | removed, it forwarded to core’s and nothing enabled it |
| `everruns-core/llm-tests` | removed, use the `everruns-llm-tests` package for live provider tests |
`everruns-core` now has an empty default feature set. OpenAPI derives remain available only with `features = ["openapi"]`; structural outlines remain available only with `features = ["tree-sitter-outlines"]`. Neither subtree is present in a default core build.
Concrete provider protocol and utility-model implementations also moved to their effectful owners:
| 0.17 (`everruns_core::`) | 0.18 |
| ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
| `OpenAIProtocolChatDriver`, `openai_protocol` | `everruns_provider::` |
| `OpenResponsesProtocolChatDriver`, `openresponses_protocol` | `everruns_provider::` |
| `driver_helpers`, `stream_reconnect` | `everruns_provider::` |
| `OpenAiUtilityLlmService` (now `ProviderUtilityLlmService`), `SystemUtilityLlmConfig`, `UTILITY_OPENAI_API_KEY_ENV` | `everruns_host::` with `features = ["utility-llm"]` |
Core no longer initializes Rustls. Provider HTTP clients install the workspace crypto provider when they are first constructed, while server, worker, and CLI startup owners install it eagerly. Custom binaries that combine TLS stacks can depend on `everruns-provider` with `features = ["tls-aws-lc-rs"]` and call `everruns_provider::install_default_crypto_provider()` once during startup; the call is idempotent and safe under concurrent initialization.
## Provider and typed-ID imports
Provider-owned modules are no longer compatibility-exported by `everruns-core`. Low-level consumers must add `everruns-provider` directly. This keeps credentials and concrete driver assembly out of the neutral kernel and makes the dependency owner visible in `Cargo.toml`.
There are two common compiler-error shapes:
1. **The module moved to another crate.** Add that crate and change the prefix.
2. **The module stayed public, but its root convenience re-export was removed.** Keep the dependency and qualify the symbol through its module.
The second case produces the misleading-looking `no X in the root` error. It does not necessarily mean the type moved. These replacements are deliberately literal so they can be applied with ordinary search-and-replace:
| before | after |
| ---------------------------------- | ---------------------------------------------------- |
| `everruns_core::ProviderStore` | `everruns_core::provider_resolution::ProviderStore` |
| `everruns_core::SessionStore` | `everruns_core::execution_loading::SessionStore` |
| `everruns_core::MessageRetriever` | `everruns_core::message_retriever::MessageRetriever` |
| `everruns_core::SessionFileSystem` | `everruns_core::session_files::SessionFileSystem` |
| 0.17 core path | 0.18 direct path |
| --------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| `everruns_core::driver_registry::*` | `everruns_provider::driver_registry::*` |
| `everruns_core::model::*` | `everruns_provider::model::*` |
| `everruns_core::model_profiles::*` | `everruns_provider::model_profiles::*` |
| `everruns_core::model_spec::ModelSpec` | `everruns_provider::model_spec::ModelSpec` |
| `everruns_core::provider::*` | `everruns_provider::provider::*` |
| `everruns_core::runtime_provider::*` | `everruns_provider::runtime_provider::*` |
| `everruns_core::typed_id::*` | `everruns_provider::typed_id::*` |
| `everruns_core::error::*` | `everruns_provider::error::*` |
| `everruns_core::tool_types::*` | `everruns_provider::tool_types::*` |
| `everruns_core::capability_types::{CapabilityId, CapabilityRef, CapabilityError}` | `everruns_capability::{CapabilityId, CapabilityRef, CapabilityError}` |
| `everruns_core::AgentCapabilityConfig` | `everruns_capability::CapabilityRef` |
| core plugin capability ID/validation helpers | the same symbol in `everruns_capability` |
| `everruns_core::ExecutionPhase` or `message::ExecutionPhase` | `everruns_provider::execution_phase::ExecutionPhase` |
| `everruns_core::ToolResultImage` or `tools::ToolResultImage` | `everruns_provider::tool_types::ToolResultImage` |
| other root-level provider symbols | the same root symbol in `everruns_provider` |
The credential-bearing `everruns_core::ResolvedModel` is removed. Store and transport boundaries now resolve two separate values:
* `ModelSpec`, safe to serialize and pass through the kernel; and
* a host-owned runtime `Provider` (or internal `ProviderConfig`) containing endpoint and authentication state.
`ProviderStore::get_model_spec` and `get_default_model_spec` return only the first value. Hosts obtain provider configuration separately and join it only while constructing a non-serializable driver/provider execution value.
`ProviderStore::get_provider_config` no longer has a default implementation. Every custom host must state where credentials live: return its resolved `ProviderConfig`, or explicitly return `None` when the provider was registered directly in the host registry or is selected but not configured. A missing credential no longer prevents turn-context/command assembly; the constructed driver rejects the first model/list/compact operation locally, before network I/O. This keeps recovery commands reachable without turning an empty token into an outbound authorization header.
## Simulator and compaction naming
`LlmSimRuntimeExt::llm_sim` now only registers/replaces the simulator provider. It never changes the selected model. Existing compact test setups that relied on implicit selection should use the explicit name:
```diff
builder.llm_sim(config)
builder.llm_sim_as_default(config)
```
When a builder already calls `default_model(...)`, keep `.llm_sim(config)`; the selected model is preserved regardless of method order.
`everruns_builtins::CompactionConfig` is the sole application-facing policy builder. The expanded implementation value previously available as `everruns_builtins::compaction::CompactionConfig` is now `everruns_builtins::compaction::RuntimeCompactionConfig` (also re-exported at the crate root). This makes an unqualified `CompactionConfig` unambiguous.
The public core test/backend conveniences are gone as well:
| removed core value | replacement |
| ----------------------------------- | ------------------------------------------------------------------- |
| `EchoTool`, `FailingTool` | define the small test `Tool` locally, or use test-support executors |
| `InMemoryCompactionCheckpointStore` | `everruns_host::InMemoryCompactionCheckpointStore` |
## What deliberately did not move
Worth knowing so you do not go looking:
* **`SessionTask`, `TaskMessage`** and the task registry stay in `everruns_core`. They are turn-execution vocabulary, `wake_queue` decides mid-turn wakes from a task’s wake policy, and they appear in the canonical `task.created` / `task.updated` / `task.message.*` event payloads.
* **`SessionSchedule`, `SessionScheduleStore`** stay. A portable built-in (`usage_limit_auto_continue`) schedules an auto-resume after a provider usage limit, and it sits below platform in the dependency graph.
* Schedule quota and minimum-interval environment variables are no longer read by core. Local/server adapters resolve deployment policy and call the parameterized core validation helpers.
* **`SessionResourceRegistry`** stays. `resource_ownership` and the portable skills capabilities consume it.
* **`SessionFileSystem`, `SessionStorageStore`** and the other neutral store contracts stay. Core owns the contract; hosts own the backend.
The rule these follow: whether something belongs in the kernel is decided by whether a portable execution path consumes it during a turn, not by whether it is persisted. All four above are persisted, and all four are essential for execution.
## Getting unstuck
If a symbol is not in these tables, import it from the crate that defines it; `everruns-core` no longer acts as a compatibility facade for provider-owned APIs. Framework applications can continue to prefer the higher-level `everruns` facade. The crate-level docs on `everruns-core` record where each remaining family lives and why.
---
# Orchestrate multi-agent pipelines
> Chain multiple Everruns agents together by passing output from one session into another.
Source:
A common pattern is splitting work across specialised agents, a researcher gathers facts, a writer turns them into prose, an editor polishes the result. Each is a separate agent and session; the application chains them.
This is the simplest orchestration pattern: no shared state, no subagent spawning, just sequential calls.
## Pipeline skeleton
```python
import asyncio
from everruns_sdk import Everruns
async def run_pipeline(client: Everruns, topic: str) -> str:
researcher = await client.agents.create(
name="Researcher",
system_prompt="Research the given topic thoroughly. Write detailed notes.",
capabilities=["web_fetch", "session_file_system"],
)
research_session = await client.sessions.create(agent_id=researcher.id)
await client.messages.create(research_session.id, f"Research {topic}")
research_output = await collect_final_text(client, research_session.id)
writer = await client.agents.create(
name="Writer",
system_prompt="Write clear, well-structured technical articles.",
)
writer_session = await client.sessions.create(agent_id=writer.id)
await client.messages.create(
writer_session.id,
f"Write a blog post based on this research:\n\n{research_output}",
)
return await collect_final_text(client, writer_session.id)
async def collect_final_text(client: Everruns, session_id: str) -> str:
final_text: str | None = None
async for event in client.events.stream(session_id):
if event.type == "output.message.completed":
message = event.data.get("message", {})
final_text = "\n".join(
p["text"] for p in message.get("content", []) if p.get("type") == "text"
)
elif event.type == "turn.failed":
raise RuntimeError(event.data.get("error", "turn failed"))
elif event.type == "turn.cancelled":
raise RuntimeError("turn cancelled")
elif event.type == "turn.completed":
break
if not final_text:
raise RuntimeError("turn completed without producing a final message")
return final_text
```
## Cleanup
Each session and agent persists until you explicitly delete it. For ephemeral pipelines, clean up at the end:
```python
for sid in (research_session.id, writer_session.id):
await client.sessions.delete(sid)
for aid in (researcher.id, writer.id):
await client.agents.delete(aid)
```
For pipelines you’ll re-run, *don’t* recreate the agents, create them once, store the IDs, and reuse them.
## When to use subagents instead
If one agent needs to delegate to another *during a turn*, use the [Sub Agents capability](https://docs.everruns.com/capabilities/sub-agents/) instead of an application-level pipeline. Subagents run inside the parent session and emit `subagent.*` events that the parent agent receives as tool results.
Pick application-level pipelines when:
* The stages are clearly separated and you want independent observability per stage.
* Stages run at different cadences (e.g., scheduled research → on-demand writeup).
* You want to reuse intermediate output across multiple downstream agents.
Pick subagents when:
* The parent agent decides at runtime which subagent to call.
* The work feels like a single user request, not a pipeline.
## See also
* [Sub Agents capability](https://docs.everruns.com/capabilities/sub-agents/)
* [Stream events](https://docs.everruns.com/how-to/stream-events/)
---
# Package an agent skill
> Author a SKILL.md, bundle scripts and references, and place it in the session workspace so agents can discover and activate it on demand.
Source:
Skills are portable instruction packages following the [Agent Skills](https://agentskills.io/) open spec. They use progressive disclosure: the agent sees only names and descriptions until it activates a skill, at which point the full instructions load.
This guide creates a skill in the session workspace. To share a skill across agents organization-wide, see [Publish a skill to the registry](https://docs.everruns.com/how-to/publish-a-skill-to-the-registry/).
## SKILL.md format
Every skill is a directory containing a `SKILL.md` with YAML front matter:
```yaml
---
name: csv-analyzer
description: Analyze CSV files and generate summary reports.
metadata:
category: data-processing
version: "1.0"
---
# CSV Analyzer
## When to Use
Activate this skill when a user provides a CSV file and wants summary statistics.
## Instructions
1. Read the CSV file using the `read_file` tool
2. Run `scripts/analyze.py` via `bash`
3. Present findings to the user
```
Required front-matter fields:
| Field | Constraint |
| ------------- | -------------------------------------------- |
| `name` | 1–64 chars, lowercase alphanumeric + hyphens |
| `description` | 1–1024 chars, describes when to activate |
Optional fields: `metadata`, `license`, `compatibility`.
## Bundle scripts and references
Skills can include arbitrary files. The agent accesses them via the session filesystem after activation:
```plaintext
/.agents/skills/csv-analyzer/
├── SKILL.md
├── scripts/
│ └── analyze.py
└── references/
└── REFERENCE.md
```
After activation, bundled files mount at `/skills/csv-analyzer/` in the session VFS and the agent reads them with the existing `read_file` / `list_files` tools.
## Enable the skills capability
Add the built-in `skills` capability to the agent so it can discover and activate skills from the workspace:
```bash
curl -X POST http://localhost:9300/api/v1/agents \
-H "Content-Type: application/json" \
-d '{
"name": "Data Analyst",
"capabilities": [
{ "ref": "skills" },
{ "ref": "session_file_system" }
]
}'
```
`skills` depends on `session_file_system`; the platform pulls it in automatically.
## How activation looks to the agent
With the capability enabled, the system prompt includes an `` block (\~100 tokens per skill):
```xml
csv-analyzer
Analyze CSV files and generate summary reports.
```
When the user’s task matches, the agent calls `activate_skill`:
```json
{ "name": "activate_skill", "arguments": { "name": "csv-analyzer" } }
```
The tool returns the full SKILL.md instructions wrapped in `` tags. The agent now has the detailed instructions in context and can run the bundled scripts.
## Test the skill
1. Start a session with an agent that has the `skills` capability enabled.
2. Write the skill files to `/.agents/skills//` in the session.
3. Send a message that matches the skill’s “When to Use” criteria.
4. Watch the event stream for an `activate_skill` tool call.
## See also
* [Agent Skills feature](https://docs.everruns.com/features/skills/)
* [Skills Registry](https://docs.everruns.com/features/skills-registry/), share skills across the organization.
* [Publish a skill to the registry](https://docs.everruns.com/how-to/publish-a-skill-to-the-registry/)
---
# Publish a skill to the registry
> Upload a SKILL.md or ZIP archive to the organization-wide Skills Registry so any agent can use it as a capability.
Source:
The Skills Registry stores skills at the organization level. Registry skills persist across sessions and can be assigned to any agent as a capability with ID `skill:{uuid}`.
For workspace-scoped skills, see [Package an agent skill](https://docs.everruns.com/how-to/package-a-skill/) instead.
## From SKILL.md
If your skill is a single Markdown file, post it directly:
```bash
curl -X POST http://localhost:9300/api/v1/skills \
-H "Content-Type: application/json" \
-d '{
"skill_md": "---\nname: hello-world\ndescription: A simple greeting skill.\n---\n\n# Hello World\n\nGreet the user warmly."
}'
```
The response includes the skill ID:
```json
{ "id": "skill_550e8400-e29b-41d4-a716-446655440000", "name": "hello-world", ... }
```
## From a ZIP archive
For skills with bundled scripts, references, or assets:
```bash
curl -X POST http://localhost:9300/api/v1/skills/upload \
-F "file=@csv-analyzer.zip"
```
Archive layout:
```plaintext
csv-analyzer/
├── SKILL.md
├── scripts/analyze.py
└── references/REFERENCE.md
```
The top-level directory name is informational; the skill `name` comes from the SKILL.md front matter and must be unique per organization.
## Validate first
Validate without creating:
```bash
curl -X POST http://localhost:9300/api/v1/skills/validate \
-H "Content-Type: application/json" \
-d '{"skill_md": "---\nname: my-skill\ndescription: Does things.\n---\n\n# Instructions"}'
```
Response:
```json
{ "valid": true, "name": "my-skill", "description": "Does things.", "warnings": [] }
```
## Assign to an agent
Registry skills appear in the capability system as virtual capabilities with ID `skill:{uuid}`:
```bash
curl -X POST http://localhost:9300/api/v1/agents \
-H "Content-Type: application/json" \
-d '{
"name": "Analyst Agent",
"capabilities": [
{ "ref": "skill:550e8400-e29b-41d4-a716-446655440000" },
{ "ref": "session_file_system" }
]
}'
```
`session_file_system` is pulled in automatically as a dependency.
## Update or delete
```bash
# Update
curl -X PATCH http://localhost:9300/api/v1/skills/$SKILL_ID \
-H "Content-Type: application/json" \
-d '{"skill_md": "..."}'
# Delete
curl -X DELETE http://localhost:9300/api/v1/skills/$SKILL_ID
```
Deleting a skill hides it from capability listings. Agents that reference it via `ref: "skill:..."` will still resolve until you update them.
## Security notes
* Archive uploads are validated for path traversal, ZIP bombs, and size limits.
* Skill instructions are returned as tool results, not injected into the system prompt, they don’t bypass capability isolation.
* Skill names are unique per organization.
* Disabled skills are hidden from listings.
## See also
* [Skills Registry feature](https://docs.everruns.com/features/skills-registry/)
* [Package an agent skill](https://docs.everruns.com/how-to/package-a-skill/), author the SKILL.md.
---
# Publish an Agent to Slack
> Add a Slack endpoint to an Agent, publish it, connect a Slack workspace, and verify the first message.
Source:
This guide deploys an Agent as a Slack bot through an Agent-owned endpoint. For Slack scopes, manual setup, and troubleshooting, see [Slack Integration](https://docs.everruns.com/integrations/slack/).
## Prerequisites
* An active Agent.
* A public HTTPS Everruns origin configured through `PUBLIC_APP_URL`.
* Permission to install an app in a Slack workspace.
## Add the Endpoint
1. Open the Agent and select **Integrations**.
2. Select **Add endpoint**, then select **Slack**.
3. Choose a session strategy and reply mode.
4. Leave the Slack credentials empty and select **Save endpoint**.
## Choose a session strategy
`session_strategy` controls how incoming Slack messages map to Everruns sessions:
| Strategy | Behaviour | Use when |
| ---------------------- | ------------------------------------ | --------------------------------------------------------------- |
| `per_thread` (default) | Each Slack thread is its own session | Support bots, Q\&A, each thread is a separate conversation |
| `per_channel` | One session per channel | Persistent channel assistant, context shared across the channel |
| `per_user` | One session per user | Personal assistant, each user has their own ongoing chat |
## Publish and Connect
1. Select **Publish** in the endpoint editor.
2. Select **Connect to Slack**.
3. Approve Slack’s consent screen and choose a workspace.
4. If one-click setup is unavailable, return to **Integrations**, expand the endpoint, and select **Create Slack app**. Copy the resulting signing secret and bot token back through **Configure**.
Publish first because Slack verifies the manifest’s endpoint Request URL when it creates the Slack app. New installs use `/v1/e/{endpoint_id}/slack/events`.
## Verify
1. In Slack, enter `/invite @botname` in a channel.
2. Mention the bot.
3. Return to the Agent’s **Integrations** tab and expand the Slack endpoint.
4. Confirm that the checklist records the first message.
To stop new Slack messages without deleting the configuration, select **Unpublish** on this endpoint. Existing sessions remain available.
## See also
* [Slack Integration](https://docs.everruns.com/integrations/slack/), including scopes, manual setup, and troubleshooting.
* [Agent Versions](https://docs.everruns.com/features/agent-versions/), including endpoint version selection.
---
# Share knowledge with Open Knowledge Format (OKF)
> Import and export Knowledge Bases as Open Knowledge Format bundles, portable markdown-with-frontmatter that any OKF consumer or agent can read, managed like code.
Source:
[Open Knowledge Format (OKF)](https://github.com/GoogleCloudPlatform/knowledge-catalog/tree/main/okf) is a vendor-neutral interchange format for the metadata and curated context around your data: a bundle is just a directory of markdown files with YAML frontmatter, shippable as a tarball or git repo. everruns Knowledge Bases speak OKF on both ends, so you can keep curated knowledge as code, ingest bundles produced elsewhere, and hand your agents’ working set to any OKF consumer.
This guide covers importing a bundle into a Knowledge Base, the OKF↔entry mapping, and exporting a bundle back out.
## What a bundle looks like
```plaintext
sales/
├── index.md # optional navigation (reserved file)
├── tables/
│ └── orders.md # one concept document per file
└── metrics/
└── revenue.md
```
A concept document is frontmatter plus markdown body:
```markdown
---
type: BigQuery Table
title: Orders
description: One row per completed customer order.
resource: https://console.cloud.google.com/bigquery?p=acme&d=sales&t=orders
tags: [sales, revenue]
---
# Schema
| Column | Type | Description |
|--------|------|-------------|
| `order_id` | STRING | Globally unique order identifier. |
```
Only `type` is required. `index.md` and `log.md` are reserved navigation/history files and never become entries.
## Import a bundle
`POST /v1/knowledge-bases/{kb_id}/okf_import` accepts either inline files or a base64-encoded `.tar.gz` bundle. It is **idempotent**: re-importing an updated bundle converges the Knowledge Base to the bundle’s state without creating duplicates.
Create a Knowledge Base, then import inline files:
```bash
KB_ID=$(curl -s -X POST "http://localhost:9300/api/v1/knowledge-bases" \
-H "Content-Type: application/json" \
-d '{"name": "Sales Knowledge"}' | jq -r .id)
curl -s -X POST "http://localhost:9300/api/v1/knowledge-bases/$KB_ID/okf_import" \
-H "Content-Type: application/json" \
-d '{
"files": [
{
"path": "tables/orders.md",
"content": "---\ntype: BigQuery Table\ntitle: Orders\nresource: https://example.com/orders\ntags: [sales]\n---\n# Schema\nOne row per order.\n"
}
]
}'
```
Or import a tarball you already have:
```bash
curl -s -X POST "http://localhost:9300/api/v1/knowledge-bases/$KB_ID/okf_import" \
-H "Content-Type: application/json" \
-d "{\"bundle_base64\": \"$(base64 -w0 sales-bundle.tar.gz)\"}"
```
The response summarizes the run:
```json
{ "created": 1, "updated": 0, "skipped": 0, "pruned": 0, "warnings": [] }
```
### Keeping a Knowledge Base in sync
Re-run the same import whenever the bundle changes, matched entries update in place (keyed on `resource`, or the bundle path when there is no `resource`). To make the Knowledge Base a strict mirror of the bundle, pass `"prune": true`; entries that came from a previous import and are absent from the new bundle are removed.
```bash
curl -s -X POST "http://localhost:9300/api/v1/knowledge-bases/$KB_ID/okf_import" \
-H "Content-Type: application/json" \
-d "{\"prune\": true, \"bundle_base64\": \"$(base64 -w0 sales-bundle.tar.gz)\"}"
```
Import is tolerant by design (per OKF conformance): reserved files are skipped, and malformed documents are reported in `warnings` rather than failing the whole bundle.
## How OKF maps onto entries
| OKF frontmatter | Knowledge entry |
| --------------- | ----------------------------------------------------------------------------------------------------- |
| `type` | `kind` (`note`/`table`/`business`/`query`/`runbook`); the raw `type` is preserved for faithful export |
| `title` | `title` (falls back to the filename) |
| `description` | folded into the body lead |
| `resource` | `resource` |
| `tags` | `tags` (lowercased) |
| markdown body | `body` |
`type` is matched case-insensitively: anything containing *table/dataset/view* → `table`, *metric/business/kpi/definition* → `business`, *query/sql* → `query`, *playbook/runbook/procedure* → `runbook`, otherwise `note`.
## Export a bundle
`GET /v1/knowledge-bases/{kb_id}/okf_export` streams a conformant OKF bundle as a gzipped tarball, including a root `index.md` that declares the format version.
```bash
curl -s "http://localhost:9300/api/v1/knowledge-bases/$KB_ID/okf_export" \
-o sales-bundle.tar.gz
tar tzf sales-bundle.tar.gz
```
Export reconstructs each entry’s frontmatter from the preserved raw `type` (or a default for its `kind`), its `resource`, tags, and timestamp, under a `kind`-derived directory. Export → import into a fresh Knowledge Base reproduces the same entries.
## Agents read OKF too
The `data_knowledge` capability mounts a readonly `/knowledge/` scaffold shaped as an OKF bundle (frontmatter + `index.md` navigation). Agents on the **Data Analyst** harness read it as ground truth before writing SQL. Import an OKF bundle into a Knowledge Base, and that curated context is available to your agents and portable to any other OKF consumer.
See also: [Data Analyst harness](https://docs.everruns.com/built-ins/harnesses/data-analyst/), and `knowledge/runtime-resources/okf-adoption.md` for the design intent.
---
# Stream events with the SDK
> Consume the SSE event stream from the Python SDK with automatic reconnection, heartbeat detection, and event filtering.
Source:
The Python SDK’s `client.events.stream(session_id)` returns an async iterator over typed events. It handles reconnection, heartbeat-based stale detection, and resumption with `since_id` automatically.
## Basic stream
```python
async for event in client.events.stream(session.id):
if event.type == "output.message.delta":
print(event.data.get("delta", ""), end="", flush=True)
elif event.type == "turn.completed":
print()
break
elif event.type == "turn.failed":
print(f"\n[failed: {event.data.get('error')}]")
break
```
## Tool visibility
To show what the agent is doing while it works, listen for `tool.started` and `tool.completed`:
```python
async for event in client.events.stream(session.id):
if event.type == "tool.started":
tool_call = event.data.get("tool_call", {})
print(f" [tool] {tool_call.get('name')}")
elif event.type == "tool.completed":
status = "ok" if event.data.get("success") else "error"
print(f" [tool] {event.data.get('tool_name')}: {status}")
elif event.type == "turn.completed":
break
```
## Get the full final message
`output.message.completed` carries the complete final message after streaming finishes:
```python
async for event in client.events.stream(session.id):
if event.type == "output.message.completed":
message = event.data.get("message", {})
for part in message.get("content", []):
if part.get("type") == "text":
print(part["text"])
elif event.type == "turn.completed":
break
```
## What the SDK handles for you
* **Reconnection.** The control plane cycles SSE connections every 5 minutes; the SDK reconnects transparently using `since_id`.
* **Stale detection.** The server sends a heartbeat every 30s; the SDK treats >45s of silence as a dead connection and reconnects.
* **Backoff.** Network errors trigger exponential backoff with jitter.
* **Typing.** Each event has `.type` and `.data` attributes parsed from SSE.
## Resuming with `since_id`
While a stream is open the SDK manages reconnection internally. You only need `since_id` when restarting your application and resuming from a previously recorded event ID:
```python
# Persisted somewhere — file, DB, etc.
last_seen_id = load_cursor()
async for event in client.events.stream(session.id, since_id=last_seen_id):
handle(event)
save_cursor(event.id) # so the next restart can resume from here
```
Inside the loop the SDK already remembers the last ID it yielded and reconnects with it on transient failures, `save_cursor` here is for *application restart* recovery, not per-iteration SDK state.
## See also
* [Event Reference](https://docs.everruns.com/event-reference/), all event types.
* [Events as the primary store](https://docs.everruns.com/explanation/events/), why the protocol is shaped this way.
* [Consume events via raw SSE](https://docs.everruns.com/how-to/consume-events-via-sse/), non-SDK clients.
---
# Use AGENTS.md for project instructions
> Inject project-level context, coding style, build commands, architecture notes, into an agent's leading user message by enabling the AGENTS.md capability.
Source:
`AGENTS.md` is an emerging open standard for providing project-level instructions to AI agents, backed by OpenAI, Google, Cursor, Sourcegraph, and others. Everruns ships it as the default file for its built-in agent instructions capability, which re-reads configured files on every turn.
## Enable the capability
```bash
curl -X PATCH http://localhost:9300/api/v1/agents/$AGENT_ID \
-H "Content-Type: application/json" \
-d '{
"capabilities": [
{ "ref": "agent_instructions" },
{ "ref": "session_file_system" }
]
}'
```
`session_file_system` isn’t required, but pairing it with `agent_instructions` lets the agent edit `AGENTS.md` itself.
To also read another instruction file, configure `files` on the capability:
```bash
curl -X PATCH http://localhost:9300/api/v1/agents/$AGENT_ID \
-H "Content-Type: application/json" \
-d '{
"capabilities": [
{
"ref": "agent_instructions",
"config": {
"files": ["AGENTS.md", "CLAUDE.md"]
}
},
{ "ref": "session_file_system" }
]
}'
```
## Write the file
Drop a plain Markdown file at `AGENTS.md` in the session workspace root for repo-wide rules. Add nested files (for example `docs/AGENTS.md`) for subdirectory-scoped rules — deeper files override shallower ones on conflict, and sibling subtrees never see each other’s files:
```markdown
## Project: Acme API
REST API built with Rust + Axum. PostgreSQL for storage.
## Style
- snake_case for variables and functions
- PascalCase for types
- Keep functions under 50 lines
## Build & Test
cargo build
cargo test --all-features
cargo clippy -- -D warnings
## Architecture
- `src/api/` — HTTP handlers
- `src/domain/` — Business logic
- `src/db/` — Database queries
## Commits
Use conventional commits: `feat(scope): description`
```
There are no required sections. Write whatever a new contributor would need to know.
## How it lands in the prompt
Every turn the model sees, top-to-bottom:
1. **System prompt**: harness safety instructions, tool guidance, role.
2. **Conversation context**: your resolved `AGENTS.md` hierarchy, broadest scope first.
3. **Conversation history and your message**.
Project files never enter the system prompt — system instructions always win on conflict, and your explicit message wins over project files.
## Limits and dynamics
* Content is capped at **32 KiB** (32,768 bytes) per file (excess truncated with a warning), plus a **128 KiB total budget** per turn across the hierarchy.
* Configured files are resolved from the filesystem root down to the working directory on every turn. Edits during a session apply on the next turn, no restart needed.
* If a configured file doesn’t exist, the agent operates normally without it.
## Other tools’ instruction files
Everruns reads `AGENTS.md` by default at every hierarchy level. Add other file names to `files` when an agent should also resolve `CLAUDE.md`, `.cursorrules`, or `.github/copilot-instructions.md` per level.
## See also
* [AGENTS.md capability reference](https://docs.everruns.com/capabilities/agent-instructions/)
* [Equip an agent with tools](https://docs.everruns.com/how-to/equip-agents-with-tools/), adding capabilities in general.
---
# Integrations
> Connect Everruns agents to cloud sandboxes, browsers, search providers, and messaging channels. Integrations are auto-registered and surface as agent capabilities.
Source:
Integrations connect Everruns agents to external services, cloud sandboxes, browsers, search providers, and messaging channels. Tool integrations surface as [capabilities](https://docs.everruns.com/features/capabilities/). Messaging integrations use endpoints owned by the Agent.
## Sandboxes & execution
Give agents an isolated environment to run code, edit files, and persist state.
| Integration | What it provides |
| ------------------------------------------------------------------------------ | -------------------------------------------------------------------------- |
| [Daytona](https://docs.everruns.com/integrations/daytona/) | Cloud sandbox environments via the Daytona REST API |
| [E2B](https://docs.everruns.com/integrations/e2b/) | Cloud sandboxes via the E2B management + runtime APIs (bring your own key) |
| [Container Sandbox](https://docs.everruns.com/integrations/container-sandbox/) | Self-hosted container sandboxes via Docker Engine, no external SaaS |
| [Sprites](https://docs.everruns.com/integrations/sprites/) | Persistent Firecracker microVMs with checkpoints and HTTP services |
| [Cursor](https://docs.everruns.com/integrations/cursor/) | Launch and manage asynchronous Cursor Cloud coding agents |
## Browser & web
| Integration | What it provides |
| -------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| [Browserless](https://docs.everruns.com/integrations/browserless/) | Cloud browser automation, screenshots, DOM, scraping, multi-step flows |
| [Brave Search](https://docs.everruns.com/integrations/brave-search/) | Web search via the Brave Search API |
| [DuckDuckGo](https://docs.everruns.com/integrations/duckduckgo/) | Instant answers via the DuckDuckGo API |
| [Parallel](https://docs.everruns.com/integrations/parallel/) | Web search, extract, and task APIs (free and paid tiers) |
## Reasoning & judgment
| Integration | What it provides |
| ------------------------------------------------------------ | -------------------------------------------------------------------- |
| [TypeSafe](https://docs.everruns.com/integrations/typesafe/) | Typed judgments: probabilities, single-choice routing, graded scores |
Agents on the [OpenRouter provider](https://docs.everruns.com/providers/openrouter/) can also get web reach without a separate integration via the [OpenRouter Server Tools capability](https://docs.everruns.com/capabilities/openrouter-server-tools/) (`web_search`, `web_fetch`), which OpenRouter executes server-side.
## Messaging channels
| Integration | What it provides |
| ------------------------------------------------------ | -------------------------------------------------- |
| [Slack](https://docs.everruns.com/integrations/slack/) | Deploy an Agent as a Slack bot through an endpoint |
## Credentials
| Guide | What it provides |
| --------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| [Secure MCP Credentials](https://docs.everruns.com/integrations/mcp-credentials/) | Write-only Agent credential bindings for secret MCP tool parameters |
## Discovery
| Integration | What it provides |
| -------------------------------------------------- | ----------------------------------------------------------------------- |
| [ARD](https://docs.everruns.com/integrations/ard/) | Client-side discovery of external MCP servers and A2A agents at runtime |
## Model providers
Integrations connect agents to tools and services. **[Providers](https://docs.everruns.com/providers/)** connect Everruns to the AI model vendors that run your agents, OpenAI, Anthropic, Google Gemini, AWS Bedrock, OpenRouter, and more. See the [Providers overview](https://docs.everruns.com/providers/) to configure one.
## Adding an integration
New integrations follow a parity checklist (connection provider, tests, live-API coverage, docs, and a threat-model section) before they ship. Daytona is the reference implementation. See the in-repo [`knowledge/integrations/integrations.md`](https://github.com/everruns/everruns/blob/main/knowledge/integrations/integrations.md) for the full contract.
---
# Agentic Resource Discovery (ARD)
> Discover and attach external MCP servers and A2A agents at runtime via the ARD protocol, with registry configuration and trust gating.
Source:
Everruns integrates with [Agentic Resource Discovery (ARD)](https://agenticresourcediscovery.org/spec/) as a **client**: a running agent can search ARD registries for capabilities it was not pre-provisioned with, MCP servers and A2A agents, and attach them to its session on the fly. Newly attached MCP tools appear on the next turn; attached A2A agents become `spawn_agent` targets.
ARD is the discovery layer *above* `tool_search`. `tool_search` defers schemas for tools already attached to a session; ARD decides **which** MCP server / A2A agent to attach in the first place.
> **Status:** Experimental (available in Dev environments).
## What You Get
* **Runtime discovery**: `discover_resources` runs a semantic search against a configured registry, outside the model context (like `tool_search`).
* **Dynamic attachment**: `attach_resource` materializes a result as a session-scoped MCP server or external A2A agent, reusing existing Everruns config-overlay machinery. The agent loop is unchanged.
* **Safety by construction**: registry allowlist, `trustManifest` verification, SSRF-safe URL validation, and a per-session attachment cap.
## Quick Start
### 1. Enable the capability on an agent
Add the `resource_discovery` capability and point it at one or more registries. The model selects a registry by `id`, it can never supply a raw URL.
```json
{
"registries": [
{ "id": "public", "url": "https://agenticresourcediscovery.org/api/v1", "federation": "none" }
],
"require_trust": [],
"allow_attach_types": ["application/mcp-server+json", "application/a2a-agent-card+json"],
"max_attachments": 5,
"allow_local_urls": false
}
```
The ready-made **Capability Scout** seed agent (Dev) ships with this wired to the public reference registry plus `tool_search`.
### 2. (Optional) Connect a registry token
For registries that require authentication, connect **Agentic Resource Discovery** under **Settings → Connections** (provider `ard`) and paste a bearer token, or set the `ARD_REGISTRY_TOKEN` session secret. Public anonymous-read registries need no token.
### 3. Discover and attach
From a session, ask for something the agent can’t yet do. It will:
1. `discover_resources({ text: "..." })`, search the registry and get ranked candidates, each with a `urn`.
2. `attach_resource({ urn })`, verify trust, validate the URL, and attach.
3. Use the new capability on the next turn, MCP tools appear prefixed `mcp___*` (surfaced through `tool_search`); A2A agents are reachable via `spawn_agent`.
4. `list_attached_resources()`, see what’s attached this session.
## Tools
| Tool | Description |
| ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `discover_resources({ text, filter?, registry_id? })` | Search a configured registry (`POST /search`). Returns ranked `{ urn, displayName, type, score, source, description, attachable }`. `registry_id` is optional when one registry is configured. |
| `attach_resource({ urn })` | Resolve a discovered entry, verify `trustManifest` + `require_trust`, SSRF-validate the URL, and attach it. Idempotent per URN. |
| `list_attached_resources()` | List attachments for the session (visibility / audit). |
## Attachment Lifecycle
* Attachments are **session-scoped** and torn down when the session ends.
* MCP entries become a session-scoped `mcpServers` record; their tools are then subject to `tool_search` deferral.
* A2A entries merge into the session’s A2A delegation config and are driven through the existing `spawn_agent` / `wait_task` / `message_task` tools.
* Re-attaching the same URN is a no-op (reports `already_attached`).
## Security
* **Registry allowlist**: only configured registries are queryable; the model picks a `registry_id`, never a URL.
* **Trust gate**: an entry’s `trustManifest` identity domain must match its URN publisher, and any `require_trust` attestations (e.g. `["soc2"]`) must be present, before it can be attached.
* **SSRF protection**: every resolved artifact and endpoint URL is validated (DNS-pinned; loopback, private, link-local, and cloud-metadata addresses are blocked). `allow_local_urls` relaxes this for local testing only.
* **Attachment cap**: `max_attachments` bounds how many capabilities a single session can attach, limiting prompt-injection-driven attach storms.
* **Untrusted data**: all registry-returned text is treated as untrusted external input.
See the co-located [`SPEC.md`](https://github.com/everruns/everruns/blob/main/crates/ard/SPEC.md) for architecture and the full security review.
---
# Brave Search
> Web search through Brave: ranked results, freshness filters, pagination, source attribution, and safe search. Requires a Brave API key.
Source:
Everruns integrates with [Brave Search](https://brave.com/search/api/) to give agents full web search capabilities. Agents can search the web and get relevant results including titles, URLs, and descriptions, perfect for research, fact-checking, and finding current information.
## What You Get
* **Full Web Search**: Query the web and get ranked results with titles, URLs, and descriptions
* **Freshness Filters**: Filter results by time (past day, week, month, year)
* **Pagination**: Navigate through large result sets
* **Source Attribution**: Every result includes a URL for citation and verification
* **Free Tier**: Brave Search offers a free plan with 2,000 queries/month
## Quick Start
### 1. Get Your API Key
1. Go to [Brave Search API](https://brave.com/search/api/)
2. Sign up for a **free** plan (2,000 queries/month)
3. Copy your **API Key** from the dashboard
### 2. Connect in Everruns
1. Go to **Settings** > **Connections**
2. Find **Brave Search** in the available providers
3. Click **Connect** and paste your API key
Once connected, the Brave Search capability is automatically available in agent sessions.
### 3. Use in Sessions
Agents with the Brave Search capability can use this tool:
| Tool | Description |
| ------------------ | ------------------------------------------ |
| `brave_web_search` | Search the web and return relevant results |
### Parameters
| Parameter | Type | Required | Description |
| ----------- | ------- | -------- | ----------------------------------------------------------------------------------- |
| `query` | string | Yes | Search query |
| `count` | integer | No | Number of results (1-20, default: 10) |
| `offset` | integer | No | Pagination offset |
| `freshness` | string | No | Time filter: `pd` (past day), `pw` (past week), `pm` (past month), `py` (past year) |
### Response Fields
The tool returns a JSON object with:
| Field | Description |
| --------- | -------------------------- |
| `query` | The original query |
| `results` | Array of result objects |
| `count` | Number of results returned |
Each result object contains:
| Field | Description |
| ------------- | ----------------------------------------------------- |
| `title` | Page title |
| `url` | Page URL (use for citations) |
| `description` | Snippet/description of the page |
| `age` | How old the result is (e.g., “2 hours ago”), optional |
## When to Use Brave Search vs DuckDuckGo
| Use Case | Brave Search | DuckDuckGo |
| ---------------------------- | ------------------------- | -------------- |
| Full web search results | Best choice | Not available |
| Current news and articles | Best choice | Limited |
| Quick facts and definitions | Works but slower | Best choice |
| Wikipedia-style summaries | Not available | Best choice |
| Calculations and conversions | Not available | Direct answers |
| API key required | Yes (free tier available) | No |
Both capabilities can be enabled simultaneously, the agent will choose the right tool based on the task.
## Security
* API keys are encrypted at rest (AES-256-GCM envelope encryption)
* Keys are validated on connection (test query to Brave Search API)
* API key never appears in tool results or message history
* Rate limiting deferred to Brave Search API (returns 429 on limit)
## Status
**Experimental**: available in dev mode only. This capability may change in future releases.
## Links
* [Brave Search API](https://brave.com/search/api/)
* [Brave Search API Documentation](https://api.search.brave.com/app/#/documentation/web-search)
---
# Browserless
> Configure Browserless for headless Chrome automation: API keys, connection pooling, screenshots, and scraping.
Source:
Everruns integrates with [Browserless](https://www.browserless.io/) to provide cloud-based browser automation. Agents can navigate web pages, take screenshots, read DOM content, scrape structured data, and interact with UI elements (click, type, keyboard, mouse, touch).
## What You Get
* **Screenshots**: Capture full-page or element-specific PNG screenshots
* **DOM Reading**: Get fully rendered HTML including JavaScript-generated content
* **Structured Scraping**: Extract data from pages using CSS selectors
* **Browser Interactions**: Click, type, press keys, use mouse/touch events
* **Persistent Sessions**: Keep a browser alive across tool calls for login-protected pages (CDP mode)
## Quick Start
### 1. Get Your API Token
1. Go to the [Browserless Dashboard](https://www.browserless.io/account/home)
2. Navigate to **API Keys** in your account settings
3. Copy your API token
### 2. Connect in Everruns
1. Go to **Settings** > **Connections**
2. Find **Browserless** in the available providers
3. Click **Connect** and paste your API token
Once connected, the Browserless capability is automatically available in agent sessions.
### 3. Use in Sessions
Agents with the Browserless capability can use these tools:
| Tool | Description |
| --------------------------- | ------------------------------------------------------------- |
| `browserless_open_browser` | Open a persistent browser session (CDP mode) |
| `browserless_close_browser` | Close the persistent browser session |
| `browserless_navigate` | Navigate to a URL and get page metadata |
| `browserless_screenshot` | Take a PNG screenshot of a page |
| `browserless_content` | Get the fully rendered HTML/DOM content |
| `browserless_scrape` | Extract structured data via CSS selectors |
| `browserless_interact` | Multi-step interactions (click, type, keyboard, mouse, touch) |
## Two Operating Modes
### Stateless Mode (Default)
Each tool call launches a fresh browser that is destroyed after the response. No state persists between calls. Best for one-shot operations like screenshots or scraping.
### Persistent Session Mode (CDP)
Use `browserless_open_browser` to create a persistent browser via Chrome DevTools Protocol. The browser stays alive between tool calls, preserving login state, cookies, and navigation history. Use `browserless_close_browser` when done.
**Example workflow for login-protected pages:**
1. `browserless_open_browser` with the login page URL
2. `browserless_interact` to fill credentials and submit the form
3. `browserless_navigate` to browse authenticated pages
4. `browserless_screenshot` to capture authenticated page state
5. `browserless_close_browser` to release resources
## Use Cases
* **Accessibility testing**: Navigate pages, read DOM, check ARIA attributes and heading structure
* **Regression testing**: Screenshot pages and verify content after changes
* **Login flows**: Use persistent sessions to authenticate and test protected pages
* **Web scraping**: Extract structured data from any website
* **Visual QA**: Take before/after screenshots to verify UI changes
## Resource Management
* **Stateless mode**: No cleanup needed, browsers are ephemeral
* **CDP mode**: Browsers auto-expire after 60 seconds of inactivity. Always call `browserless_close_browser` when done for immediate cleanup.
## Security
* API tokens are encrypted at rest (AES-256-GCM envelope encryption)
* Browser sessions are fully isolated on Browserless servers
* CDP session state stores only the WebSocket endpoint (no secrets), scoped per session
* Large DOM responses are truncated to 100KB to prevent context flooding
## Links
* [Browserless Website](https://www.browserless.io/)
* [Browserless Dashboard](https://www.browserless.io/account/home)
* [Browserless Documentation](https://docs.browserless.io/)
---
# Container Sandbox
> Self-hosted container sandboxes for code execution via Docker Engine, with no external SaaS dependency.
Source:
Everruns provides self-hosted container sandboxes via Docker Engine for secure, isolated code execution. Agents can create, manage, and interact with multiple containers per session, each an isolated Linux environment with real filesystem, process execution, and network access.
## What You Get
* **Self-Hosted**: Runs on your own infrastructure, no SaaS dependency
* **Isolated Containers**: Each sandbox is an isolated Linux container with cgroup resource limits
* **Multi-Sandbox Sessions**: Create and manage multiple containers within a single session
* **File Operations**: Read, write, upload, and download files between session storage and containers
* **Shell Execution**: Run arbitrary commands with stdout/stderr/exit\_code capture
## Quick Start
### 1. Docker Engine Access
Ensure Docker Engine is accessible from the server/worker. The capability communicates via Docker Engine REST API (not the CLI).
* **Local**: Docker Desktop or `dockerd` on the host (default socket: `/var/run/docker.sock`)
* **Remote**: TCP or TCP+TLS endpoint (e.g., `http://10.0.0.3:2375`)
Set `CONTAINER_SANDBOX_DOCKER_HOST` to override the default Docker host.
### 2. Enable the Feature and Assign the Capability
Set `FEATURE_CONTAINER_SANDBOX=true` anywhere capabilities are registered or executed to enable the capability and built-in **Coding (Container)** harness. In most deployments, that means both the server and any workers.
For legacy deployments, `FEATURE_DOCKER_CAPABILITY=true` still enables the same feature until operators switch to the new flag name, and it must be enabled in the same places.
Once the flag is enabled in the relevant processes, add the `container_sandbox` capability to a custom harness or use the built-in **Coding (Container)** harness.
### 3. Use in Sessions
Agents with the Container Sandbox capability can use these tools:
| Tool | Description |
| -------------------- | ----------------------------------------------------- |
| `sandbox_create` | Create and start a new container with resource limits |
| `sandbox_exec` | Execute shell commands in a container |
| `sandbox_read_file` | Read files from container filesystem |
| `sandbox_write_file` | Write files to container filesystem |
| `sandbox_upload` | Copy files from session storage into container |
| `sandbox_download` | Copy files from container to session storage |
| `sandbox_list` | List active containers in the session |
| `sandbox_manage` | Stop, start, or remove containers |
## Container Lifecycle
1. **Create**: `sandbox_create` pulls the image, creates an isolated network, and starts the container
2. **Use**: `sandbox_exec`, `sandbox_read_file`, `sandbox_write_file` for coding work
3. **Transfer**: `sandbox_upload`/`sandbox_download` to move files between session storage and container
4. **Remove**: `sandbox_manage` with action “remove” deletes the container and network
Containers auto-stop after 10 minutes of inactivity (configurable). Leased resource cleanup handles abandoned containers after 20 minutes.
## Configuration
| Parameter | Default | Description |
| -------------- | -------------- | ----------------------------------------------------------- |
| `docker_host` | Auto-detect | Docker Engine endpoint |
| `runtime` | `runc` | Container runtime (`runc`, `sysbox-runc`, `kata`, `gvisor`) |
| `image` | `ubuntu:24.04` | Default container image |
| `memory_limit` | 2 GiB | Memory limit per container |
| `cpu_limit` | 1 core | CPU limit per container |
| `pids_limit` | 256 | Max processes per container |
## Security
* Each container runs in its own isolated Docker network
* Resource limits (memory, CPU, PIDs) enforced via cgroups
* Container names derived from session ID, no cross-session access
* Docker socket never mounted into containers
* Configurable runtime: use `sysbox-runc` for user-namespace isolation in production
## Links
* [Docker Engine API Reference](https://docs.docker.com/engine/api/)
* [Sysbox Runtime](https://github.com/nestybox/sysbox)
---
# Cursor
> Launch and manage Cursor Cloud Agents from Everruns agents.
Source:
Everruns integrates with [Cursor Cloud Agents](https://docs.cursor.com/en/background-agents) so agents can delegate asynchronous coding work to Cursor. A triage agent can inspect a request, split it into focused tasks, launch Cursor agents against GitHub repositories, send follow-ups, and summarize status or results.
## What You Get
* **Launch Cloud Agents**: Start Cursor agents with repository, base ref, task prompt, optional branch name, and PR behavior.
* **Track Progress**: Read status, target branch, PR URL, summary, and conversation history.
* **Send Follow-ups**: Add more instructions to running Cursor agents.
* **Connection Prompt**: Configure a Cursor Cloud Agents API key in Settings > Connections.
* **Seed Agent**: Use the built-in **Cursor Agent Manager** example to triage and delegate work.
## Quick Start
### 1. Get a Cursor API Key
1. Open [Cursor Dashboard](https://cursor.com/dashboard?tab=cloud-agents)
2. Go to **Cloud Agents** > **My Settings** > **API Keys**
3. Create a Cloud Agents API key
4. Make sure Cursor’s GitHub app can access the repositories agents should work on
Use a Cloud Agents API key. A general Cursor dashboard API key may not be enough to create agents.
### 2. Connect in Everruns
1. Go to **Settings** > **Connections**
2. Find **Cursor**
3. Click **Connect**
4. Paste the Cloud Agents API key
### 3. Use in Sessions
Agents with the Cursor capability can use these tools:
| Tool | Description |
| -------------------------- | -------------------------------------------- |
| `cursor_launch_agent` | Start a Cursor Cloud Agent |
| `cursor_get_agent` | Get one agent’s status and result metadata |
| `cursor_list_agents` | List agents for the connected Cursor account |
| `cursor_add_followup` | Send extra instructions to a running agent |
| `cursor_get_conversation` | Read an agent’s conversation transcript |
| `cursor_delete_agent` | Delete an agent record/resources |
| `cursor_list_models` | List recommended Cursor model ids |
| `cursor_list_repositories` | List GitHub repositories Cursor can access |
| `cursor_key_info` | Check the active Cursor connection |
## Delegation Pattern
Use **Cursor Agent Manager** when you want Everruns to triage first and then delegate:
1. Provide the repository URL and base branch
2. Ask the agent to break the work into scoped tasks
3. Let it launch Cursor agents with clear acceptance criteria
4. Review returned Cursor links, branches, PR URLs, summaries, and transcripts
## Lifecycle
Cursor owns Cloud Agent lifecycle state. Everruns stores no per-agent state beyond normal session messages and tool results. Keep the returned `agent_id` if you want to poll, send follow-ups, read the conversation, or delete the agent later.
`cursor_list_repositories` is heavily rate-limited by Cursor and can be slow for large accounts. Prefer passing the repository URL directly.
## Security
* Cursor API keys are encrypted at rest when stored as Everruns user connections
* Everruns prompts for missing Cursor credentials through the inline connection dialog, not chat text
* Cursor agents run in Cursor-managed remote environments with internet access and command execution
* Repository access is controlled by the Cursor GitHub app and Cursor account settings
* Webhook secrets and image prompt payloads are intentionally not exposed in the first tool surface
## Links
* [Cursor Cloud Agents](https://docs.cursor.com/en/background-agents)
* [Cursor Background Agents API](https://docs.cursor.com/en/background-agent/api/overview)
* [Launch Agent API](https://docs.cursor.com/en/background-agent/api/launch-an-agent)
* [Cursor GitHub app](https://docs.cursor.com/en/github)
---
# Daytona
> Configure Daytona cloud sandboxes: API keys, workspace templates, and session-scoped sandbox lifecycle.
Source:

Everruns integrates with [Daytona](https://www.daytona.io/) to provide cloud-based sandbox environments for secure, isolated code execution. Agents can create, manage, and interact with multiple sandboxes per session, each a fully isolated Linux environment with network access.
## What You Get
* **Isolated Sandboxes**: Each sandbox is a secure, isolated Linux environment
* **Multi-Sandbox Sessions**: Create and manage multiple sandboxes within a single session
* **File Operations**: Read, write, and download files from sandbox filesystems
* **Git Integration**: Clone repositories with automatic GitHub credential forwarding
* **Shell Execution**: Run arbitrary commands with configurable timeouts
## Quick Start
### 1. Get Your API Key
1. Go to the [Daytona Dashboard](https://app.daytona.io)
2. Navigate to **API Keys** in your account settings
3. Click **Create New API Key**
4. Copy the key
### 2. Connect in Everruns
1. Go to **Settings** > **Connections**
2. Find **Daytona** in the available providers
3. Click **Connect** and paste your API key
Once connected, the Daytona capability is automatically available in agent sessions.
### 3. Use in Sessions
Agents with the Daytona capability can use these tools:
| Tool | Description |
| ---------------------------- | --------------------------------------------------- |
| `daytona_create_sandbox` | Create and start a new sandbox |
| `daytona_exec` | Execute shell commands |
| `daytona_read_file` | Read files from sandbox |
| `daytona_write_file` | Write files to sandbox |
| `daytona_download_workspace` | Download workspace to session storage |
| `daytona_list_sandboxes` | List active sandboxes |
| `daytona_manage_sandbox` | Stop or delete sandboxes |
| `daytona_git_clone` | Clone repositories (auto-authenticates with GitHub) |
| `daytona_git_credentials` | Configure git push/pull credentials |
## Git Integration
Daytona sandboxes integrate with your connected GitHub account:
* **Clone private repos**: `daytona_git_clone` automatically uses your GitHub credentials
* **Push/pull/fetch**: Call `daytona_git_credentials` once after creating a sandbox, then use `daytona_exec` for any git command
* **Shorthand syntax**: Use `user/repo` format instead of full URLs
## Sandbox Lifecycle
Sandboxes auto-stop after 5 minutes of inactivity as a safety net. Best practice is to explicitly delete sandboxes when done, stopping only pauses them (they remain visible on your Daytona dashboard).
## Security
* API keys are encrypted at rest (AES-256-GCM envelope encryption)
* Each sandbox is fully isolated from other sandboxes and the host
* Git credentials are short-lived and scoped to the sandbox
* Sandbox state is stored in encrypted session secrets
## Links
* [Daytona Website](https://www.daytona.io/)
* [Daytona Dashboard](https://app.daytona.io)
* [Daytona Documentation](https://www.daytona.io/docs)
---
# DuckDuckGo
> Instant answers, definitions, and topic summaries via DuckDuckGo. No API key required.
Source:
Everruns integrates with [DuckDuckGo](https://duckduckgo.com/) to provide instant answers via the [DuckDuckGo Instant Answer API](https://api.duckduckgo.com/api). Agents can look up facts, definitions, topic summaries (from Wikipedia and other sources), and related topics, all without an API key.
## What You Get
* **Instant Answers**: Direct answers for calculations, IP lookups, conversions, and more
* **Topic Abstracts**: Wikipedia-style summaries for well-known topics
* **Definitions**: Dictionary definitions from Wiktionary and other sources
* **Related Topics**: Links to related topics for deeper exploration
* **No API Key Required**: The DuckDuckGo Instant Answer API is completely free
## Quick Start
### 1. No Setup Needed
Unlike other integrations, DuckDuckGo requires no API key or configuration. The DuckDuckGo Instant Answer API is free and public.
### 2. Enable the Capability
Add the `duckduckgo` capability to your agent or harness configuration. In dev mode, it’s available as an experimental capability.
### 3. Use in Sessions
Agents with the DuckDuckGo capability can use this tool:
| Tool | Description |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `duckduckgo_instant_answer` | Look up instant answers, abstracts, definitions, and related topics. Instant-answer lookup only, not a full web/SERP search |
### Parameters
| Parameter | Type | Required | Description |
| --------- | ------- | -------- | --------------------------------------------- |
| `query` | string | Yes | Search query |
| `no_html` | boolean | No | Strip HTML from result text (default: `true`) |
### Response Fields
The tool returns a JSON object with available fields:
| Field | Description |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `query` | The original query |
| `type` | Response type: `article`, `disambiguation`, `category`, `name`, `exclusive`, or `nothing` |
| `heading` | Topic heading |
| `abstract` | `{ text, source, url }`, topic summary |
| `answer` | `{ text, type }`, direct answer (calculations, etc.) |
| `definition` | `{ text, source, url }`, dictionary definition |
| `related_topics` | Array of `{ text, url }`, related topics (max 10) |
| `results` | Array of `{ text, url }`, official/direct results |
| `note` | Present only when no instant answer was found, a caveat that this is not a definitive web-search result and matching web pages may still exist |
Only non-empty fields are included in the response.
Note
This tool queries the DuckDuckGo **Instant Answer** API, not full web search. A `nothing` result (or a `note` field) means DuckDuckGo has no curated instant answer for the query, it does **not** mean no web pages match. Prefer a web-search or web-fetch tool (or Brave Search) for general web discovery; when none is available, this tool can still serve as a lightweight search for quick facts, definitions, and related topics.
## When to Use DuckDuckGo vs Brave Search
| Use Case | DuckDuckGo | Brave Search |
| ---------------------------- | ---------------- | ---------------- |
| Quick facts and definitions | ✅ Best choice | Works but slower |
| Wikipedia-style summaries | ✅ Best choice | Not available |
| Calculations and conversions | ✅ Direct answers | Not available |
| Full web search results | ❌ Not available | ✅ Best choice |
| Current news and articles | ❌ Limited | ✅ Best choice |
| API key required | No | Yes |
Both capabilities can be enabled simultaneously, the agent will choose the right tool based on the task.
## Security
* **No secrets**: No API key or credentials to manage
* **Read-only**: The API only returns information, no write operations
* **Privacy**: DuckDuckGo does not track searches
## Status
**Experimental**: available in dev mode only. This capability may change in future releases.
## Links
* [DuckDuckGo](https://duckduckgo.com/)
* [DuckDuckGo Instant Answer API](https://api.duckduckgo.com/api)
---
# E2B
> Configure E2B cloud sandboxes: bring your own API key, create multiple sandboxes per session, and manage their lifecycle.
Source:
Everruns integrates with [E2B](https://e2b.dev/docs) to provide cloud sandbox environments for secure, isolated code execution. Agents can create, pause, resume, delete, and interact with multiple isolated Linux sandboxes per session. You bring your own E2B API key, there is no platform-owned or environment-variable fallback, so sandbox costs and quotas stay scoped to your own E2B account.
## What You Get
* **Isolated Sandboxes**: Each sandbox is a secure, isolated Linux environment
* **Multi-Sandbox Sessions**: Create and manage multiple sandboxes within a single session
* **File Operations**: Read and write files in sandbox filesystems
* **Shell Execution**: Run commands with stdout/stderr/exit-code capture
* **Lifecycle Control**: Pause, resume, and delete sandboxes; auto-timeout limits cost
## Quick Start
### 1. Get Your API Key
1. Go to the [E2B Dashboard](https://e2b.dev/dashboard)
2. Create an API key
3. Copy the key
### 2. Connect in Everruns
1. Go to **Settings** > **Connections**
2. Find **E2B** in the available providers
3. Click **Connect** and paste your API key
Once connected, the E2B capability is automatically available in agent sessions. Every E2B operation requires a user-provided key, if none is configured, the agent surfaces an inline connection prompt.
### 3. Use in Sessions
Agents with the E2B capability can use these tools:
| Tool | Description |
| -------------------- | -------------------------------------------------------------------- |
| `e2b_create_sandbox` | Create a sandbox from a template, optionally uploading session files |
| `e2b_exec` | Execute a shell command |
| `e2b_read_file` | Read a file from the sandbox filesystem |
| `e2b_write_file` | Write a file into the sandbox filesystem |
| `e2b_list_sandboxes` | List sandboxes created in this session |
| `e2b_manage_sandbox` | Pause, resume, or delete a sandbox |
`e2b_create_sandbox` accepts an optional `template` (default `base`), a `timeout_seconds` (default `3600`), `env_vars`, and `upload_files` mapping session paths into the sandbox.
## How It Works
E2B exposes two surfaces, and the integration uses both:
* **Management API** (`api.e2b.app`), sandbox lifecycle, metadata, and timeout control.
* **envd sandbox endpoint**: in-sandbox file access and command execution.
Per-sandbox state (sandbox ID, domain, access token, workspace path, timeout) is stored in encrypted session secrets and registered as a leased resource, so orphaned sandboxes are cleaned up on the worker side. Every sandbox is tagged with Everruns ownership metadata (session, harness, org, and agent IDs) for dashboard traceability and audit review.
## Security
* API keys resolve fresh from your user connection on each tool call, never stored in sandbox state, env vars, or emitted in tool output
* envd access tokens are session-scoped and stored only in encrypted session secrets (AES-256-GCM envelope encryption)
* Sandbox isolation depends on E2B’s runtime boundaries plus Everruns session-scoped secret lookups
* Resource leaks are mitigated by E2B timeouts and auto-pause plus Everruns leased-resource cleanup
## Links
* [E2B Documentation](https://e2b.dev/docs)
* [E2B Dashboard](https://e2b.dev/dashboard)
---
# Secure MCP Credentials
> Configure write-only Agent credentials for MCP tools that require a secret parameter.
Source:
Use an Agent credential binding when an MCP tool requires a secret in its input, such as Visti’s `visti_send.channel_key`. Do not paste the value into chat, Agent instructions, memory, or session storage.
1. Attach the MCP server capability to the Agent.
2. Open the Agent and select **Credentials**.
3. Add or open the exact server, tool, and parameter binding.
4. Enter the value in the masked form and save it.
The value is encrypted and is never shown again. Everruns removes the bound parameter from the model-visible tool schema and injects the value only when it sends the MCP request. The same Agent binding works for a shared session and for triggers that create a new session per invocation.
Use **Rotate** to replace a value. Use the revoke action to delete the binding; future calls then return a setup-required result with a link back to the Credentials tab.
Session Storage has a separate encrypted secret lifecycle for session-local workflows. Those secrets do not follow per-invocation sessions, and a model can read them with `secret_store get`, so they are not a substitute for an MCP credential binding.
---
# Parallel
> Use Parallel's hosted MCP server for free web search and URL fetching, with optional API-key authentication and OAuth-compatible endpoint selection.
Source:
# Parallel
Parallel provides hosted MCP tools for web search and URL fetching.
## Setup
Add the `parallel_search` capability to an agent or harness. It works for free without any connection.
To use a Parallel API key, add a `Parallel` connection in Settings > Connections, then configure the capability with `auth: "connection"`.
To use Parallel’s OAuth-compatible MCP endpoint, configure the capability with `endpoint: "oauth"`. This mode requires the `Parallel` connection because the endpoint rejects anonymous requests.
## Tools
| Tool | Purpose |
| -------------------------- | ---------------------------------------------------- |
| `mcp_parallel__web_search` | Search the web and return ranked URLs with excerpts. |
| `mcp_parallel__web_fetch` | Fetch and extract focused content from known URLs. |
Agents should reuse one stable `session_id` across Parallel tool calls in the same conversation.
## Paid machine payments
Operators can separately enable Parallel’s paid search, extraction, and task tools with `FEATURE_MACHINE_PAYMENTS=true`. This deployment flag is off by default in every environment. When it is off, Everruns does not expose Settings > Payments or the payment account, policy, and attempt APIs, so the deployment does not ask organization owners to entrust wallet keys for a capability that cannot spend.
---
# Slack
> Deploy Everruns agents as Slack bots that respond to messages, threads, and mentions. Configure endpoint publishing, Slack installation, and channel routing.
Source:
Everruns connects an Agent to Slack through an Agent-owned endpoint. The endpoint receives Slack Events API requests, routes each conversation to a session, and posts the Agent’s responses back to Slack.
## What You Get
* **Conversational agents in Slack**: Users interact with the Agent in channels, threads, direct messages, or Slack’s agent pane.
* **Session routing**: Conversations map to sessions by thread, channel, or user.
* **Secure webhooks**: Everruns verifies requests with Slack’s signing secret.
* **Async responses**: Everruns acknowledges Slack immediately and posts the Agent’s response when it is ready.
* **Per-endpoint Slack bots**: Each Slack endpoint has its own Slack app, credentials, identity, and lifecycle.
## Before You Start
* Create an active Agent.
* Give Everruns a public HTTPS origin. Set `PUBLIC_APP_URL` to that origin and restart Everruns.
* Ask a Slack workspace administrator for permission to install an app.
Slack cannot verify `localhost`. For local development, expose Everruns through a public HTTPS tunnel before you create the Slack app.
## Connect an Agent to Slack
### 1. Create a Slack Endpoint
1. Open the Agent.
2. Select **Integrations**.
3. Select **Add endpoint**.
4. Select **Slack**.
5. Choose the session strategy and reply mode. Leave the Slack credentials empty.
6. Select **Save endpoint**.
Everruns opens the endpoint editor after it saves the endpoint.
### 2. Publish the Endpoint
Select **Publish** in the endpoint editor. Publishing makes only this endpoint live.
Publish before you create the Slack app. The generated Slack manifest contains the endpoint’s Request URL, and Slack verifies that URL when it creates the app.
### 3. Connect to Slack
Select **Connect to Slack** in the endpoint editor. Approve the Slack consent screen and choose a workspace. Everruns creates and installs the Slack app, then stores its signing secret, bot token, and workspace ID on this endpoint.
Some self-hosted deployments do not configure one-click Slack app creation. If Everruns reports that one-click setup is unavailable:
1. Return to the Agent’s **Integrations** tab.
2. Expand the live Slack endpoint.
3. Select **Create Slack app**.
4. Review Slack’s pre-filled manifest and select **Create**.
5. Install the app to your workspace.
6. Copy the **Signing Secret** from **Basic Information**.
7. Copy the **Bot User OAuth Token** (`xoxb-...`) from **OAuth & Permissions**.
8. Select **Configure** on the endpoint, enter both values, and select **Save**.
The manifest already contains the bot scopes, event subscriptions, interactivity URL, and canonical endpoint Request URL:
```text
https://your-everruns-host/api/v1/e/{endpoint_id}/slack/events
```
Do not replace `{endpoint_id}` with an Agent ID or an App ID.
### 4. Invite and Test
1. In Slack, enter `/invite @botname` in a channel.
2. Mention the bot or send it a direct message.
3. Return to the Agent’s **Integrations** tab and expand the Slack endpoint.
4. Confirm that the setup checklist records the first message.
## Configure a Slack App Manually
Use this flow only when you cannot use **Connect to Slack** or **Create Slack app**.
1. Create and publish a Slack endpoint from the Agent’s **Integrations** tab.
2. Copy its Request URL from the expanded endpoint row.
3. In [Slack API Apps](https://api.slack.com/apps), select **Create New App** > **From scratch**.
4. Add these bot token scopes under **OAuth & Permissions**:
* `chat:write`
* `channels:history`
* `groups:history`
* `im:history`
* `mpim:history`
* `app_mentions:read`
* `users:read`
5. Add these bot events under **Event Subscriptions**:
* `message.channels`
* `message.groups`
* `message.im`
* `message.mpim`
* `app_mention`
6. Paste the endpoint Request URL into Slack’s **Request URL** field.
7. Install the Slack app to your workspace.
8. Copy the signing secret and bot token into the endpoint’s **Configure manually** fields.
9. Save the endpoint.
## Existing Installs
Existing Slack installs that use `/v1/apps/{app_id}/…` URLs continue to work. Everruns keeps those routes as permanent compatibility aliases. New installs use `/v1/e/{endpoint_id}/…`, which is the canonical endpoint-owned form.
## Endpoint Configuration
| Field | Required | Description |
| ----------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `signing_secret` | Before use | Slack app signing secret for HMAC-SHA256 verification. It can be empty while you create the endpoint, but the endpoint rejects all Slack requests until it is set. |
| `bot_token` | Before use | Bot User OAuth Token (`xoxb-...`) for sending responses. It can be empty while you create the endpoint. |
| `channel_id` | No | Restrict the endpoint to one channel, such as `C0123456789`. |
| `team_id` | No | Slack workspace ID. |
| `session_strategy` | No | `per_thread` by default, `per_channel`, or `per_user`. |
| `agent_surface_enabled` | No | `false` by default. Also serve Slack’s agent pane; see [Agent Surface](#agent-surface). |
## Agent Surface
Slack apps can also appear as an **agent**, a dedicated assistant pane separate from channel conversations. Enabling `agent_surface_enabled` adds this pane without changing channel replies.
The generated manifest adds:
* the `features.agent_view` block with an `agent_description` derived from the Agent;
* the `assistant:write` bot scope; and
* the `app_home_opened`, `app_context_changed`, `agent_session_stopped`, and `agent_session_title_changed` bot events.
Enabling the pane on an existing Slack app requires a reinstall because a configuration change cannot grant the new `assistant:write` OAuth scope. Generate a fresh manifest, update the Slack app, and reinstall it. Channel replies continue while you do this.
Session strategy in the pane is always `per_thread`. The configured strategy still applies to channel conversations.
### Streaming Replies
Replies in the agent pane render as the Agent produces them. Channel threads receive one finished message instead of token-by-token updates.
## Session Strategies
| Strategy | Behavior | Tag Pattern |
| ------------- | ---------------------------------------- | -------------------------- |
| `per_thread` | Each Slack thread is a separate session. | `slack:thread:{thread_ts}` |
| `per_channel` | One session serves the channel. | `slack:channel:{channel}` |
| `per_user` | One session serves each Slack user. | `slack:user:{user}` |
Use `per_thread` for most Slack bots.
## How It Works
### Architecture

### Message Flow

**Inbound path:**
1. Slack posts a message event to `/v1/e/{endpoint_id}/slack/events`.
2. The endpoint verifies the signing secret and rejects duplicates.
3. Everruns acknowledges Slack within three seconds.
4. The endpoint finds or creates a session from the configured strategy.
5. Everruns creates a user message and starts an Agent turn.
**Outbound path:**
6. The Slack delivery dispatcher watches the turn.
7. The RuntimeAgent emits completed output messages.
8. The dispatcher posts each response with `chat.postMessage`.
9. Transient delivery failures retry with exponential backoff.
10. The dispatcher unregisters when the turn completes or fails.
## Troubleshooting
### URL Verification Failed
* Confirm that the endpoint is published.
* Confirm that `PUBLIC_APP_URL` is a public HTTPS origin and that Everruns restarted after the value changed.
* Confirm that the Request URL contains `/v1/e/{endpoint_id}/slack/events`.
### Bot Does Not Respond
* Confirm that the endpoint is published and enabled.
* Invite the bot to the channel with `/invite @botname`.
* Confirm that the Slack app has the required bot events and the `chat:write` scope.
* Confirm that the endpoint has the correct signing secret and bot token.
### Request Verification Failed
* Confirm that the endpoint’s signing secret matches the value under the Slack app’s **Basic Information** page.
* Confirm that the Everruns server clock is accurate.
## Approvals
When an Agent pauses for approval, Slack renders **Approve** and **Decline** buttons in the thread. Only the person whose message the Agent is answering can use them.
The generated manifest already points Slack interactivity at this endpoint. A Slack app created before interactive approvals existed must save a fresh manifest once.
## Task Progress
When an Agent delegates work, Slack shows one **Tasks** message and updates it as workers finish. The message lists up to five tasks and summarizes larger groups by count.
## Links
* [Slack API Documentation](https://api.slack.com/docs)
* [Slack Events API](https://api.slack.com/events-api)
* [Publish an Agent to Slack](https://docs.everruns.com/how-to/publish-to-slack/)
* [Retired Apps compatibility](https://docs.everruns.com/features/apps/)
---
# Sprites
> Sprites persistent Firecracker microVMs for code execution, with filesystem persistence, checkpoints, and HTTP services.
Source:
Everruns integrates with [Sprites](https://sprites.dev/) to provide persistent, hardware-isolated Linux microVMs powered by Firecracker. Unlike ephemeral sandboxes, Sprites maintain their filesystem across idle periods, support instant checkpoint/restore, and expose public HTTP endpoints.
## What You Get
* **Persistent Filesystem**: Full ext4 filesystem survives between sessions, backed to durable object storage
* **Hardware Isolation**: Firecracker VM-level isolation (stronger than containers)
* **Checkpoints**: Snapshot filesystem state in \~300ms for safe rollback before risky operations
* **HTTP Services**: Each sprite gets a unique public URL for exposing web services
* **Instant Wake**: Sprites wake from hibernation in <1 second
* **Multi-Sprite Sessions**: Create and manage multiple sprites per session
## Quick Start
### 1. Get Your API Token
1. Install the Sprites CLI: `curl https://sprites.dev/install.sh | bash`
2. Run `sprite login` to authenticate
3. Copy your token from the CLI output or dashboard
### 2. Connect in Everruns
1. Go to **Settings** > **Connections**
2. Find **Sprites** in the available providers
3. Click **Connect** and paste your API token
Once connected, the Sprites capability is automatically available in agent sessions.
Sprites default to `/home/sprite` as the working directory for commands and file paths.
### 3. Use in Sessions
Agents with the Sprites capability can use these tools:
| Tool | Description |
| ---------------------------- | ---------------------------------------------------- |
| `sprites_create_sprite` | Create a new Firecracker microVM |
| `sprites_exec` | Execute shell commands (wakes sprite if hibernating) |
| `sprites_read_file` | Read files from sprite filesystem |
| `sprites_write_file` | Write files to sprite filesystem |
| `sprites_list_sprites` | List sprites in this session |
| `sprites_manage_sprite` | Delete sprites |
| `sprites_checkpoint` | Create a filesystem checkpoint |
| `sprites_restore_checkpoint` | Restore to a previous checkpoint |
| `sprites_service_url` | Get the public HTTP URL for a sprite |
## Checkpoints
Sprites support instant filesystem checkpointing, a unique capability not found in other sandbox providers:
1. **Before risky operations**: Call `sprites_checkpoint` to snapshot the current state
2. **If something goes wrong**: Call `sprites_restore_checkpoint` to roll back
3. **Checkpoints are fast**: \~300ms without interrupting the running sprite
This makes Sprites ideal for iterative development where agents need to experiment safely.
## HTTP Services
Each sprite gets a unique public URL. To expose a web service:
1. Start a web server inside the sprite listening on **port 8080**
2. Call `sprites_service_url` to get the public URL
3. Share the URL for testing or preview
## Sprite Lifecycle

* **Running**: Active, consuming compute resources
* **Hibernating**: Idle, no compute charges, filesystem preserved on durable storage
* **Deleted**: Permanently removed, all data lost
Sprites persist indefinitely until explicitly deleted. They hibernate automatically when idle (no compute charges while idle). Always delete sprites when done to avoid storage charges.
## Pricing
Sprites bill per-second for compute and per-GB-hour for storage:
* **CPU**: $0.07/CPU-hour
* **Memory**: $0.04375/GB-hour
* **Storage**: $0.000027/GB-hour (persistent), $0.000683/GB-hour (hot NVMe cache)
* **Idle**: No compute charges (filesystem still persisted)
New users receive $30 trial credits (\~500 sprite sessions).
## Security
* **Firecracker VMs**: Hardware-level isolation between sprites
* **L3 Network Policies**: Domain whitelisting for outbound connections
* **Encrypted Credentials**: API token stored in user connections (encrypted at rest)
* **Leased Resources**: Sprites registered for automatic cleanup on session end
---
# TypeSafe
> Typed decision from TypeSafe's System One model: calibrated probabilities, single-choice routing, and graded scores. Requires a TypeSafe API key.
Source:
Everruns integrates with [TypeSafe](https://typesafe.ai) so agents can ask for a **judgment** rather than an opinion. TypeSafe’s System One model answers typed questions about content and returns numbers your agent — and your code — can act on directly: the probability that something is true, which option out of a set applies, or where something falls on a scale you define.
It does not write prose. That is the point: there is no answer to interpret, and no JSON to parse out of a paragraph.
## What You Get
* **Yes/no with a probability**: “Is a refund being requested?” → `0.97`, not “Yes, it appears so.”
* **Single choice with a distribution**: pick one option and see how close the runners-up were
* **Graded scores**: rate against ordered levels you write, with the probability of each level
* **Confidence**: how concentrated the answer is, so the agent can escalate instead of guessing
* **One call, many questions**: every question in a call is answered together over the same content
## Quick Start
### 1. Get Your API Key
1. Sign in at [typesafe.ai](https://typesafe.ai)
2. Create an **API key** in the dashboard
3. Copy it
### 2. Connect in Everruns
1. Go to **Settings** > **Connections**
2. Find **TypeSafe** in the available providers
3. Click **Connect** and paste your API key
Once connected, the TypeSafe capability is available in agent sessions.
### 3. Use in Sessions
Agents with the TypeSafe capability get one tool:
| Tool | Description |
| -------------- | ------------------------------------------------------------ |
| `jev_decision` | Ask typed questions about content and get calibrated answers |
A call gives it the content plus the questions to ask about it:
```json
{
"state": "Why did the chicken cross the road? To get to the other side.",
"questions": [
{
"id": "is_funny",
"type": "noul",
"instructions": "Would a general audience laugh at this?"
},
{
"id": "humor",
"type": "score",
"instructions": "How funny is this joke?",
"levels": ["Not funny at all", "Mildly amusing", "Genuinely funny", "Hilarious"]
}
]
}
```
And gets back numbers, not a review:
```json
{
"model": "jev-1.13.0",
"answers": {
"is_funny": { "type": "noul", "probability_yes": 0.43 },
"humor": {
"type": "score",
"score": 0.58,
"normalized": 0.19,
"level": 1,
"label": "Mildly amusing",
"probabilities": { "0": 0.44, "1": 0.55, "2": 0.01, "3": 0.0 },
"confidence": 0.57
}
}
}
```
## Question Types
| Type | Ask it when | You get |
| ------------------------------------------------------ | --------------------------------------- | ---------------------------------------------------------------------------------- |
| [`noul`](https://docs.typesafe.ai/primitives/noul) | A condition either holds or it doesn’t | The probability of yes, from 0 to 1 |
| [`choice`](https://docs.typesafe.ai/primitives/choice) | Exactly one option out of a set applies | The selected option, every option’s probability, and a confidence |
| [`score`](https://docs.typesafe.ai/primitives/score) | Something falls somewhere on a scale | A weighted position across your levels, each level’s probability, and a confidence |
These are System One’s own primitives, kept under the same names here; TypeSafe documents them in full under [Primitives](https://docs.typesafe.ai/primitives).
Two things worth knowing when you write the questions:
* A `noul` near **0.5** means yes and no are roughly equally likely. It does not mean “somewhat” — for degree, use a `score`.
* `choice` options and `score` levels must each describe a concrete situation and stand on their own. The question id is never shown to the model, so the instructions have to carry the whole meaning.
The `confidence` on a `choice` or `score` is a second axis, not a restatement of the probability: the answer tells you *what*, confidence tells you *whether to act*. See [Confidence](https://docs.typesafe.ai/confidence), and [confidence-gated routing](https://docs.typesafe.ai/patterns/confidence-routing) for the pattern it enables.
## Good Fits
* **Verification**: does this answer actually follow from the source it cites?
* **Rating**: how severe is this report, how good is this draft, how funny is this joke
* **Routing**: which handler, team, or tool should take this — with a confidence to gate on
* **Screening**: does this content match a policy, and how clearly
## Embedding the Framework
Running the Everruns Framework in your own application rather than on the platform? The same capability attaches to an agent you build yourself, and the decisions is also callable directly with no agent at all. See [Direct decision](https://docs.everruns.com/framework/direct-decisions/).
## Guardrails
The same model backs Everruns [guardrails](https://docs.everruns.com/capabilities/guardrails/) when a `llm_judge` or `moderation` check sets `"engine": "jev"`. Instead of asking the utility model to write a verdict, the check gets a calibrated probability and your configured `threshold` decides — and every check on a stage is answered in a single call. That path uses a deployment-owned key (`UTILITY_TYPESAFE_API_KEY`), not your personal connection.
## Security
* The API key is stored as a user connection and never exposed to the agent or written into session transcripts.
* Content passed to `jev_decision` leaves the platform for TypeSafe, like any other integration that inspects content. Calls are capped at 20 questions and 32 KiB of content.
* The content being judged is sent as **data**, and every question states so — a document that tries to instruct the model is being rated, not obeyed.
## Learn more
The model and its concepts are TypeSafe’s, and their documentation is the reference for both:
* [System One](https://docs.typesafe.ai/concepts/system-one) — the class of model, and how it differs from an LLM
* [Primitives](https://docs.typesafe.ai/primitives) — [Noul](https://docs.typesafe.ai/primitives/noul), [Choice](https://docs.typesafe.ai/primitives/choice), [Score](https://docs.typesafe.ai/primitives/score), and [structured criteria](https://docs.typesafe.ai/primitives/advanced)
* [State](https://docs.typesafe.ai/concepts/state) — what to send as the thing being judged
* [Confidence](https://docs.typesafe.ai/confidence) — certainty as a second axis, distinct from the probability
* [Patterns](https://docs.typesafe.ai/patterns) — including [speculative fan-out](https://docs.typesafe.ai/patterns/fan-out) and [confidence-gated routing](https://docs.typesafe.ai/patterns/confidence-routing)
---
# Observability
> Send Everruns session traces, token usage, and tool-call timings to your observability platform of choice.
Source:
Everruns emits structured events for every agent turn, model calls, tool invocations, retries, token usage, latency. The integrations in this section forward those signals to observability platforms so you can monitor agents in production, evaluate prompt changes, and debug failures with full trace context.
## Available Integrations
* [OpenTelemetry](https://docs.everruns.com/observability/opentelemetry/), export traces over OTLP to any tracing backend. Spans follow the Gen-AI semantic conventions and the OpenInference conventions at once, so Grafana Tempo, Jaeger, Datadog, Langfuse, and Arize Phoenix all read them.
* [Braintrust](https://docs.everruns.com/observability/braintrust/), LLM observability, evaluation, and trace visualization. Turn traces are grouped by session, with token usage, time-to-first-token, and tool execution times.
## Related
* [Events](https://docs.everruns.com/features/events/), the streaming event protocol that backs every observability export.
* [Environment Variables](https://docs.everruns.com/sre/environment-variables/), configure exporters, sampling, and OTLP endpoints.
---
# Braintrust
> Send Everruns traces to Braintrust for evaluation and trace visualization.
Source:

Preview
This integration is in preview. APIs and behavior may change.
Everruns integrates with [Braintrust](https://www.braintrust.dev/) to provide LLM observability, evaluation, and trace visualization for your agentic workflows.
## What You Get
* **Turn Traces Grouped by Session**: Keep one trace per turn while grouping the conversation by `metadata.session_id`
* **Token Usage Tracking**: Monitor input/output tokens and prompt cache efficiency
* **Performance Metrics**: Time-to-first-token, LLM call duration, tool execution times
* **Durable-ish Delivery**: Buffered batch delivery with retries for rate limits, `5xx`, and timeout/connect failures
* **Privacy Controls**: Raw content, thinking, tool args, and tool results are independently configurable
## Quick Start
### 1. Get Your API Key
1. Sign up at [braintrust.dev](https://www.braintrust.dev/)
2. Go to **Settings** → **API Keys**
3. Create a new API key
### 2. Configure Everruns
Set environment variables:
```bash
# Optional explicit switch
export BRAINTRUST_ENABLED=true
# Required
export BRAINTRUST_API_KEY=sk-bt-your-api-key
# Recommended: specify your project name
export BRAINTRUST_PROJECT_NAME="My Project"
# Conservative defaults
export BRAINTRUST_RECORD_CONTENT=false
export BRAINTRUST_RECORD_THINKING=none
export BRAINTRUST_TOOL_ARGS_MODE=redacted
export BRAINTRUST_TOOL_RESULTS_MODE=summary
```
| Variable | Required | Default | Description |
| -------------------------------- | -------- | ------------------------------- | --------------------------------------------------------------- |
| `BRAINTRUST_ENABLED` | No | enabled when API key is present | Explicit Braintrust on/off switch |
| `BRAINTRUST_API_KEY` | Yes | - | API key from Braintrust settings |
| `BRAINTRUST_PROJECT_NAME` | No | `My Project` | Project name for organizing traces |
| `BRAINTRUST_PROJECT_ID` | No | - | Direct project UUID (skips name lookup) |
| `BRAINTRUST_API_URL` | No | `https://api.braintrust.dev` | API base URL |
| `BRAINTRUST_QUEUE_CAPACITY` | No | `1024` | Buffered event capacity before new exports are dropped |
| `BRAINTRUST_MAX_BATCH_SIZE` | No | `50` | Max events per Braintrust insert call |
| `BRAINTRUST_FLUSH_INTERVAL_MS` | No | `500` | Max delay before a partial batch flushes |
| `BRAINTRUST_REQUEST_TIMEOUT_MS` | No | `10000` | Per-request timeout |
| `BRAINTRUST_MAX_RETRIES` | No | `3` | Retries for `429`, `5xx`, and timeout/connect failures |
| `BRAINTRUST_RETRY_BASE_DELAY_MS` | No | `250` | Initial retry backoff |
| `BRAINTRUST_RETRY_MAX_DELAY_MS` | No | `5000` | Retry backoff cap |
| `BRAINTRUST_RECORD_CONTENT` | No | `false` | Export raw turn and LLM text content |
| `BRAINTRUST_RECORD_THINKING` | No | `none` | Export thinking as `none`, `summary`, or `full` |
| `BRAINTRUST_TOOL_ARGS_MODE` | No | `redacted` | Export tool args as `full`, `redacted`, or `none` |
| `BRAINTRUST_TOOL_RESULTS_MODE` | No | `summary` | Export tool results as `full`, `summary`, `redacted`, or `none` |
| `BRAINTRUST_DEBUG_PAYLOADS` | No | `false` | Print full outbound Braintrust payload JSON to local debug logs |
### 3. View Traces
1. Open the Braintrust dashboard
2. Navigate to your project
3. Go to **Logs**
4. Group or filter by `metadata.session_id` to reconstruct the full session timeline across turn traces
## Trace Hierarchy
Each Everruns turn creates its own trace with the following structure:
```plaintext
agent turn (root span)
├── reason (iteration 1)
│ └── llm.generation (gpt-5.2)
├── act (iteration 1)
│ ├── tool.call (search)
│ └── tool.call (fetch)
├── reason (iteration 2)
│ └── llm.generation (gpt-5.2)
└── (no more tool calls - turn complete)
```
### Span Types
| Span | Type | Description |
| -------------- | ------ | ------------------------------------- |
| Agent Turn | `task` | Root span for the entire user request |
| Reason | `task` | LLM reasoning phase (may iterate) |
| Act | `task` | Tool execution phase |
| LLM Generation | `llm` | Individual LLM API call |
| Tool Call | `tool` | Individual tool execution |
## Session Grouping
Everruns does not export one giant trace for the whole conversation.
* Each turn remains its own Braintrust trace.
* Every root turn span carries `metadata.session_id`.
* Session lifecycle events (`session.started`, `session.activated`, `session.idled`) are exported as lightweight logs with the same `session_id`.
* Root turn metadata also carries stable filtering fields when available, such as `input_message_id`, monotonic event ordering, deployment grade, session status, model/provider summary, retry info, and compaction info.
Use Braintrust grouping, timeline, or thread views on `metadata.session_id` to analyze the session as a whole while keeping per-turn debugging sharp.
## Metrics Captured
### LLM Generations
* `prompt_tokens` - Input token count
* `completion_tokens` - Output token count
* `cache_read_tokens` - Tokens read from prompt cache (Claude)
* `cache_creation_tokens` - Tokens written to prompt cache (Claude)
* `time_to_first_token` - Time until first token received
* `duration_ms` - Total LLM call duration
### Tool Calls
* `status` - Success/failure
* `duration_ms` - Execution time
* `error` - Error message (on failure)
## Delivery Behavior
* Exports enqueue into a bounded in-memory buffer.
* The exporter flushes batches to `POST /v1/project_logs/{project_id}/insert`.
* `429`, `5xx`, timeout, and connect failures are retried with jittered backoff.
* If the queue fills, new events are dropped and the exporter logs the drop counter.
This is best-effort durability, not a disk-backed queue.
## Privacy Controls
The Braintrust exporter defaults to conservative content handling:
* raw turn and LLM text are off unless `BRAINTRUST_RECORD_CONTENT=true`
* when raw content is off, the exporter emits structural metadata only; it does not emit truncated prompt/completion previews
* extended thinking is off unless `BRAINTRUST_RECORD_THINKING` says otherwise
* tool arguments default to `redacted`
* tool results default to `summary`
* tool arg/result modes still apply inside recorded LLM input/output payloads
* full outbound payload logging is off unless `BRAINTRUST_DEBUG_PAYLOADS=true`
## Troubleshooting
### Traces Not Appearing
1. **Check API key**: Verify `BRAINTRUST_API_KEY` is set correctly
2. **Check project resolution**: If `BRAINTRUST_PROJECT_NAME` does not match an existing project, startup logs will show a project resolution failure
3. **Check exporter logs**: Look for rate-limit retries, timeout retries, queue drops, or permanent insert failures
### Session Views Are Fragmented
1. Confirm root turn spans include `metadata.session_id`
2. Group Braintrust logs by `metadata.session_id`
3. Check whether privacy controls removed content you expected; the default is conservative
## Links
* [Braintrust Documentation](https://www.braintrust.dev/docs)
* [API Reference](https://www.braintrust.dev/docs/api-reference/introduction)
* [Insert Logs API](https://www.braintrust.dev/docs/api-reference/logs/insert-project-logs-events)
---
# OpenTelemetry
> Export Everruns agent traces over OTLP. Spans follow the OpenTelemetry Gen-AI and OpenInference conventions, so any tracing backend reads them.
Source:
Everruns turns every agent run into an OpenTelemetry trace and exports it over OTLP. Spans carry two attribute vocabularies at once, the [OpenTelemetry Gen-AI semantic conventions](https://github.com/open-telemetry/semantic-conventions-genai/tree/main/docs/gen-ai) and the [OpenInference conventions](https://arize-ai.github.io/openinference/spec/semantic_conventions.html), so one endpoint feeds general-purpose backends such as Grafana Tempo, Jaeger, and Datadog as well as LLM-native ones such as Arize Phoenix and Langfuse.
## What You Get
* **A trace per turn**: an `invoke_agent` root span named after your agent, with model calls, tool runs, and reasoning phases nested underneath
* **Real timings**: spans start and end at the moment each event happened, so waterfalls show true model latency and tool duration
* **Token and cost detail**: input, output, and prompt-cache tokens per call and per turn, plus cost where the provider reports it
* **Tool visibility**: every tool call is its own span with name, description, call id, and outcome
* **Failure detail**: a low-cardinality `error.type`, an error span status carrying the message, and an `exception` event
* **Privacy by default**: prompts, completions, reasoning, and tool payloads are never exported unless you turn them on
## Quick Start
### 1. Point Everruns at a collector
Set one variable. Any OTLP/HTTP endpoint works.
```bash
# Local collector, Grafana Tempo, Datadog agent, ...
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
# Name the service in your traces
export OTEL_SERVICE_NAME=everruns-server
```
Traces are exported over OTLP HTTP/protobuf. Point the variable at the base endpoint, usually port `4318`, and Everruns appends the `/v1/traces` path; a full signal URL is used as given.
### 2. Configure
| Variable | Required | Default | Description |
| ---------------------------------------------------- | -------- | ------------------------------------ | --------------------------------------------------------- |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | Yes | - | OTLP/HTTP endpoint. Tracing stays off while unset |
| `OTEL_SERVICE_NAME` | No | `everruns-server`, `everruns-worker` | Service name on the spans |
| `OTEL_SERVICE_VERSION` | No | - | Service version on the spans |
| `OTEL_ENVIRONMENT` | No | - | Deployment environment label |
| `OTEL_SDK_DISABLED` | No | `false` | Disable tracing without unsetting the endpoint |
| `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` | No | `false` | Export instructions, messages, tool arguments and results |
| `EVERRUNS_TRACE_CONVENTIONS` | No | `gen_ai,openinference` | Which attribute vocabularies to write |
### 3. View traces
Open your backend and look for the `invoke_agent` spans. In Arize Phoenix, point the same variable at Phoenix and its spans appear as AGENT, LLM, and TOOL rows with no extra configuration:
```bash
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:6006
```
## Trace Structure
Each turn is one trace. Reasoning and acting phases give the trace its shape, and extended thinking sits inside the model call it belongs to:
```plaintext
invoke_agent {agent name} (root, INTERNAL)
├── reason reasoning phase
│ └── chat {model} model call (CLIENT)
│ └── thinking extended thinking, when enabled
├── act tool execution phase
│ ├── execute_tool {name}
│ └── execute_tool {name}
├── reason
│ └── chat {model}
└── (no further tool calls, turn complete)
```
| Span | Kind | `gen_ai.operation.name` | `openinference.span.kind` |
| --------------------------- | ---------- | ----------------------- | ------------------------- |
| `invoke_agent {agent name}` | `INTERNAL` | `invoke_agent` | `AGENT` |
| `chat {model}` | `CLIENT` | `chat` | `LLM` |
| `execute_tool {name}` | `INTERNAL` | `execute_tool` | `TOOL` |
| `reason`, `act`, `thinking` | `INTERNAL` | none | `CHAIN` |
The reason, act, and thinking spans are Everruns phases rather than Gen-AI operations, so they carry no `gen_ai.operation.name` and backends do not count them as model calls.
## OpenTelemetry Gen-AI Support
| | Supported | Attributes |
| - | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ✅ | Agent span per turn | `gen_ai.agent.id`, `gen_ai.agent.name`, `gen_ai.agent.description` |
| ✅ | Model call spans | `chat {model}`, CLIENT kind, real call duration |
| ✅ | Provider and model identity | `gen_ai.provider.name`, `gen_ai.request.model`, `gen_ai.response.model`, `gen_ai.response.id` |
| ✅ | Finish reasons | `gen_ai.response.finish_reasons` as a string array |
| ✅ | Token usage with cache split | `gen_ai.usage.input_tokens`, `output_tokens`, `cache_read.input_tokens`, `cache_write.input_tokens` |
| ✅ | Request parameters | `gen_ai.request.temperature`, `max_tokens`, `reasoning.level`, `stream` |
| ✅ | Streaming latency and compaction | `gen_ai.response.time_to_first_chunk`, `gen_ai.conversation.compacted` |
| ✅ | Tool execution spans | `gen_ai.tool.name`, `gen_ai.tool.type`, `gen_ai.tool.call.id`, `gen_ai.tool.description` |
| ✅ | Extended thinking | Nested inside the model call it belongs to |
| ✅ | Errors | `error.type`, error span status, `exception` events |
| ✅ | Conversation correlation | `gen_ai.conversation.id` on every span |
| ✅ | Content capture (opt-in) | `gen_ai.system_instructions`, `gen_ai.input.messages`, `gen_ai.output.messages`, `gen_ai.tool.definitions`, `gen_ai.tool.call.arguments`, `gen_ai.tool.call.result` |
| ✅ | Accurate timestamps | Spans start and end at the times the events record |
Not emitted yet: `server.address` and `server.port` on model calls, and parameter schemas inside `gen_ai.tool.definitions`.
## OpenInference Support
| | Supported | Attributes |
| - | -------------------------------- | ---------------------------------------------------------------------------------------- |
| ✅ | Span kinds | `openinference.span.kind`: `AGENT`, `CHAIN`, `LLM`, `TOOL` |
| ✅ | Session and agent identity | `session.id`, `agent.name`, `metadata` |
| ✅ | Model identity | `llm.model_name`, `llm.provider`, `llm.system` |
| ✅ | Token counts | `llm.token_count.prompt`, `.completion`, `.total` |
| ✅ | Prompt cache detail | `llm.token_count.prompt_details.cache_read`, `.cache_write` |
| ✅ | Cost | `llm.cost.total` in USD, when the provider reports it |
| ✅ | Invocation parameters and tools | `llm.invocation_parameters`, `llm.tools.N.tool.json_schema` |
| ✅ | Input and output values (opt-in) | `input.value`, `output.value`, with `input.mime_type` and `output.mime_type` |
| ✅ | Flattened messages (opt-in) | `llm.input_messages.N.message.*`, `llm.output_messages.N.message.*`, tool calls included |
| ✅ | Tool spans | `tool.name`, `tool.description`, arguments and results as input and output values |
| ✅ | Errors | Error span status with `exception` events |
| ✅ | Phoenix out of the box | Point the OTLP endpoint at Phoenix, nothing else to configure |
## Everruns Attributes
Spans also carry a few Everruns-specific attributes under their own namespace, so they never collide with either convention:
| Attribute | Spans | Description |
| ------------------------------------------------------------------------------------------------------------------- | --------------------------- | ----------------------------------------------------------------------------------------- |
| `everruns.turn.id`, `everruns.exec.id`, `everruns.input_message.id` | All | Correlation ids that match the [event stream](https://docs.everruns.com/features/events/) |
| `everruns.phase` | `reason`, `act`, `thinking` | Which phase the span represents |
| `everruns.turn.iterations`, `everruns.turn.tool_call_count`, `everruns.turn.llm_call_count`, `everruns.turn.status` | `invoke_agent` | Turn counters and outcome |
| `everruns.tool.status`, `everruns.tool.capability.id` | `execute_tool` | Tool outcome and the capability that provided it |
| `everruns.usage.cost_usd` | `chat`, `invoke_agent` | Cost when known |
| `everruns.llm.retry.attempts`, `everruns.llm.retry.total_wait_ms` | `chat` | Provider retries behind a single call |
| `everruns.span.orphaned`, `everruns.span.unterminated` | Any | Diagnostics: a span rebuilt from a terminal event, or closed because its turn ended first |
## Choosing Conventions
Both vocabularies are written by default. Narrow to one when a backend only reads one and you want smaller spans:
```bash
# OpenTelemetry Gen-AI only (Tempo, Jaeger, Datadog, Langfuse)
export EVERRUNS_TRACE_CONVENTIONS=gen_ai
# OpenInference only (Arize Phoenix)
export EVERRUNS_TRACE_CONVENTIONS=openinference
```
Span names, kinds, and hierarchy are the same either way; only the attributes differ. An unrecognized value falls back to writing both.
## Content Capture
Prompts, completions, reasoning, and tool payloads are **not** exported by default. Turn them on with the standard OpenTelemetry variable:
```bash
export OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true
```
With capture on:
* model call spans carry `gen_ai.system_instructions`, `gen_ai.input.messages`, `gen_ai.output.messages`, and `gen_ai.tool.definitions` in the conventions’ `{role, parts}` JSON, plus the OpenInference `input.value` and `output.value` and the flattened `llm.input_messages.N.message.*` keys
* tool spans carry their arguments and results
* the turn root carries the user’s message and the final answer
* the thinking span carries the model’s reasoning text
Two details worth knowing. Everruns keeps the agent’s instructions separate from the conversation, so system messages are exported as `gen_ai.system_instructions` and left out of `gen_ai.input.messages`. Image bytes are never copied into a span; an image becomes a reference that names its media type only.
Treat this as a data-retention decision. Everything captured leaves your deployment for whatever backend the OTLP endpoint points at.
## Troubleshooting
### No traces appear
1. Confirm `OTEL_EXPORTER_OTLP_ENDPOINT` is set and reachable from the server and worker processes; without it, tracing is off and startup logs say so
2. Confirm the endpoint speaks OTLP over **HTTP**, typically port `4318`, not the gRPC port `4317`
3. Check startup logs for `OpenTelemetry tracing enabled` with your endpoint, or a warning that the exporter failed to initialize
4. Confirm `OTEL_SDK_DISABLED` is not set to `true`
### Traces appear but spans look empty
1. Prompts and completions require `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true`; without it, spans carry structure and metrics only, which is the default
2. If your backend reads only one vocabulary, check `EVERRUNS_TRACE_CONVENTIONS` has not been narrowed to the other one
### Turns are split across traces
Each turn is deliberately its own trace. Group by `gen_ai.conversation.id`, or `session.id` in Phoenix, to follow a whole session.
## Related
* [Braintrust](https://docs.everruns.com/observability/braintrust/), LLM observability and evaluation with a dedicated exporter
* [Events](https://docs.everruns.com/features/events/), the event stream every exporter is built on
* [Environment Variables](https://docs.everruns.com/sre/environment-variables/), the full operator reference
---
# Model Providers
> Connect Everruns to OpenAI, Anthropic, Google Gemini, Meta Model API, AWS Bedrock, OpenRouter, Fireworks AI, and more.
Source:
A **provider** is an organization-scoped account on an AI model vendor, OpenAI, Anthropic, AWS Bedrock, OpenRouter, and others. You configure a provider once with credentials and connection settings, and it powers the models your agents run on. Everruns abstracts every vendor behind one uniform driver interface, so the same agent, prompt, and capabilities run unchanged whether the model is served by OpenAI, Claude, Gemini, or any OpenAI-compatible endpoint.
## Supported providers
| Provider | Driver | Notes |
| ----------------------------------------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
| [OpenAI](https://docs.everruns.com/providers/openai/) | `openai`, `openai_completions` | Responses API (recommended) and Chat Completions. Also drives OpenAI-compatible endpoints via a base URL. |
| [Azure OpenAI](https://docs.everruns.com/providers/azure-openai/) | `azure_openai` | OpenAI models deployed in your Azure OpenAI resource. A dedicated provider type. |
| [Anthropic](https://docs.everruns.com/providers/anthropic/) | `anthropic` | Claude models via the Messages API, with extended thinking. |
| [Google Gemini](https://docs.everruns.com/providers/gemini/) | `gemini` | Gemini models, with implicit and explicit context caching. |
| [AWS Bedrock](https://docs.everruns.com/providers/bedrock/) | `bedrock` | Models hosted on Amazon Bedrock via the `ConverseStream` API. |
| [OpenRouter](https://docs.everruns.com/providers/openrouter/) | `openrouter` | One key for a large multi-vendor catalog, with provider routing controls. |
| [Microsoft MAI](https://docs.everruns.com/providers/mai/) | `mai` | Microsoft MAI models via Azure AI Foundry, with API-key or Entra ID (OAuth) auth. |
| [Fireworks AI](https://docs.everruns.com/providers/fireworks/) | `fireworks` | Fast, low-cost inference for open models (Llama, Qwen, DeepSeek, Kimi, GLM, gpt-oss, …), with automatic model discovery. |
| [Meta Model API](https://docs.everruns.com/providers/meta/) | `meta` | Muse Spark through Meta’s stateful, OpenAI-compatible Responses API. |
Need a vendor that isn’t listed? Any OpenAI-compatible endpoint works through the [OpenAI](https://docs.everruns.com/providers/openai/) provider with a custom base URL (use the dedicated [Azure OpenAI](https://docs.everruns.com/providers/azure-openai/) provider for Azure deployments), and embedders can register additional drivers through the platform definition.
## Configure a provider
Providers are managed by organization admins:
1. Go to **Settings** → **Providers**.
2. Click **Add provider** and choose the provider type.
3. Enter the credentials (API key, or the provider-specific fields described on each provider’s page). Credentials are validated before they are saved.
4. Save. Everruns discovers the provider’s available models and makes them selectable for agents and sessions.
You can configure more than one provider for the same vendor, for example two Azure OpenAI regions, or a direct OpenAI key alongside an OpenRouter key.
## Providers vs. connections
Providers and [connections](https://docs.everruns.com/integrations/) look similar but are deliberately separate:
| | **Provider** | **Connection** |
| ------------- | ------------------------------- | ------------------------------------------------------- |
| Scope | Organization | User |
| Purpose | Infrastructure that runs agents | A user’s identity on an external service, used by tools |
| Configured in | Settings → Providers | Settings → Connections |
| Examples | OpenAI, Anthropic, Bedrock | Daytona, GitHub, Slack |
Use a provider to decide **which model runs your agents**. Use a connection to give an agent access to an **external tool or service**.
## Models and switching providers
Agents and sessions bind to a specific model on a specific provider. Because the driver interface is uniform, you can move an agent from one provider to another without rewriting prompts or capabilities. See [Migrate between providers](https://docs.everruns.com/how-to/migrate-providers/) for the model-resolution rules and the API calls to add a provider, switch an agent’s default model, or run an A/B comparison per session.
## Credential security
* Credentials are encrypted at rest with AES-256-GCM envelope encryption.
* Credential values are never returned by the API, only a “configured” flag is exposed.
* Resolution is **fail-closed**: each organization resolves its own configured credentials. A turn with no configured provider fails with a clear error rather than running on another organization’s credentials.
---
# Anthropic
> Run Everruns agents on Anthropic Claude models via the Messages API, with streaming, tool use, and extended thinking.
Source:
Everruns runs agents on [Anthropic](https://www.anthropic.com/) Claude models through the Claude Messages API, mapping its provider-neutral messages, tools, and reasoning onto the Anthropic wire format.
## What you get
* **Claude Messages API** streaming.
* **Tool use** mapped to provider-neutral Everruns tools.
* **Extended thinking**: adaptive thinking on recent Claude families and budget-based thinking on older ones, with the chain-of-thought signature preserved across multi-turn conversations.
* **Prompt caching** via bounded `cache_control` breakpoints on stable, high-value sections of the request.
## Configure in Everruns
1. Go to **Settings** → **Providers** and click **Add provider**.
2. Choose **Anthropic**.
3. Paste your Anthropic API key. Get one from the [Anthropic Console](https://console.anthropic.com/).
4. Save. Everruns discovers available Claude models automatically.
## Models
Anthropic’s `/v1/models` endpoint returns capability metadata, which Everruns merges with its built-in model profiles. Hardcoded profiles take precedence for cost data; discovered data fills gaps for newer models.
`max_tokens` is required on every Anthropic request, so Everruns always resolves a value from the model profile (falling back to a safe default) and will retry once with a lower limit if a stale profile causes the provider to reject it.
Thinking counts toward `max_tokens`. When you set `max_tokens` yourself, Everruns treats it as the budget for the visible answer and adds room for thinking on top, so a small limit does not come back empty.
Claude models that always think (Opus 5.5, Fable 5.x) always get an explicit effort: the model’s default when you choose none, and `low` when you choose `none`. These and the other adaptive-thinking models reject assistant prefill, so a conversation must end with a user or tool message.
## Links
* [Anthropic](https://www.anthropic.com/)
* [Anthropic Console](https://console.anthropic.com/)
* [`everruns-anthropic` on crates.io](https://crates.io/crates/everruns-anthropic)
* [Migrate between providers](https://docs.everruns.com/how-to/migrate-providers/)
---
# Azure OpenAI
> Run Everruns agents on OpenAI models deployed in Azure OpenAI, using a dedicated provider type with your resource endpoint and key.
Source:
[Azure OpenAI](https://azure.microsoft.com/products/ai-services/openai-service) serves OpenAI models from your own Azure resource. Everruns ships a dedicated `azure_openai` provider type, distinct from the [OpenAI](https://docs.everruns.com/providers/openai/) provider, so Azure deployments resolve with the right endpoint and model behavior rather than being configured as a generic OpenAI base-URL override.
## What you get
* **Azure OpenAI Responses API** through your Azure resource endpoint.
* **Stateful continuation and context compaction**: Azure OpenAI hosts are recognized as stateful, like `api.openai.com`.
* **Streaming, tool calls, and reasoning** mapped to provider-neutral Everruns types.
## Configure in Everruns
1. Go to **Settings** → **Providers** and click **Add provider**.
2. Choose **Azure OpenAI** (not plain OpenAI).
3. Set the **base URL** to your Azure OpenAI resource endpoint.
4. Paste the API key for your Azure OpenAI resource.
5. Save. Note that Azure model availability depends on the deployments you have created in your resource.
## Models
Azure deployment names are operator-chosen, so a deployment whose name does not match a known model profile falls back to a minimal profile. Capability and cost metadata for recognized models come from Everruns’ built-in model profiles.
## Links
* [Azure OpenAI Service](https://azure.microsoft.com/products/ai-services/openai-service)
* [Azure AI Foundry](https://ai.azure.com/)
* [Migrate between providers](https://docs.everruns.com/how-to/migrate-providers/)
---
# AWS Bedrock
> Run Everruns agents on models hosted in Amazon Bedrock via the ConverseStream API, with AWS credential and region resolution.
Source:
Everruns runs agents on models hosted in [Amazon Bedrock](https://aws.amazon.com/bedrock/) through the Bedrock Runtime `ConverseStream` API, mapping its provider-neutral messages, tools, and reasoning onto the Bedrock wire format.
## What you get
* **Bedrock Runtime `ConverseStream`** streaming.
* **Tool calls and reasoning** mapped to provider-neutral Everruns types.
* **AWS credential and region** resolution from explicit fields.
## Configure in Everruns
1. Go to **Settings** → **Providers** and click **Add provider**.
2. Choose **AWS Bedrock**.
3. Enter your AWS credentials as separate fields:
* **Access key ID**
* **Secret access key**
* **Region** (e.g. `us-east-1`)
* **Session token** (optional, for temporary credentials)
4. Save. Make sure the models you want are enabled in your Bedrock account.
Unlike most providers, Bedrock uses a multi-field credential rather than a single API key, so each field has its own input.
## Models
Enable the model access you need in the [Amazon Bedrock console](https://console.aws.amazon.com/bedrock/) first. Everruns resolves Bedrock model ids to its built-in model profiles for capability and cost metadata.
## Links
* [Amazon Bedrock](https://aws.amazon.com/bedrock/)
* [Amazon Bedrock console](https://console.aws.amazon.com/bedrock/)
* [`everruns-bedrock` on crates.io](https://crates.io/crates/everruns-bedrock)
* [Migrate between providers](https://docs.everruns.com/how-to/migrate-providers/)
---
# Fireworks AI
> Run Everruns agents on open models hosted by Fireworks AI (Llama, Qwen, DeepSeek, Kimi, GLM, gpt-oss), with automatic model discovery.
Source:
Everruns runs agents on [Fireworks AI](https://fireworks.ai/) through its OpenAI-compatible Chat Completions API. Fireworks serves frontier **open models**: Llama, Qwen, DeepSeek, Kimi, GLM, gpt-oss, and more, on a fast, cost-efficient inference platform, so the same Everruns agent, prompt, and capabilities run unchanged on open weights.
## What you get
* **One key, many open models**: a single provider exposing Fireworks’ serverless model catalog.
* **Automatic model discovery**: Fireworks’ `/models` endpoint advertises rich metadata (chat, tool calling, image input, context window), which Everruns parses into capability profiles on sync, so tool and vision support surface correctly per model.
* **Full chat capabilities**: streaming, tool/function calling, vision, and structured output, through the same uniform driver as every other provider.
* **Host-gated discovery**: model sync runs only against Fireworks’ own host, so a custom proxy base URL is never probed.
## Configure in Everruns
1. Go to **Settings** → **Providers** and click **Add provider**.
2. Choose **Fireworks AI**.
3. Paste your Fireworks API key. Create one from the [Fireworks API keys page](https://fireworks.ai/account/api-keys).
4. Save. Everruns discovers available models and their capability profiles automatically.
You can optionally set a base URL to route through a proxy; leave it blank to use Fireworks’ hosted API (`https://api.fireworks.ai/inference/v1`).
## Models
Fireworks model ids are namespaced, for example `accounts/fireworks/models/llama-v3p1-70b-instruct`. After a sync, models appear in the agent and session model pickers with their discovered capabilities. Only chat models are imported, image and other non-chat endpoints are filtered out.
## Links
* [Fireworks AI](https://fireworks.ai/)
* [Fireworks docs](https://docs.fireworks.ai/)
* [`everruns-fireworks` on crates.io](https://crates.io/crates/everruns-fireworks)
* [Migrate between providers](https://docs.everruns.com/how-to/migrate-providers/)
---
# Google Gemini
> Run Everruns agents on Google Gemini models, with streaming, tool calls, reasoning, and context caching.
Source:
Everruns runs agents on [Google Gemini](https://ai.google.dev/) models, implementing the provider-neutral driver contract over the Gemini API.
## What you get
* **Gemini API** streaming.
* **Tool calls and reasoning** mapped to provider-neutral Everruns types.
* **Context caching**: explicit caching via `cachedContent` when a cached-content resource is supplied, otherwise Gemini’s default implicit caching on supported models.
## Configure in Everruns
1. Go to **Settings** → **Providers** and click **Add provider**.
2. Choose **Google Gemini**.
3. Paste your Gemini API key. Get one from [Google AI Studio](https://aistudio.google.com/apikey).
4. Save. Everruns discovers available Gemini models automatically.
## Models
Gemini models resolve to Everruns’ built-in model profiles for capability and cost metadata. When `max_tokens` is not set, the driver resolves a default from the model profile, falling back to a safe value.
## Links
* [Google AI for Developers](https://ai.google.dev/)
* [Google AI Studio](https://aistudio.google.com/)
* [`everruns-gemini` on crates.io](https://crates.io/crates/everruns-gemini)
* [Migrate between providers](https://docs.everruns.com/how-to/migrate-providers/)
---
# Microsoft MAI
> Run Everruns agents on Microsoft MAI models served via Azure AI Foundry, authenticated with an API key or Microsoft Entra ID (OAuth).
Source:
Everruns runs agents on Microsoft MAI models (for example `MAI-Code-1-Flash`), which are served via [Azure AI Foundry](https://ai.azure.com) behind an OpenAI-compatible Chat Completions API. The MAI provider exists as its own driver mainly because of its authentication options.
## What you get
* **OpenAI-compatible Chat Completions** streaming through Azure AI Foundry.
* **Two authentication schemes**: an Azure AI Foundry API key, or Microsoft Entra ID (OAuth) service-principal credentials with bearer tokens minted and refreshed automatically.
* **Model discovery** against Foundry’s `/models` endpoint where available, with capabilities supplied by Everruns’ built-in Microsoft model profiles.
## Configure in Everruns
1. Go to **Settings** → **Providers** and click **Add provider**.
2. Choose **Microsoft MAI**.
3. Set the **base URL** to your Azure AI Foundry resource (e.g. `https://.services.ai.azure.com`).
4. Provide credentials for one of the two methods, entered as discrete fields:
* **API key**: your Azure AI Foundry resource key.
* **Entra ID (OAuth)**: a client-credentials service principal: `tenant_id`, `client_id`, and `client_secret` (with optional `scope` and `authority`, which default to the Azure Cognitive Services scope and public Microsoft Entra authority).
5. Save.
Authentication is fail-closed: a stored credential is always required, and OAuth tokens are refreshed transparently for both chat execution and model sync.
## Models
Foundry’s `/models` listing is bare (ids only), so capabilities come from Everruns’ built-in Microsoft MAI model profiles, matched by id. Because Azure deployment names are operator-chosen, a deployment whose name does not match a known profile falls back to a minimal profile.
## Links
* [Azure AI Foundry](https://ai.azure.com/)
* [`everruns-mai` on crates.io](https://crates.io/crates/everruns-mai)
* [Migrate between providers](https://docs.everruns.com/how-to/migrate-providers/)
---
# Meta Model API
> Run Everruns agents on Muse Spark 1.3 through Meta Model API, including the discounted Contributor tier.
Source:
Everruns runs Muse models through [Meta Model API](https://dev.meta.ai/). The dedicated `meta` driver uses Meta’s OpenAI-compatible Responses API at `https://api.meta.ai/v1`, including streaming, parallel tool calls, reasoning replay, message phases, hosted tool search, and server-managed response history.
## Configure in Everruns
1. Create an API key in the [Meta Model API dashboard](https://dev.meta.ai/).
2. Go to **Settings** → **Providers** and click **Add provider**.
3. Choose **Meta Model API**, paste the key, and save.
4. Sync models to import the Muse models available to your team.
The hosted endpoint is used by default. An optional base URL can point the driver at a compatible proxy; model discovery is disabled for non-Meta hosts.
## Muse Spark 1.3 tiers
Muse Spark 1.3 is built for long-horizon coding and multi-step agentic work, with native tool calling and MCP support. Both 1.3 model IDs have a 1,048,576-token context window and accept text, images, audio, video, and PDFs while producing text.
| Model | Data use | Input / cached input / output per million tokens |
| ---------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------ |
| `muse-spark-1.3` | Prompts and completions are not used to train Meta models | $1.25 / $0.15 / $4.25 |
| `muse-spark-1.3-contributor` | Prompts and completions are used to train and improve Meta models; rate-limited by tokens | $0.10 / $0.002 / $0.20 |
Choose the Contributor model only when the organization accepts its data-use terms. The distinction is part of the model ID, so changing tiers is an explicit model selection rather than a hidden provider setting.
The previous `muse-spark-1.2` and `muse-spark-1.2-contributor` IDs remain available with the same tiers and pricing.
## Links
* [Meta Model API documentation](https://dev.meta.ai/docs/overview/)
* [Muse Spark model page](https://developer.meta.com/ai/models/muse-spark/)
* [Migrate between providers](https://docs.everruns.com/how-to/migrate-providers/)
---
# OpenAI
> Run Everruns agents on OpenAI models via the Responses API or Chat Completions, including Azure OpenAI and any OpenAI-compatible endpoint.
Source:
Everruns runs agents on [OpenAI](https://openai.com/) models through a uniform driver, mapping its provider-neutral messages, tools, and reasoning onto the OpenAI wire format. The same driver also powers any **OpenAI-compatible endpoint** through a custom base URL. For Azure deployments, use the dedicated [Azure OpenAI](https://docs.everruns.com/providers/azure-openai/) provider instead.
## What you get
* **Responses API** (recommended), the default driver, with stateful continuation and server-side context compaction on `api.openai.com` and Azure OpenAI hosts.
* **Chat Completions**: a compatibility driver (`openai_completions`) for legacy integrations and OpenAI-compatible gateways.
* **Streaming, tool calls, and reasoning** mapped to provider-neutral Everruns types, including extended thinking on reasoning models.
* **Prompt caching** via a deterministic cache key derived from stable request inputs.
## Configure in Everruns
1. Go to **Settings** → **Providers** and click **Add provider**.
2. Choose **OpenAI** (Responses API) for new setups, or **OpenAI Completions** for Chat Completions compatibility. For Azure, choose the dedicated [Azure OpenAI](https://docs.everruns.com/providers/azure-openai/) provider instead.
3. Paste your OpenAI API key. Get one from the [OpenAI API keys page](https://platform.openai.com/api-keys).
4. (Optional) Set a **base URL** to target another OpenAI-compatible endpoint.
5. Save. Everruns discovers available models automatically.
## OpenAI-compatible endpoints
The OpenAI driver works with any OpenAI-compatible API. Set the **base URL** to your endpoint. Self-hosted and proxy gateways are treated as stateless, so the driver replays the full transcript each turn instead of relying on server-side continuation. For Microsoft Azure deployments, use the dedicated [Azure OpenAI](https://docs.everruns.com/providers/azure-openai/) provider, which is recognized as a stateful host.
## Models
Models are discovered when the provider is created and on each sync. OpenAI’s `/models` listing carries only identifiers, so capability and cost metadata come from Everruns’ built-in model profiles, matched by model id.
## Links
* [OpenAI Platform](https://platform.openai.com/)
* [`everruns-openai` on crates.io](https://crates.io/crates/everruns-openai)
* [Migrate between providers](https://docs.everruns.com/how-to/migrate-providers/)
---
# OpenRouter
> Run Everruns agents across OpenRouter's multi-vendor model catalog with one key, plus provider routing, fallbacks, and capacity controls.
Source:
Everruns runs agents on [OpenRouter](https://openrouter.ai/)’s model catalog through its OpenAI-compatible Responses API. One OpenRouter key gives you access to a large multi-vendor catalog, plus routing controls that decide which upstream provider actually serves each request.
## What you get
* **One key, many models**: a single provider exposing OpenRouter’s full catalog.
* **OAuth one-click connect**: authorize in the browser instead of copy-pasting an API key. See [Connecting](#connecting) below.
* **Actual-cost reporting**: OpenRouter returns the real money spent per generation, which Everruns records as authoritative cost. See [Actual cost](#actual-cost) below.
* **Built-in server tools**: let the model use OpenRouter-executed tools such as `web_search` and `web_fetch` without wiring up a separate integration. See [Server tools](#server-tools) below.
* **Provider routing**: order, allow/deny lists, data-retention and zero-data-retention policies, and price/throughput sorting.
* **Capacity strategy**: use OpenRouter’s shared capacity, prefer your own bring-your-own-key (BYOK) providers first, or require BYOK-only routing.
* **Routing presets**: high-level intents such as cheapest-with-tools, lowest-latency, or reasoning-required that compile into the underlying routing flags.
* **Capability profiling**: OpenRouter’s richer `/models` metadata is parsed into capability profiles, so reasoning support surfaces correctly even for models without a built-in profile.
* **Session grouping & logs**: Everruns forwards its session id so all generations from one session group together in OpenRouter’s dashboard, where you can inspect traces and forward them to observability tools. See [Logs, traces, and observability](#logs-traces-and-observability) below.
## Connecting
Providers are configured by organization admins under **Settings** → **Providers** → **Add provider** → **OpenRouter**. You can connect two ways:
### OAuth (recommended)
Click **Connect with OpenRouter** and authorize in the browser. OpenRouter’s one-click [PKCE](https://openrouter.ai/docs/use-cases/oauth-pkce) flow returns a user-controlled API key that Everruns stores org-wide, no key copy-pasting, and no app registration. An admin authorizes once; everyone in the org then uses the models the provider serves against the single stored credential. The key is encrypted at rest and never returned by the API.
> OpenRouter’s callback URL must be HTTPS on port 443 or 3000 for non-localhost deployments.
### API key
Alternatively, paste an OpenRouter API key from the [OpenRouter keys page](https://openrouter.ai/keys).
Either way, save and Everruns discovers available models and their capability profiles automatically.
## Routing controls
OpenRouter-specific routing (model fallbacks, provider ordering, capacity strategy, and presets) is configured per agent and applied only to OpenRouter requests, direct OpenAI or other providers ignore these extensions. BYOK-only routing requires you to list at least one upstream provider, and fails closed if none is configured.
## Server tools
OpenRouter can run **provider-executed server tools**: `web_search`, `web_fetch`, `datetime`, `image_generation`, and more, during a generation. OpenRouter executes them server-side and folds the results into the same answer, so your agent gets built-in web reach without a separate search [integration](https://docs.everruns.com/integrations/) or an extra round-trip through Everruns.
Enable them per agent with the [OpenRouter Server Tools capability](https://docs.everruns.com/capabilities/openrouter-server-tools/). The capability is a harmless no-op on non-OpenRouter providers, so it is safe to leave on for agents that may switch providers. Because the model gains provider-executed web access, the capability is rated High risk, grant it only to agents you trust with outbound web access.
## Actual cost
OpenRouter’s API reports the **real money spent** per generation, not just an estimate. Everruns records this as the generation’s authoritative `actual_cost_usd` (sourced from OpenRouter’s `usage.cost`, reconciled against the [generation endpoint](https://openrouter.ai/docs/api-reference/get-a-generation) when needed) alongside its own estimated cost. Budgets debit the actual cost when present and fall back to the estimate otherwise, so spend tracking and [budgeting](https://docs.everruns.com/capabilities/budgeting/) reflect what OpenRouter actually charged. See [Usage tracking](https://github.com/everruns/everruns/blob/main/knowledge/security/usage-tracking.md) for how estimate-vs-actual reconciliation works.
## Logs, traces, and observability
Everruns forwards its session id to OpenRouter, so every generation from one Everruns session groups together in OpenRouter’s dashboard. OpenRouter’s own activity logs show each request’s trace, often enough to debug a run without leaving OpenRouter. When you need richer tracing, OpenRouter can forward your generations to external observability tools such as Braintrust or LangSmith; configure that on the OpenRouter side. This complements Everruns’ own [Framework context inspection](https://docs.everruns.com/framework/agents/#inspect-effective-context) rather than replacing it.
## Links
* [OpenRouter](https://openrouter.ai/)
* [OpenRouter docs](https://openrouter.ai/docs)
* [OpenRouter Server Tools capability](https://docs.everruns.com/capabilities/openrouter-server-tools/)
* [`everruns-openrouter` on crates.io](https://crates.io/crates/everruns-openrouter)
* [Integrations overview](https://docs.everruns.com/integrations/)
* [Migrate between providers](https://docs.everruns.com/how-to/migrate-providers/)
---
# Admin Container
> Admin container tools for checking database migration status, rotating encryption keys, and running diagnostics.
Source:
The admin container provides tools for key rotation, migration status checks, and other administrative tasks in production environments.
> **Note**: Migrations are **auto-applied on server startup**. The admin container’s `migrate` command is primarily for checking status or running migrations separately in special cases.
## Building
```bash
docker build --target admin -f docker/Dockerfile.unified -t everruns-admin .
```
## Commands
| Command | Description |
| -------------- | ------------------------------- |
| `migrate` | Run pending database migrations |
| `migrate-info` | Show migration status |
| `reencrypt` | Re-encrypt secrets with new key |
| `shell` | Interactive shell for debugging |
| `help` | Show usage information |
## Usage
### Check Migration Status
Use this before deployments to verify migration state:
```bash
docker run --rm \
-e DATABASE_URL="postgres://user:pass@host:5432/db" \
everruns-admin migrate-info
```
### Run Migrations Manually
Migrations auto-apply on server startup. Use this only for:
* Running migrations without starting the server
* Debugging migration issues (with `--no-migrations` on server)
```bash
docker run --rm \
-e DATABASE_URL="postgres://user:pass@host:5432/db" \
everruns-admin migrate
```
### Re-encrypt Secrets (Dry Run)
```bash
docker run --rm \
-e DATABASE_URL="postgres://user:pass@host:5432/db" \
-e SECRETS_ENCRYPTION_KEY="kek-v2:..." \
-e SECRETS_ENCRYPTION_KEY_PREVIOUS="kek-v1:..." \
everruns-admin reencrypt --dry-run
```
### Re-encrypt Secrets (Execute)
```bash
docker run --rm \
-e DATABASE_URL="postgres://user:pass@host:5432/db" \
-e SECRETS_ENCRYPTION_KEY="kek-v2:..." \
-e SECRETS_ENCRYPTION_KEY_PREVIOUS="kek-v1:..." \
everruns-admin reencrypt --batch-size 50
```
## Environment Variables
| Variable | Required | Description |
| --------------------------------- | ------------- | ---------------------------- |
| `DATABASE_URL` | Yes | PostgreSQL connection string |
| `SECRETS_ENCRYPTION_KEY` | For reencrypt | Primary encryption key |
| `SECRETS_ENCRYPTION_KEY_PREVIOUS` | For rotation | Previous encryption key |
| `RUST_LOG` | No | Log level (default: info) |
## TLS/SSL Connections
The admin container supports TLS connections to PostgreSQL. Use the `sslmode` parameter in your connection string:
```bash
DATABASE_URL="postgres://user:pass@host:5432/db?sslmode=require"
```
## Migration Troubleshooting
### Migration Fails on Startup
If the server won’t start due to a migration error:
1. Check server logs for the specific SQL error
2. Fix the migration file
3. Rebuild and redeploy
### Bad Migration Deployed
If a migration succeeded but caused issues, use **forward-fix**:
```bash
# Create a new migration that fixes the problem
sqlx migrate add -r fix_bad_migration
# Edit the migration, then redeploy
```
### Emergency: Manual Database Fix
For emergencies where you need to manually fix the database:
```bash
# Start server without auto-migrations
everruns-server --no-migrations
# Connect and fix manually
psql -h host -U everruns -d everruns
> -- Fix schema issues
> DELETE FROM _sqlx_migrations WHERE version = 006; -- If needed
# Restart server normally
everruns-server
```
## Production Deployment
The admin container can be run as a one-off task in any container orchestration platform:
* **Kubernetes**: Use a Job or run via `kubectl run`
* **ECS**: Use `aws ecs run-task` with command override
* **Docker Compose**: Use `docker compose run`
* **Nomad**: Use a batch job
---
# Environment Variables
> Every Everruns environment variable: database connections, authentication, encryption, and development mode.
Source:
## DEV\_MODE
Enable development mode with in-memory storage. No PostgreSQL required.
| Property | Value |
| ------------ | ------- |
| **Required** | No |
| **Default** | `false` |
**Example:**
```bash
# Start in dev mode (no database required)
DEV_MODE=true ./target/debug/everruns-server
# Or with 1
DEV_MODE=1 ./target/debug/everruns-server
```
**Notes:**
* When enabled, uses in-memory storage instead of PostgreSQL
* All data is lost when the server stops
* gRPC server and worker communication are disabled
* Stale task reclamation is disabled
* Useful for quick local development and testing
* Not suitable for production or multi-instance deployments
**Limitations in dev mode:**
* No persistence (data is lost on restart)
* No worker support (all execution happens in-process)
* No distributed tracing of worker activities
* Single-instance only
## DEPLOYMENT\_GRADE
Deployment environment grade. Controls which features and capabilities are available.
| Property | Value |
| ------------ | ------------------------------------ |
| **Required** | No |
| **Default** | `prod` (or `dev` if `DEV_MODE=true`) |
**Valid values:**
| Grade | Description |
| --------- | ----------------------------------------------- |
| `dev` | Development - all experimental features enabled |
| `poc` | Proof of concept / demo environment |
| `preview` | Preview/staging environment |
| `prod` | Production - only stable features |
**Example:**
```bash
# Run in development mode with experimental features
DEPLOYMENT_GRADE=dev ./target/debug/everruns-server
# Production mode (default)
DEPLOYMENT_GRADE=prod ./target/debug/everruns-server
```
**Notes:**
* If not set, falls back to `DEV_MODE`: if `DEV_MODE=true`, uses `dev`; otherwise uses `prod`
* Experimental capabilities (e.g., Docker Container) are only available in `dev` grade
* Experimental seed agents (e.g., Python Coder) are only created in `dev` grade
* Use `dev` for local development and testing experimental features
* Use `prod` for production deployments
## API\_PREFIX
Path prefix for REST API routes.
| Property | Value |
| ------------ | ------ |
| **Required** | No |
| **Default** | `/api` |
**Example:**
```bash
# Routes at /api/v1/agents
API_PREFIX=/api
```
**Notes:**
* `/health`, `/api-doc/openapi.json`, `/mcp`, `/.well-known/*`, `/oauth/*`, and `/cli/login-success` stay at the server root
* `/mcp` is always mounted and authenticated; there is no `FEATURE_MCP_ENDPOINT` deployment variable or organization feature toggle
* REST API routes including auth (`/v1/auth/*`) are mounted under this prefix
* OAuth callback URLs use `AUTH_BASE_URL`, which defaults to `PUBLIC_APP_URL` plus `API_PREFIX` when unset
* Override only if you need a non-`/api` REST prefix behind a reverse proxy or gateway
## PUBLIC\_APP\_URL
Public browser origin for the Everruns app. In single-origin deployments, set this once and the server derives `FRONTEND_URL` and `AUTH_BASE_URL` from it.
| Property | Value |
| ------------ | ----------------------- |
| **Required** | No |
| **Default** | `http://localhost:9300` |
**Example:**
```bash
PUBLIC_APP_URL=https://everruns.example.com
```
**Notes:**
* `FRONTEND_URL` defaults to `PUBLIC_APP_URL`
* `AUTH_BASE_URL` defaults to `PUBLIC_APP_URL` plus `API_PREFIX` (for example, `https://everruns.example.com/api`)
* Set `FRONTEND_URL` only when browser redirects must land on a different origin
* Set `AUTH_BASE_URL` only when OAuth callbacks use a different public API base
## AUTH\_LOGIN\_ORIGIN
Trusted browser origin that hosts the login page. Set this when an OSS-UI-based app delegates `/login` to a central identity origin.
| Property | Value |
| ------------ | --------------------------------------- |
| **Required** | No |
| **Default** | Not set (same-origin relative `/login`) |
**Example:**
```bash
AUTH_LOGIN_ORIGIN=https://id.example.com
```
**Notes:**
* Supply only an HTTP(S) origin, with no credentials, path, query, or fragment
* Set the same value in the server and UI runtime environments
* The value is trusted deployment configuration; request/query input cannot override it
* `return_to` remains a relative path and is still sanitized against open redirects
* Configured absolute login redirects use full-page navigation
## CORS\_ALLOWED\_ORIGINS
Comma-separated list of allowed origins for cross-origin requests. Only needed when the UI is served from a different domain than the API.
| Property | Value |
| ------------ | ----------------------- |
| **Required** | No |
| **Default** | Not set (CORS disabled) |
**Example:**
```bash
# Allow requests from a different frontend origin
CORS_ALLOWED_ORIGINS=https://app.example.com
# Multiple origins
CORS_ALLOWED_ORIGINS=https://app.example.com,https://admin.example.com
```
**Notes:**
* Not needed for local development (Caddy reverse proxy keeps UI and backend on one origin)
* Not needed in production if using a reverse proxy on the same domain
* If set, credentials are allowed (`Access-Control-Allow-Credentials: true`)
* Wildcard (`*`) is not supported when using credentials
## HTTP\_ADDR
Bind address for the server HTTP API.
| Property | Value |
| ------------ | -------------- |
| **Required** | No |
| **Default** | `0.0.0.0:9000` |
**Example:**
```bash
HTTP_ADDR=0.0.0.0:9000
```
**Notes:**
* `ADDR` is supported as a legacy alias
* Container images already default to `0.0.0.0:9000`; most deployments do not need to set this
## VALKEY\_URL
Connection URL for Valkey (Redis-compatible) used for distributed rate limiting across control-plane instances.
| Property | Value |
| ------------ | --------------------------------------------------- |
| **Required** | No |
| **Default** | Not set (uses per-instance in-memory rate limiting) |
**Example:**
```bash
# Local Valkey
VALKEY_URL=redis://localhost:6379
# With authentication
VALKEY_URL=redis://user:password@valkey.example.com:6379
# TLS (managed cloud service)
VALKEY_URL=rediss://user:password@valkey.example.com:6380
```
**Notes:**
* When not set, rate limiting falls back to in-memory governor (per-instance, no coordination)
* With N instances behind a load balancer, per-instance rate limiting allows N× the intended budget per IP, set `VALKEY_URL` for coordinated limits
* Accepts `redis://`, `rediss://` (TLS), `valkey://`, `valkeys://` (TLS) schemes
* Fail-open: if Valkey is unreachable, requests are allowed (availability over strictness)
* Only used by control-plane (server); workers don’t need this variable
* Uses sliding-window counters via Lua scripts for atomic rate limit checks
## DATABASE\_UNPOOLED\_URL
Direct PostgreSQL connection URL used only for session-scoped `LISTEN/NOTIFY` listeners.
| Property | Value |
| ------------ | --------------------------------------------------------------------- |
| **Required** | No |
| **Default** | Not set (listeners reuse `DATABASE_URL` if it is a direct connection) |
**Example:**
```bash
# Query traffic through a pooler, listeners through a direct endpoint
DATABASE_URL=postgres://app:secret@ep-foo-pooler.us-east-1.aws.neon.tech/everruns?sslmode=require
DATABASE_UNPOOLED_URL=postgres://app:secret@ep-foo.us-east-1.aws.neon.tech/everruns?sslmode=require
```
**Notes:**
* Use this when `DATABASE_URL` points at Neon `-pooler`, PgBouncer, or another proxy that does not preserve session-scoped `LISTEN/NOTIFY` semantics.
* Listener paths include PostgreSQL-backed event wakeups, notification SSE, and PG task notification fallback when NATS is unavailable.
* If `DATABASE_URL` or `DATABASE_UNPOOLED_URL` appears to point at a pooled/proxied endpoint, startup now fails fast with guidance to set a direct listener URL.
* Ordinary query traffic still uses `DATABASE_URL`.
## Object Storage (S3-compatible blob backend)
Optional backend that offloads workspace-file and image *content bytes* to an S3-compatible object store while keeping all metadata in PostgreSQL. Everruns remains the proxy for every read/write, no presigned URLs are handed to clients or workers. See [knowledge/runtime-resources/object-storage.md](https://github.com/everruns/everruns/blob/main/knowledge/runtime-resources/object-storage.md).
| Variable | Required | Default | Description |
| ------------------------------------- | --------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `STORAGE_BLOB_BACKEND` | No | `db` | `db` keeps bytes inline in PostgreSQL (current behavior); `s3` offloads to object storage. |
| `STORAGE_S3_BUCKET` | When `s3` | , | Target bucket name. |
| `STORAGE_S3_REGION` | No | , | Bucket region / region label. |
| `STORAGE_S3_ENDPOINT` | No | , | Custom endpoint for S3-compatible stores (SeaweedFS, R2). Unset for AWS S3. |
| `STORAGE_S3_ACCESS_KEY_ID` | No | , | Static access key. Omit to use the AWS credential chain (IAM role/instance). |
| `STORAGE_S3_SECRET_ACCESS_KEY` | No | , | Static secret key. |
| `STORAGE_S3_PREFIX` | No | (empty) | Key prefix isolating multiple deployments within one bucket. |
| `STORAGE_S3_ALLOW_HTTP` | No | `false` | Allow plaintext HTTP (local/dev only, e.g. SeaweedFS over HTTP). |
| `STORAGE_S3_FORCE_PATH_STYLE` | No | `true` | Use path-style requests (required by SeaweedFS; harmless on AWS S3). |
| `STORAGE_BLOB_GC_INTERVAL_SECONDS` | No | `21600` (6h) | Interval between blob GC sweeps that reclaim orphaned objects. `0` disables GC. Only effective with the `s3` backend (inline `db` storage has no orphans). |
| `STORAGE_BLOB_GC_GRACE_SECONDS` | No | `86400` (24h) | Safety grace period; orphaned objects younger than this are never deleted (avoids racing in-flight creates). |
| `STORAGE_BLOB_GC_MAX_DELETES_PER_RUN` | No | `10000` | Per-sweep deletion cap to bound work; remaining orphans are reclaimed next sweep. |
| `STORAGE_BLOB_GC_MAX_LIST_PER_RUN` | No | `100000` | Per-sweep cap on objects listed per prefix, bounding GC memory; larger buckets are reconciled across sweeps in key-order windows. |
**Example (local SeaweedFS via `just seaweedfs`):**
```bash
STORAGE_BLOB_BACKEND=s3
STORAGE_S3_BUCKET=everruns-dev
STORAGE_S3_ENDPOINT=http://127.0.0.1:8333
STORAGE_S3_REGION=us-east-1
STORAGE_S3_ACCESS_KEY_ID=everruns
STORAGE_S3_SECRET_ACCESS_KEY=everruns-secret
STORAGE_S3_ALLOW_HTTP=true
```
Any S3-compatible store works (AWS S3, SeaweedFS, R2); only the endpoint and credentials differ.
**Notes:**
* The backend is selected per process at startup; a deployment runs entirely on `db` or `s3`. Enabling `s3` offloads newly written content; pre-existing inline content is still served transparently.
* Tenant isolation is by object key (`workspaces/{workspace_id}/…`, `images/org-{org_id}/…`) plus the existing org/workspace authorization layer.
## NATS\_URL
Connection URL for NATS with JetStream, used for push-based event delivery and task notifications.
| Property | Value |
| ------------ | ------------------------------------------------------------------------------------------- |
| **Required** | No |
| **Default** | Not set (uses PG NOTIFY for task notifications, in-memory broadcast for SSE event delivery) |
**Example:**
```bash
# Local NATS
NATS_URL=nats://localhost:4222
# Cluster
NATS_URL=nats://nats1:4222,nats://nats2:4222,nats://nats3:4222
# Server with `authorization { users: [...] }`
NATS_URL=nats://control:s3cret@nats:4222
```
**Notes:**
* When not set, the system behaves exactly as before, all events persist to PG, SSE polls PG, task notifications use PG NOTIFY. Zero behavioral change.
* When set, enables two features:
* **Ephemeral event delivery**: delta events (`output.message.delta`, `reason.thinking.delta`, `tool.output.delta`, `llm.generation`) skip PostgreSQL and flow only through NATS JetStream. SSE streams subscribe to NATS instead of polling PG.
* **Task notifications**: `task.available.{activity_type}` subjects replace PG NOTIFY for push-based worker notification. Lower latency (\~1ms vs \~30ms), supports multi-instance deployments.
* When NATS event delivery is active, the server skips the legacy PostgreSQL event listener used only for SSE wakeups.
* NATS JetStream must be enabled on the server (`--jetstream` flag)
* Credentials embedded in the URL (`nats://user:password@host`) are sent as user/password auth. Reserved characters in the password (`/`, `@`, `:`, `%`) are accepted as-is, so a generated secret can be pasted unmodified; percent-encoded passwords are decoded and also work
* Fail-graceful: if NATS connection fails at startup, falls back to PG NOTIFY + in-memory delivery with a warning that includes the connection error
* Only used by control-plane (server); workers communicate via gRPC and don’t need NATS access
* Default port: 4222 (or `PORT_PREFIX22` with `PORT_PREFIX`)
* `just start-all` automatically starts NATS and exports `NATS_URL` if `nats-server` is installed
## LLM Provider API Keys
LLM provider API keys (OpenAI, Anthropic, Gemini) are primarily stored encrypted in the database and managed via the Settings > Providers UI.
| Property | Value |
| ----------------------- | ---------------------------------------------- |
| **Storage** | Database (encrypted with AES-256-GCM) |
| **Configuration** | Settings > Providers UI or `/v1/providers` API |
| **Supported Providers** | OpenAI, Anthropic, Google Gemini |
**Required for encryption:**
The `SECRETS_ENCRYPTION_KEY` environment variable must be set for the control-plane API to encrypt/decrypt API keys. Workers receive decrypted API keys via gRPC and do not need this variable.
```bash
# Generate a new key
python3 -c "import os, base64; print('kek-v1:' + base64.b64encode(os.urandom(32)).decode())"
# Set in environment (control-plane only)
SECRETS_ENCRYPTION_KEY=kek-v1:your-generated-key-here
```
### Default API Keys (Development Convenience)
For development, you can set default API keys via environment variables on the **control-plane only**. These are used as fallbacks when providers don’t have keys configured in the database.
| Variable | Description |
| --------------------------- | --------------------------------------------- |
| `DEFAULT_OPENAI_API_KEY` | Fallback API key for OpenAI providers |
| `DEFAULT_ANTHROPIC_API_KEY` | Fallback API key for Anthropic providers |
| `DEFAULT_GEMINI_API_KEY` | Fallback API key for Google Gemini providers |
| `DEFAULT_META_API_KEY` | Fallback API key for Meta Model API providers |
**Example:**
```bash
# Set in .env or environment (control-plane only)
DEFAULT_OPENAI_API_KEY=sk-...
DEFAULT_ANTHROPIC_API_KEY=sk-ant-...
DEFAULT_GEMINI_API_KEY=AIza...
DEFAULT_META_API_KEY=...
```
**Notes:**
* These variables are only used by the control-plane, not workers
* Workers receive API keys via gRPC from the control-plane
* Database-stored keys always take priority over environment variables
* These are intended for development convenience, not production use
* The `just start-all` command automatically sets these from `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, and `GEMINI_API_KEY` if present
* If no API key is configured for a provider, LLM calls will fail and users will see an error message in the chat: “I encountered an error while processing your request. Please try again later.”
## System Model Keys
Two deployment-owned models sit outside the provider system above. Neither is selectable by an agent, neither is stored in the database, and neither is reachable from session or agent configuration — they are host services the platform uses for its own internal work.
| Variable | Powers | Unset means |
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| `UTILITY_OPENAI_API_KEY` | Agent Analyze/Health checks, and guardrail checks with `engine: "utility_llm"` (the default), called directly against OpenAI | Those checks are skipped; Analyze and Health are unavailable |
| `UTILITY_OPENROUTER_API_KEY` | The same work, routed through OpenRouter instead. Setting it selects OpenRouter; it wins when both keys are set, and the startup log says so | The utility LLM falls back to `UTILITY_OPENAI_API_KEY` |
| `UTILITY_LLM_MODEL` | The model the utility LLM calls on whichever backend was selected | Defaults to `gpt-6-luna` on OpenAI, `openai/gpt-6-luna` on OpenRouter |
| `UTILITY_TYPESAFE_API_KEY` | Guardrail checks with `engine: "jev"` | Those checks are skipped with a warning and the turn proceeds |
The keys are read from the process environment at startup. Missing keys **fail open**: a guardrail whose engine is not configured never blocks, so a missing key weakens policy rather than wedging traffic. Check the startup logs if a configured guardrail appears to do nothing.
There is no separate provider variable: the utility LLM’s backend is whichever key you supply. An OpenRouter model id is namespaced by its upstream provider, so override `UTILITY_LLM_MODEL` with an id that backend accepts (`anthropic/claude-sonnet-4.5`, not `claude-sonnet-4.5`).
```bash
# Control-plane and workers both read these.
UTILITY_OPENAI_API_KEY=sk-...
# ...or route the utility LLM through OpenRouter instead:
UTILITY_OPENROUTER_API_KEY=sk-or-...
UTILITY_LLM_MODEL=openai/gpt-6-luna
UTILITY_TYPESAFE_API_KEY=ts-...
```
Agents can also be given the TypeSafe capability directly, which is a **separate** credential: a per-user connection configured in Settings > Connections, never this deployment key. See [TypeSafe](https://docs.everruns.com/integrations/typesafe/) and [Guardrails](https://docs.everruns.com/capabilities/guardrails/).
## System Email Delivery
System email delivery is an internal service used by product and operational flows. It is not an agent capability, public API, or UI setting.
| Variable | Required | Default | Description |
| --------------------- | ------------------------------------- | ------------------------ | ---------------------------------------------------------------- |
| `EMAIL_PROVIDER` | Yes, when sending email in production | unset / disabled | Email provider. Supported values: `disabled`, `resend` |
| `RESEND_API_KEY` | Yes, when `EMAIL_PROVIDER=resend` | unset | Resend API key |
| `RESEND_API_BASE_URL` | No | `https://api.resend.com` | Resend API base URL override for tests or controlled deployments |
**Example:**
```bash
EMAIL_PROVIDER=resend
RESEND_API_KEY=re_...
```
**Notes:**
* Set these on the control-plane process that performs system email sends.
* The sender is fixed in code as `Everruns `.
* The Resend account must have `everruns.com` verified and enabled for sending.
## UI API Proxy Architecture
The UI makes all REST API requests (including SSE) to `/api/*` paths. The backend serves those routes under `/api` directly. Root-level backend routes like `/oauth/*`, `/mcp`, and `/.well-known/*` bypass the UI and are proxied straight to the backend.
**Local Development:**
* Caddy on `:9300` routes `/api/*`, `/oauth/*`, `/mcp`, and `/.well-known/*` to backend at `:9301`
* Example: `/api/v1/agents` → `http://localhost:9301/api/v1/agents`
* Example: `/oauth/authorize?...` → `http://localhost:9301/oauth/authorize?...`
* Example: `/mcp` → `http://localhost:9301/mcp`
* Example: `/.well-known/oauth-authorization-server` → `http://localhost:9301/.well-known/oauth-authorization-server`
* SSE streaming works via `flush_interval -1` in Caddy config
* No CORS needed (same-origin through Caddy)
**Production:**
* Configure your reverse proxy (nginx, Caddy, etc.) to route `/api/*`, `/oauth/*`, `/mcp`, and `/.well-known/*` to the API server
* Disable response buffering for SSE endpoints
* Example Caddy config: see `local/Caddyfile`
## SSE Streaming Configuration
| Variable | Default | Description |
| ----------------------------- | ------- | ------------------------------------------------------------------ |
| `SSE_REALTIME_CYCLE_SECS` | `300` | Connection cycle interval for session event streams (seconds) |
| `SSE_MONITORING_CYCLE_SECS` | `600` | Connection cycle interval for durable monitoring streams (seconds) |
| `SSE_HEARTBEAT_INTERVAL_SECS` | `30` | Interval between heartbeat comments on all SSE streams (seconds) |
| `SSE_GLOBAL_MAX` | `10000` | Maximum total SSE connections across all users |
| `SSE_PER_SESSION_MAX` | `12` | Maximum SSE connections per session |
| `SSE_PER_ORG_MAX` | `1000` | Maximum SSE connections per organization |
**Notes:**
* Heartbeat comments (`: heartbeat\n\n`) are sent on all SSE streams to detect stale connections
* The heartbeat interval must be less than the SDK read timeout (default: 60s) with safety margin
* Connection cycling prevents stale connections through proxies and load balancers
* When running behind HTTP/1.1 proxies, increase `SSE_REALTIME_CYCLE_SECS` to reduce reconnection frequency
## Worker gRPC Configuration
### SERVER\_GRPC\_ADDRESS
Address of the server gRPC endpoint for worker communication.
| Property | Value |
| ------------ | ---------------- |
| **Required** | No (worker only) |
| **Default** | `127.0.0.1:9001` |
**Example:**
```bash
SERVER_GRPC_ADDRESS=127.0.0.1:9001
```
**Notes:**
* Workers communicate with the server via gRPC for all database operations
* `WORKER_GRPC_ADDRESS` is supported as a legacy alias
* The server exposes both HTTP (default `9000`) and gRPC (default `9001`) interfaces
* Workers are stateless and do not connect directly to the database
### WORKER\_GRPC\_AUTH\_TOKEN
Bearer token for authenticating worker gRPC connections to the control-plane.
| Property | Value |
| ------------ | ------------------------------- |
| **Required** | Yes (production); No (dev mode) |
| **Default** | Unset (auth disabled) |
**Example:**
```bash
WORKER_GRPC_AUTH_TOKEN=your-secret-token
```
**Notes:**
* Must be set on both the server and all workers (same value)
* When unset, gRPC auth is disabled (acceptable for local development only)
* Server panics on startup if unset when not in dev mode
### SERVER\_GRPC\_BIND\_ADDR
Bind address for the server-side gRPC listener.
| Property | Value |
| ------------ | ---------------- |
| **Required** | No (server only) |
| **Default** | `0.0.0.0:9001` |
**Example:**
```bash
SERVER_GRPC_BIND_ADDR=0.0.0.0:9001
```
**Notes:**
* `WORKER_GRPC_ADDR` is supported as a legacy alias
### WORKER\_GRPC\_CONNECT\_TIMEOUT
Timeout in seconds for worker initial connection to control-plane gRPC.
| Property | Value |
| ------------ | ---------------- |
| **Required** | No (worker only) |
| **Default** | `30` |
**Example:**
```bash
WORKER_GRPC_CONNECT_TIMEOUT=60
```
### WORKER\_GRPC\_TLS\_CERT
Path to PEM-encoded certificate file. On the server, this is the gRPC server certificate. On the worker, this is the client certificate presented during mTLS handshake.
| Property | Value |
| ------------ | ---------------------- |
| **Required** | No |
| **Default** | Not set (TLS disabled) |
**Example:**
```bash
WORKER_GRPC_TLS_CERT=/etc/everruns/grpc-cert.pem
```
**Notes:**
* Must be set together with `WORKER_GRPC_TLS_KEY`
* Server: enables TLS on the gRPC listener when both cert and key are set
* Worker: presents client certificate to the server when both cert and key are set (requires `WORKER_GRPC_TLS_CA_CERT`)
### WORKER\_GRPC\_TLS\_KEY
Path to PEM-encoded private key file corresponding to `WORKER_GRPC_TLS_CERT`.
| Property | Value |
| ------------ | ------- |
| **Required** | No |
| **Default** | Not set |
**Example:**
```bash
WORKER_GRPC_TLS_KEY=/etc/everruns/grpc-key.pem
```
### WORKER\_GRPC\_TLS\_CA\_CERT
Path to PEM-encoded CA certificate bundle for verifying the remote peer.
| Property | Value |
| ------------ | ------- |
| **Required** | No |
| **Default** | Not set |
**Example:**
```bash
WORKER_GRPC_TLS_CA_CERT=/etc/everruns/grpc-ca.pem
```
**Notes:**
* Server: when set, requires workers to present valid client certificates signed by this CA (mutual TLS)
* Worker: when set, verifies the server’s certificate against this CA and switches to `https://` transport
* For full mTLS, set on both server and worker alongside their respective cert/key pairs
### WORKER\_GRPC\_TLS\_DOMAIN
Override the expected server domain name for TLS certificate verification (worker only).
| Property | Value |
| ------------ | ------------------------------------------- |
| **Required** | No |
| **Default** | Derived from `SERVER_GRPC_ADDRESS` hostname |
**Example:**
```bash
WORKER_GRPC_TLS_DOMAIN=control-plane.internal
```
**Notes:**
* Useful when the server certificate CN/SAN differs from the connection hostname (e.g., connecting via IP but cert has a DNS name)
## OpenTelemetry Configuration
Everruns supports distributed tracing via OpenTelemetry with OTLP export. Agent traces follow the OpenTelemetry [Gen-AI agent and inference conventions](https://github.com/open-telemetry/semantic-conventions-genai/tree/main/docs/gen-ai) and, on the same spans, the [OpenInference conventions](https://arize-ai.github.io/openinference/spec/semantic_conventions.html) read by Arize Phoenix, so one OTLP endpoint serves both families of backends.
### OTEL\_EXPORTER\_OTLP\_ENDPOINT
OTLP endpoint for trace export (e.g., Grafana Tempo, Arize Phoenix, Datadog, or any OTLP-compatible backend).
| Property | Value |
| ------------ | -------------------------- |
| **Required** | No |
| **Default** | Not set (tracing disabled) |
**Example:**
```bash
# For a local OTLP collector or Phoenix
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
# For production Tempo
OTEL_EXPORTER_OTLP_ENDPOINT=http://tempo.monitoring:4318
```
**Notes:**
* When set, traces are exported via OTLP over HTTP/protobuf (use the backend’s HTTP port, typically 4318, not the gRPC port 4317)
* Point this at the base endpoint; the `/v1/traces` path is appended for you, and a full signal URL is used as given
* Connect to any OTLP-compatible backend for trace visualization
* See [OpenTelemetry](https://docs.everruns.com/observability/opentelemetry/) for the span model and attributes
* Without this variable, only console logging is enabled
### OTEL\_SERVICE\_NAME
Service name for traces.
| Property | Value |
| ------------ | --------------------------------------------------- |
| **Required** | No |
| **Default** | `everruns-server` (API), `everruns-worker` (Worker) |
**Example:**
```bash
OTEL_SERVICE_NAME=everruns-prod-api
```
### OTEL\_SERVICE\_VERSION
Service version for traces.
| Property | Value |
| ------------ | --------------------- |
| **Required** | No |
| **Default** | Cargo package version |
### OTEL\_ENVIRONMENT
Deployment environment label.
| Property | Value |
| ------------ | ------- |
| **Required** | No |
| **Default** | Not set |
**Example:**
```bash
OTEL_ENVIRONMENT=production
```
### OTEL\_RECORD\_CONTENT
Enable recording of LLM input/output content in traces. **Warning:** May contain sensitive data.
| Property | Value |
| ------------ | ------- |
| **Required** | No |
| **Default** | `false` |
**Example:**
```bash
# Standard OTel env var (preferred)
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true
# Legacy alias (also works)
OTEL_RECORD_CONTENT=true
```
**Notes:**
* When enabled, the chat span records `gen_ai.system_instructions`, `gen_ai.input.messages`, `gen_ai.output.messages`, and `gen_ai.tool.definitions` (plus the OpenInference `input.value`, `output.value`, and flattened `llm.input_messages.*`); tool spans record `gen_ai.tool.call.arguments` and `gen_ai.tool.call.result`; the turn root records the input message and final answer; the thinking span records the reasoning text
* Disabled by default for privacy and data size concerns
* Only enable in development or when debugging specific issues
### EVERRUNS\_TRACE\_CONVENTIONS
Which attribute vocabularies agent spans carry.
| Property | Value |
| ------------ | ---------------------- |
| **Required** | No |
| **Default** | `gen_ai,openinference` |
**Example:**
```bash
# Only the OpenTelemetry Gen-AI attributes (Tempo, Jaeger, Datadog, Langfuse)
EVERRUNS_TRACE_CONVENTIONS=gen_ai
# Only the OpenInference attributes (Arize Phoenix)
EVERRUNS_TRACE_CONVENTIONS=openinference
```
**Notes:**
* Span names, kinds, and hierarchy are the same under either vocabulary; only attributes differ
* Unknown values are ignored, and an empty selection falls back to both
## Local Development with OpenTelemetry
To visualize traces locally, point `OTEL_EXPORTER_OTLP_ENDPOINT` at any OTLP-compatible collector:
```bash
# Set OTLP endpoint for API and Worker
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
# Start services
just start-all
```
To see traces in Arize Phoenix, run Phoenix locally and point the same variable at its OTLP/HTTP port (`http://localhost:6006`); spans render as AGENT, LLM, and TOOL spans with token counts and, when content capture is on, the messages.
### Gen-AI Trace Structure
Traces follow the agentic execution lifecycle with 13 event types; every span starts and ends at the timestamp of the event it records:
```plaintext
invoke_agent {agent name} (root span, INTERNAL)
├── reason (LLM reasoning phase)
│ └── chat {model} (LLM API call, CLIENT)
│ └── thinking (extended thinking, if enabled)
├── act (tool execution phase)
│ ├── execute_tool {name}
│ └── execute_tool {name}
├── reason (iteration 2)
│ └── chat {model}
└── ...
```
### Gen-AI Trace Attributes
Spans carry the OpenTelemetry Gen-AI attributes and the OpenInference attributes side by side (see `EVERRUNS_TRACE_CONVENTIONS`). The most useful ones:
| Attribute | Span Types | Description |
| -------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `gen_ai.operation.name` | invoke\_agent, chat, execute\_tool | `invoke_agent`, `chat`, or `execute_tool` |
| `gen_ai.agent.id`, `gen_ai.agent.name`, `gen_ai.agent.description` | invoke\_agent | Agent the turn runs as |
| `gen_ai.conversation.id` / `session.id` | All | Session identifier |
| `gen_ai.provider.name` / `llm.provider` | chat | Provider (`openai`, `anthropic`, `gcp.gemini`, `aws.bedrock`, …) |
| `gen_ai.request.model`, `gen_ai.response.model` / `llm.model_name` | chat | Model name |
| `gen_ai.response.id` | chat | Provider response identifier |
| `gen_ai.response.finish_reasons` | chat | Why generation stopped (string array) |
| `gen_ai.usage.input_tokens`, `gen_ai.usage.output_tokens` / `llm.token_count.*` | chat, invoke\_agent | Token usage (turn root carries the cumulative total) |
| `gen_ai.usage.cache_read.input_tokens`, `gen_ai.usage.cache_write.input_tokens` | chat, invoke\_agent | Prompt-cache tokens |
| `gen_ai.request.temperature`, `gen_ai.request.max_tokens`, `gen_ai.request.reasoning.level`, `gen_ai.request.stream` / `llm.invocation_parameters` | chat | Request parameters |
| `gen_ai.response.time_to_first_chunk` | chat | Streaming latency in seconds |
| `gen_ai.conversation.compacted` | chat | Context was compacted before the call |
| `llm.cost.total` / `everruns.usage.cost_usd` | chat, invoke\_agent | Cost in USD when known |
| `gen_ai.tool.name`, `gen_ai.tool.call.id`, `gen_ai.tool.type`, `gen_ai.tool.description` / `tool.name`, `tool.description` | execute\_tool | Tool identity |
| `openinference.span.kind` | All | `AGENT`, `LLM`, `TOOL`, or `CHAIN` |
| `error.type` | All | Low-cardinality error class on failure (error code, HTTP status, `timeout`, or `_OTHER`); the span status carries the message |
| `everruns.phase` | reason, act, thinking | Phase span marker |
| `everruns.turn.id`, `everruns.exec.id` | All | Everruns correlation ids |
| `everruns.turn.iterations`, `everruns.turn.tool_call_count`, `everruns.turn.llm_call_count` | invoke\_agent | Turn counters |
| `everruns.tool.status` | execute\_tool | `success`, `error`, `timeout`, or `cancelled` |
## Braintrust Integration
Everruns supports sending turn, reasoning, tool, and session lifecycle events to [Braintrust](https://www.braintrust.dev/) for observability, evaluation, and logging.
For setup instructions and configuration details, see the [Braintrust Integration Guide](https://docs.everruns.com/observability/braintrust/).
| Variable | Required | Default | Description |
| -------------------------------- | -------- | ------------------------------- | --------------------------------------------------------------- |
| `BRAINTRUST_ENABLED` | No | enabled when API key is present | Explicit Braintrust on/off switch |
| `BRAINTRUST_API_KEY` | Yes | - | API key from Braintrust settings |
| `BRAINTRUST_PROJECT_NAME` | No | `My Project` | Project name for organizing traces |
| `BRAINTRUST_PROJECT_ID` | No | - | Direct project UUID (skips name lookup) |
| `BRAINTRUST_API_URL` | No | `https://api.braintrust.dev` | API base URL |
| `BRAINTRUST_QUEUE_CAPACITY` | No | `1024` | Buffered event capacity before new exports are dropped |
| `BRAINTRUST_MAX_BATCH_SIZE` | No | `50` | Max events per Braintrust insert call |
| `BRAINTRUST_FLUSH_INTERVAL_MS` | No | `500` | Max delay before flushing a partial batch |
| `BRAINTRUST_REQUEST_TIMEOUT_MS` | No | `10000` | Per-request timeout for Braintrust insert calls |
| `BRAINTRUST_MAX_RETRIES` | No | `3` | Retries for `429`, `5xx`, and timeout/connect failures |
| `BRAINTRUST_RETRY_BASE_DELAY_MS` | No | `250` | Initial retry backoff |
| `BRAINTRUST_RETRY_MAX_DELAY_MS` | No | `5000` | Retry backoff cap |
| `BRAINTRUST_RECORD_CONTENT` | No | `false` | Export raw turn and LLM text content |
| `BRAINTRUST_RECORD_THINKING` | No | `none` | Extended thinking export mode: `none`, `summary`, `full` |
| `BRAINTRUST_TOOL_ARGS_MODE` | No | `redacted` | Tool argument export mode: `full`, `redacted`, `none` |
| `BRAINTRUST_TOOL_RESULTS_MODE` | No | `summary` | Tool result export mode: `full`, `summary`, `redacted`, `none` |
| `BRAINTRUST_DEBUG_PAYLOADS` | No | `false` | Print full outbound Braintrust payload JSON to local debug logs |
---
# Authentication Configuration Runbook
> Configure authentication modes, personal access tokens, OAuth providers, JWT secrets, token lifetimes, and sign-up controls.
Source:
## Overview
This runbook covers configuring and managing authentication for Everruns.
## Authentication Modes
### 1. No Authentication (Development)
Use for local development when authentication isn’t needed:
```bash
export AUTH_MODE=none
```
All requests will be allowed with full admin access.
### 2. Admin Mode (Simple Development)
Use for local development with basic access control:
```bash
export AUTH_MODE=admin
export AUTH_ADMIN_EMAIL=admin@example.com
export AUTH_ADMIN_PASSWORD=your-secure-password
export AUTH_JWT_SECRET=$(openssl rand -hex 32)
```
Only the admin user can authenticate.
### 3. Full Authentication (Production)
Use for production deployments:
```bash
export AUTH_MODE=full
export PUBLIC_APP_URL=https://your-domain.com
export AUTH_JWT_SECRET=$(openssl rand -hex 32)
# Optional: Configure OAuth
export AUTH_GOOGLE_CLIENT_ID=your-google-client-id
export AUTH_GOOGLE_CLIENT_SECRET=your-google-client-secret
export AUTH_GITHUB_CLIENT_ID=your-github-client-id
export AUTH_GITHUB_CLIENT_SECRET=your-github-client-secret
```
## Environment Variables Reference
### Core Settings
| Variable | Required | Description |
| ------------------- | -------------- | ---------------------------------------------------------------------------------- |
| `AUTH_MODE` | No | `none`, `admin`, or `full` (default: `none`) |
| `PUBLIC_APP_URL` | For OAuth | Public app origin used to derive auth callback URLs |
| `AUTH_BASE_URL` | No | Override callback base URL when it differs from `PUBLIC_APP_URL` + `API_PREFIX` |
| `AUTH_LOGIN_ORIGIN` | No | Trusted remote origin hosting `/login`; set identically for server and UI runtimes |
| `AUTH_JWT_SECRET` | For admin/full | JWT signing secret (min 32 chars recommended) |
### Admin Mode Settings
| Variable | Required | Description |
| --------------------- | ---------------- | ------------------- |
| `AUTH_ADMIN_EMAIL` | Yes (admin mode) | Admin user email |
| `AUTH_ADMIN_PASSWORD` | Yes (admin mode) | Admin user password |
### JWT Settings
| Variable | Required | Description |
| --------------------------------- | -------- | ---------------------------------------------------- |
| `AUTH_JWT_ACCESS_TOKEN_LIFETIME` | No | Access token lifetime in seconds (default: 900) |
| `AUTH_JWT_REFRESH_TOKEN_LIFETIME` | No | Refresh token lifetime in seconds (default: 2592000) |
### Feature Toggles
| Variable | Required | Description |
| ----------------------- | -------- | ------------------------------------------ |
| `AUTH_DISABLE_PASSWORD` | No | Set to `true` to disable password login |
| `AUTH_DISABLE_SIGNUP` | No | Set to `true` to disable user registration |
### Google OAuth
| Variable | Required | Description |
| ----------------------------- | ---------------- | ------------------------------------- |
| `AUTH_GOOGLE_CLIENT_ID` | For Google OAuth | Google OAuth client ID |
| `AUTH_GOOGLE_CLIENT_SECRET` | For Google OAuth | Google OAuth client secret |
| `AUTH_GOOGLE_REDIRECT_URI` | No | Custom redirect URI |
| `AUTH_GOOGLE_ALLOWED_DOMAINS` | No | Comma-separated allowed email domains |
### GitHub OAuth
| Variable | Required | Description |
| --------------------------- | ---------------- | -------------------------- |
| `AUTH_GITHUB_CLIENT_ID` | For GitHub OAuth | GitHub OAuth client ID |
| `AUTH_GITHUB_CLIENT_SECRET` | For GitHub OAuth | GitHub OAuth client secret |
| `AUTH_GITHUB_REDIRECT_URI` | No | Custom redirect URI |
## Common Tasks
### Generate JWT Secret
```bash
# Using OpenSSL
openssl rand -hex 32
# Using Python
python3 -c "import secrets; print(secrets.token_hex(32))"
```
### Verify Authentication is Working
```bash
# Check auth config endpoint
curl http://localhost:9300/api/v1/auth/config
# Should return:
# {"mode":"none","password_auth_enabled":false,"oauth_providers":[],"signup_enabled":false}
# For admin mode:
curl -X POST http://localhost:9300/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"admin@example.com","password":"your-password"}'
```
### Create Personal Access Token
```bash
# Login first to get access token
TOKEN=$(curl -s -X POST http://localhost:9300/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"password"}' | jq -r '.access_token')
# Create personal access token
curl -X POST http://localhost:9300/api/v1/auth/personal-access-tokens \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"my-token"}'
# Response includes the full token - save it, it's shown only once
```
### Revoke Personal Access Token
```bash
curl -X DELETE http://localhost:9300/api/v1/auth/personal-access-tokens/{token_id} \
-H "Authorization: Bearer $TOKEN"
```
### Force Logout User
Delete their refresh tokens from database:
```sql
DELETE FROM refresh_tokens WHERE user_id = 'user-uuid-here';
```
## Troubleshooting
### ”Authentication required” when AUTH\_MODE=none
* Verify `AUTH_MODE` environment variable is set correctly
* Restart the server after changing environment variables
### JWT Validation Fails
* Ensure `AUTH_JWT_SECRET` hasn’t changed
* Check token hasn’t expired
* Verify the token is for the correct environment
### OAuth Redirect Fails
* Verify `AUTH_BASE_URL` matches the OAuth app configuration, or that `PUBLIC_APP_URL` derives the expected `{PUBLIC_APP_URL}/api` base
* Check that redirect URI in provider matches `{AUTH_BASE_URL}/v1/auth/callback/{provider}` or `{PUBLIC_APP_URL}/api/v1/auth/callback/{provider}`
* If you set `AUTH_BASE_URL`, ensure it already includes your REST API prefix (default: `/api`)
* Ensure client ID and secret are correct
### Password Login Returns Unauthorized
* In admin mode: check `AUTH_ADMIN_EMAIL` and `AUTH_ADMIN_PASSWORD` match
* In full mode with password disabled: check `AUTH_DISABLE_PASSWORD` isn’t set
* Verify user exists and password is correct
## Database Migration
Authentication tables are included in the base schema and **auto-applied on server startup**.
No manual migration step is required. To check migration status:
```bash
# Via admin container
docker run --rm -e DATABASE_URL="$DATABASE_URL" everruns-admin migrate-info
```
## Health Check
The `/health` endpoint shows current auth mode:
```bash
curl http://localhost:9300/health
# {"status":"ok","version":"0.2.0","auth_mode":"None"}
```
## Agent Discovery Endpoints
The server publishes two public documents so an AI agent can work out how to authenticate before it has any credentials:
| Path | Contents |
| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `/auth.md` | How to obtain credentials: OAuth 2.1 with dynamic client registration for the MCP endpoint, and personal access tokens for the REST API |
| `/.well-known/mcp/server-card.json` | MCP Server Card (SEP-1649): server identity, transport, capabilities, and where its OAuth metadata lives |
Both are generated from the running configuration rather than hardcoded, so a self-hosted deployment describes itself:
* URLs come from `AUTH_BASE_URL` / `BASE_URL` (or `PUBLIC_APP_URL` plus `API_PREFIX`), the same value used for OAuth callbacks and the MCP resource binding. Set them to the public origin, not an internal container address, or the documents will advertise URLs an agent cannot reach.
* Content follows `AUTH_MODE`. Under `AUTH_MODE=none` both documents state that no credentials are required instead of describing an OAuth flow that is not enforced.
### Reverse proxy configuration (required)
`/auth.md` sits at the server root, so a deployment that puts the UI on `/` must route this one path to the server explicitly. Without the rule the request falls through to the UI and returns its 404, and the endpoint is unreachable even though the server serves it.
The bundled proxies (`local/Caddyfile`, `infra/railway/caddy/Caddyfile`, and `examples/docker-compose-full.yaml`) already include it. For a custom proxy, add `/auth.md` wherever `/.well-known/*` is routed:
```caddyfile
handle /.well-known/* {
reverse_proxy server:9000
}
handle /auth.md {
reverse_proxy server:9000
}
```
nginx:
```nginx
location = /auth.md {
proxy_pass http://server:9000;
}
```
Verify after deploying:
```bash
curl -fsS https://your-host/auth.md | head -1 # expect "# auth.md"
curl -fsS https://your-host/.well-known/mcp/server-card.json
```
A response of `text/html` rather than `text/markdown` means the UI answered and the proxy rule is missing.
## Security Best Practices
1. **Never commit secrets**: Use environment variables or secret management
2. **Rotate JWT secret**: Change `AUTH_JWT_SECRET` periodically (invalidates all tokens)
3. **Use HTTPS**: Always use HTTPS in production for OAuth callbacks
4. **Limit OAuth domains**: Use `AUTH_GOOGLE_ALLOWED_DOMAINS` to restrict access
5. **Monitor personal access token usage**: Track `last_used_at` for suspicious activity
6. **Set token expiration**: Use shorter `AUTH_JWT_ACCESS_TOKEN_LIFETIME` for higher security
---
# Durable Execution Engine Setup
> Run Everruns with the PostgreSQL-backed durable execution engine: database setup, migrations, and worker configuration.
Source:
This guide explains how to run Everruns with the custom PostgreSQL-backed durable execution engine.
## Overview
The durable execution engine is a PostgreSQL-backed workflow orchestration system that provides:
* Event-sourced workflows with automatic retries
* Distributed task queue with backpressure support
* Circuit breakers and dead letter queues
* No additional infrastructure required (uses existing PostgreSQL)
## Quick Start
### 1. Prerequisites
* PostgreSQL running and accessible
* `DATABASE_URL` environment variable set
* Migrations applied (includes durable tables)
### 2. Start API in Durable Mode
```bash
# Set runner mode to durable
export RUNNER_MODE=durable
export DATABASE_URL="postgres://postgres:postgres@localhost/everruns"
# Start the API server
cargo run -p everruns-server
```
You should see:
```plaintext
Using Durable execution engine runner (PostgreSQL-backed)
```
### 3. Start Durable Worker
In a separate terminal:
```bash
# Workers only need gRPC address - NO DATABASE_URL required!
export SERVER_GRPC_ADDRESS="127.0.0.1:9001"
# Start the task worker
cargo run -p everruns-worker --bin durable-worker
```
**Important:** Workers communicate with the control-plane via gRPC and do not require direct database access. This improves security and simplifies deployment.
Or programmatically:
```rust
use everruns_worker::{TaskWorkerConfig, WorkerAppBuilder};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
WorkerAppBuilder::new(TaskWorkerConfig::from_env())
.run()
.await
}
```
## Configuration
### Environment Variables
| Variable | Description | Default |
| ------------------------ | -------------------------------------------------------- | ---------------- |
| `RUNNER_MODE` | Runner mode (durable only) | `durable` |
| `DATABASE_URL` | PostgreSQL connection URL | Required |
| `SERVER_GRPC_ADDRESS` | Server gRPC address (`WORKER_GRPC_ADDRESS` legacy alias) | `127.0.0.1:9001` |
| `WORKER_GRPC_AUTH_TOKEN` | Bearer token for gRPC auth | Unset (disabled) |
| `WORKER_ID` | Unique worker identifier | Auto-generated |
| `MAX_CONCURRENT_TASKS` | Max tasks per worker | `1000` |
### Database Tables
The durable engine uses these tables (created by migration 002\_durable\_execution):
* `durable_workflow_instances` - Workflow state and metadata
* `durable_workflow_events` - Event sourcing log
* `durable_task_queue` - Distributed task queue
* `durable_dead_letter_queue` - Failed tasks for manual inspection
* `durable_workers` - Worker registration and heartbeats
* `durable_signals` - Workflow signals (cancel, custom)
* `durable_circuit_breaker_state` - Circuit breaker states
## Testing
### Unit Tests (No Dependencies)
```bash
cargo test -p everruns-durable --lib
```
Expected: 91+ tests passing
### Integration Tests (Requires PostgreSQL)
```bash
# Create test database
psql -U postgres -c "CREATE DATABASE everruns_test;"
# Run migrations (required for tests - server auto-migrates but tests don't start server)
DATABASE_URL="postgres://postgres:postgres@localhost/everruns_test" \
sqlx migrate run --source crates/server/migrations
# Run integration tests
DATABASE_URL="postgres://postgres:postgres@localhost/everruns_test" \
cargo test -p everruns-durable --test postgres_integration_test -- --test-threads=1
```
Expected: 17 tests passing
> **Note**: In production, migrations are auto-applied when `everruns-server` starts. For tests, we run migrations manually since tests don’t start the server.
## Workflow Lifecycle
1. **Message Created**: User sends message via API
2. **Workflow Started**: `DurableRunner` creates workflow and enqueues `process_input` task
3. **Input Processing**: Worker claims task, processes input, enqueues `reason` task
4. **LLM Reasoning**: Worker executes LLM call, may enqueue `act` tasks for tools
5. **Completion**: Workflow marked as `completed` after final response
## Monitoring
### Check Active Workflows
```sql
SELECT id, workflow_type, status, created_at
FROM durable_workflow_instances
WHERE status IN ('pending', 'running')
ORDER BY created_at DESC;
```
### Check Pending Tasks
```sql
SELECT id, workflow_id, activity_type, status, attempt
FROM durable_task_queue
WHERE status = 'pending'
ORDER BY created_at;
```
### Check Dead Letter Queue
```sql
SELECT id, workflow_id, activity_type, last_error, dead_at
FROM durable_dead_letter_queue
ORDER BY dead_at DESC;
```
### Check Worker Status
```sql
SELECT id, status, current_load, last_heartbeat_at
FROM durable_workers
WHERE status = 'active';
```
## Crash Recovery
The durable execution engine provides automatic crash recovery through:
### Worker Heartbeats
Workers send heartbeats every 10 seconds while executing tasks. If a worker crashes:
1. The task remains in `claimed` status with stale `heartbeat_at`
2. Control-plane background task detects stale tasks (30s threshold)
3. Stale tasks are automatically reset to `pending` status
4. Another worker can claim and retry the task
### Stale Task Reclamation
The control-plane runs a background task (every 10s) that:
* Finds tasks with `status = 'claimed'` and `heartbeat_at` older than 30s
* Resets them to `pending` status
* Logs reclaimed task IDs for monitoring
```sql
-- View tasks that may need reclamation
SELECT id, workflow_id, activity_type, claimed_by, heartbeat_at
FROM durable_task_queue
WHERE status = 'claimed'
AND heartbeat_at < NOW() - INTERVAL '30 seconds';
```
## Troubleshooting
### Worker Not Processing Tasks
1. Check worker is running and connected to correct `SERVER_GRPC_ADDRESS`
2. Verify `activity_types` match task types in queue
3. Check worker heartbeat in `durable_workers` table
### Workflows Stuck in Running
1. Check for claimed tasks that haven’t completed
2. Look for errors in worker logs
3. Check DLQ for failed tasks
4. Wait for stale task reclamation (30s threshold)
### Task Retries Exhausted
Tasks moved to DLQ after exhausting retries:
```sql
-- View DLQ entries
SELECT * FROM durable_dead_letter_queue ORDER BY dead_at DESC;
-- Requeue a task
UPDATE durable_dead_letter_queue SET requeued_at = NOW() WHERE id = '';
```
## Implementation Status
| Phase | Status | Description |
| --------- | --------------- | -------------------------------------------------------- |
| Phase 1-4 | ✅ Complete | Core abstractions, persistence, reliability, worker pool |
| Phase 5 | 🔄 Planned | Observability & Metrics (OpenTelemetry integration) |
| Phase 6 | 🔄 Planned | Scale Testing (1000+ concurrent workers) |
| Phase 7 | ✅ Core Complete | gRPC-based worker integration, crash recovery |
The durable execution engine is production-ready for single-instance deployments.
---
# Encryption Key Rotation
> Rotate the secrets encryption key (KEK): key deployment, data re-encryption, and old key removal.
Source:
This runbook describes how to rotate the secrets encryption key (KEK) used to encrypt sensitive data in the database.
## Overview
Everruns uses envelope encryption with versioned keys. Key rotation is a multi-phase process:
1. **Deploy new key** alongside old key
2. **Re-encrypt data** from old key to new key
3. **Remove old key** after all data is migrated
## Prerequisites
* Access to secrets management (environment config or secrets manager)
* Ability to run the admin container in production
* Ability to deploy application updates
* The `reencrypt-secrets` CLI tool (available in the admin container)
## Rotation Procedure
### Phase 1: Generate New Key
Generate a new encryption key with an incremented version:
```bash
# Generate new key (increment version number from current)
python3 -c "import os, base64; print('kek-v2:' + base64.b64encode(os.urandom(32)).decode())"
```
Store the output securely. Example output:
```plaintext
kek-v2:xR7qW2mN9pL4kJ8vB3tY6fE1hG5sD0cA9uI7oP2nM6w=
```
### Phase 2: Deploy with Both Keys
Update environment configuration:
```bash
# Current key becomes the new one
SECRETS_ENCRYPTION_KEY=kek-v2:xR7qW2mN9pL4kJ8vB3tY6fE1hG5sD0cA9uI7oP2nM6w=
# Previous key is preserved for decryption
SECRETS_ENCRYPTION_KEY_PREVIOUS=kek-v1:8B3uCQ4Znx45hl5nB+PKVriRrj/KtEVM+wBZ2VGa9vY=
```
Deploy the application with both keys configured. At this point:
* **New encryptions** use `kek-v2`
* **Existing data** encrypted with `kek-v1` is still decryptable
### Phase 3: Re-encrypt Existing Data
Use the `reencrypt-secrets` CLI tool to migrate all data to the new key.
#### Step 1: Dry Run (Preview Changes)
First, run in dry-run mode to see what would be re-encrypted:
```bash
docker run --rm \
-e DATABASE_URL="$DATABASE_URL" \
-e SECRETS_ENCRYPTION_KEY="kek-v2:..." \
-e SECRETS_ENCRYPTION_KEY_PREVIOUS="kek-v1:..." \
everruns-admin reencrypt --dry-run
```
Example output:
```plaintext
2024-01-15T10:00:00Z INFO Encryption service initialized. Primary key: kek-v2
2024-01-15T10:00:00Z INFO Available keys: ["kek-v2", "kek-v1"]
2024-01-15T10:00:00Z INFO Connected to database
2024-01-15T10:00:00Z INFO Processing table: llm_providers
2024-01-15T10:00:01Z INFO Would re-encrypt llm_providers.api_key_encrypted (id=..., current_key=kek-v1)
2024-01-15T10:00:01Z INFO DRY RUN: Would re-encrypt 42 of 100 records
```
#### Step 2: Execute Re-encryption
Once satisfied with the dry run, execute the actual re-encryption:
```bash
docker run --rm \
-e DATABASE_URL="$DATABASE_URL" \
-e SECRETS_ENCRYPTION_KEY="kek-v2:..." \
-e SECRETS_ENCRYPTION_KEY_PREVIOUS="kek-v1:..." \
everruns-admin reencrypt --batch-size 50
```
#### CLI Options
```plaintext
USAGE:
reencrypt-secrets [OPTIONS]
OPTIONS:
-n, --dry-run Show what would be changed without making changes
-b, --batch-size Process N records at a time (default: 100)
-t, --table Only process specified table (default: all)
-h, --help Show this help message
```
### Phase 4: Verify Migration
Confirm all data has been migrated by running another dry run:
```bash
docker run --rm \
-e DATABASE_URL="$DATABASE_URL" \
-e SECRETS_ENCRYPTION_KEY="kek-v2:..." \
-e SECRETS_ENCRYPTION_KEY_PREVIOUS="kek-v1:..." \
everruns-admin reencrypt --dry-run
```
Expected output:
```plaintext
2024-01-15T11:00:00Z INFO DRY RUN: Would re-encrypt 0 of 100 records
```
You can also verify directly in the database:
```sql
-- Check for any remaining records with old key
SELECT COUNT(*)
FROM llm_providers
WHERE api_key_encrypted::text LIKE '%"key_id":"kek-v1"%';
-- Should return 0
```
### Phase 5: Remove Old Key
Once verified, remove the old key from configuration:
```bash
# Remove the previous key
SECRETS_ENCRYPTION_KEY=kek-v2:xR7qW2mN9pL4kJ8vB3tY6fE1hG5sD0cA9uI7oP2nM6w=
# SECRETS_ENCRYPTION_KEY_PREVIOUS= (remove or leave empty)
```
Deploy the updated configuration.
**Important**: Keep the old key archived securely for disaster recovery. You may need it if backup restoration is required.
## Rollback Procedure
If issues occur during rotation:
### During Phase 2-3 (Both Keys Active)
No rollback needed - both keys work. Simply stop the re-encryption CLI if causing issues.
### After Phase 5 (Old Key Removed)
If old key was removed but some data wasn’t migrated:
1. Re-add the old key as `SECRETS_ENCRYPTION_KEY_PREVIOUS`
2. Deploy
3. Run the re-encryption CLI again
4. Verify again before removing
## Monitoring
During rotation, monitor:
* **CLI Progress Output**: The tool logs progress every 1000 records
* **API Error Rates**: Watch for decryption failures in application logs
* **Database Load**: Ensure re-encryption isn’t causing performance issues
## Emergency: Compromised Key
If a key is suspected compromised:
1. **Immediately** generate new key and deploy with both keys
2. Run re-encryption CLI with **highest priority**:
```bash
docker run --rm \
-e DATABASE_URL="$DATABASE_URL" \
-e SECRETS_ENCRYPTION_KEY="kek-v2:..." \
-e SECRETS_ENCRYPTION_KEY_PREVIOUS="kek-v1:..." \
everruns-admin reencrypt
```
3. Remove compromised key as soon as all data is migrated
4. Rotate any credentials that may have been exposed
## Key Storage Best Practices
* Store keys in a secrets manager (AWS Secrets Manager, HashiCorp Vault, etc.)
* Enable audit logging for key access
* Rotate keys on a regular schedule (e.g., annually)
* Keep previous key archived for disaster recovery (separate secure storage)
* Never commit keys to source control
---
# Tutorial: Build your first agent
> Create an Everruns agent in Python, send a message, and stream the response.
Source:
By the end of this tutorial you will have created an Everruns agent, started a session, sent a message, and streamed the response — using the official Python SDK.
This is a *tutorial*: a guided lesson. We make all the choices for you. When you want to do something different (different language, different tools, different patterns), follow up with the [How-to guides](https://docs.everruns.com/how-to/).
If you prefer a Jupyter notebook walkthrough, start with [Run an Agent](https://docs.everruns.com/tutorials/run-an-agent/) instead.
## What you’ll build
A research-assistant agent with web access. You’ll send it a topic and watch it answer.
## What you need
* A running Everruns instance. The easiest path is the [Docker Compose quickstart](https://docs.everruns.com/getting-started/docker-compose/).
* Python 3.10 or newer.
* An LLM provider configured (an OpenAI or Anthropic API key set in the Everruns UI).
```bash
pip install everruns-sdk
```
## Step 1 — Connect to the server
The `Everruns` client reads `EVERRUNS_API_KEY` and `EVERRUNS_API_URL` from the environment. For a local `just start-dev` deployment, API key `"dev"` works.
```python
import asyncio
from everruns_sdk import Everruns
client = Everruns(api_key="dev", base_url="http://localhost:9300/api")
```
## Step 2 — Create an agent
An **agent** is the configuration: a name, a system prompt, and a set of capabilities (tools).
```python
async def main():
agent = await client.agents.create(
name="Research Assistant",
system_prompt=(
"You are a research assistant. When given a topic, you:\n"
"1. Fetch relevant web pages\n"
"2. Save your notes to /workspace\n"
"3. Produce a concise summary"
),
capabilities=["web_fetch", "session_file_system", "current_time"],
)
print(f"Agent: {agent.id}")
asyncio.run(main())
```
The capabilities give the agent its tools: `web_fetch` to retrieve URLs, `session_file_system` for an isolated workspace, `current_time` to know what day it is.
## Step 3 — Start a session
A **session** is a working conversation with the agent. It owns the conversation history, an isolated virtual filesystem, and key/value storage.
```python
session = await client.sessions.create(
agent_id=agent.id,
title="Research: Durable Execution",
)
print(f"Session: {session.id}")
```
## Step 4 — Send a message
Sending a user message queues a durable workflow that runs the agent’s reason–act loop. The call returns immediately — the response arrives as events.
```python
await client.messages.create(
session.id,
"Research durable execution engines. What are the main approaches?"
)
```
## Step 5 — Stream the response
`client.events.stream(...)` returns an async iterator over typed events. It handles SSE reconnection, heartbeats, and resumption for you.
```python
async for event in client.events.stream(session.id):
if event.type == "output.message.delta":
print(event.data.get("delta", ""), end="", flush=True)
elif event.type == "tool.started":
tool = event.data.get("tool_call", {}).get("name", "")
print(f"\n [tool] {tool}")
elif event.type == "turn.completed":
print("\n[done]")
break
elif event.type == "turn.failed":
print(f"\n[failed: {event.data.get('error')}]")
break
```
You’ll see the agent’s reasoning stream token-by-token, with `[tool]` markers each time it fetches a URL or writes to its workspace.
## Step 6 — Put it together
Here’s the complete program:
```python
import asyncio
from everruns_sdk import Everruns
async def main():
client = Everruns(api_key="dev", base_url="http://localhost:9300/api")
agent = await client.agents.create(
name="Research Assistant",
system_prompt=(
"You are a research assistant. When given a topic, you:\n"
"1. Fetch relevant web pages\n"
"2. Save your notes to /workspace\n"
"3. Produce a concise summary"
),
capabilities=["web_fetch", "session_file_system", "current_time"],
)
session = await client.sessions.create(
agent_id=agent.id,
title="Research: Durable Execution",
)
await client.messages.create(
session.id,
"Research durable execution engines. What are the main approaches?",
)
async for event in client.events.stream(session.id):
if event.type == "output.message.delta":
print(event.data.get("delta", ""), end="", flush=True)
elif event.type == "tool.started":
tool = event.data.get("tool_call", {}).get("name", "")
print(f"\n [tool] {tool}")
elif event.type == "turn.completed":
print("\n[done]")
break
elif event.type == "turn.failed":
print(f"\n[failed: {event.data.get('error')}]")
break
await client.close()
asyncio.run(main())
```
Save as `tutorial.py` and run:
```bash
python tutorial.py
```
You should see the agent stream a research response, fetching a few URLs along the way.
## What just happened
You configured an agent (long-lived), started a session (per-conversation), and consumed the event stream (per-turn). Those three layers — configuration, runtime, data — are the spine of every Everruns application.
The agent loop you watched run is the **reason–act cycle**. The model reasons (returns text or tool calls), tools execute, results feed back in, repeat until the model produces a final answer. See [The agentic loop](https://docs.everruns.com/explanation/agentic-loop/) for the design.
## Next steps
Common follow-ups, each as a focused how-to:
* [Equip an agent with tools](https://docs.everruns.com/how-to/equip-agents-with-tools/) — explore the full capability catalog.
* [Define agents as files](https://docs.everruns.com/how-to/define-agents-as-files/) — version-control your agent definitions.
* [Stream events with the SDK](https://docs.everruns.com/how-to/stream-events/) — richer streaming patterns.
* [Handle errors and cancel turns](https://docs.everruns.com/how-to/handle-errors-and-cancellation/) — what to do when things go wrong.
* [Orchestrate multi-agent pipelines](https://docs.everruns.com/how-to/orchestrate-multi-agent-pipelines/) — chain agents together.
* [Publish an agent as a Slack app](https://docs.everruns.com/how-to/publish-to-slack/) — deploy to a channel.
For background, read [Core concepts](https://docs.everruns.com/explanation/concepts/) and [The agentic loop](https://docs.everruns.com/explanation/agentic-loop/).