Sanityops Agent
A modular, extensible AI Agent framework with OOP design, multi-provider support, and a plugin architecture for tools, hooks, and multi-agent workflows.
Features
- Unified Message Model: Subclassed content blocks (
TextBlock,ToolUseBlock,ThinkingBlock, etc.) with type-safe access andFinishReasonenums. - LLM Provider Abstraction: Swap between OpenAI, Anthropic, or any custom provider
via the
LLMProviderinterface. Adapters handle protocol differences. - Plugin Tools: Register custom tools by subclassing
Toolwith JSON Schema parameters. Tools support tags (e.g.,parent_only) for access control. - Multi-Agent: Parent agent can spawn sub-agents with filtered tool access and tighter limits (max_loops=10, timeout=120s).
- Hook System: Intercept tool calls, LLM responses, and agent lifecycle events for approval workflows, logging, or blocking.
- State Persistence: Save/restore conversation context as JSON.
- Streaming: Token-by-token streaming with
StreamChunkdataclass. - Retry Logic: Exponential backoff for transient LLM errors.
Installation
Using uv (recommended)
# Install uv if you don't have it
curl -LsSf https://astral.sh/uv/install.sh | sh
# Create virtual environment and install dependencies
cd new_agent
uv sync
# Install with dev dependencies (pytest, etc.)
uv sync --all-extras
Manual pip install
pip install -e ".[dev]"
Quick Start
Minimal Example
import asyncio
from sanityops_agent.llm.providers.openai import OpenAIProvider
from sanityops_agent.agents.factory import AgentFactory
from sanityops_agent.tools.registry import ToolRegistry
async def main():
provider = OpenAIProvider(
api_key="your-key", # or set OPENAI_API_KEY env var
model="gpt-4o-mini",
)
factory = AgentFactory(
provider=provider,
tool_registry=ToolRegistry(),
)
agent = factory.create_parent_agent(
system_prompt="You are a helpful assistant.",
)
result = await agent.run("What is the capital of France?")
print(result.text)
print(f"Tokens: {result.tokens_used}, Loops: {result.loops_used}")
await provider.close()
asyncio.run(main())
Custom Tool
from sanityops_agent.tools.base import Tool, ToolResult
class WeatherTool(Tool):
name = "get_weather"
description = "Get weather for a city."
parameters = {
"type": "object",
"properties": {
"city": {"type": "string"},
},
"required": ["city"],
}
async def execute(self, city: str, **kwargs) -> ToolResult:
# Call weather API here
return ToolResult(content=f"Sunny, 25°C in {city}")
# Register it
registry = ToolRegistry()
registry.register(WeatherTool())
factory = AgentFactory(provider=provider, tool_registry=registry)
agent = factory.create_parent_agent()
result = await agent.run("What's the weather in Tokyo?")
State Persistence
from sanityops_agent.managers.state import StateManager
state = StateManager(state_path="session.json")
# Save
await state.save(agent.context)
# Resume
context = await state.load()
agent.context = context
Sub-Agent Creation
# Sub-agents have filtered tools (no parent_only) and tighter limits
sub = factory.create_sub_agent(
system_prompt="You are a code reviewer.",
)
result = await sub.run("Review this function: def foo(x): return x + 1")
Project Structure
sanityops_agent/
├── core/ # Message model, Agent base class, Context
├── llm/ # Provider abstraction, adapters, exceptions
│ └── providers/ # OpenAI, Anthropic implementations
├── tools/ # Tool base class, registry, context
│ └── builtins/ # Built-in tools (bash, file_ops, todo, etc.)
├── agents/ # Parent agent, Sub-agent, Factory
├── hooks/ # Hook system (before/after tool, LLM, etc.)
├── managers/ # State, skill, todo, history persistence
└── legacy/ # Backward compatibility adapter
tests/
├── unit/ # Isolated component tests
├── integration/ # Multi-component workflow tests
├── mocks/ # MockLLMProvider, mock tools
└── conftest.py # Pytest fixtures
examples/
├── basic_usage.py # Interactive examples (6 scenarios)
└── streaming_react_agent.py # Streaming ReAct agent with .env config
Configuration
Using .env File
Copy .env.example to .env and fill in your credentials:
cp .env.example .env
Supported environment variables:
| Variable | Default | Description |
|---|---|---|
LLM_PROVIDER |
openai |
Provider name: openai or anthropic |
API_KEY |
(required) | Your API key |
BASE_URL |
(empty) | Custom endpoint (vLLM, Azure, etc.) |
MODEL |
gpt-4o-mini |
Model name |
MAX_LOOPS |
30 |
Max agent loops per run |
MAX_TOKENS |
8000 |
Max tokens per LLM call |
TOTAL_TIMEOUT |
300 |
Total timeout in seconds |
TEMPERATURE |
0.7 |
Sampling temperature |
SYSTEM_PROMPT |
(see .env) | System prompt for the agent |
Running Examples
# 1. Set up your .env first
cp .env.example .env
# Edit .env with your API key
# 2. Streaming ReAct Agent (reads .env, streams output)
uv run python examples/streaming_react_agent.py "What's 25 * 13?"
# 3. Interactive basic examples menu
uv run python examples/basic_usage.py
Key Design Decisions
| Decision | Rationale |
|---|---|
| Subclassed content blocks | Type safety, static analysis catches wrong field access |
FinishReason enum over strings |
No magic strings, IDE autocomplete |
content: list[...] always |
Simplifies adapter logic, no str/list branching |
| Provider is pure call, no retry | Single responsibility, retry belongs in Agent layer |
| Tools use JSON Schema params | Compatible with OpenAI/Anthropic tool calling APIs |
| Sequential tool execution | Simpler error handling, predictable ordering |
| AgentFactory owns config | Factory config propagates to created agents |
License
Apache 2.0 - See LICENSE for details.
Contributing
See CONTRIBUTING.md for development setup and guidelines.
Release files for sanityops-agent 0.0.6
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| sanityops_agent-0.0.6.tar.gz | 164.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sanityops_agent-0.0.6-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 231.0 kB
Release files / sanityops_agent-0.0.6.tar.gz
| Download URL | sanityops_agent-0.0.6.tar.gz |
|---|---|
| Size | 164.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0139c6e42794bdb5cf8091a1184498e2571ab56c924408e2390cd7c51ba8511f
|
|
BLAKE2b-256 checksum How to use checksums |
ba774adf00157de9b4565884c1cd27305f30535ea22c8eb36371a26edf302457
|
| 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 21, 2026.
Transparency logRelease files / sanityops_agent-0.0.6-py3-none-any.whl
| Download URL | sanityops_agent-0.0.6-py3-none-any.whl |
|---|---|
| Size | 66.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e08bc15f9704a472042dee0ec3bf3532eac215845dc796132ceeca8ec1cee36d
|
|
BLAKE2b-256 checksum How to use checksums |
02fa49d83e932a8f69735d2057a32045382245e10d979b249fb4c349c7999fbc
|
| 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 21, 2026.
Transparency log