yoagent 0.25.13

The agent loop for Rust: stream from 7 LLM protocols, run tools, loop until done. Native or wasm32 (Cloudflare Workers).
Documentation
# Architecture Overview

## Layered Design

yoagent is organized as three conceptual layers within a single crate. Dependencies flow strictly downward — upper layers use lower layers, never the reverse.

```
┌─────────────────────────────────────────────┐
│  Layer 3: Orchestration   (partly planned)   │
│  Delegation today; work modes planned        │
├─────────────────────────────────────────────┤
│  Layer 2: Agent + Providers                  │
│  Concrete providers, tools, retry, caching,  │
│  context management, MCP                     │
├─────────────────────────────────────────────┤
│  Layer 1: Core Loop                          │
│  agent_loop, types, traits                   │
│  Provider-agnostic. Tool-agnostic.           │
└─────────────────────────────────────────────┘
```

### Layer 1: Core Loop

The pure agent loop. No opinions about LLMs, no built-in tools. Just the control flow.

**Modules:** `types.rs`, `agent_loop.rs`, `provider/traits.rs`

**Owns:**
- `agent_loop()` / `agent_loop_continue()` — the loop itself
- `AgentTool` trait — interface tools must implement
- `StreamProvider` trait — interface providers must implement
- `AgentMessage`, `AgentEvent`, `StreamDelta` — message & event types
- `AgentContext` — system prompt + messages + tools
- Tool execution strategies (parallel/sequential/batched)
- Streaming tool output (`ToolUpdateFn`)
- Steering & follow-up message injection

**Does not own:** Any concrete provider or tool implementation.

### Layer 2: Agent + Providers

Batteries-included single-agent layer. Most users interact with this.

**Modules:** `agent.rs`, `context.rs`, `retry.rs`, `provider/*.rs`, `tools/*.rs`, `mcp/*.rs`

**Adds on top of Layer 1:**
- Concrete providers — Anthropic, OpenAI-compat, OpenAI Responses, Azure OpenAI, Google Gemini, Vertex, Bedrock (7 protocols)
- Provider registry — dispatch by API protocol
- Prompt caching — automatic cache breakpoint placement
- Retry with backoff — exponential, jitter, respects retry-after
- Context management — token estimation, smart truncation, execution limits
- Built-in tools — bash, read_file, write_file, edit_file, list_files, search (feature `native`)
- MCP client — stdio (feature `native`) + HTTP transports, tool adapter
- `ToolSource` — per-run tool sources, consulted at the start of every run
- `rt` — runtime shims (Tokio on native targets, the host executor on wasm32)
- `Agent` struct — stateful builder wrapping it all together

### Layer 3: Orchestration (partly planned)

Delegation exists today as `SubAgentTool` (a tool that runs a child loop with its own model, tools and limits; nestable) with `SharedState` for passing artifacts by reference. A higher-level `Orchestrator` with work modes is not implemented.

**Planned capabilities:**
- `Orchestrator` struct — spawn, delegate, and coordinate multiple agents
- Work modes:
  - **Interactive** — multi-turn, human in the loop (current default)
  - **Autonomous** — runs to completion without input (background tasks, CI)
  - **Pipeline** — input → output, chainable (scan → fix → verify)
  - **Supervisor** — delegates to other agents, synthesizes results
- Fan-out — same task to multiple agents (different providers for diversity)
- Pipeline chaining — output of agent A feeds input of agent B
- Agent communication through the orchestrator event bus

**Why not yet:** Multi-agent orchestration adds complexity. The single-agent loop handles 95% of use cases. Layer 3 will be built when a concrete use case drives it, not speculatively.

---

## Module Layout

```
yoagent/
├── src/
│   ├── lib.rs                  # Public re-exports
│   │
│   │── Layer 1: Core Loop ─────────────────────
│   ├── types.rs                # Message, Content, AgentTool, AgentEvent
│   ├── agent_loop.rs           # Core loop: prompt → LLM → tools → repeat
│   │
│   ├── rt.rs                   # spawn/sleep/timeout/Instant, MaybeSend/MaybeSync
│   ├── tool_source.rs          # ToolSource (tools resolved per run)
│   ├── extension.rs            # Extension / RunHooks, Budget (the plug-in contract)
│   │
│   │── Layer 2: Agent + Providers ─────────────
│   ├── agent.rs                # Agent struct (stateful wrapper)
│   ├── context.rs              # Token estimation, compaction, limits
│   ├── llm_compaction.rs       # LlmCompaction (background LLM summaries)
│   ├── retry.rs                # Retry with exponential backoff
│   ├── sub_agent.rs            # SubAgentTool (delegation to a child loop)
│   ├── shared_state.rs         # SharedState + backends
│   ├── session.rs              # Session trees, JSONL persistence
│   ├── skills.rs               # AgentSkills SKILL.md loading
│   ├── gasp.rs                 # GASP run recording (feature gasp)
│   ├── provider/
│   │   ├── traits.rs           # StreamProvider trait, StreamEvent, ProviderError
│   │   ├── model.rs            # ModelConfig, ApiProtocol, OpenAiCompat
│   │   ├── registry.rs         # ProviderRegistry (protocol → provider)
│   │   ├── anthropic.rs        # Anthropic Messages API
│   │   ├── openai_compat.rs    # OpenAI Chat Completions (15+ providers)
│   │   ├── openai_responses.rs # OpenAI Responses API
│   │   ├── azure_openai.rs     # Azure OpenAI
│   │   ├── responses_request.rs # Request body shared by Responses and Azure
│   │   ├── responses_stream.rs # SSE parser shared by Responses and Azure
│   │   ├── google.rs           # Google Generative AI
│   │   ├── google_vertex.rs    # Google Vertex AI
│   │   ├── bedrock.rs          # AWS Bedrock ConverseStream
│   │   ├── eventstream.rs      # AWS binary eventstream decoder
│   │   ├── sigv4.rs            # SigV4 request signing
│   │   ├── turn_hook.rs        # TurnHookProvider
│   │   ├── tool_args.rs        # Tool-argument parsing
│   │   ├── prices.rs           # PriceTable (+ prices/global.rs, prices/fetch.rs)
│   │   ├── prices.json         # Bundled price snapshot (applied only on opt-in)
│   │   ├── mock.rs             # Mock provider for testing
│   │   └── sse.rs              # SSE utilities (crate-private)
│   ├── tools/
│   │   ├── bash.rs             # BashTool (native)
│   │   ├── file.rs             # ReadFileTool, WriteFileTool (native)
│   │   ├── edit.rs             # EditFileTool (native)
│   │   ├── list.rs             # ListFilesTool (native)
│   │   ├── search.rs           # SearchTool (native)
│   │   ├── sandbox.rs          # PathSandbox
│   │   └── shared_state_tool.rs # SharedStateTool
│   ├── mcp/
│   │   ├── client.rs           # MCP client (stdio on native, HTTP everywhere)
│   │   ├── tool_adapter.rs     # McpToolAdapter (MCP tool → AgentTool)
│   │   ├── transport.rs        # Transport implementations
│   │   └── types.rs            # MCP protocol types
│   ├── openapi/                # OpenAPI 3.0 → tools (feature openapi)
│   └── decision/               # Decision models (feature decision)
```

## Data Flow

```
                    ┌─────────────┐
                    │   Caller    │
                    └──────┬──────┘
                           │ prompt / prompt_messages
                    ┌──────▼──────┐
                    │    Agent    │  Layer 2: stateful wrapper
                    │  (agent.rs) │  Manages queues, tools, state
                    └──────┬──────┘
                           │
                    ┌──────▼──────┐
                    │ agent_loop  │  Layer 1: core loop
                    │             │  Prompt → LLM → Tools → Repeat
                    └──┬───────┬──┘
                       │       │
              ┌────────▼──┐ ┌──▼────────┐
              │ Provider  │ │   Tools   │  Layer 2: implementations
              │ .stream() │ │ .execute()│
              └────────┬──┘ └──┬────────┘
                       │       │
              ┌────────▼──┐ ┌──▼────────┐
              │ LLM API   │ │ OS / FS   │
              │ (HTTP)    │ │ (shell)   │
              └───────────┘ └───────────┘

Events flow back via mpsc::UnboundedSender<AgentEvent>
```

## How Providers Plug In

1. Implement `StreamProvider` trait (Layer 1 interface)
2. Register with `ProviderRegistry` under an `ApiProtocol` (Layer 2)
3. Set `ModelConfig.api` to match that protocol
4. The registry dispatches `stream()` calls to the right provider

Each provider translates between yoagent's `Message`/`Content` types and the provider's native API format. All providers emit `StreamEvent`s through the channel for real-time updates.

## How Tools Plug In

1. Implement `AgentTool` trait (Layer 1 interface)
2. Add to the tools vec (via `default_tools()` or custom)
3. The agent loop converts tools to `ToolDefinition` (name, description, schema) for the LLM
4. When the LLM returns `Content::ToolCall`, the loop finds the matching tool and calls `execute()`
5. Results are wrapped in `Message::ToolResult` and added to context

Tools receive a `CancellationToken` child token — they should check it for cooperative cancellation during long operations.

## Design Principles

- **Layers are conceptual, not physical.** One crate, clean module boundaries. Feature flags only gate host capabilities (`native`) and optional integrations (`openapi`, `gasp`, `decision`).
- **Dependencies flow down.** Layer 1 never imports from Layer 2. Layer 2 never imports from Layer 3.
- **Layer 1 is stable.** The core loop and traits change rarely. New features are added in Layer 2 or 3.
- **Build what's needed.** Layer 3's `Orchestrator` is designed but not implemented. It will be built when a use case demands it, not speculatively.
- **Simple over clever.** A straightforward loop with good defaults beats an elegant abstraction nobody can debug.