avo
Health Warn
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 5 GitHub stars
Code Pass
- Code scan — Scanned 12 files during light audit, no dangerous patterns found
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
Provider-agnostic reliability runtime for bounded, observable, resumable AI agent loops.
Provider-agnostic reliability runtime for bounded, observable, resumable, replayable AI agent loops.
Bounded. Resumable. Provider-agnostic. Honest about why it stopped.
⚠️ 0.1 is an alpha foundation. Suitable for evaluation, deterministic tests, and local prototypes; not production-ready.
Install
Requires Python 3.11+. Core runtime depends only on Pydantic.
git clone https://github.com/Fqih/avo.git
cd avo
python -m pip install -e ".[dev,providers,sandbox]"
Optional extras
| Extra | Adds | When you need it |
|---|---|---|
[dev] |
pytest, mypy, ruff, coverage | Local dev + tests |
[providers] |
httpx | Talking to MiniMax, Anthropic, OpenAI-compatible endpoints |
[sandbox] |
docker-py | Using run_shell against a real Docker daemon |
[otel] |
opentelemetry-api, sdk, otlp exporter | Emitting gen_ai.* spans for a run |
[live-benchmark] |
httpx, matplotlib | Running python benchmark/run_benchmark.py |
[mcp] |
mcp SDK | Authoring MCP servers or non-stdio transports |
Verify the install:
avo doctor
Prints resolved provider / model / endpoint without an HTTP call — cheapest smoke test.
Quickstart
One typed tool call, then a final reply. No API key:
import asyncio
from pydantic import BaseModel
from avo import (
AgentRuntime, FunctionTool, ModelResponse, TokenUsage, ToolCall,
)
from avo.providers import FakeProvider
class AddArguments(BaseModel):
left: int
right: int
async def add(arguments: AddArguments) -> object:
return {"sum": arguments.left + arguments.right}
async def main() -> None:
provider = FakeProvider(
[
ModelResponse(
tool_call=ToolCall(
tool_call_id="addition-1", name="add",
arguments={"left": 2, "right": 3},
),
usage=TokenUsage(input_tokens=12, output_tokens=5),
),
ModelResponse(
content="The sum is 5.",
usage=TokenUsage(input_tokens=18, output_tokens=6),
),
]
)
runtime = AgentRuntime(
provider=provider,
tools=[
FunctionTool(
name="add",
description="Add two integers.",
arguments_model=AddArguments,
function=add,
)
],
)
result = await runtime.run("What is 2 + 3?")
print(result.status.value, result.stop_reason.value, result.output)
asyncio.run(main())
examples/basic_agent.py ships this runnable end-to-end.
Configuration
All knobs live in AVO_* env vars. The chat REPL's first-run wizard can persist them to ~/.zshrc / ~/.bashrc.
Provider selection
| Variable | Required | Purpose |
|---|---|---|
AVO_PROVIDER |
yes | ollama | minimax | anthropic | openai | groq | cerebras |
AVO_MODEL |
yes | Default model name for the active provider |
AVO_OLLAMA_BASE_URL |
no | Ollama endpoint (default http://localhost:11434) |
AVO_OLLAMA_MODEL |
no | Ollama-specific model override |
AVO_OLLAMA_API_KEY |
no | Ollama auth header (rarely needed) |
AVO_MINIMAX_API_KEY |
yes for minimax | API key |
AVO_MINIMAX_BASE_URL |
no | Default https://api.minimax.io |
AVO_MINIMAX_MODEL |
no | Provider-specific override |
AVO_MINIMAX_API_STYLE |
no | anthropic (default) or openai |
AVO_ANTHROPIC_API_KEY |
yes for anthropic | API key |
AVO_ANTHROPIC_BASE_URL |
no | Default https://api.anthropic.com |
AVO_ANTHROPIC_MODEL |
no | Provider-specific override |
AVO_OPENAI_API_KEY |
yes for openai | API key |
AVO_OPENAI_BASE_URL |
no | Default https://api.openai.com/v1 |
AVO_OPENAI_MODEL |
no | Provider-specific override |
AVO_GROQ_API_KEY |
yes for groq | Groq API key |
AVO_GROQ_BASE_URL |
no | Default https://api.groq.com/openai/v1 |
AVO_GROQ_MODEL |
no | Provider-specific override |
AVO_CEREBRAS_API_KEY |
yes for cerebras | Cerebras API key |
AVO_CEREBRAS_BASE_URL |
no | Default https://api.cerebras.ai/v1 |
AVO_CEREBRAS_MODEL |
no | Provider-specific override |
Runtime + policy
| Variable | Default | Purpose |
|---|---|---|
AVO_DATABASE_PATH |
in-memory | SQLite path for the run/event store |
AVO_MAX_TOTAL_TOKENS |
unlimited | Override LoopPolicy.max_total_tokens |
AVO_MAX_RUNTIME_SECONDS |
300 |
Override LoopPolicy.max_runtime_seconds |
AVO_REPEATED_ACTION_LIMIT |
3 |
Override LoopPolicy.repeated_action_limit |
AVO_PERMISSION_MODE |
default |
default / accept_edits / plan / bypass |
AVO_TOOLS_REQUIRE_APPROVAL |
empty | Comma-separated tool names gating on approval_callback |
AVO_USAGE_RATES_INPUT_PER_1K |
unset | Cost rate for input tokens |
AVO_USAGE_RATES_OUTPUT_PER_1K |
unset | Cost rate for output tokens |
AVO_NOTIFY_WEBHOOK |
unset | URL to POST run lifecycle events to |
AVO_NOTIFY_DESKTOP |
0 |
Set to 1 to enable desktop notifications |
See .env.example for a copy-paste template.
Providers
| Provider | Adapter | Notes |
|---|---|---|
| Ollama | OllamaProvider |
Local HTTP, no key. Default for offline dev. |
| MiniMax | MiniMaxProvider |
Anthropic-compatible (default) or OpenAI-compatible style. |
| Anthropic | AnthropicProvider |
Native Anthropic Messages API. |
| OpenAI | OpenAICompatibleProvider |
Any /v1/chat/completions endpoint — OpenAI, vLLM, llama.cpp. |
| Groq | GroqProvider |
OpenAI-compatible Llama / Mixtral inference, low latency. |
| Cerebras | CerebrasProvider |
OpenAI-compatible inference on Cerebras wafer-scale hardware. |
All six implement the same ModelProvider Protocol. Swapping providers is one line.
Application tools
avo.app_tools is the optional-but-default toolkit. Tools plug into the existingFunctionTool / ToolRegistry contract — no changes to the runtime, state machine, or
event log.
| Tool | What it does |
|---|---|
read_file / write_file / edit_file |
Workspace-scoped file I/O |
glob / grep / workspace_map |
Workspace enumeration + search |
git_status |
Branch, modified, optional untracked files |
run_shell |
One shell command in an ephemeral Docker container |
plan_tasks / submit_plan |
Structured plan declaration + persistence |
task |
Dispatch isolated sub-agent run |
web_fetch / web_search |
HTTP GET with hard byte cap / DuckDuckGo HTML search |
Workspace safety
Workspace(root).validate_path(...) rejects ../, symlink escapes, absolute-path
escapes, and null bytes before any I/O. validate_for_write refuses to follow
symlinks at the leaf or any parent. write_file / edit_file open with O_NOFOLLOW on
POSIX. No path the model can ask for exits the workspace root.
Shell sandbox
SandboxExecutor wraps docker-py. Each run_shell call creates a fresh container
(remove=True), runs with network_mode="none" by default, applies a mem_limit andcpu_quota, times out via the runtime's LoopPolicy.tool_timeout_seconds, and removes
the container before returning. run_shell never calls subprocess on the host — the
sandbox is the only path to the shell.
Suit the network policy to your task:
from avo.app_tools.sandbox import SandboxExecutor
sandbox = SandboxExecutor(
network_mode="bridge", # default "none" — switch when network is required
mem_limit="512m",
cpu_quota=100000,
)
Approval policy
AVO_TOOLS_REQUIRE_APPROVAL lists tool names that must wait for explicit operator
approval. Tools not in the list auto-approve. Wire a custom callback:
from avo.app_tools.approval import build_approval_callback
callback = build_approval_callback(
on_require=lambda call: input(f"approve {call.name}? [y/N] ").lower() == "y",
)
runtime = AgentRuntime(provider=provider, tools=[...], approval_callback=callback)
Observability (OpenTelemetry)
Set AVO_OTEL_ENABLED=1 and the runtime wraps every _drive invocation in a span
tagged with the gen_ai.* semantic conventions:
python -m pip install -e ".[otel]"
export AVO_OTEL_ENABLED=1
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
avo chat
Spans carry avo.run_id, gen_ai.system (provider name), and gen_ai.request.model.avo.observability.record_usage(...) writes input / output token counts into the
active span, so cost rollups line up with trace data. The module is a noop when the
extra is not installed — no exceptions at import time, no runtime overhead.
Cost tracking
avo cost aggregates every persisted ledger entry (the same avo.db the event store
uses) and prints total tokens + USD spend, with per-run and per-model breakdowns.
Output is human-readable by default and machine-readable with --json:
avo cost --database avo.db --json
{
"run_count": 2,
"total": {"input_tokens": 700, "output_tokens": 370, "total_tokens": 1070},
"cost_usd": "0.0142",
"runs": [...],
"models": [...]
}
Set AVO_USAGE_RATES_INPUT_PER_1K / AVO_USAGE_RATES_OUTPUT_PER_1K to seed the ledger
with USD costs as each provider call returns.
Scaffolding plugins
avo plugin init writes a working plugin to disk so you can iterate on a new tool
without touching project structure by hand:
mkdir ~/projects/my-tool && cd ~/projects/my-tool
avo plugin init my-tool
cd my-tool
avo plugin install . # registers the sample echo tool
The scaffold ships a pyproject.toml declaring an avo.tools entry point, aregister() stub returning a sample FunctionTool, a README.md, and a .gitignore.
Replace the sample tool with your own and the runtime picks it up on the nextavo plugin install ..
CLI
avo [-d DATABASE] <command> [args]
| Command | What it does |
|---|---|
avo doctor |
Verify AVO_* config without an HTTP call. |
avo chat [-d PATH] [--workspace-root DIR] [--session ID] [--new-session] |
Interactive REPL. First run with no provider triggers the setup wizard. |
avo runs list |
Print one line per run. |
avo runs inspect RUN_ID |
Render the chronological trace. |
avo runs resume RUN_ID |
Resume a persisted FakeProvider run with no pending tool call. |
avo plugin install URL | PATH |
Install a plugin from git URL or local path. |
avo plugin list / show NAME / remove NAME [-y] |
Manage installed plugins. |
avo plugin init [NAME] [-d DIR] [--force] |
Scaffold a new plugin (pyproject + sample FunctionTool). |
avo mcp add NAME [--env KEY=VAL]... CMD ARGS... |
Register an MCP server. |
avo mcp list / remove NAME [-y] |
Manage MCP server registrations. |
avo skill install PATH / list / show NAME / remove NAME [-y] |
Manage skill packs. |
avo bench [--turns N] [--task ID] [--output PATH] |
Deterministic FakeProvider benchmark. |
avo runs diff RUN_A RUN_B [--json] |
Compare two persisted runs. |
avo cost [--database PATH] [--json] |
Aggregate token + USD spend across runs. |
avo sandbox run --image IMG --workspace DIR [--network MODE] -- COMMAND ARGS... |
One-shot ephemeral docker sandbox. |
Chat REPL slash commands
| Slash command | Action |
|---|---|
/help |
Print the full slash-command list. |
/provider |
Print provider / model / base URL / key-presence. |
/model [NAME] |
Switch to NAME or pick from the catalog (/model alone). |
/inspect RUN_ID |
Render a stored trace. |
/resume RUN_ID |
Resume a stored run. |
/skills |
List skills under <workspace>/.avo/skills. |
/skill NAME |
Inject a skill body as the next user turn. |
/quit / /exit |
Exit the REPL. |
Examples
examples/ ships runnable Python files, all offline (no API key):
| File | Demonstrates |
|---|---|
examples/basic_agent.py |
One typed tool call, then a final reply. |
examples/repeated_action.py |
Deterministic repeated-action containment. |
examples/resume_after_interrupt.py |
Interrupt mid-flight, reopen SQLite, resume. |
examples/app_tools_demo.py |
Workspace + file tools + permissive approval. |
examples/live_providers/ |
Real-API smoke tests per provider (need AVO_* keys). |
python examples/basic_agent.py
python examples/repeated_action.py
python examples/resume_after_interrupt.py
python examples/app_tools_demo.py
Development
python -m pip install -e ".[dev,providers,sandbox]"
ruff check .
ruff format --check .
mypy src/avo
pytest
Quality gates:
- ruff lint + format — line-length 100, per-file ignores for
examples/,benchmark/. - mypy strict on
src/avo(Pydantic plugin). - pytest
--strict-config --strict-markers, asyncio mode auto. - coverage branch coverage, fail-under 90%.
The suite needs no Docker — SandboxExecutor accepts an injectable client so tests
inject a fake and assert the container config that would be sent. Live Docker integration
is opt-in, same pattern as benchmark/live/tests/.
License
MIT — see LICENSE.
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found