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
[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
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

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.

All four 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)

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 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.

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.2

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.2
File Size Uploaded
avo-0.1.2.tar.gz 380.9 kB Details

Built distribution (wheel)

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

Total release size: 565.0 kB

Release files / avo-0.1.2.tar.gz

Download URL avo-0.1.2.tar.gz
Size 380.9 kB
Tags Source
SHA-256 checksum
How to use checksums
4bfbe57309f18476133b21c4e486ae5d7ad3decd088a560452a5ac6ee5fa89ef
BLAKE2b-256 checksum
How to use checksums
7eb3d4429ffd95c1edbaf7a7ccd0e7ff552a3ce81ca7f218b34b01543f597408
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.13

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

Download URL avo-0.1.2-py3-none-any.whl
Size 184.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
86f14f308ea88c08afbf579c2580cbed7f8af35636e077d5d63f16467f867805
BLAKE2b-256 checksum
How to use checksums
1908b97ea41f3ea70e56c56de29fac47a05caa873be942b9042ee3bc0da28f7e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.13

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

0.1.4

2 release files

0.1.3

2 release files

This release

0.1.2 This release

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