Skip to content

Releases: yologdev/yoagent

v0.13.0

Choose a tag to compare

@yuanhao yuanhao released this 14 Jul 20:53
09183e0

Serializable event stream — the wire contract release

AgentEvent and StreamDelta now derive Serialize/Deserialize/PartialEq. The wire format is internally tagged camelCase JSON:

{"type":"messageUpdate","message":{...},"delta":{"type":"text","delta":"hi"}}
{"type":"toolExecutionEnd","toolCallId":"tc_1","toolName":"bash","result":{...},"isError":false}

It is a frozen public contract: variant tags, field names, and the tagging scheme are guarded by exhaustive-match snapshot tests (a new variant fails to compile until its tag is pinned) and an exact-JSON snapshot of the full nested payload. External frontends — websocket fanout servers, TypeScript clients, JSONL pipes — can consume the agent's event stream directly. First consumer: yoyo-serve.

Changed

  • Message payload serialization normalized to uniform camelCase (usage.cacheRead/cacheWrite/totalTokens, errorMessage, providerMetadata). Session files written by older versions still load (serde aliases). ⚠️ Files written by 0.13 load in older versions without error but silently drop the renamed fields (token counts read 0; errorMessage/providerMetadata — including Gemini thought signatures — are lost). Don't round-trip session files through yoagent < 0.13.
  • serde floor raised for rename_all_fields support.

Docs

  • Wire-format section incl. streaming semantics: accumulate from delta, reset at each MessageStart, resync at MessageEnd.

Full changelog: https://github.com/yologdev/yoagent/blob/main/CHANGELOG.md

v0.12.0

Choose a tag to compare

@yuanhao yuanhao released this 11 Jul 12:36
c13c966

0.12.0 headline: your agent is a git repo — and yoagent is the first tested GASP-conformant runtime.

GASP bridge (features = ["gasp"])

let recorder = GaspRecorder::init("./my-agent-repo", "my-agent", "worker-1",
    GoalRef::New { title: "ship the feature".into() }).await?;

let (tx, record_handle) = recorder.recording_sender("implement the parser", None);
agent.prompt_with_sender("implement the parser", tx).await;
let run_id = record_handle.await??.expect("run recorded");

Record agent runs into a GASP agent repo: an append-only semantic event log (goals, runs, model calls, tool calls) that folds into a typed graph, one git commit per run. Restore = git clone — the agent's durable self travels as a repo. Crash-safe (stale runs closed, dropped streams handled), UI-safe (events tee to your app before recording; a recorder failure never blinds the UI), and redactable (with_summarizer).

Conformance-checked in CI: every commit emits an agent repo and runs the GASP protocol's 7-check suite against a fresh clone — envelope round-trip, replay, vocabulary, append-only git history, causation integrity, restore, domain↔ops consistency. All pass.

Pairs with 0.11's session trees: Session::to_jsonl is a natural format for the transcripts/ cold tier.

Meta Model API — day-one support

// provider auto-selected; key from META_API_KEY (or Meta's MODEL_API_KEY)
let agent = Agent::from_config(ModelConfig::meta("muse-spark-1.1", "Muse Spark 1.1"));

Meta opened its first paid API this week; yoagent supports it out of the box — 1M context, reasoning wired (ThinkingLevel tunes Meta's reasoning_effort; note their server default is medium), launch pricing pre-configured including the $0.15/M cached-input rate, so session_cost_usd() and telemetry cost fields are accurate from day one. Specs fact-checked against Meta's official docs. US-only public preview at launch.

Also

  • MSRV coverage restored for all features (via yoagent-state 0.4.2, whose declared 1.85 MSRV is now compiler-enforced upstream).
  • --provider meta in the CLI example.

Full details in CHANGELOG.md.

v0.11.0

Choose a tag to compare

@yuanhao yuanhao released this 10 Jul 22:48
6f5024a

0.11.0 is the differentiators release: five features that move yoagent from a solid agent loop to a full agent runtime.

Tool middleware — permission hooks for every tool call

#[async_trait::async_trait]
impl ToolMiddleware for ReadOnlyPolicy {
    async fn before_tool(&self, call: &ToolCallRequest<'_>) -> ToolDecision {
        match call.tool_name {
            "write_file" | "bash" => ToolDecision::Deny("read-only session".into()),
            _ => ToolDecision::Allow,
        }
    }
}
agent.with_tool_middleware(ReadOnlyPolicy)

Async approve/deny/modify hooks gating every tool call — the mechanism behind approval prompts and policy engines. A denial becomes an error tool result the LLM adapts to; the loop never aborts. Panicking middleware is contained. yoagent ships the hook, you ship the policy.

Structured outputs — prompt_structured::<T>()

let invoice: Invoice = agent.prompt_structured("Extract the invoice: ...", schema).await?;

Typed, schema-validated replies enforced natively: Anthropic tool-forcing, OpenAI json_schema strict, Gemini responseSchema. Provider failures surface as StructuredPromptError::Provider (never masked as parse errors); the raw text is preserved on parse failure.

Session trees — branching history

session.checkpoint("first-draft")?;
// ...later: rewind and take a different direction
session.seek_checkpoint("first-draft")?;
let agent = Agent::from_config(cfg).with_messages(session.path_messages());

id/parent_id conversation trees: fork from any point, checkpoints, edit-and-rerun without losing the original branch, JSONL persistence with load-time validation. The shape of GASP's transcripts/ tier.

Cross-provider thinking — 7/7 protocols

thinking_level is now honored everywhere: Gemini/Vertex thinkingConfig (thought summaries streamed back), Bedrock budget-based thinking (reasoning deltas + signature replay for multi-turn tool use), Azure reasoning effort. The "not yet wired" warnings are gone.

Telemetry — tracing spans

agent_loop → llm_stream (tokens_in/out/cached, cost_usd, error) → tool (is_error)

Structured spans with token and dollar-cost fields. Bridge to OpenTelemetry app-side via tracing-opentelemetry — yoagent gains no OTel dependency; negligible overhead with no subscriber. cargo run --example telemetry to see it live.


Compatibility: no breaking changes to released APIs. StreamConfig is now #[non_exhaustive] (use StreamConfig::new) — this only affects code constructing it literally for custom providers.

Full details in CHANGELOG.md.

v0.10.0

Choose a tag to compare

@yuanhao yuanhao released this 06 Jul 06:44
5720089

The headline of 0.10.0 is a config-first construction API. Build an agent from a single ModelConfig — the provider, model id, context window, pricing, and API key resolution all come from one place.

// before (0.9): pair provider + config by hand, model id passed twice
let agent = Agent::new(OpenAiCompatProvider)
    .with_model_config(ModelConfig::zai("glm-4.7", "GLM 4.7"))
    .with_model("glm-4.7")
    .with_api_key(key);

// after (0.10): provider inferred from config.api; key from ZAI_API_KEY
let agent = Agent::from_config(ModelConfig::zai("glm-4.7", "GLM 4.7"));

Added

  • Agent::from_config(ModelConfig) — the new primary constructor (provider + env key resolved automatically).
  • Agent::from_provider(provider, ModelConfig) — explicit provider / test doubles.
  • Agent::from_config_with(&registry, ModelConfig) -> Result<_, AgentBuildError> — custom registry.
  • Agent::set_model(ModelConfig) — switch model mid-session (never silently clobbers an explicit provider).
  • SubAgentTool::from_config / from_config_with / from_provider mirrors.
  • ModelConfig::mock(), exported AgentBuildError, ProviderRegistry::resolve(), StreamProvider::protocol().

Deprecated (removed in 1.0 — still work, with a compiler nudge)

  • Agent::new, Agent::with_model, Agent::with_model_config
  • SubAgentTool::new, SubAgentTool::with_model, SubAgentTool::with_model_config

with_api_key is not deprecated. Nothing is removed in 0.10 — existing 0.9 code compiles and runs (with deprecation warnings) unless you build with deny(warnings).

Fixed

  • Google/Vertex usage no longer double-counts cached tokens.
  • Retry-After clamped to max_delay_ms.
  • Compaction budget calibration subtracts measured overhead instead of ratio-scaling (the old formula could collapse the budget toward zero).
  • session_cost_usd() returns None for unpriced models instead of 0.0.
  • Missing API keys log a warning naming the env var to set.

Full migration guide (before/after table) in CHANGELOG.md.

v0.9.0

Choose a tag to compare

@yuanhao yuanhao released this 05 Jul 12:51
6e2aa52

A breaking release batching the changes anticipated in #33: the reqwest 0.13/rustls upgrade, support for the current model generation, two new provider gateways, queue inspection, and a crate-wide exhaustiveness policy.

⚠️ Breaking changes

Dependencies / MSRV

  • reqwest 0.12 → 0.13 with rustls as the TLS backend — OpenSSL is no longer a build dependency (faster builds on Android/Windows). reqwest-eventsource replaced by the maintained kameleoon-reqwest-eventsource fork (aliased; reqwest_eventsource:: paths unchanged). Rust 1.85+ required (was 1.75). Public API surfaces reqwest 0.13 / fork types (classify_eventsource_error, OpenApiError::HttpError). (#52)

Anthropic provider

  • Adaptive thinking is the default (thinking: {"type": "adaptive"} + output_config.effort) — required by Claude Fable 5 / Opus 4.7+/4.8 / Sonnet 5. Pre-4.6 models with thinking enabled need AnthropicCompat::legacy() (budget-based, now clamped to the API's 1024-token minimum). (#53)
  • ModelConfig.base_url is now honored ({base}/messages); the anthropic() preset default includes /v1 (old persisted configs with the un-versioned host keep working). Custom headers apply; Bearer auth available via AnthropicCompat.bearer_auth.
  • New StopReason::Refusal variant (safety-system declines, e.g. Claude Fable 5); model_context_window_exceeded maps to the standard overflow-error shape so compaction-retry hooks fire.
  • anthropic() preset defaults: max_tokens 8192 → 16000, reasoning: true.

Exhaustiveness policy (#33 item 1, #55)

  • #[non_exhaustive] on Content (matches need a wildcard arm), on the ToolCall/Thinking variants and Message::Assistant (construct via Content::tool_call() / tool_call_with_metadata() / thinking() / thinking_signed() and Message::assistant(); patterns need ..), and on ModelConfig (construct via presets or the new ModelConfig::custom(); field mutation still works, literals/FRU do not). Future field additions become non-breaking.
  • StopReason stays deliberately exhaustive: a new variant should be a compile error downstream, not a silent wildcard fall-through.
  • ModelConfig gains the anthropic: Option<AnthropicCompat> field (serde-compatible with older persisted configs).

Features

  • New model presets with real pricing: claude_fable_5(), claude_opus_4_8(), claude_sonnet_5(), claude_haiku_4_5(), gpt_5_5() — correct context windows, output caps, and CostConfig values. (#53)
  • OpenCode Zen & Go gateways (#48): ModelConfig::opencode_zen(id) / opencode_go(id) select the API protocol from the model family (Responses / Anthropic Messages with Bearer auth / Chat Completions). See docs/providers/opencode.md.
  • Queue inspection API (#50): steering_queue_snapshot() / follow_up_queue_snapshot(), _len(), atomic take_*_queue(), and batch steer_all() / follow_up_all() — enables pi-style pending-message UIs. Edit-window semantics documented in docs/concepts/agent-loop.md.
  • ModelConfig::custom(api, provider, base_url, id, name) for protocols without a preset (Bedrock, Vertex, Azure).

Fixes

  • Google/Gemini: mid-stream {"error": ...} payloads and transport failures now surface as errors (previously a silently truncated "successful" turn); SAFETY-family finish reasons map to StopReason::Refusal; SSE splitting takes the earliest separator (mixed CRLF/LF buffers no longer merge events); an empty text part no longer swallows a functionCall in the same part. (#55)
  • Anthropic: a refused turn no longer poisons the conversation (empty assistant messages are skipped when serializing history) and carries an explanatory error_message; legacy thinking budgets below the API minimum no longer 400.
  • CI now builds with --all-features, so the openapi feature is compiled and linted on every PR.

Testing

New wiremock-based provider stream tests (Anthropic + Google) cover refusal/overflow stop-reason mapping, all auth branches, thought-signature round-trips, and SSE edge cases — scenarios previously reachable only with live API keys.

Full changelog: v0.8.4...v0.9.0

v0.8.4

Choose a tag to compare

@yuanhao yuanhao released this 21 Jun 18:25
6bf9acb

What's Changed

  • feat: SubAgentTool::with_skills (#46) — attach a SkillSet to a sub-agent so it sees the skills index. The index is appended to the sub-agent's system prompt at dispatch time, mirroring Agent::with_skills. Backward compatible (additive API). Closes #45.

Full Changelog: v0.8.3...v0.8.4

v0.8.3

Choose a tag to compare

@yuanhao yuanhao released this 26 May 16:26
3f6495c

What's Changed

  • Add first-class Qwen / DashScope support via ModelConfig::qwen(...).
  • Add OpenAiCompat::qwen() for Qwen reasoning-content parsing and OpenAI-compatible token behavior.
  • Add ModelConfig::openai_compat(...) for local/custom deployments that need explicit model-family compatibility flags.
  • Document hosted Qwen, regional DashScope URLs, local Qwen, and combined Qwen-on-Ollama compatibility.

Validation

  • CI passed on PR #42 and PR #43.
  • Local cargo test passed before release.

v0.8.2

Choose a tag to compare

@yuanhao yuanhao released this 26 May 15:51
cfc3b6f

What's Changed

  • Add first-class Ollama support via ModelConfig::ollama(base_url, model_id).
  • Honor requires_assistant_after_tool_result for OpenAI-compatible providers, fixing Ollama tool-result continuation compatibility.
  • Keep generic ModelConfig::local(...) behavior neutral for other local OpenAI-compatible servers.
  • Preserve backward compatibility for serialized OpenAiCompat configs by defaulting the new flag to false.

Validation

  • CI passed on PR #40 and PR #41.
  • Local cargo test passed before release.

v0.8.1

Choose a tag to compare

@yuanhao yuanhao released this 26 May 15:17
14841f4

What's Changed

  • Updated DeepSeek support for the current V4 API shape:
    • default CLI model is now deepseek-v4-flash
    • DeepSeek uses https://api.deepseek.com
    • DeepSeek requests use max_tokens, thinking, and reasoning_effort
    • DeepSeek cache hit/miss usage fields are parsed when present
  • Added Model Presets documentation covering first-class provider constructors and DeepSeek legacy aliases.
  • Fixed execution-limit accounting so cached prompt tokens count toward max_total_tokens for Claude and OpenAI-compatible providers.
  • Added regression coverage for cached-token execution limits.

Validation

  • cargo fmt --check
  • cargo test

v0.8.0

Choose a tag to compare

@yuanhao yuanhao released this 27 Apr 13:48
b49a7a2

Changes since v0.7.5

Breaking

  • Content::ToolCall now includes provider_metadata: Option<serde_json::Value> (#32)

Features

  • SharedState: pluggable key-value store for sub-agent communication (#35)
    • SharedStateBackend trait with MemoryBackend (default) and FileBackend backends
    • SubAgentTool::with_shared_state() — opt-in, injects shared_state tool automatically
  • Multi-provider sub-agents: SubAgentTool::with_model_config() for non-Anthropic providers (#35)
  • Inter-turn delay: turn_delay on AgentLoopConfig and SubAgentTool (#35)
  • Sub-agent error surfacing: StopReason::Error now propagated as ToolError::Failed (#35)

Fixes

  • Gemini: fix streaming and function calling (#32)
  • Filter empty text blocks from sub-agent output (#35)

Examples

  • examples/rlm.rs — recursive 2-level agent delegation with Grok
  • examples/code_review.rs — 3 parallel sub-agents reviewing via shared state
  • examples/shared_state.rs — parallel analysis with shared state