Architecture¶
Design Overview¶
agentabi follows a layered architecture with three key concepts:
- Session — The consumer-facing API
- Provider — The adapter layer for each agent
- IR (Intermediate Representation) — The common event format
graph TD
A[Your Code] --> B[Session]
B --> C{Provider Registry}
C --> D[ClaudeNativeProvider]
C --> E[CodexNativeProvider]
C --> F[GeminiNativeProvider]
C --> G[OpenCodeNativeProvider]
D --> H[claude CLI]
E --> I[codex CLI]
F --> J[gemini CLI]
G --> K[opencode CLI]
D --> L[IR Events]
E --> L
F --> L
G --> L
L --> B
Provider Model¶
Provider Protocol¶
Every provider implements a common protocol with four methods:
is_available()— Can this provider be used?capabilities()— What features does it support?stream(task)— Execute and yield IR eventsrun(task)— Execute and return aggregated result
Provider Types¶
Native Providers run the agent CLI as a subprocess, parsing its structured output (JSON/JSONL) into IR events. They have zero extra Python dependencies.
SDK Providers use the agent's official Python SDK. They provide tighter integration but require installing optional dependencies.
Fallback Chains¶
Each agent has an ordered list of providers. The registry tries each in order:
claude_code → [ClaudeNativeProvider, ClaudeSDKProvider]
codex → [CodexNativeProvider, CodexSDKProvider]
gemini_cli → [GeminiNativeProvider, GeminiSDKProvider]
opencode → [OpenCodeNativeProvider]
If a native provider is available (CLI in PATH), it is preferred. If not, the SDK provider is tried.
Intermediate Representation (IR)¶
The IR is a set of TypedDict event types that normalize all agent output into a common format. This is inspired by compiler IRs (like LLVM IR) — each agent's native event format is "compiled" into IR events.
Design Principles¶
- Union of capabilities — The IR supports all features from all agents. Agent-specific fields are optional.
- TypedDict over dataclass — Events are plain dictionaries for easy serialization and zero overhead.
- Discriminated union — Every event has a
typefield for pattern matching. - Additive evolution — New event types and optional fields can be added without breaking existing consumers.
Event Categories¶
| Category | Events | Purpose |
|---|---|---|
| Session lifecycle | session_start, session_end |
Session boundaries |
| Message flow | message_start, message_delta, message_end |
Text streaming |
| Tool execution | tool_use, tool_result |
Tool call tracking |
| Metadata | usage, error, file_diff |
Stats and diagnostics |
| Permissions | permission_request, permission_response |
Approval flow |
Project Structure¶
src/agentabi/
├── __init__.py # Public API exports
├── session.py # Session class + run_sync()
├── auto_detect.py # Agent discovery
├── providers/
│ ├── base.py # Provider protocol + default_run()
│ ├── registry.py # Provider chain registry
│ ├── claude_native.py # Claude subprocess provider
│ ├── claude_sdk.py # Claude SDK provider
│ ├── codex_native.py # Codex subprocess provider
│ ├── codex_sdk.py # Codex SDK provider
│ ├── gemini_native.py # Gemini subprocess provider
│ ├── gemini_sdk.py # Gemini SDK provider
│ └── opencode_native.py # OpenCode subprocess provider
└── types/
└── ir/
├── events.py # IR event TypedDicts
├── session.py # SessionResult
├── task.py # TaskConfig
├── capabilities.py # AgentCapabilities
├── permissions.py # Permission types
├── helpers.py # Event creation helpers
└── type_guards.py # Runtime type guards