yoagent

mcp
Guvenlik Denetimi
Gecti
Health Gecti
  • License — License: MIT
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Community trust — 173 GitHub stars
Code Gecti
  • Code scan — Scanned 8 files during light audit, no dangerous patterns found
Permissions Gecti
  • Permissions — No dangerous permissions requested

Bu listing icin henuz AI raporu yok.

SUMMARY

The agent loop for Rust — stream from 7 LLM protocols, run tools, loop until done.

README.md
yoagent

crates.io · Docs · API · GitHub · DeepWiki · Changelog





The agent loop for Rust. Stream from any of 7 LLM protocols, run tools, loop until done.

The yoagent loop: prompt, LLM stream, tool execution, loop

Try it in one command — no API key

git clone https://github.com/yologdev/yoagent && cd yoagent
ollama serve &                                    # any local model works
cargo run --example cli -- --provider ollama

That's a working coding agent in your terminal — file read/write/edit, shell, ripgrep search,
streaming output, skills. No signup, no key, nothing to configure.

  yoagent cli — mini coding agent
  Type /quit to exit, /clear to reset

  model: llama3.1:8b
  cwd:   /home/user/my-project

> find all TODO comments in src/

  ▶ search 'TODO' ✓

Found 3 TODOs:
  src/main.rs:42: // TODO: handle edge case
  src/lib.rs:15:  // TODO: add tests
  src/utils.rs:8: // TODO: optimize this

  tokens: 1250 in / 89 out

Point it at a hosted model instead by swapping the flag:

ANTHROPIC_API_KEY=sk-... cargo run --example cli
GROQ_API_KEY=...        cargo run --example cli -- --provider groq --model llama-3.3-70b-versatile
cargo run --example cli -- --api-url http://localhost:1234/v1 --model my-model   # LM Studio, llama.cpp, vLLM

Install

[dependencies]
yoagent = "0.15"
tokio = { version = "1", features = ["full"] }

Quick start

An agent that actually uses a tool — the thing the crate exists for:

use yoagent::provider::ModelConfig;
use yoagent::{tools, Agent, AgentEvent, StreamDelta};

#[tokio::main]
async fn main() {
    // The provider is selected from the config's protocol and the key is read
    // from ANTHROPIC_API_KEY. Call `.with_api_key(k)` to pass one explicitly.
    let mut agent = Agent::from_config(ModelConfig::claude_sonnet_5())
        .with_system_prompt("You are a coding assistant.")
        .with_tools(tools::default_tools());

    let mut events = agent.prompt("Find every TODO in src/ and summarise them").await;

    while let Some(event) = events.recv().await {
        match event {
            AgentEvent::MessageUpdate { delta: StreamDelta::Text { delta }, .. } => print!("{delta}"),
            AgentEvent::ToolExecutionStart { tool_name, .. } => println!("\n▶ {tool_name}"),
            AgentEvent::AgentEnd { .. } => break,
            _ => {}
        }
    }
    agent.finish().await;
}

Swap the model by swapping the config — the provider follows, and the key is read from that
provider's conventional env var:

Agent::from_config(ModelConfig::groq("llama-3.3-70b-versatile", "Llama 3.3 70B")); // GROQ_API_KEY
Agent::from_config(ModelConfig::google("gemini-2.5-pro", "Gemini 2.5 Pro"));       // GEMINI_API_KEY
Agent::from_config(ModelConfig::ollama("http://localhost:11434", "llama3.1:8b"));  // no key

How yoagent differs

yoagent is deliberately narrow. It is the loop, tool execution, and the machinery you need to
run that loop in production. It ships no vector stores, embedding pipelines, or task-graph
layer — if your problem is retrieval or orchestration, one of these is the better fit:

If you need Look at
RAG pipelines, vector stores, embeddings, transcription and image generation rig"Build modular and scalable LLM Applications in Rust"
Typed task graphs and streaming RAG indexing alongside agents swiftide"Composable LLM agents and harness, typed task graphs, and streaming RAG pipelines in Rust"
A tool-calling loop you host, gate, steer, branch, and record yoagent

What that focus bought:

  • The loop is a free function. agent_loop() is stateless and takes
    everything it needs as arguments. Agent is an optional wrapper that adds history and queues.
    You can drive the loop yourself without adopting our state model.
  • 7 native wire protocols, not one OpenAI-compat shim with adapters bolted on. Anthropic
    Messages, OpenAI Completions, OpenAI Responses, Azure, Gemini, Vertex, and Bedrock each have a
    real implementation, so provider-specific features (thinking budgets, prompt-cache breakpoints,
    reasoning deltas) survive instead of being flattened away.
  • Every tool call passes one gate. ToolMiddleware can allow, modify, or deny each call
    at a single choke point shared by all execution strategies — the mechanism behind approval
    prompts and policy engines.
  • Steer a run that's already going. Inject guidance mid-flight; it's picked up between tool
    batches without restarting the turn.
  • History is a tree, not a list. Session forks, checkpoints, and seeks.
    Edit an earlier turn and re-run it without destroying the original branch.
  • Runs are recordable. With features = ["gasp"], a run becomes an append-only semantic
    event log in a git repo — restore is clone + replay. Conformance-checked in CI.
  • The whole loop is testable offline. MockProvider scripts multi-turn tool-calling
    conversations and honours cancellation, so abort and steering paths are testable with no
    network. 456 of our 463 tests need no key.

Built with yoagent

yoyo-evolve — a coding
agent that evolves its own source in public. It began as 200 lines of Rust; every commit since has
been agent-written and gated on tests. It runs on this loop with the openapi feature enabled.

Also built on yoagent:

Project What it is
rab A lightweight, extensible Rust coding agent
greatsage "Rimuru's Unique Skill, you know the one"
yoclaw OpenClaw reborn in Rust — a single-binary agent that remembers you

Built something on yoagent? Open a PR and add it here — we'd like to see it.


What's in the box

The loop & control
  • Full event stream: AgentStartTurnStartMessageUpdate (deltas) → ToolExecution*TurnEndAgentEnd
  • Parallel tool execution by default; Sequential and Batched { size } strategies available
  • Steering — interrupt mid-run; follow-ups — queue work after completion; both queues are inspectable and editable
  • ToolMiddleware — async Allow / Modify(args) / Deny(reason) hooks gating every call. A denial becomes an error tool result the model sees, so the loop keeps going
  • InputFilter — rewrite or reject user input before it reaches the model (PII redaction, prompt-injection guards)
  • Execution limits (max turns, max tokens, wall-clock timeout), abort(), and lifecycle callbacks (before_turn, after_turn, on_error)
  • Automatic retry with exponential backoff and ±20% jitter, for rate-limit and network errors only
Providers — 7 protocols, 20+ providers
Protocol Providers
Anthropic Messages Anthropic (Claude)
OpenAI Completions OpenAI, xAI, Groq, Cerebras, OpenRouter, Mistral, DeepSeek, MiniMax, Z.ai, Qwen, Meta (Muse Spark), Ollama, local servers, custom compatible APIs
OpenAI Responses OpenAI (Responses API)
Azure OpenAI Azure OpenAI
Google Generative AI Google Gemini
Google Vertex Google Vertex AI
Bedrock ConverseStream Amazon Bedrock

ModelConfig presets cover the common providers; ModelConfig::openai_compat(..) handles anything
else with a base_url. Per-provider quirks (auth style, reasoning format, max_tokens field name)
live in OpenAiCompat / AnthropicCompat flags — 12 compat profiles ship in the box.

The opencode_zen(..) / opencode_go(..) gateways pick the wire protocol from the model id
automatically, so one config reaches models across several vendors.

Thinking/reasoning controls are wired for all 7 protocols. Client-side prompt-cache breakpoints are
Anthropic-specific; most other providers cache server-side, and Bedrock does not cache automatically.
Context-overflow detection is centralised across 15+ provider-specific error strings.

Tools — built-in, custom, MCP, OpenAPI

Built in: bash (timeout, deny patterns), read_file / write_file (line numbers, path
restrictions), edit_file (fuzzy-match hints on failure), list_files, search (ripgrep).
Tools return stdout and stderr even on failure, so the model can self-correct.

Custom tools implement one trait:

#[async_trait::async_trait]
impl AgentTool for GreetTool {
    fn name(&self) -> &str { "greet" }
    fn label(&self) -> &str { "Greet" }
    fn description(&self) -> &str { "Greets someone" }
    fn parameters_schema(&self) -> serde_json::Value {
        serde_json::json!({ "type": "object", "properties": { "name": { "type": "string" } } })
    }
    async fn execute(&self, params: serde_json::Value, _ctx: ToolContext)
        -> Result<ToolResult, ToolError>
    {
        let name = params["name"].as_str().unwrap_or("stranger");
        Ok(ToolResult {
            content: vec![Content::Text { text: format!("Hello, {name}!") }],
            details: serde_json::Value::Null,
        })
    }
}

MCPwith_mcp_server_stdio() / with_mcp_server_http() connect to Model Context Protocol
servers over stdio or Streamable HTTP (session ids, SSE framing, incremental parsing) and register
their tools transparently.

OpenAPI (features = ["openapi"]) — point with_openapi_url() at a spec and every operation
becomes a tool, filtered by OperationFilter.

Sub-agents & shared state Sub-agents sharing artifacts by reference through SharedState

SubAgentTool delegates to a child loop with its own model, system prompt, tools, skills,
middleware, retry policy, and turn limits — a fully independent configuration, not a thin shim.
Run a cheap model for triage and an expensive one for the hard step in the same session.

SharedState is a pluggable key-value store (MemoryBackend, FileBackend, or your own via the
SharedStateBackend trait). A parent stores a large artifact once and sub-agents read it by key,
so it never gets re-pasted into every context window. Opt in with .with_shared_state(state)
it injects the shared_state tool and a state summary into the sub-agent's system prompt.

Context, sessions & skills
  • ContextTracker — hybrid real-usage + estimation, calibrated against actual provider usage
  • Tiered compaction — truncate tool outputs → summarise old turns → drop middle turns
  • Session — history as an id/parent tree with append, seek, checkpoint, branch_tips, and JSONL persistence. Appending after a seek forks a branch; it never overwrites
  • Skills — load AgentSkills-standard SKILL.md directories. The agent sees a compact index and reads the full skill on demand, so skills stay cross-compatible with Claude Code, Codex CLI, Cursor, and others
  • Structured outputsprompt_structured::<T>() returns typed, schema-validated replies, enforced natively where supported (Anthropic tool-forcing, OpenAI json_schema, Gemini responseSchema)
Production concerns
  • Cost trackingCostConfig carries separate input/output/cache-read/cache-write rates; session_cost_usd() gives a running total, and is_configured() distinguishes "free" from "pricing unknown"
  • Telemetrytracing spans per loop / LLM stream / tool, recording tokens and cost. OpenTelemetry is bridged app-side via tracing-opentelemetry; the library carries no OTel dependency by design
  • GASP (features = ["gasp"]) — record runs into a GASP agent repo; yoagent is a tested-conformant runtime, with the 7-check suite running in CI
  • Serde throughout — every core type is Serialize / Deserialize / PartialEq, so sessions persist and replay
  • set_model() — hot-swap the model mid-session without rebuilding the agent

Examples

Ten runnable examples in examples/. Five need no API key at all.

Example What it shows Key needed
cli A 370-line coding agent — all tools, skills, streaming, colored output. Like a baby Claude Code optional¹
rlm An LLM that explores a codebase on its own by spawning sub-agents yes
code_review Three sub-agents reviewing a diff in parallel, results merged yes
shared_state Passing a large artifact between sub-agents by reference yes
sub_agent Delegation basics with a per-sub-agent model yes
basic The smallest possible agent yes
callbacks Lifecycle hooks and a custom tool no
persistence Save and restore a session no
telemetry tracing spans with token and cost fields no
gasp_emit Recording a run into a GASP repo no

¹ --provider ollama or --api-url needs no key; hosted providers read their conventional env var.


Testing & CI

MockProvider scripts a whole multi-turn tool-calling conversation with no network:

use yoagent::provider::mock::{MockProvider, MockResponse, MockToolCall};

let provider = MockProvider::new(vec![
    MockResponse::ToolCalls(vec![MockToolCall {
        name: "search".into(),
        arguments: serde_json::json!({ "pattern": "TODO" }),
        provider_metadata: None,
    }]),
    MockResponse::Text("Found 3 TODOs.".into()),
]);
let agent = Agent::from_provider(provider, ModelConfig::mock());

It emits real StreamEvents and honours the CancellationToken, so abort and steering paths are
testable too.

  • 463 tests, of which 456 run with no network and no API keyscargo test --all-features
  • Provider SSE streams tested at the HTTP level with wiremock across 8 suites
  • clippy --all-targets --all-features with -Dwarnings, cargo fmt --check
  • Linux + macOS test matrix, a Windows compile check, a pinned MSRV 1.86 job, and a GASP conformance job

Module map

Module What lives there
agent_loop The loop itself — agent_loop, agent_loop_continue, AgentLoopConfig, execution strategies
agent Optional stateful wrapper — history, tool registry, steering/follow-up queues
types Message, Content, AgentEvent, AgentTool, ToolMiddleware, InputFilter
provider/ StreamProvider trait, ModelConfig, registry, and the 7 protocol implementations + MockProvider
tools/ bash, file, edit, list, search, shared_state_tool
sub_agent SubAgentTool — delegation to child loops
shared_state SharedState + pluggable backends
session Branching conversation trees with JSONL persistence
context Token tracking, tiered compaction, execution limits
skills AgentSkills SKILL.md loading
retry Backoff with jitter
mcp/ MCP client, stdio + HTTP transports, tool adapter
openapi/ OpenAPI 3.0 → tools (feature openapi)
gasp Run recording into a GASP repo (feature gasp)

Documentation

MSRV is 1.86, enforced in CI. Raising it is a minor-version change.

License

MIT — see LICENSE.

Inspired by pi-agent-core (TypeScript).

Yorumlar (0)

Sonuc bulunamadi