Skip to main content
avo logo

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 | openrouter | gemini
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
AVO_OPENROUTER_API_KEY yes for openrouter OpenRouter API key
AVO_OPENROUTER_BASE_URL no Default https://openrouter.ai/api/v1
AVO_OPENROUTER_MODEL no Provider-specific override
AVO_GEMINI_API_KEY yes for gemini Gemini API key
AVO_GEMINI_BASE_URL no Default https://generativelanguage.googleapis.com
AVO_GEMINI_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.
OpenRouter OpenRouterProvider OpenAI-compatible gateway to 300+ models, free tier included.
Google Gemini GeminiProvider Native Gemini generateContent REST API with function calling.

All eight 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 existing FunctionTool / 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 and cpu_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, a register() 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 next avo 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.

Metadata

Release files for avo 0.1.4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for avo 0.1.4
File Size Uploaded
avo-0.1.4.tar.gz 582.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for avo 0.1.4
File Interpreter ABI Platform
avo-0.1.4-py3-none-any.whl Python 3 none any Details

Total release size: 913.3 kB

Release files / avo-0.1.4.tar.gz

Download URL avo-0.1.4.tar.gz
Size 582.4 kB
Tags Source
SHA-256 checksum
How to use checksums
2e8eb5250c52c709e4f25adf25bf51c20e2259ae12bbde6e923161d0329e0c73
BLAKE2b-256 checksum
How to use checksums
5a382861d0320c932f83eca032e1e3a141394adcc88974f92e88ddb9931da764
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.

Transparency log

Release files / avo-0.1.4-py3-none-any.whl

Download URL avo-0.1.4-py3-none-any.whl
Size 330.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0f9538785ba546798d062c5acd769f778c5dbc5d04fda9fdcaaf21918c1ca967
BLAKE2b-256 checksum
How to use checksums
9d1eff464fe6c6f8046337d635f459f7804ce1f41e0031175a3fc441e1308a43
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.

Transparency log

Release history Release notifications | RSS feed

0.7.4

2 release files

0.7.2

2 release files

0.7.1

2 release files

0.1.6

2 release files

0.1.5

2 release files

This release

0.1.4 This release

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page