vv-agent-rs
vv-agent-rs is the Rust workspace for the vv-agent crate: an embeddable
agent runtime, SDK, CLI, tool system, memory layer, and workspace abstraction
for model-driven automation.
Install
The current crate version is 0.10.0. Repository HEAD locks language-neutral
Contract 6.0.1 while keeping a Rust-idiomatic API. The current cross-repository
adoption state and verified revisions live in the central support matrix.
Enable the Apalis adapter with:
Repository HEAD is forward-only: current readers accept only the current
strict public and wire shapes.
0.10.0 Highlights
- Every admitted model dispatch is recorded in
result.token_usage().model_calls, including agent cycles, Session Memory, full memory compaction, failures, retries, and ambiguous outcomes. Missing provider token or cache fields remain unavailable instead of being reported as zero. - Tool arguments are validated as a complete JSON Schema Draft 2020-12 value
before approval or side effects. Invalid calls return structured
invalid_tool_argumentsdetails without invoking the handler. - Optional host output validation is disabled by default and can make at most one tools-free repair callback before a terminal result is committed.
- Resolved instructions and context travel as one immutable
PromptBundle; metadata is not a prompt-section transport, and checkpoint resume reuses the frozen bundle without rerunning producers. - Enabled Session Memory is loaded once while a new run is compiled. Entries extracted during that run are persisted for the next new run and never rewrite the active run's frozen prompt.
- Truncated command and file results use sparse artifact or cursor recovery
fields. Local artifacts live in private storage outside the shell working
directory and complete terminal captures are written as streams. The
model-visible
compress_memorytool and deferred exposure mode are removed; automatic memory compaction remains internal. MicrocompactionPolicycontrols the trigger ratio, target ratio, protected recent cycles, and minimum result size. Old results from built-in and custom tools default to archive retention; replacement requires a complete immutable artifact and a model-visibleread_filerecovery path. Compact markers expose only a bounded excerpt and logical recovery path.- Durable execution uses
vv-agent.checkpoint.v5,vv-agent.run-definition.v5,vv-agent.distributed-run.v5, andvv-agent.distributed-worker-response.v3for strict recovery and distributed-controller boundaries.RunEventuses wire versionv2, and SQLite session stores usePRAGMA user_version=2.
See output validation and checkpoint/resume for the detailed contracts.
It is designed around explicit agent control flow. The default uses
task_finish to complete and ask_user to pause. Hosts can
instead opt into NoToolPolicy::Finish or NoToolPolicy::WaitUser when a
normal assistant response should be terminal. The runtime applies the declared
policy without classifying whether the text looks like a final answer.
Architecture
AgentRuntime
├── LLM client # vv-llm backed chat client, endpoint resolution, streaming
├── CycleRunner # one model turn: prompt, response, tool-call plan
├── ToolOrchestrator # tool policy, approval, dispatch, timeout, telemetry
├── RuntimeHookManager # before/after hooks for LLM, tools, and memory
├── MemoryManager # context budgeting, compaction, artifacts, session memory
├── RunHandle / RunEvent # live control, typed events, event-store replay
├── RuntimeExecutionBackend # run scheduling
│ ├── InlineBackend # synchronous default
│ ├── ThreadBackend # non-blocking task submission
│ └── DistributedBackend # checkpointed cycles with pluggable dispatch
└── WorkspaceBackend # file/object I/O boundary for tools
├── LocalWorkspaceBackend
├── MemoryWorkspaceBackend
└── S3WorkspaceBackend
Provider request building, endpoint transport, retries, streaming deltas, token
limits, usage accounting, and provider-specific protocol details are delegated
to the published vv-llm crate. vv-agent focuses on agent execution: prompts,
tools, hooks, memory, sessions, workspace access, and orchestration.
Repository Setup
Run commands from this repository root:
Most real-model examples and the CLI read a local vv-llm settings file. Keep
the credential-bearing file untracked:
# Fill endpoint keys in local_settings.json.
The default settings path is local_settings.json. You can override it with
VV_AGENT_LOCAL_SETTINGS for examples or --settings-file for the CLI.
Quick Start
CLI
CLI flags:
| Flag | Purpose |
|---|---|
--prompt |
Required user task. |
--backend |
Backend key under LLM_SETTINGS.backends. |
--model |
Model key under the selected backend. |
--settings-file |
Local vv-llm settings file. |
--workspace |
Directory exposed to workspace tools. |
--max-cycles |
Maximum runtime cycles before stopping. |
--language |
Prompt/tool guidance locale. |
--agent-type |
Optional agent profile type such as computer. |
--verbose |
Emit per-cycle runtime events. |
Agent + Runner SDK
Use Agent + Runner for new embedded applications. Agent
describes instructions, model, tools, handoffs, hooks, and defaults. Runner
owns model providers, workspace defaults, and execution. RunConfig overrides
one run without changing the agent definition, including the public
ExecutionMode for inline, threaded, or distributed execution.
Per-run controls also cover tool registry factories, before-cycle and
interruption messages, sub-task management, runtime observers, log previews,
and LLM request debug dumps. See docs/runtime-control.md for precedence and
language-adaptation details.
use ;
async
A handoff is an outer Runner control transfer, not an agent-as-tool call. The
target Agent resolves its own model and model settings, while the active
session, cancellation token, and mutated shared state continue across the
transition. max_handoffs defaults to 10 and limits control transfers
independently from max_cycles. Approval resume preserves the same behavior.
No-tool completion is an explicit host control. Configure it on an Agent or
override it for one run; per-run configuration wins over a configured Runner
default, which wins over the Agent value. Omitting every layer keeps
NoToolPolicy::Continue.
use ;
let natural_answer_agent = builder
.instructions
.no_tool_policy
.build?;
let force_tool_driven_run = builder
.no_tool_policy
.build;
Inspect RunResult::completion_reason(), completion_tool_name(), and
partial_output() to distinguish natural completion, tool-driven completion,
waits, cancellation, failure, and max-cycle exhaustion.
Reusable run defaults belong in
Runner::builder().default_run_config(...). Provider resolution is per-run
then Runner. Model resolution is per-run, Agent, Runner, then the selected
provider default. Model settings merge from provider to Runner to Agent to
per-run, with each later layer overriding earlier fields. Replacing the
provider for one run does not reuse a backend-bound model from the Runner.
Sessions keep conversation history across runner calls:
use ;
let session = new;
runner
.run_with_config
.await?;
let result = runner
.run_with_config
.await?;
Live Runs and Events
Runner::run() and run_with_config() are the one-shot entrypoints. Use
Runner::start() when an application needs live UI/server control: subscribe
to events, approve pending tools, cancel a run, or await the final result from
one RunHandle. Runner::stream() is a convenience wrapper over start() for
typed live events.
use ;
let handle = runner
.start
.await?;
let mut events = handle.events;
while let Some = events.next.await
let result = handle.result.await?;
Each RunEvent is a v2 envelope with event_id, run_id, trace_id,
optional session and parent identifiers, timing, metadata, and a typed
RunEventPayload. JsonlRunEventStore can append events and replay a run,
including child events linked by parent run id.
Live tool approval uses ApprovalProvider and the handle-owned broker. The
model-facing ask_user tool remains for requesting user input as part of the
conversation. Host applications can also attach ContextProvider values for
ordered prompt fragments and MemoryProvider values for external search, save,
and compaction lifecycle hooks.
ToolPolicy exposes Default, Always, Never, and OnRequest approval
modes. Default inherits the next configured policy; explicit OnRequest
follows each tool's static or dynamic approval declaration. Always forces
approval and Never bypasses it without evaluating a dynamic tool predicate.
Tool Metadata and Execution Telemetry
Tools may declare optional, host-visible capabilities with ToolMetadata.
Attach the declaration with
FunctionTool::builder(...).tool_metadata(...) (or
StaticTool::with_tool_metadata) and narrow a run with the additive denial
methods on ToolPolicy:
use Value;
use ;
let inspect = builder
.description
.tool_metadata
.handler
.build?;
let policy = default
.deny_side_effect
.deny_capability_tag?
.deny_terminal_tools
.deny_cost_dimension?;
let run_config = builder.tool_policy.build;
side_effect is one coarse declaration with no hierarchy. terminal=true
only declares that a tool may return finish or wait_user; it never ends a
run by itself. capability_tags and cost_dimensions are normalized,
exact-match labels. Cost dimensions are not prices, usage measurements, or run
budgets. Typed declarations remain separate from generic tool metadata and
are never added to the model-visible function schema.
Metadata denials compose with existing name, argument, approval, planned-name,
budget, and runtime checks. Agent, Runner-default, and per-run denials form a
set union (deny_terminal_tools uses logical OR); configured sub-agents,
agent-as-tool runs, handoffs, and distributed workers inherit them and can only
add denials. A matching denial returns tool_not_allowed without starting the
executor. Omitting typed metadata and leaving the four new policy fields at
their defaults preserves existing tool eligibility, schemas, completion, and
approval behavior.
The typed runtime sequence is ToolCallPlanned, optional approval events,
ToolCallStarted immediately before effects may begin, and
ToolCallCompleted after a result exists. Completed events expose directive,
error_code, execution_started, and duration_ms; a pre-execution denial has
no started event, execution_started=false, and duration_ms=null on the
wire. See Architecture,
Durable Checkpoint And Resume, and the
App Server protocol for lifecycle,
persistence, and projection details.
Run Budgets
RunConfig::budget_limits can independently limit total tokens, uncached input
tokens, total or exact-name tool calls, active wall time, and host-metered cost.
All limits are optional and task-neutral: the framework does not inspect the
prompt, task category, milestones, or answer quality when enforcing them.
Inspect result.budget_usage() and result.budget_exhaustion(). A budget stop
is a typed failed result with completion reason budget_exhausted, not a
successful answer. Runs without configured limits preserve the existing event
flow. See Run Budgets and
crates/vv-agent/examples/07_token_budget_guard.rs.
App Server
Use the App Server when a product shell needs to drive vv-agent over a stable
JSON-RPC protocol instead of linking directly to runtime internals. It supports
stdio JSONL transport, thread and turn lifecycle requests, live item
notifications, approval server requests, replay, schema generation, and a typed
Rust test client.
See crates/vv-agent/docs/app_server.md for protocol examples and client
responsibilities.
Low-Level Runtime
Use the runtime directly only when you need to assemble the LLM client, prompt,
tool registry, workspace, and run controls yourself. New embedded applications
should start with Agent + Runner.
use PathBuf;
use build_vv_llm_from_local_settings;
use ;
use AgentTask;
use ;
See crates/vv-agent/examples/01_quick_start.rs for a complete low-level
runtime version with event logging.
Core Capabilities
| Area | What vv-agent provides |
|---|---|
| Runtime | Multi-cycle model execution, explicit terminal states, live RunHandle, cancellation, typed events, event replay, and max-cycle handling. |
| Tools | Built-in tools plus a ToolOrchestrator path for policy, approval, dispatch, timeout, and telemetry. |
| SDK | Agent, Runner, RunConfig, ModelSettings, PromptBundle, PromptSection, ToolExecutionResult, ToolArtifactRef, ToolResultCursor, typed tools, Agent::as_tool(), RunEvent, providers, and Session. |
| Memory | Token budgeting, prompt-too-long retries, micro and full compaction, artifact-backed large tool results, image trimming, session memory, and external provider hooks. |
| Hooks | Rust RuntimeHook implementations can inspect or patch LLM calls, tool calls, memory compaction, and run lifecycle behavior. |
| Sub-agents | Runtime-backed sub-task creation, batch submission, background status queries with wait-for-completion support, continuation, steering, and inherited streaming callbacks. |
| Skills | Skill directory discovery, frontmatter parsing, validation, prompt rendering with budget limits, activation, and activation history. |
| Workspace | Local, in-memory, and S3 object-store backends behind one WorkspaceBackend boundary. |
Execution Backends
The public SDK selects scheduling through ExecutionMode. Lower-level runtime
backend structs remain available for advanced integrations:
| Backend | Use case |
|---|---|
ExecutionMode::Inline |
Default synchronous execution in the current process. |
ExecutionMode::Threaded |
Submit runs without blocking the caller. |
ExecutionMode::Distributed |
Checkpointed cycle execution with serializable runtime recipes and pluggable dispatch. |
Checkpointed runs can store state in memory, SQLite, or Redis. The optional
apalis feature adds an Apalis job bridge for applications that already use
Apalis workers:
For event-driven hosts, DistributedBackend::start enqueues the first cycle
and returns a passive handle; each short completion callback calls advance
once to enqueue, defer, wait, request terminal finalization, or replay the
durable terminal. ApalisCycleEnqueuer is enqueue-only and does not require a
result backend. Runner::start_distributed performs framework preparation,
and a separate bounded Runner::finalize_distributed consumes
FinalizeRequired through the normal terminal pipeline. The existing blocking
dispatcher remains available.
The distributed API also has an inline fallback, which is useful for local
development and tests. See crates/vv-agent/examples/23_distributed_backend.rs.
Workspace Backends
All built-in file tools go through WorkspaceBackend. That keeps local files,
memory-backed files, and S3-compatible object storage on the same tool contract.
find_files and search_files include safety defaults for large workspaces:
bounded result counts, hidden/dependency directory filtering, explicit ignored
path inclusion, and local rg acceleration when available.
Examples
The numbered examples are the best way to explore the public API:
See crates/vv-agent/examples/README.md for the full example index covering
Agent + Runner, runtime hooks, custom tools, handoffs, live approval,
background tasks, tracing, sub-agent pipelines, skills, streaming, cancellation,
state stores, execution backends, workspace backends, and temporary tool
injection.
Live Smoke Tests
Live tests are opt-in and use a local settings file without printing
credentials. By default they read the untracked
crates/vv-agent/tests/dev_settings.json; start from
crates/vv-agent/tests/dev_settings.example.json.
VV_AGENT_RUN_LIVE_TESTS=1 \
VV_AGENT_RUN_LIVE_TESTS=1 \
The live suite exercises direct runtime completion, SDK completion,
ask_user, todo updates, memory notes, skill activation, workspace tools,
image reading, safe edit_file recovery, foreground and background shell
commands, sub-agent waiting/status checks, and configured sub-agent delegation.
Verification
Run the standard checks from vv-agent-rs/:
Focused checks that are useful while editing public docs and examples:
Repository Layout
vv-agent-rs/
Cargo.toml
crates/vv-agent/
src/
cli/ # CLI entrypoint and task construction
config/ # LLM settings loading and model resolution
llm/ # LLM trait, scripted test client, vv-llm client bridge
memory/ # compaction, artifacts, session memory, token budgeting
prompt/ # system prompt sections and provider cache projection
agent.rs # public Agent builder
runner.rs # public Runner over runtime execution
run_config.rs
model.rs
model_settings.rs
sessions.rs
runtime/ # agent runtime, hooks, backends, cancellation, sub-agents
skills/ # skill discovery, parsing, validation, activation
tools/ # registry, schemas, dispatcher, built-in handlers
workspace/ # local, memory, and S3 workspace backends
examples/
tests/
docs/
Additional design notes live under docs/, especially docs/architecture.md
and docs/model-settings.md.