OpenAgentFramework — minimal, fast, transparent AI agent framework
Project description
OAF — OpenAgentFramework
Minimal, fast, transparent AI agent framework for Python
Build AI agents that call tools, manage conversation context, and stream responses — with zero magic. Every prompt, tool call, and LLM decision is fully inspectable. Works with OpenAI and Anthropic out of the box.
📖 Full project documentation:
OAF.md— architecture deep-dive, API reference, design decisions, and roadmap.
Why OAF?
| OAF | Typical frameworks | |
|---|---|---|
| Abstraction | Flat — one agent loop, one tool decorator | Deep chains, hidden prompt wrangling |
| Debuggability | Full prompt/response inspection via hooks | Opaque internal state |
| Surface area | ~10 top-level exports | Hundreds of classes |
| Tool definition | Decorate any async function |
Special base classes, schemas, descriptors |
| Context engineering | Single subclass point — you own the prompt | Scattered across prompt templates, chains, memory |
| Multi-agent | Shared context, isolated internal tools | Tight coupling, global state |
| Providers | OpenAI + Anthropic, same API | Often single-provider or heavy adapter layer |
Install
pip install scope-oaf
Requires Python 3.11+
Quick Start
import asyncio
from oaf import Agent, ToolRegistry
registry = ToolRegistry()
@registry.register
async def get_weather(city: str) -> str:
"""Get the current weather for a city."""
return f"Weather in {city}: 72°F, sunny"
agent = Agent(model="gpt-4.1-nano", tools=registry)
response = asyncio.run(agent.message("What's the weather in Tokyo?", role="user"))
print(response.text)
A full agent with tool calling in 10 lines.
Features
Tool Calling
Decorate any async function — type hints and docstrings become JSON Schema automatically:
@registry.register
async def search(query: str, max_results: int = 5) -> str:
"""Search the web for a query."""
return f"Results for {query}"
Supports: str, int, float, bool, list[T], dict[K,V], Optional[T], Union, Enum, nested generics.
Tool Groups
Organize related tools with auto-prefixed names:
from oaf import BaseToolGroup, tool_method
class MathTools(BaseToolGroup):
name = "math"
@tool_method
async def add(self, a: int, b: int) -> str:
"""Add two numbers."""
return str(a + b)
registry.register_group(MathTools())
# → "math.add"
Streaming
async for chunk in agent.message_stream("Tell me a story"):
print(chunk.delta_text, end="")
Lifecycle Hooks
Tap into every stage — subclass or register ad-hoc:
from oaf import Hooks
class MyHooks(Hooks):
async def before_message(self, message, role, messages):
print(f"→ {message}")
async def after_tool_call(self, tool_name, arguments, result):
print(f" {tool_name}({arguments}) = {result}")
Available events: before_message, after_message, before_tool_call, after_tool_call, on_stream_chunk, on_error, on_context_update, system_prompt_change.
Context Engineering
BaseContext is the single subclass point for custom prompt assembly. The default ConversationalInMemory handles message/token limits. Build your own for vector DB injection, RAG, dynamic prompt sections, and more:
from oaf import BaseContext
class MyRAGContext(BaseContext):
def build_messages(self, **kwargs):
# Inject retrieved documents, manage tool output retention,
# add dynamic system prompt fields — you control everything.
...
Provider Agnostic
Swap models by changing a string — OpenAI and Anthropic use the same interface:
agent_openai = Agent(model="gpt-4.1-nano", ...)
agent_claude = Agent(model="claude-sonnet-4-20250514", ...)
LLM Client (standalone)
The built-in LLMClient works independently of the agent framework:
from oaf.llmclient import LLMClient, SyncLLMClient, Message
# Async
client = LLMClient()
response = await client.chat("gpt-4.1-nano", [Message(role="user", content="Hello")])
# Sync
client = SyncLLMClient()
response = client.chat("gpt-4.1-nano", [Message(role="user", content="Hello")])
# Embeddings
emb = await client.embed("text-embedding-3-small", "Hello world")
Model Registry
Built-in metadata for all current models — context windows, pricing, output limits:
from oaf.llmclient import Models, get_model, list_models
model = Models.GPT_4_1_NANO
print(model.context_window) # 1_000_000
print(model.input_price_per_mtok) # 0.10
all_openai = list_models("openai")
Architecture
┌──────────────────────────────────────────────────┐
│ Your Application │
├──────────────────────────────────────────────────┤
│ OpenAgentFramework (OAF) │
│ ┌──────────┐ ┌──────────┐ ┌───────────────┐ │
│ │ Agent │ │ Tools │ │ Context │ │
│ │ │ │ │ │ + Prompts │ │
│ └──────────┘ └──────────┘ └───────────────┘ │
├──────────────────────────────────────────────────┤
│ llmclient (built-in) │
│ OpenAI + Anthropic providers │
└──────────────────────────────────────────────────┘
| Component | What it does |
|---|---|
| Agent | Async LLM call + tool execution loop. Configurable max rounds, temperature, model. |
| Tools | @tool decorator, ToolRegistry, BaseToolGroup. Python types → JSON Schema. |
| Context | Owns message storage, tool awareness, and prompt assembly. Single subclass point. |
| Hooks | 8 lifecycle events. Class-based or ad-hoc. before_* events support mutation. |
| LLMClient | Unified async/sync client for OpenAI + Anthropic. Streaming, embeddings, tool calling. |
| Prompts | build_tool_prompt() renders tool descriptions. Override in your context for full control. |
Examples
| File | Description |
|---|---|
chat.py |
Interactive terminal REPL with tool-calling agent |
web.py + web_ui.html |
Browser-based chat UI with streaming |
# Terminal REPL
export OPENAI_API_KEY=sk-... # or set ANTHROPIC_API_KEY
python chat.py
# Web UI
pip install -e ".[web]"
python web.py # → http://localhost:8080
Contributing
We use GitHub issues and labels to track work. See below for the label taxonomy.
Setup
git clone https://github.com/devincii-io/scope-oaf.git
cd scope-oaf
pip install -e ".[dev]"
pytest # 82 tests, no API keys needed
Branch Workflow
| Branch | Purpose |
|---|---|
dev |
All development. Push after every logical change. |
master |
Releases only. Push triggers PyPI publish via GitHub Actions. |
GitHub Labels
| Label | Color | Description |
|---|---|---|
bug |
🔴 #d73a4a |
Something isn't working |
enhancement |
🟢 #a2eeef |
New feature or improvement |
breaking |
🟠 #e99695 |
Breaking API change |
agent |
🔵 #1d76db |
Agent loop, tool execution, multi-agent |
context |
🔵 #1d76db |
Context system, prompt assembly |
tools |
🔵 #1d76db |
Tool decorator, registry, groups, type inference |
llmclient |
🔵 #1d76db |
LLM client, providers, streaming |
hooks |
🔵 #1d76db |
Lifecycle event system |
docs |
🟡 #fef2c0 |
Documentation only |
tests |
🟡 #fef2c0 |
Test coverage |
good first issue |
🟢 #7057ff |
Good for newcomers |
help wanted |
🟢 #008672 |
Looking for contributors |
wontfix |
⚪ #ffffff |
Not planned |
Release
- Bump
versioninpyproject.toml - Merge
dev→master - GitHub Actions publishes to PyPI automatically (trusted publishing via OIDC)
Documentation
| Document | Contents |
|---|---|
OAF.md |
Full project docs — architecture, API reference, design decisions, task list |
README.md |
This file — quick start, features, contributing |
LICENSE |
MIT License |
Glossary
| Term | Definition |
|---|---|
| Agent | The core orchestrator. Sends messages to an LLM, parses tool calls from responses, executes tools, and loops until the LLM gives a final text answer. |
| Tool | An async Python function decorated with @tool or @registry.register. Its signature and docstring are converted to JSON Schema and injected into the system prompt for the LLM to call. |
| ToolRegistry | A collection of tools. Passed to the agent or context. Supports registration via decorators and tool groups. |
| Tool Group | A class extending BaseToolGroup with @tool_method methods. Methods are registered with a group.method naming convention. |
| Context | An object implementing BaseContext that owns message storage, tool awareness, and prompt assembly. The single customization point for what the LLM sees. |
| ConversationalInMemory | The default context implementation. In-memory message history with configurable message count and token budget limits. |
| Hooks | Lifecycle event system. Fires callbacks at key points: before/after messages, before/after tool calls, on errors, on stream chunks. |
| LLMClient | Unified async client for OpenAI and Anthropic APIs. Auto-detects provider from model name. Supports chat, streaming, and embeddings. |
| SyncLLMClient | Thread-safe synchronous wrapper around LLMClient for non-async code. |
| System Prompt | The initial instruction text sent to the LLM as a role="system" message. Tool descriptions are prepended to it by the context. |
| Tool Prompt | The formatted text block describing available tools and the JSON response schema. Built by build_tool_prompt() in prompts/raw.py. |
| Internal Tools | Agent-owned ToolRegistry (currently empty, reserved for built-in capabilities). Passed to build_messages() at call time — not stored on the context — so multiple agents can share a context safely. |
| JSON Tool Calling | OAF's approach to tool use: tools are described in the system prompt as JSON Schema, and the LLM returns a JSON object with tool_calls. This is owned by OAF, not using native OpenAI/Anthropic function-calling APIs. |
| Provider | An LLM backend (OpenAI or Anthropic). Auto-detected from the model name prefix (gpt-* → OpenAI, claude* → Anthropic). |
License
MIT — use it however you want.
Built by devincii-io
Project details
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 scope_oaf-0.2.2.tar.gz.
File metadata
- Download URL: scope_oaf-0.2.2.tar.gz
- Upload date:
- Size: 58.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
21189d7490c32436a11acb81871902b2b7f8da2c99d9dad4fb3259450cade2d3
|
|
| MD5 |
52d40b283c06aa7223b5af55d6449ed9
|
|
| BLAKE2b-256 |
5998d1ce698a1ffe8d9b6a344046f13e2756ecafd0b28a76275bfe5924a06482
|
Provenance
The following attestation bundles were made for scope_oaf-0.2.2.tar.gz:
Publisher:
publish.yml on devincii-io/scope-oaf
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
scope_oaf-0.2.2.tar.gz -
Subject digest:
21189d7490c32436a11acb81871902b2b7f8da2c99d9dad4fb3259450cade2d3 - Sigstore transparency entry: 1231494792
- Sigstore integration time:
-
Permalink:
devincii-io/scope-oaf@49f76c312d44e3608eb83b82e3bafb09c376728c -
Branch / Tag:
refs/heads/master - Owner: https://github.com/devincii-io
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@49f76c312d44e3608eb83b82e3bafb09c376728c -
Trigger Event:
push
-
Statement type:
File details
Details for the file scope_oaf-0.2.2-py3-none-any.whl.
File metadata
- Download URL: scope_oaf-0.2.2-py3-none-any.whl
- Upload date:
- Size: 41.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b5292902e5d8e6e144f3eff7747995b30f9fe8466e8ced51e01668e3a6908786
|
|
| MD5 |
4585ca5d8c27a4eaa9d3508b429f2106
|
|
| BLAKE2b-256 |
e9ef1af3bcabbd721834491aafae9f28e627d99748c3d0e26adb0aedd80ddfbd
|
Provenance
The following attestation bundles were made for scope_oaf-0.2.2-py3-none-any.whl:
Publisher:
publish.yml on devincii-io/scope-oaf
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
scope_oaf-0.2.2-py3-none-any.whl -
Subject digest:
b5292902e5d8e6e144f3eff7747995b30f9fe8466e8ced51e01668e3a6908786 - Sigstore transparency entry: 1231494802
- Sigstore integration time:
-
Permalink:
devincii-io/scope-oaf@49f76c312d44e3608eb83b82e3bafb09c376728c -
Branch / Tag:
refs/heads/master - Owner: https://github.com/devincii-io
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@49f76c312d44e3608eb83b82e3bafb09c376728c -
Trigger Event:
push
-
Statement type: