Skip to main content

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 and FinishReason enums.
  • LLM Provider Abstraction: Swap between OpenAI, Anthropic, or any custom provider via the LLMProvider interface. Adapters handle protocol differences.
  • Plugin Tools: Register custom tools by subclassing Tool with 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 StreamChunk dataclass.
  • 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

sanityops_agent-0.0.2.tar.gz (40.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sanityops_agent-0.0.2-py3-none-any.whl (70.3 kB view details)

Uploaded Python 3

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

Hashes for sanityops_agent-0.0.2.tar.gz
Algorithm Hash digest
SHA256 e35adeffde76b187cbc45bdf6d0f790e710a2be5672198a15e23c137c1fa2dfe
MD5 0ede791e52d9862b4e2f1f8b1da8d35b
BLAKE2b-256 601079da5bc5f23bf5c41239fb19e6cf028881b3040b4882350a19d9b523f751

See more details on using hashes here.

Provenance

The following attestation bundles were made for sanityops_agent-0.0.2.tar.gz:

Publisher: publish.yml on sanityops-org/sanityops_agent

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for sanityops_agent-0.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 db340d06bf99b1c0fb6863487fc6eab0dc6fa5df0852ea8db6818881c6119c1f
MD5 fe82e680cbdd795fb8eb8a5ecb931b09
BLAKE2b-256 79a22794fe11d86edcb3d0750dad157cb0d01fb1e467d4db5b4b2ca0d2ec70c4

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page