Concepts
Apiary is a task-driven agent orchestration harness. It connects project management tools (GitHub Issues, Plane, …) to AI agent runners and routes each task to the right agent and model based on declarative rules — no human dispatcher in the middle.
Vocabulary
| Term | Meaning |
|---|---|
| Hive | A configured Apiary instance — one apiary.yaml plus its .apiary/ state directory |
| Source | A task-system integration that polls work items and writes results back (GitHub, Plane, …) |
| Task | A unit of work flowing through the system — born from a source item (an issue) or spawned internally by another task |
| Runner | How an agent executes — a CLI subprocess (claude, opencode, cursor) or an API call |
| Agent | A named LLM persona: runner + model + optional soul file, skills, identity, and fallbacks |
| Workflow | A pipeline of steps, fired by a trigger that matches tasks; each agent step runs an agent |
| Trigger | The matching rule (labels, states, source, …) that decides which workflow handles a task |
| Workflow instance | One execution of a workflow against one task — what apiary instances lists |
| Step run | One step of an instance: an agent invocation, an approval gate, a CI wait, a split decision |
| Soul file | A markdown file holding an agent's system prompt / persona |
| Agent memory | Opt-in persistent memory: per-task working notes plus durable daemon-wide facts, written by agents via APIARY_MEMORIZE and recalled into step prompts — see Agent Memory |
Architecture
flowchart LR
subgraph external [Task systems]
GH[GitHub Issues]
PL[Plane]
end
subgraph daemon ["apiary run (daemon)"]
POLL[Source pollers]
ROUTER[Router<br/>workflow triggers]
ENGINE[Workflow engine]
STORE[(SQLite<br/>.apiary/apiary.db)]
end
subgraph runners [Runners]
CLAUDE[claude CLI]
OC[opencode CLI/API]
end
GH -->|poll| POLL
PL -->|poll| POLL
POLL --> ROUTER
ROUTER --> ENGINE
ENGINE -->|dispatch step| CLAUDE
ENGINE -->|dispatch step| OC
CLAUDE -->|result| ENGINE
OC -->|result| ENGINE
ENGINE -->|comments, labels, state| GH
ENGINE -->|comments, labels, state| PL
ENGINE <--> STORE
DASH[apiary dashboard] -.reads.-> STORE
The daemon (apiary run) does all the work; the dashboard and
the CLI inspection commands (status, instances, task) are read-only
windows into the same SQLite store, connected over a local IPC socket.
Lifecycle of a task
- Poll. Each source polls its tracker on its
poll_intervaland returns the items passing itsfilters. Polling is read-only and idempotent — the same open issue shows up every cycle. - Route. The router evaluates workflow
triggers in ascendingpriorityorder (lower number first). Every matching workflow gets an instance — a task can fan out to several workflows — unless a matching trigger is markedexclusive: true, which claims the task and stops evaluation. In-flight and already-completed work is tracked internally, so a task is not re-dispatched while it runs (and never again, foronce: truetriggers). - Execute. The workflow engine runs the instance step by step: agent
steps dispatch a runner subprocess/API call, approval and
wait_forsteps park the instance until a human or CI resolves it, splits and conditions pick a path. Every attempt, token count, and log line is recorded. - Write back. Agents can publish progress to the source item mid-run
(
APIARY_PUBLISH), and the workflow'son_complete/on_failhooks set states and labels when the instance finishes. - Settle. When the last instance for a task reaches a terminal state, the
top-level
tasks:hooks fire once for the task as a whole.
Internal tasks
Source items are not the only way work enters the system. An agent can
spawn child tasks at runtime (APIARY_SPAWN) — for
example, a product-owner agent decomposing a spec into implementation tasks.
Spawned tasks are first-class: they have lineage (parent → children), drive
their own workflow instances, and appear in the dashboard's Tasks tab. The
canonical unit of work is therefore the internal task; a source item (a
GitHub issue) is one possible binding of a task to the outside world.
Where state lives
Everything is project-scoped, in a .apiary/ directory next to your config:
.apiary/
apiary.yaml ← your config (alternative location)
apiary.db ← SQLite database (auto-created)
apiary.sock ← IPC socket between daemon and dashboard/CLI
logs/ ← log files
memory/ ← agent memory (markdown; only when settings.memory is enabled)
apiary.yaml ← your config (default location)
.env ← auto-loaded environment variables (don't commit)
There is no server, no cloud component, and no telemetry by default. The full table-level reference is on the Data Model page.
Design principles
- Declarative routing. Which agent handles which task is config, not code. Change a label rule, restart, done.
- Everything is recorded. Every runner invocation — including rate-limit
failovers and retries — is its own execution row with timing, tokens, and
cost. The dashboard and
apiary task <id>reconstruct the full history. - Credentials stay outside. CLI runners authenticate however the underlying tool does; Apiary never stores or proxies provider credentials. Source tokens are read from environment variables.
- Safe to leave running. Rate-limit failover, a re-dispatch failure cap, non-blocking dispatch, and restart rehydration are built in — see Rate limits & resilience.