# crates/agent reference

> Reference for the code under `crates/agent`: what it exports, how it is invoked, its options and defaults, and its error cases.

- Repository: egoist/lorca
- GitHub: https://github.com/egoist/lorca
- Human docs: https://grok-wiki.com/public/docs/egoist-lorca-2cf67495e5e6
- Complete Markdown: https://grok-wiki.com/public/docs/egoist-lorca-2cf67495e5e6/llms-full.txt

## Source Files

- `crates/agent/src/lib.rs`
- `docs/agent/harness.md`
- `docs/agent/providers.md`

---

---
title: "crates/agent reference"
description: "Reference for the code under `crates/agent`: what it exports, how it is invoked, its options and defaults, and its error cases."
---

`lorca-agent` (`crates/agent`) is Lorca’s Rust agent runtime: stream one assistant turn, run tool calls, drain steering/follow-up queues, and emit lifecycle events until the model stops. Depend on the Cargo package `lorca-agent`; import as `lorca_agent`.

```toml
lorca-agent = { path = "crates/agent" }
```

Smoke run (about one command once a key is set):

```bash
DEEPSEEK_API_KEY=… cargo run -p lorca-agent --example chat -- deepseek/deepseek-flash
```

## Surfaces

| Surface | Type | Role |
| --- | --- | --- |
| `agent_loop` / `run_agent_loop*` | functions | Stateless turn loop over an `AgentContext` |
| `Agent` | struct | Owns transcript + queues between prompts |
| `Provider` | trait | Model adapter → `AssistantEvent` stream |
| `Tool` | trait | Model-callable function with JSON Schema args |
| `AgentHarness` | struct | General agent: skills, templates, compaction, retry, hooks |

```text
Host (CLI / harness / your app)
        │
        ▼
 Agent / AgentHarness / run_agent_loop
        │
        ├─► Provider.stream(ModelRequest) ──► AssistantEvents
        ├─► Tool.execute(...)              ──► ToolResult
        └─► mpsc / EventBus                ──► AgentEvent / HarnessEvent
```

## Crate layout

:::files
crates/agent/
  Cargo.toml          # package name: lorca-agent
  examples/chat.rs    # terminal harness demo
  src/
    lib.rs            # re-exports
    agent.rs          # Agent, AgentOptions, AgentError
    agent_loop.rs     # loop, LoopHooks, LoopError
    harness/          # AgentHarness, factory, skills, templates
    providers/        # Anthropic, OpenAI-compat, ChatGPT, Grok
    tools/            # read, write, edit, bash, grep, find, ls
    compaction.rs
    retry.rs
    request.rs
    types.rs
:::

## Public exports (`lib.rs`)

### Core re-exports

| Export | Kind |
| --- | --- |
| `Agent`, `AgentError`, `AgentHandle`, `AgentMessageQueue`, `AgentOptions`, `QueueMode` | Agent API |
| `agent_loop`, `run_agent_loop`, `run_agent_loop_continue` | Loop entry points |
| `AgentContext`, `AgentLoopConfig`, `LoopHooks`, `NoHooks`, `EventSink` | Loop config/hooks |
| `ToolExecutionMode`, `TurnUpdate`, before/after tool contexts | Loop control |
| `Provider`, `ModelRequest`, `AssistantEvent`, `AssistantEventStream`, `ToolSpec` | Provider contract |
| `Tool`, `ToolError`, `ToolResult`, `ToolUpdateFn` | Tool contract |
| `AgentEvent`, `AgentMessage`, `AssistantMessage`, `StopReason`, `ThinkingLevel`, `Usage`, … | Transcript + events |
| `RequestOptions`, `RequestOptionsPatch`, `RequestHooks`, `ResponseInfo` | Per-call request settings |
| `RetryPolicy`, `is_retryable_error`, `is_context_overflow` | Retry helpers |
| `ModelInfo` (via `models`) | Catalog snapshot |
| `now_ms()` | Unix epoch ms |

### Modules (not all re-exported at crate root)

`harness`, `providers`, `tools`, `compaction`, `estimate`, `transform`, `schema`, `json`, `sse`, `login_shell`, `models`, `retry`, `request`.

Host code in `crates/cli` imports `lorca_agent` and wires providers/tools into turns; the CLI feature flag is `runner` → `dep:lorca-agent`.

## Invoke: three paths

### 1. `Agent` (multi-prompt conversation)

```rust
use std::sync::Arc;
use lorca_agent::providers::AnthropicProvider;
use lorca_agent::tools::coding_tools;
use lorca_agent::{Agent, AgentEvent, AgentOptions};
use tokio::sync::mpsc;

let provider = AnthropicProvider::deepseek(&std::env::var("DEEPSEEK_API_KEY")?, None);
let mut options = AgentOptions::new(Arc::new(provider));
options.system_prompt = "You are a concise coding assistant.".into();
options.tools = coding_tools(std::env::current_dir()?);
let mut agent = Agent::new(options);

let (tx, mut rx) = mpsc::channel(256);
tokio::spawn(async move { while rx.recv().await.is_some() {} });

let added = agent.prompt("Summarize README.md", tx).await?;
// agent.messages holds the full transcript
```

`prompt` accepts `&str`, `String`, `Vec<ContentPart>`, one `AgentMessage`, `Vec<AgentMessage>`, or `PromptInput::with_images(...)`.

Drain the event channel on another task. A held-but-unread receiver stalls the run when the buffer fills; a dropped receiver is fine.

### 2. Low-level loop (host owns transcript)

```rust
use lorca_agent::{agent_loop, AgentContext, AgentLoopConfig, AgentMessage};
use tokio_util::sync::CancellationToken;

let context = AgentContext {
    system_prompt: "...".into(),
    messages: history,
    tools,
    cache_points: vec![],
};
let config = AgentLoopConfig::new(provider);
let cancel = CancellationToken::new();
let (rx, join) = agent_loop(vec![AgentMessage::user("hi")], context, config, cancel);
let produced = join.await?;
```

### 3. `AgentHarness` (skills, compaction, model switch)

```rust
use std::sync::Arc;
use lorca_agent::harness::{
    build_system_prompt, load_skills, AgentHarness, EnvProviderFactory,
    HarnessOptions, ModelIdentity, ProviderFactory, SystemPromptParts,
};
use lorca_agent::tools::coding_tools;

let factory: Arc<dyn ProviderFactory> = Arc::new(EnvProviderFactory::new());
let provider = factory.provider(&ModelIdentity::parse("deepseek/deepseek-flash"), None)?;
let cwd = std::env::current_dir()?;
let skills = load_skills(&[cwd.join("skills")]);

let mut options = HarnessOptions::new(provider);
options.factory = Some(factory);
options.system_prompt = build_system_prompt(&SystemPromptParts {
    cwd: Some(&cwd),
    coding_tools: true,
    skills: &skills.skills,
    ..Default::default()
});
options.tools = coding_tools(cwd);
let mut harness = AgentHarness::new(options);

let result = harness.prompt("Summarize README.md").await?;
// result.outcome: Completed | Aborted | Failed { error } | Declined
```

Also: `harness.skill(name, extra)`, `harness.prompt_from_template(name, args)`, `harness.continue_run()`, `harness.compact(instructions)`.

## Options and defaults

### `AgentOptions`

| Field | Default |
| --- | --- |
| `provider` | required (`AgentOptions::new`) |
| `system_prompt` | `""` |
| `tools` | `[]` |
| `messages` | `[]` |
| `hooks` | `NoHooks` |
| `steering_mode` | `QueueMode::OneAtATime` |
| `follow_up_mode` | `QueueMode::OneAtATime` |
| `tool_execution` | `ToolExecutionMode::Parallel` |
| `retry` | `None` (one try) |
| `request` | empty `RequestOptions` |

### `HarnessOptions`

| Field | Default |
| --- | --- |
| `provider` | required |
| `factory` | `None` (`set_model` / `set_thinking_level` fail without it) |
| `thinking_level` | `None` |
| `system_prompt` | `""` |
| `tools` / `active_tools` | `[]` / `None` (all tools visible) |
| `resources` | empty skills + templates |
| `request` | empty |
| `retry` | `RetryPolicy::default()` — enabled, 3 retries, 1s base, 60s cap |
| `compaction` | enabled; `reserve_tokens: 16384`; `keep_recent_tokens: 20000`; `max_input_tokens: 0` |
| `steering_mode` / `follow_up_mode` | `QueueMode::All` |
| `tool_execution` | `Parallel` |
| `messages` | `[]` |

<Warning>
`Agent` defaults queue drain to `OneAtATime`. `AgentHarness` defaults to `All`. Match the host you are using before assuming drain behavior.
</Warning>

### `AgentLoopConfig`

| Field | Default |
| --- | --- |
| `hooks` | `NoHooks` |
| `tool_execution` | `Parallel` |
| `sink` | `None` |
| `retry` | `None` |
| `request` | empty |

### `RequestOptions`

| Field | Default | Effect |
| --- | --- | --- |
| `headers` | empty | Merged over adapter headers |
| `timeout` | `None` | Whole-request timeout |
| `session_id` | `None` | Sent as `x-session-affinity` / `prompt_cache_key` where supported |
| `metadata` | empty | Anthropic forwards `user_id` |
| `hooks` | `None` | `api_key`, `before_payload`, `after_response` |

### `RetryPolicy`

```rust
RetryPolicy { enabled: true, max_retries: 3, base_delay_ms: 1_000, max_delay_ms: Some(60_000) }
```

HTTP adapters also retry pre-stream failures (408/409/429/5xx/transport) with `max_retries` typically `2` and `DEFAULT_MAX_RETRY_DELAY_MS = 60_000`.

### `CompactionSettings`

Compaction runs when `context_tokens > window - reserve_tokens` (window must be known and non-zero).

## Handles while a run is active

### `AgentHandle`

| Method | Effect |
| --- | --- |
| `steer(message)` | Inject after current turn’s tools, before next model call |
| `follow_up(message)` | Inject when the run would otherwise stop |
| `abort()` | Cancel active run → `StopReason::Aborted` |
| `clear_*_queue()` / `clear_all_queues()` | Drop queued messages |
| `has_queued_messages()` / `is_running()` | Status |

### `HarnessHandle`

Same steering/follow-up/abort idea, plus `next_run(message)` (new run after current ends), `cancel_queued(id)`, and `queued()`. Each queue call returns a message id and emits `queue_update`.

## Events

### `AgentEvent` (loop / `Agent`)

Tagged `type` when serialized: `agent_start`, `agent_end`, `turn_start`, `turn_end`, `retry`, `message_start`, `message_update`, `message_end`, `tool_execution_start`, `tool_execution_update`, `tool_execution_end`.

### `HarnessEvent`

Adds `run_start` / `run_end`, `retry_scheduled`, `config_update`, `compaction_start` / `compaction_end`, `usage`, `handler_error`, plus renamed tool events (`tool_start` / `tool_update` / `tool_end`).

## Providers

`Provider::stream` never returns `Err`. Failures are `AssistantEvent::Error { message, aborted }`.

| Adapter | Auth | Default model | Notes |
| --- | --- | --- | --- |
| `AnthropicProvider::anthropic` | `x-api-key` | `claude-opus-5` | Messages API; server web search/fetch |
| `AnthropicProvider::deepseek` | `x-api-key` | `deepseek-flash` | `https://api.deepseek.com/anthropic` |
| `OpenAiCompatProvider` | bearer | (required) | `/chat/completions` |
| `OpenAiResponsesProvider` | bearer | (required) | API-key `/responses` |
| `ChatGptProvider` | OAuth `TokenSource` | `gpt-6-sol` | Codex responses backend |
| `GrokProvider` | OAuth `GrokTokenSource` | `grok-4.7` | `https://api.x.ai/v1` |

### `EnvProviderFactory` credentials

| Provider id | Key / tokens | Base URL override |
| --- | --- | --- |
| `deepseek` | `DEEPSEEK_API_KEY` | `DEEPSEEK_BASE_URL` (appends `/anthropic` if missing) |
| `anthropic` | `ANTHROPIC_API_KEY` | `ANTHROPIC_BASE_URL` |
| `openai` | `OPENAI_API_KEY` | `OPENAI_BASE_URL` (default `https://api.openai.com/v1`) |
| `chatgpt` | `with_chatgpt(TokenSource)` | — |
| `grok` | `with_grok(GrokTokenSource)` | `GROK_BASE_URL` |

`ModelIdentity::parse("provider/model")` splits on the first `/`.

`ThinkingLevel`: `off` | `minimal` | `low` | `medium` | `high` | `xhigh` | `max`. Adapters map to provider-native effort/budget knobs.

## Tools

`coding_tools(cwd)` returns seven tools bound to one working directory: `read`, `write`, `edit`, `bash`, `grep`, `find`, `ls`. Relative paths and `~` resolve via `resolve_to_cwd`.

`coding_tools_with_sessions(cwd, sessions)` uses a shared bash session and, on Unix, adds `bash_input` / `bash_output`.

Implement `Tool`: `name`, `description`, `parameters` (JSON Schema), `execute(...) -> Result<ToolResult, ToolError>`. Prefer `Err(ToolError)` over encoding failure in content. `ToolResult::terminating()` hints the loop to stop only when every tool in the batch sets it.

## Errors

### `AgentError`

| Variant | When |
| --- | --- |
| `Busy` | `prompt` / `continue_run` while a run is active — use `steer` / `follow_up` / wait |
| `NoMessages` | `continue_run` with empty transcript |
| `LastIsAssistant` | Transcript ends on assistant and neither steering nor follow-up is queued |

### `LoopError`

| Variant | When |
| --- | --- |
| `Empty` | Continue with no messages in context |
| `LastIsAssistant` | Continue from an assistant message without queued injects |

### `HarnessError`

| Variant | When |
| --- | --- |
| `Busy` | Run already active |
| `NoMessages` / `LastIsAssistant` | Same continue rules as `Agent` |
| `UnknownSkill` / `UnknownTemplate` | Name missing from `resources` |
| `NoFactory` | `set_model` / `set_thinking_level` without `factory` |
| `Provider(String)` | Factory/build failure (missing key, unknown provider, …) |
| `Compaction(String)` / `NothingToCompact` | Manual/auto compaction failure or empty cut |

### Provider / stream failures (not Rust `Err`)

| Case | Result |
| --- | --- |
| HTTP/auth/malformed response | `AssistantEvent::Error { aborted: false }` → `StopReason::Error` |
| Cancellation | `Error { aborted: true }` → `StopReason::Aborted` |
| Stream ends without terminal event | Loop synthesizes error (`Provider stream ended before completion`) or aborted |
| Transient pre-stream failure with retry | `AgentEvent::Retry` / `HarnessEvent::RetryScheduled`, then retry |
| Context overflow (harness) | Compact + one retry of the turn |

`is_retryable_error` matches overloaded/rate-limit/5xx/transport patterns and never quota/billing exhaustion.

`ToolError(String)` — tool execution failure; schema validation rejects bad args before `execute`.

## Stop reasons

| `StopReason` | Meaning |
| --- | --- |
| `Stop` | Normal end |
| `Length` | Hit max tokens / incomplete |
| `ToolUse` | Assistant emitted tool calls |
| `Error` | Provider/runtime failure |
| `Aborted` | Cancellation |

## Verification

1. Unit/integration: `cargo test -p lorca-agent`
2. Live DeepSeek (ignored): `DEEPSEEK_API_KEY=… cargo test -p lorca-agent live_deepseek -- --ignored --nocapture`
3. Interactive: `cargo run -p lorca-agent --example chat -- deepseek/deepseek-flash` — expect streamed text and `[tool]` lines

## Related pages

<CardGroup>
  <Card title="Architecture" href="/architecture">
    How agent, CLI, relay, and clients bound each other.
  </Card>
  <Card title="crates/cli reference" href="/ref-crates-cli">
    How the CLI hosts `lorca_agent` for turns, tools, and providers.
  </Card>
  <Card title="crates/provider-auth reference" href="/ref-crates-provider-auth">
    OAuth token helpers used by ChatGPT and Grok adapters.
  </Card>
  <Card title="Configuration reference" href="/configuration-reference">
    Env keys and runtime settings the host layers on this crate.
  </Card>
</CardGroup>

Next: open `crates/agent/examples/chat.rs` and run `cargo run -p lorca-agent --example chat -- deepseek/deepseek-flash` with `DEEPSEEK_API_KEY` set.
