yoagent
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.
The agent loop for Rust — stream from 7 LLM protocols, run tools, loop until done.
crates.io · Docs · API · GitHub · DeepWiki · Changelog
The agent loop for Rust. Stream from any of 7 LLM protocols, run tools, loop until done.
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.Agentis 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.
ToolMiddlewarecan 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.
Sessionforks, 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.
MockProviderscripts 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:
AgentStart→TurnStart→MessageUpdate(deltas) →ToolExecution*→TurnEnd→AgentEnd - Parallel tool execution by default;
SequentialandBatched { size }strategies available - Steering — interrupt mid-run; follow-ups — queue work after completion; both queues are inspectable and editable
ToolMiddleware— asyncAllow/Modify(args)/Deny(reason)hooks gating every call. A denial becomes an error tool result the model sees, so the loop keeps goingInputFilter— 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
| 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.
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,
})
}
}
MCP — with_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.
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 theSharedStateBackend 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.
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 withappend,seek,checkpoint,branch_tips, and JSONL persistence. Appending after a seek forks a branch; it never overwrites- Skills — load AgentSkills-standard
SKILL.mddirectories. 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 outputs —
prompt_structured::<T>()returns typed, schema-validated replies, enforced natively where supported (Anthropic tool-forcing, OpenAIjson_schema, GeminiresponseSchema)
- Cost tracking —
CostConfigcarries separate input/output/cache-read/cache-write rates;session_cost_usd()gives a running total, andis_configured()distinguishes "free" from "pricing unknown" - Telemetry —
tracingspans per loop / LLM stream / tool, recording tokens and cost. OpenTelemetry is bridged app-side viatracing-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 keys —
cargo test --all-features - Provider SSE streams tested at the HTTP level with
wiremockacross 8 suites clippy --all-targets --all-featureswith-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
- The book — concepts, guides, and a page per provider (source)
- API reference — built with all features enabled
- CHANGELOG — every release
- CONTRIBUTING — how to build, test, and send a PR
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)
Yorum birakmak icin giris yap.
Yorum birakSonuc bulunamadi