> ## Documentation Index
> Fetch the complete documentation index at: https://anyagent.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Features

> Everything anyagent does. Each section says what it is for, how to use it, what confirms it, and which capability gates it.

Every section has the same four parts: **what** it does, **how** to call it,
what **confirms** it happened, and what **gates** it. Types and signatures are
on the [Core API](/core-api) page. Which agents support what is on the
[Agents](/agents#capability-matrix) page.

```text theme={"theme":"vesper"}
Runtime   discover · probe · open · generate · plan_usage
Session   prompt · answer · configure · cancel · rollback · compact · close
Events    text · tools · plans · requests · status · usage · turn boundaries
```

## Discovery

**What:** find every supported agent on the machine, instantly, without
launching anything. Checks env overrides, `PATH`, the login-shell `PATH`
(GUI apps often miss it), and known install directories. Agents that are
supported but missing come back with an install hint.

```rust theme={"theme":"vesper"}
let report = runtime.discover().await;
for agent in &report.agents {
    println!("{} at {}", agent.name, agent.executable_path.display());
}
for missing in &report.missing {
    println!("{} not installed: {}", missing.name, missing.install_hint);
}
let agent = report.require("claude")?;   // AgentError::NotInstalled if absent
```

**Confirms:** the report itself. Discovery never guesses login state; `probe`
or `probe_auth` asks the agent.
**Gates:** none. Call `runtime.prewarm()` at startup to scan early.

**Not found?** `MissingAgent.searched` lists every directory that was checked,
so an app can show it and offer a file picker. A user with an unusual install
sets `ANYAGENT_<AGENT>_BIN` (see [Agents](/agents#pointing-at-a-specific-binary)).
An installer that edits the user's `PATH` is invisible to an app that is
already running: say "installed? restart the app", not "not found".

Some agents have a richer, separately installed runtime (Antigravity's ACP
server). When only the base CLI is found, `agent.upgrade` names what to
install for more capabilities.

## Probe and auth

**What:** ask the agent itself what it can do. Opens a throwaway session
(about 1 s) and returns version, real login state, capabilities, config
options, and slash commands. `probe_auth` is the cheaper call when you only
need the login state.

```rust theme={"theme":"vesper"}
let details = runtime.probe(&agent).await?;
match &details.auth {
    AuthStatus::Authenticated { kind, account } => {}       // Subscription | ApiKey | CloudProvider
    AuthStatus::Unauthenticated { login } => {
        for method in login {
            match method {
                LoginMethod::Terminal { command, .. } => ui.show_command(command),  // e.g. ["claude", "auth", "login"]
                LoginMethod::EnvVar { name } => ui.show_env_var(name),
                _ => {}
            }
        }
    }
    AuthStatus::Unknown => {}
}
```

**Confirms:** `AgentDetails`. The same struct is available later as
`session.info().details`.
**Gates:** none.

anyagent never reads or holds credentials and never drives a login flow. It
tells you the exact command or env var to show the user. Keep several
logins of one agent apart with `SessionOptions::config_home(dir)`.

## Sessions

**What:** one live conversation with one agent process. `open` spawns the
agent, handshakes, and returns two halves: `Session` (commands, cheap to
clone) and `Events` (the stream, one consumer).

```rust theme={"theme":"vesper"}
let (session, events) = runtime
    .open(&agent, SessionOptions::in_dir("/path/to/repo"))
    .await?;
```

**Confirms:** `open` returns. `session.info()` has the resume token,
capabilities, and config options.
**Gates:** none. Fails typed: `AuthRequired { login }` when logged out,
`HandshakeTimeout` after 30 s, `ResumeFailed` for a bad token.

Drain `Events` continuously. It buffers 1024 events; a consumer that falls a
full buffer behind is treated as gone and the session closes. `close()` ends
the process; dropping `Events` does the same.

## Prompting

**What:** send text, slash commands, and file attachments. What happens
depends on session state, and the returned `Delivery` tells you which.

```rust theme={"theme":"vesper"}
let delivery = session
    .prompt(Input::text("what's wrong here?").attach("screenshot.png"))
    .await?;
match delivery.kind {
    DeliveryKind::Started { turn_id } => {}      // session was idle; a turn began
    DeliveryKind::Steered { turn_id } => {}      // a turn was running; this went into it
    DeliveryKind::Queued { position } => {}      // a turn was running; this waits its turn
}
```

| Session state | Agent has `Steer` | Result                                                  |
| ------------- | ----------------- | ------------------------------------------------------- |
| Idle          | any               | `Started`                                               |
| Turn running  | yes               | `Steered`: the agent changes course mid-turn            |
| Turn running  | no                | `Queued`: starts its own turn when the current one ends |

**Confirms:** `TurnStarted { origin: Prompt(id) }` for started and queued
prompts.
**Gates:** `Steer` decides steer vs queue. `Images` decides whether an
attachment goes inline or degrades to a path in the prompt text.

Drop a queued prompt before it starts with `session.dequeue(prompt_id)`.
Slash commands from `details.commands` are sent as plain text:
`session.prompt("/review")`. An unknown slash prompt is just text.

## Permissions and questions

**What:** the agent's "may I run this?" and "which one?" moments, forwarded
as typed requests with the choices the agent actually offers. Answer exactly
once.

```rust theme={"theme":"vesper"}
EventKind::RequestOpened(Request::Permission(r)) => {
    // r.tool is the ToolUpdate awaiting approval; r.options is what the agent accepts
    session.answer(r.id, Answer::Permission(PermissionChoice::AllowOnce)).await?;
}
EventKind::RequestOpened(Request::Question(r)) => {
    // one QuestionAnswer per question, in order; Choices(ids) or Text(string)
    let answers = r.questions.iter().map(|q| QuestionAnswer::Choices(vec![q.choices[0].id.clone()])).collect();
    session.answer(r.id, Answer::Question(answers)).await?;
}
EventKind::RequestClosed { request_id } => ui.clear(request_id),
```

**Confirms:** `RequestClosed`. It also arrives when a cancelled turn
withdraws the request, so clear your UI on that event, not on your own
answer. While a request is open, `session.status()` is `NeedsInput`.
**Gates:** `Permissions` and `Questions`. An agent without `Permissions`
runs tools without asking.

For unattended runs, open with
`SessionOptions::permission_mode(PermissionMode::AutoApprove)`: anyagent
allows each request once without forwarding it.

## Cancel

**What:** stop the running turn. The session and its context survive.

```rust theme={"theme":"vesper"}
session.cancel(false).await?;   // stop the turn, keep queued prompts
session.cancel(true).await?;    // stop the turn, drop the queue too
```

**Confirms:** `TurnEnded { stop: Cancelled }`, then `RequestClosed` for any
request the turn had open.
**Gates:** none.

## Configuration

**What:** models, effort, mode, sandbox, switched without a restart. Agents
advertise their settings as `ConfigOption`s with typed choices, so a picker
renders what the agent offers instead of a hardcoded list.

```rust theme={"theme":"vesper"}
// at open: creation-only and live options alike
let options = SessionOptions::in_dir(".").configure("model", "sonnet").configure("effort", "low");

// mid-session: options with `live: true`
session.configure("fast", true).await?;
```

| id        | Kind    | Notes                                                                 |
| --------- | ------- | --------------------------------------------------------------------- |
| `model`   | select  | Switching it rebuilds the other options' choices                      |
| `effort`  | select  | Choices follow the model; a model without levels drops the option     |
| `fast`    | boolean | Only when the selected model supports it. Lower latency, higher usage |
| `mode`    | select  | Agent-specific: `plan`, `accept-edits`, `ask`, …                      |
| `sandbox` | select  | Codex                                                                 |

**Confirms:** `SessionUpdated`, carrying the new `configuration` and the
rebuilt `config_options`. Wait for it rather than for `configure` to return.
**Gates:** the option must be in `details.config_options`, else
`InvalidConfiguration`.

## Status

**What:** the one value a thread list needs. `Idle`, `Working`, or
`NeedsInput`, pushed on every change and readable on demand.

```rust theme={"theme":"vesper"}
EventKind::StatusChanged(status) => ui.set_badge(thread, status),
let now = session.status();    // for a component that mounts late
```

**Confirms:** `StatusChanged` is emitted only on change. A turn ending with
another prompt queued stays `Working`, never flashing `Idle`.
**Gates:** none.

## One-shot generation

**What:** prompt in, string out, no session to manage. For thread titles,
commit messages, branch names, PR bodies.

```rust theme={"theme":"vesper"}
let title = runtime
    .generate(&agent, SessionOptions::in_dir("."), "a five-word title for: fix the parser")
    .await?;
```

It opens a throwaway session, disables tools where the wire allows (claude,
pi) and declines every permission elsewhere, collects the text until the
turn ends, and closes.

**Confirms:** the returned `String`.
**Gates:** `Permissions`, or a wire that can launch without tools. Requires
a new session: `resume` and `fork_from` options are rejected. A tool event or
a question needing a choice cancels generation. Put context inline;
attachments cannot be opened without tools.

## Resume

**What:** continue a conversation from a new process. Every session mints an
opaque `resume_token`; store it with your transcript.

```rust theme={"theme":"vesper"}
let token = session.info().resume_token.clone().unwrap();   // grab it while alive
// later, any process
let (session, events) = runtime
    .open(&agent, SessionOptions::in_dir(".").resume(token))
    .await?;
```

**Confirms:** `open` returns; the agent has its context back. No old events
are replayed, so keep your own transcript.
**Gates:** `Resume`, else `ResumeFailed`. The token is agent-owned: store it
as-is, never parse it. The session id is not a resume token.

## Fork

**What:** a new session starting from an old one's history. The original is
untouched, which is what "try this a different way" needs.

```rust theme={"theme":"vesper"}
let options = SessionOptions::in_dir(".").fork_from(token, Some(message_id));  // None = from the end
let (branch, branch_events) = runtime.open(&agent, options).await?;
```

**Confirms:** `open` returns a new session with its own resume token.
**Gates:** `Fork`.

## Rollback

**What:** rewind this session in place by whole turns.

```rust theme={"theme":"vesper"}
session.rollback(NonZeroU32::new(2).unwrap(), RollbackScope::Conversation).await?;
session.rollback(NonZeroU32::new(1).unwrap(), RollbackScope::ConversationAndFiles).await?;
```

| Scope                  | Rewinds                                   | Gate            |
| ---------------------- | ----------------------------------------- | --------------- |
| `Conversation`         | The agent's context only                  | `Rollback`      |
| `ConversationAndFiles` | Context and the files those turns changed | `RollbackFiles` |

**Confirms:** `SessionUpdated`. A refusal comes back as a `Diagnostic`, not
an error from the call.
**Gates:** above, plus an idle session (`SessionBusy` otherwise).

<Warning>
  `ConversationAndFiles` writes to the working tree. Check `RollbackFiles`
  before offering the button and tell the user what it does.
</Warning>

## Compact

**What:** ask the agent to summarize its own context, freeing room in the
window without losing the thread.

```rust theme={"theme":"vesper"}
session.compact().await?;
```

**Confirms:** `ContextCompacted`, then a lower `ContextUsage`. Compaction
runs as an agent-originated turn: prompts sent meanwhile queue behind it. An
agent that finds nothing to summarize says so as a `Diagnostic`.
**Gates:** `Compact` (claude, codex, opencode, pi) and an idle session.

## Subagents

**What:** agents that spawn agents render as a tree, not interleaved noise.

```rust theme={"theme":"vesper"}
EventKind::ToolUpdated(tool) if tool.kind == ToolKind::Subagent => ui.open_child(tool.id),
// every event the child produces:
let parent = event.turn_info.as_ref().and_then(|t| t.parent_tool_id.clone());
```

**Confirms:** `parent_tool_id` on the child's events. A child can never end
the parent's turn; anyagent consumes the child's turn bookkeeping.
**Gates:** `Subagents`.

## Context and plan usage

**What:** the two gauges apps show. They measure different things.

| Gauge         | Source                                                             | Measures                                                   |
| ------------- | ------------------------------------------------------------------ | ---------------------------------------------------------- |
| Context usage | `EventKind::ContextUsage { used_tokens, window_tokens, cost_usd }` | How full this session's window is                          |
| Plan usage    | `runtime.plan_usage(&agent)`, `EventKind::PlanUsageUpdated`        | How much of the account's subscription is used, per window |

```rust theme={"theme":"vesper"}
let usage = runtime.plan_usage(&agent).await?;        // plan name, 5-hour and weekly windows
for entry in runtime.plan_usage_all().await {          // every installed agent, for a dashboard
    ui.card(&entry.agent.name, entry.usage);
}
```

**Confirms:** the events and return values above. Numbers come from the
agent itself: no HTTP endpoints, no token handling.
**Gates:** `ContextUsage` and `PlanUsage`. Plan usage also needs a
subscription login (`AuthKind::Subscription`).

## MCP servers

**What:** hand the agent your app's MCP servers for this session.

```rust theme={"theme":"vesper"}
let options = SessionOptions::in_dir(".")
    .mcp_server(McpServer::stdio("local", "/usr/bin/my-server", ["--stdio"]))
    .mcp_server(McpServer::http("remote", "https://example.com/mcp").with("Authorization", "Bearer …"))
    .mcp_server(McpServer::sse("stream", "https://example.com/sse"));
```

**Confirms:** calls into your servers arrive as `ToolUpdated` with
`ToolKind::Mcp { server, tool }`.
**Gates:** the agent's supported transports. An unsupported transport fails
`open` typed rather than dropping the server.

## Wire recording

**What:** a bug report that contains the actual bug. Tees every raw protocol
frame, both directions, to a JSONL file.

```rust theme={"theme":"vesper"}
SessionOptions::in_dir(".").record_wire("/tmp/claude.jsonl")
```

**Confirms:** the file. A recording failure never fails a turn.
**Gates:** none.

<Warning>
  The recording is unredacted: prompts, file contents, command output, paths.
  Treat it as sensitive and delete it after debugging.
</Warning>

## Testing without a subprocess

**What:** run your app over a scripted agent. The engine, turn rules, and
event shapes are real; only the agent is fake.

```toml Cargo.toml theme={"theme":"vesper"}
[dev-dependencies]
anyagent = { version = "0.0.1", features = ["mock"] }
```

```rust theme={"theme":"vesper"}
use anyagent::mock::{Script, Step, completed, permission, text};

let script = Script::default().turn(vec![
    Step::Emit(text("m1", "hello")),     // TextDelta
    Step::Emit(permission("p1")),        // RequestOpened
    Step::AwaitAnswer,                   // pause until your app answers
    Step::End(completed()),              // TurnEnded
]);
let runtime = Runtime::with_mock(script);
let agent = runtime.discover().await.require("mock")?.clone();
```

**Confirms:** the same events your real code handles.
**Gates:** feature `mock`. `Script` flags (`steer`, `deterministic`,
`buffer`, …) model the wire shapes the engine has to cope with.
