Architecture Overview
Version: 1.0 | Date: 2026-03-25 | Author: Abdelaziz Abdelrasol
Executive Summary
orchcore is a Python package (3.12 or later, built on asyncio) for launching, monitoring, and managing AI coding agent CLIs in phase-based pipelines. It is not an agent: it runs external agent CLIs (Claude Code, Codex, Antigravity, Copilot, OpenCode) as subprocesses.
The package was extracted from four production orchestration systems: Planora, Articles, Finvault, and Raven/Ralph. The infrastructure they shared, an estimated 60-70% of their orchestration code, moved into orchcore. Domain-specific logic (prompt content, output interpretation, presentation) stays in each consuming project.
Key Architectural Decisions
- The engine reports progress through the
UICallbackprotocol, so each project supplies its own CLI, TUI, or headless output (ADR-003). - A four-stage stream processing pipeline (Filter, Parse, Monitor, Stall Detect) normalizes the different JSONL formats of the agent CLIs into one event model (ADR-004).
- Agent configurations are data (TOML or dict) rather than classes, so a new agent CLI that uses a supported stream format needs no code changes (ADR-007).
System Context (C4 Level 1)
C4Context
title System Context — orchcore
Person(dev, "Developer", "Uses consuming project CLIs")
System(orchcore, "orchcore", "Reusable orchestration core")
System_Ext(claude, "Claude Code CLI", "Anthropic")
System_Ext(codex, "Codex CLI", "OpenAI")
System_Ext(antigravity, "Antigravity CLI", "Google")
System_Ext(copilot, "Copilot CLI", "GitHub/Microsoft")
System_Ext(opencode, "OpenCode CLI", "Open-source")
System(planora, "Planora", "Multi-agent planning")
System(articles, "Articles", "Article writing pipeline")
System(finvault, "Finvault", "Performance audit")
System(raven, "Raven/Ralph", "Task-driven development")
Rel(dev, planora, "Uses")
Rel(dev, articles, "Uses")
Rel(dev, finvault, "Uses")
Rel(dev, raven, "Uses")
Rel(planora, orchcore, "Imports")
Rel(articles, orchcore, "Imports")
Rel(finvault, orchcore, "Imports")
Rel(raven, orchcore, "Imports")
Rel(orchcore, claude, "Launches as subprocess")
Rel(orchcore, codex, "Launches as subprocess")
Rel(orchcore, antigravity, "Launches as subprocess")
Rel(orchcore, copilot, "Launches as subprocess")
Rel(orchcore, opencode, "Launches as subprocess")
Package Layout
src/orchcore/
├── stream/ # 4-stage JSONL processing pipeline
│ ├── events.py # StreamEvent, StreamFormat, AgentState models
│ ├── filter.py # StreamFilter: pre-parse noise reduction
│ ├── parser.py # StreamParser: dispatches each line to its format module
│ ├── formats/ # one wire-format parser per agent CLI
│ ├── monitor.py # AgentMonitor: real-time state tracking
│ └── stall.py # StallDetector: timeout detection
├── pipeline/ # Phase orchestration engine
│ ├── phase.py # Phase, PhaseResult, PipelineResult models
│ ├── engine.py # PhaseRunner: per-phase execution
│ ├── pipeline.py # PipelineRunner: cross-phase coordination
│ └── control.py # FlowControl: pause, resume, skip
├── runner/ # Async subprocess management
│ └── subprocess.py # AgentRunner
├── registry/ # Agent configuration
│ ├── agent.py # AgentConfig, ToolSet models
│ ├── registry.py # AgentRegistry: TOML/dict lookup
│ └── versioning.py # CLI version compatibility checks
├── config/ # Layered configuration
│ ├── settings.py # OrchcoreSettings, load_settings_with_profile
│ └── schema.py # AgentOverrideConfig
├── recovery/ # Rate-limit & error recovery
│ ├── rate_limit.py # RateLimitDetector, ResetTimeParser, BackoffStrategy
│ ├── retry.py # RetryPolicy, FailureMode
│ └── git_recovery.py # GitRecovery
├── workspace/ # Artifact lifecycle
│ └── manager.py # WorkspaceManager
├── prompt/ # Jinja2 templates
│ ├── template.py # render_template, render_string, strip_frontmatter
│ └── loader.py # TemplateLoader
├── display/ # ANSI colored logging (no Rich)
│ ├── logging.py # log_info, log_error, status_line, phase_header
│ └── formatting.py # format_cost, format_duration, format_tokens
├── ui/ # UICallback protocol
│ └── callback.py # UICallback, NullCallback, LoggingCallback
├── signals/ # Graceful shutdown
│ └── handler.py # SignalManager
├── observability/ # Optional OpenTelemetry
│ └── telemetry.py # OrchcoreTelemetry
└── __init__.py
Component Diagram
┌─────────────────────────────────────────────────────────────────┐
│ Consuming Project │
│ (Custom UICallback, Phase Definitions, Prompt Templates, TOML) │
└──────────────────────────────┬──────────────────────────────────┘
│
┌──────────────────────────────▼──────────────────────────────────┐
│ orchcore │
│ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌───────────┐ │
│ │ pipeline/ │ │ runner/ │ │ registry/ │ │ config/ │ │
│ │ DAG phases │─▶│ subprocess │─▶│ agent TOML │ │ layered │ │
│ │ seq/parallel│ │ async I/O │ │ lookup │ │ settings │ │
│ └─────┬──────┘ └─────┬──────┘ └─────────────┘ └───────────┘ │
│ │ │ │
│ ┌─────▼───────────────▼──────────────────────────────────────┐ │
│ │ stream/ (4-stage pipeline) │ │
│ │ JSONL ─▶ Filter ─▶ Parse ─▶ Monitor ─▶ Stall Detect │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────┐ ┌──────────┐ ┌─────────┐ ┌──────────┐ ┌────────┐│
│ │recovery/ │ │workspace/│ │ prompt/ │ │ signals/ │ │ ui/ ││
│ │rate-limit│ │ artifact │ │ Jinja2 │ │ graceful │ │Protocol││
│ │retry,git │ │lifecycle │ │templates│ │ shutdown │ │callback││
│ └──────────┘ └──────────┘ └─────────┘ └──────────┘ └────────┘│
└──────────────────────────────────────────────────────────────────┘
Core Abstractions
Pipeline Execution Model
Execution has three levels:
PipelineRunnerruns the phases in topological dependency order: dependencies first, with declaration order kept among independent phases (ADR-010). It handles the resume, skip, and only-phase options. A required phase whose dependencies are unmet fails the pipeline instead of counting as a success.PhaseRunnerexecutes a single phase. It launches the phase's agents sequentially or in parallel throughAgentRunner, enforces the concurrency limit, and aggregates the results.AgentRunnerlaunches each subprocess with an explicit command, a filtered environment, an optional working directory, and a stream parser, and terminates the whole process tree on shutdown.
sequenceDiagram
participant C as Consumer
participant PR as PipelineRunner
participant PhR as PhaseRunner
participant AR as AgentRunner
participant A as Agent CLI
C->>PR: run_pipeline(phases, prompts, callback)
loop each phase in dependency order
PR->>PhR: run_phase(phase, prompt, callback)
alt parallel phase
par for each agent
PhR->>AR: run(agent, prompt, output_path, cwd)
AR->>A: subprocess launch with filtered env + cwd
A-->>AR: JSONL stream
AR-->>PhR: AgentResult
end
else sequential phase
loop for each agent
PhR->>AR: run(agent, prompt, output_path, cwd)
AR->>A: subprocess launch with filtered env + cwd
A-->>AR: JSONL stream
AR-->>PhR: AgentResult
end
end
PhR-->>PR: PhaseResult
end
PR-->>C: PipelineResult
Stream Processing Pipeline
Every line of JSONL output passes through four stages:
StreamFilterdrops high-volume lines with plain string matching beforejson.loads()runs (an estimated 95% of lines in typical Claude output).StreamParserturns each remaining line into normalizedStreamEventinstances, using one parser module per format.AgentMonitortracks the agent's state (a nine-state machine), tools, cost, and tokens.StallDetectorinjects a syntheticSTALLevent when the agent is silent for longer than the timeout.
Stream Pipeline describes each stage.
Tool Resolution Order
For each agent in a phase, the effective ToolSet is the first one found in this order (from highest to lowest priority):
Phase.agent_tools[agent] > explicit toolset > Phase.tools > none
Flag profiles (AgentConfig.flags, selected via Phase.flag_profile or the
run_pipeline fallback) are independent of this resolution: a selected
profile's flags are always applied, and the ToolSet translation (when one
resolves) is appended after them.
See ADR-009: Tool assignment as phase-level concern and ADR-011: Consumer-defined flag profiles.
Design Principles
| Principle | How It's Applied |
|---|---|
| Composability | Each of the 12 modules can be used on its own |
| Extensibility | A new agent CLI with a supported stream format needs only TOML configuration |
| Reliability | Configurable partial-failure semantics; optional integrations degrade to no-ops |
| Performance | Design targets of < 5 ms per event and < 100 ms per subprocess launch |
| Type Safety | mypy strict, with Pydantic validation at the public boundaries |
| Async-First | Stdlib asyncio only, with explicit task creation, fail-fast waits, and gather-based result collection |
| Protocol-Based DI | UICallback is a Protocol, not a base class |
| Registry-as-Data | Agents are defined in TOML or dicts, not as classes |
Quality Attributes
| Priority | Attribute | Target |
|---|---|---|
| 1 | Extensibility | New agent CLI via TOML only |
| 2 | Reliability | Graceful degradation under partial failure |
| 3 | Composability | Each component usable standalone |
| 4 | Performance | Stream < 5ms/event, launch < 100ms |
| 5 | Type Safety | mypy strict, zero errors |
Constraints
- Python 3.12 or later, for
tomllib, modern type syntax, and current asyncio APIs. - asyncio only, with no trio or gevent. Blocking file operations run in worker threads through
asyncio.to_thread(). - Four core dependencies: pydantic >= 2.10, pydantic-settings >= 2.7, jinja2 >= 3.1, and tzdata >= 2024.1.
- orchcore handles no agent API keys; each agent CLI manages its own authentication.
- Signal handling uses event-loop handlers where available and falls back to classic
signal.signal()handlers on Windows.
Related
- Design Document: problem statement, requirements, and key interfaces
- Stream Pipeline: the four stream-processing stages
- Architecture Decision Records: the list of all 11 ADRs