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 agent.llm.providers.openai import OpenAIProvider
from agent.agents.factory import AgentFactory
from 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 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 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
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
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sanityops_agent-0.0.2.tar.gz.
File metadata
- Download URL: sanityops_agent-0.0.2.tar.gz
- Upload date:
- Size: 40.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e35adeffde76b187cbc45bdf6d0f790e710a2be5672198a15e23c137c1fa2dfe
|
|
| MD5 |
0ede791e52d9862b4e2f1f8b1da8d35b
|
|
| BLAKE2b-256 |
601079da5bc5f23bf5c41239fb19e6cf028881b3040b4882350a19d9b523f751
|
Provenance
The following attestation bundles were made for sanityops_agent-0.0.2.tar.gz:
Publisher:
publish.yml on sanityops-org/sanityops_agent
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sanityops_agent-0.0.2.tar.gz -
Subject digest:
e35adeffde76b187cbc45bdf6d0f790e710a2be5672198a15e23c137c1fa2dfe - Sigstore transparency entry: 2406217469
- Sigstore integration time:
-
Permalink:
sanityops-org/sanityops_agent@10e3ef938751c26e55aba985425f53968bb8fbc3 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/sanityops-org
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@10e3ef938751c26e55aba985425f53968bb8fbc3 -
Trigger Event:
push
-
Statement type:
File details
Details for the file sanityops_agent-0.0.2-py3-none-any.whl.
File metadata
- Download URL: sanityops_agent-0.0.2-py3-none-any.whl
- Upload date:
- Size: 70.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
db340d06bf99b1c0fb6863487fc6eab0dc6fa5df0852ea8db6818881c6119c1f
|
|
| MD5 |
fe82e680cbdd795fb8eb8a5ecb931b09
|
|
| BLAKE2b-256 |
79a22794fe11d86edcb3d0750dad157cb0d01fb1e467d4db5b4b2ca0d2ec70c4
|
Provenance
The following attestation bundles were made for sanityops_agent-0.0.2-py3-none-any.whl:
Publisher:
publish.yml on sanityops-org/sanityops_agent
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sanityops_agent-0.0.2-py3-none-any.whl -
Subject digest:
db340d06bf99b1c0fb6863487fc6eab0dc6fa5df0852ea8db6818881c6119c1f - Sigstore transparency entry: 2406217490
- Sigstore integration time:
-
Permalink:
sanityops-org/sanityops_agent@10e3ef938751c26e55aba985425f53968bb8fbc3 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/sanityops-org
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@10e3ef938751c26e55aba985425f53968bb8fbc3 -
Trigger Event:
push
-
Statement type: