@agrimsingh/apiary
v0.1.1
Published
Repo-local autonomous swarm conductor for Codex app-server
Readme
Apiary
Repo-local swarm conductor for autonomous, role-based software execution.
Apiary runs a loop of specialized roles (queen, worker, auditor, medic) against your repository, verifies progress through configurable gates, and persists full run state/events in local SQLite.
What Apiary does
At a high level, apiary run:
- Validates project config (
apiary.md,apiary.gates.yml,apiary.promptpacks.yml) - Requires a clean git tree
- Creates a new branch (
apiary/<timestamp>-swarm) - Starts (or connects to) a local daemon
- Executes iterative role turns:
- Queen plans a bounded next slice
- Worker implements it
- Auditor verifies it
- Medic repairs failures
- Runs required gates (tests/typecheck/smoke/etc.)
- Commits successful output and optionally pushes
- Stores full timeline, artifacts, and summaries in
.apiary/state.sqlite
Architecture
┌──────────────┐ JSON lines over unix socket ┌─────────────────────┐
│ apiary CLI │ ───────────────────────────────▶ │ local daemon │
│ (commands/*) │ │ (daemon/server.ts) │
└──────┬───────┘ └──────┬───────────────┘
│ │
│ │ JSON-RPC over stdio
│ ▼
│ ┌─────────────────────┐
│ │ codex app-server │
│ │ (role threads/turns)│
│ └─────────────────────┘
│
▼
┌─────────────────────┐
│ .apiary/state.sqlite│
│ runs, events, gates,│
│ approvals, snapshots│
└─────────────────────┘Main modules
src/cli.ts- command surface (init,up,run,stop,status,replay,auth,cleanup,stats)src/daemon/server.ts- daemon RPC server, subscriptions, approval handling, app-server lifecycle/restartsrc/daemon/ipc.ts- newline-delimited JSON IPC protocol helperssrc/orchestrator/loopEngine.ts- core queen/worker/auditor/medic loop and budgetssrc/orchestrator/gateRunner.ts- gate execution with timeout/env requirements/log capturesrc/orchestrator/approvalPolicy.ts- command/file-change approval decisions per profilesrc/orchestrator/gitOps.ts- branch creation, commit, push, remote detectionsrc/codex/appServerSession.ts- role thread management + turn dispatch to app-serversrc/codex/stdioRpcClient.ts- JSON-RPC client over child process stdiosrc/config/loaders.ts- YAML/frontmatter config loading with Zod validationsrc/storage/db.ts- persistence, summaries, event queries, cleanupsrc/utils/env.ts- shared.envloading, upserting, and validationsrc/utils/helpers.ts- sharedpickStringutility for nested key extractionsrc/utils/tokenUsage.ts- shared token usage estimation from heterogeneous payloadssrc/utils/shellTokenizer.ts- shared POSIX-style shell command tokenizersrc/tui/*- Ink terminal UI for live run status and interaction
Prerequisites
- Git repository (run Apiary from repo root)
- Node.js + npm (recent LTS)
codexCLI available in PATH (used forcodex app-serverandcodex login)
Install / run
npm package (fastest path)
npx @agrimsingh/apiary init
npx @agrimsingh/apiary up
npx @agrimsingh/apiary run --profile fastOptional global install:
npm i -g @agrimsingh/apiary
apiary statusDev mode
npm install
npm run dev -- init
npm run dev -- up
npm run dev -- run --profile fastBuilt binary
npm install
npm run build
node dist/cli.js init
node dist/cli.js run --profile safePublish to npm (@agrimsingh)
npm login
npm publish --access publicCommand reference
apiary init
Creates default project config (if missing):
apiary.mdapiary.gates.ymlapiary.promptpacks.yml
Also ensures .gitignore contains:
.apiary/.env
apiary up
Ensures daemon is running and waits for app-server readiness (appServer=online).
- Timeout default: 90s
- Override with
APIARY_UP_TIMEOUT_SEC(seconds)
apiary run
apiary run \
--profile fast \
--iterations 25 \
--max-minutes 180 \
--max-tokens 3000000 \
--turn-timeout-sec 300 \
--steer "focus on P0 regressions" \
--pushOptions:
--profile <fast|safe>(if omitted, usesdefaults.profilefromapiary.gates.yml; fallback isfast)--iterations <n>--max-minutes <n>--max-tokens <n>--turn-timeout-sec <n>--steer <text>(initial steering text)--push(force push on success; redundant when effective profile isfast)
Notes:
- Numeric options must be positive integers.
- Run fails early if working tree is dirty.
- Run always creates a new branch before execution.
apiary status
apiary status
apiary status --summary
apiary status --summary --compact
apiary status --health
apiary status --summary --run-id <runId>Shows daemon/app-server status; optional run summary and health probes.
apiary replay
apiary replay --run-id <runId>
apiary replay --run-id <runId> --followReplays timeline events from SQLite; --follow tails in near-real-time.
apiary stop
Requests active run interruption and daemon shutdown, then removes local pid/socket artifacts.
apiary auth
apiary auth --status
apiary auth --device-auth
apiary auth --with-api-keyDelegates to codex login flow.
apiary cleanup
apiary cleanup --days 30Prunes old completed/failed/interrupted runs and associated artifacts from SQLite.
Default days:
- CLI default:
30 - Env override:
APIARY_CLEANUP_DAYS
apiary stats
apiary stats
apiary stats --days 7
apiary stats --compactShows aggregate analytics from local SQLite run history.
Options:
--days <n>include only runs started in the last N days--compactprint single-line key=value output
Default JSON output shape:
{
"totalRuns": 12,
"byStatus": { "completed": 8, "failed": 3, "interrupted": 1 },
"passRate": "66.7%",
"iterations": { "avg": 4.2, "max": 15 },
"completionReasons": [
{ "reason": "objective_done", "count": 5 },
{ "reason": "no_edit_gates_passed", "count": 3 }
],
"mostFailedGates": [
{ "gateId": "tests", "failures": 5 },
{ "gateId": "typecheck", "failures": 2 }
],
"timeSpan": {
"firstRun": "2026-02-18T10:00:00.000Z",
"latestRun": "2026-02-20T15:30:00.000Z"
}
}Compact output example:
totalRuns=12 completed=8 failed=3 interrupted=1 passRate=66.7% avgIterations=4.2 maxIterations=15 topCompletionReason=objective_done:5 topFailedGate=tests:5Run lifecycle (state machine)
Loop phases from LoopEngine:
- bootstrap - initialize run, load config/prompts
- plan - queen produces JSON dispatch (
decision, prompts, success criteria) - implement - worker applies bounded code changes
- verify - run gates + auditor review
- repair - medic applies minimal fixes using failure packet
- repeat until done / budget exhausted / failure
Termination conditions include:
- Objective completed (
queensaysdoneand gates are clean) - Max iterations reached
- Wall-clock deadline exceeded
- Token budget exceeded
- Worker no-edit limit hit (auto-completes if all required gates pass; fails otherwise)
- No-progress limit hit
- Explicit stop/interruption
- Internal error (unexpected exception in run loop)
Interactive TUI (apiary run in TTY)
Hotkeys:
aaccept approvalsaccept approval for sessionddecline approvalccancel approvaliinterrupt runqquit view:command mode
Command mode:
:status:steer <text>:secret KEY VALUE:quit
Secret handling
:secret KEY VALUE writes/updates .env with secure validation:
- key must match
^[A-Z_][A-Z0-9_]*$ - value cannot contain newline / CR / null byte
.envpermissions are forced to0600(best-effort).envloader ignores invalid key lines from manual edits
Config files
apiary.md (frontmatter contract)
Required frontmatter fields:
title: stringobjective: stringconstraints: string[]acceptance_criteria: string[]out_of_scope: string[]
apiary.gates.yml
Schema highlights:
version: 1defaults.profile: fast|safedefaults.timeoutSec: positive integergates[]:id(alphanumeric/_/-)required: booleancommand: stringtimeoutSec: positive integerenvRequired?: string[]pass.exitCode
Gate logs are written to:
.apiary/gate-<sanitized-id>.log
Profile resolution:
- CLI
--profileoverrides config - if
--profileis omitted, daemon usesdefaults.profile - if config cannot be loaded, daemon falls back to
fast
Timeout resolution:
- if a gate omits
timeoutSec, loader appliesdefaults.timeoutSec - if a gate explicitly sets
timeoutSec, that explicit value wins
apiary.promptpacks.yml
Per-role prompt contracts:
queen.system,queen.firstTurnworker.system,worker.firstTurnauditor.system,auditor.firstTurnmedic.system,medic.firstTurn
Safety / approval model
Execution profile affects both sandboxing and approvals.
Profile behavior
| Profile | App-server approvalPolicy | Network access | Typical auto-approval behavior |
|---|---|---|---|
| fast | never | enabled | aggressive auto-approval (accepted_session) when path/command checks pass |
| safe | untrusted | disabled | stricter; many actions require explicit approval |
Role write boundaries
- Writable roles:
worker,medic - Read-only roles:
queen,auditor
All file/command approvals are path-checked against repo root. Out-of-root actions are not auto-approved. Shell control operators (&&, ||, ;, pipes, backticks, $(...), $VAR, ${VAR}) always require approval in both profiles to prevent command chaining attacks.
Git semantics during run
apiary run performs opinionated git automation:
- requires clean working tree up front
- creates new branch:
apiary/<timestamp>-swarm - on successful run:
- stages everything (
git add -A) - commits (
apiary: successful run <runId>) - pushes current branch when effective profile is
fast(or when--pushis set), only if a commit was created
- stages everything (
If no remote exists, push is skipped with a clear message.
Local data layout (.apiary/)
Typical contents:
daemon.sock- local IPC socketdaemon.pid- daemon process id filestate.sqlite- run/event/state databasegate-*.log- per-gate execution output
SQLite includes:
runs,threads,turns,items,item_deltaseventsgate_resultsfailure_packetsapprovalssnapshots
Environment variables
APIARY_UP_TIMEOUT_SEC-upreadiness timeout (seconds, default: 90)APIARY_CLEANUP_DAYS- default retention window for cleanupAPIARY_MODEL- force model selection if available server-sideAPIARY_REASONING_EFFORT-low|medium|high|xhighAPIARY_RPC_TIMEOUT_MS- app-server RPC timeout per requestAPIARY_LOG_LEVEL-debug|info|warn|error
Also common gate env:
OPENAI_API_KEY(required by sampleapi_smokegate)
Troubleshooting
status shows daemon offline
- Run
apiary up - If stale artifacts exist, run
apiary stopthenapiary up
run fails with dirty tree
- Commit/stash before running; Apiary enforces clean start.
run.start says app-server not ready
- Wait for
apiary upreadiness or inspectapiary status --health.
Gate blocked on missing env
- Provide with
:secret KEY VALUEin TUI or add to.env.
Only one run allowed
- If
run already in progress, finish/stop current run first.
Frequent RPC timeouts
- Raise
APIARY_RPC_TIMEOUT_MS. - Inspect app-server health via
apiary status --health.
Development
npm run typecheck
npm test
npm run buildDefault smoke gate:
npm run api:smokeDogfooding
Apiary builds itself. The apiary stats command was implemented entirely by an apiary run session:
- A sprint objective was defined in
apiary.mdspecifying the stats command, its flags (--days,--compact), output shape, and acceptance criteria. apiary run --profile fast --iterations 10was executed against the repo.- The worker created
src/commands/stats.ts,src/commands/stats.test.ts, addedgetAggregateStats()tosrc/storage/db.ts, registered the CLI subcommand, and updated the README. - All required gates (typecheck + tests) passed on every iteration.
- The run completed with 7 new tests, all passing, typecheck clean.
This validated the full loop: queen planning, worker implementation, gate verification, and autonomous convergence on a real feature.
Current maturity
Project is intentionally local-first and stateful. The daemon, approval flow, and loop guards are test-covered across 124 tests in 19 files spanning command helpers, daemon behavior, policy checks, gate parsing/execution, DB constraints, session routing, and shared utility modules.
