A Rust-native, local-first orchestrator that races the AI coding CLIs you already pay for — in parallel, in isolated git worktrees, consuming zero extra tokens of its own.
In chemistry, a catalyst dramatically lowers the activation energy of a reaction without being consumed. Apohara Catalyst does the same to your engineering work: it lowers Time-To-First-Token by dispatching a goal across Claude Code, Codex, and OpenCode simultaneously — while burning zero tokens of its own. The name fuses the chemical catalyst with the LLM TTFT metric.
You already pay for one or more AI coding CLIs. Today you run them one at a time, babysit them, and hope the first answer is the good one.
Apohara Catalyst sits between you and your CLIs. It is Bring-Your-Own-CLI: you stay logged into claude / codex / opencode yourself, and Catalyst only invokes them — it never sees your tokens, opens no OAuth flow, phones no home, and is a single native Rust binary on your machine. You type a goal; it dispatches the CLIs you already have in parallel — each in its own isolated git worktree — runs every result through a quality-gate verification mesh, and surfaces only the winning diff for a one-click Accept (a real git apply).
The race above is today's default mode. The north star — already wired behind APOHARA_MESH and in active validation — is a collaborative mesh: instead of N CLIs redoing the same goal and discarding the losers, a zero-token planner decomposes your goal into a file-disjoint DAG, hands each CLI a different slice, and lets them coordinate live (atomic cross-process claims, a mailbox bus, a single incremental integrator). Every token is spent once, not N times; the CLIs stop competing and start dividing the work — heterogeneous agents (Claude + Codex + OpenCode) cooperating on one repo, with the same BYOC contract and the same human-gated Accept. That's the shape Apohara is converging on: your CLIs, as a coordinated swarm.
┌──────────────────────────────────────────────────────────────┐
│ you type a goal ➜ press Run │
└──────────────────────────────────────────────────────────────┘
│
decompose into role-fit tasks
│
┌────────────────────────────┼────────────────────────────┐
│ git worktree #1 │ git worktree #2 │ git worktree #3
▼ ▼ ▼
claude-code-cli codex-cli opencode-go
(Implement/Debug/Test) (Refactor) (Document)
│ stdout stream │ stdout stream │ stdout stream
▼ ▼ ▼
└──────── each worktree diff ➜ quality-gate mesh (7 gates) ──────┘
│
pick the gate-WINNING diff
│
┌──────────────────┴──────────────────┐
│ CodeDiffPane: provider badge + │
│ files changed + unified diff │
│ [ Accept (git apply) ] [ Reject ] │
└──────────────────────────────────────┘
The role→provider routing shown above is the opt-in smart router (
APOHARA_SMART_ROUTER=1); by default each task fans out to all installed CLIs.
flowchart LR
G[Goal] --> D[Decompose into tasks]
D --> W{Per-task git worktree<br/>created BEFORE spawn}
W --> C[claude-code-cli]
W --> X[codex-cli]
W --> O[opencode-go]
C --> V[Quality-gate mesh<br/>7 gates, no short-circuit]
X --> V
O --> V
V --> P[Pick winning diff<br/>gates passed + non-empty]
P --> A["Accept ➜ git apply -"]
P --> R[Reject]
Every provider runs through the same Rust crates — apohara_dispatch::api::list_active_providers, CliDriver, apohara_worktree::lifecycle, and apohara_verification::run_all_gates — whether you drive it from the desktop, the TUI, or the CLI. One engine, three surfaces.
| Typical AI orchestrator | Apohara Catalyst | |
|---|---|---|
| UI stack | Electron / Tauri + React webview | Native Rust — Dioxus 0.7 desktop (lto + strip + panic=abort), no Node, no webview, no localhost server |
| Token cost | Adds its own model calls / API key | Zero extra tokens — pure stdin/stdout CLI wrappers |
| Auth | OAuth flows, stores tokens | Zero OAuth, by hard design rule — wraps CLIs you're already logged into |
| Code search | External vector DB or hosted embeddings | In-process tree-sitter + sqlite-vec + blake3 feature-hashing — no model download, ~0 resident RAM |
| Isolation | File locks / shared branch | Per-task git worktree created before spawn, with recovery-branch rescue on failure |
| Conflict handling | Coarse file-level locks | Semantic (file, symbol, kind) read/write/rename matrix with rename-dominance |
| Sandbox | Shell-out to firejail / bwrap, or none |
seccomp-bpf allowlist (fail-closed EPERM) + user/mount/PID namespaces, all Rust-native |
| Secrets | .env / plaintext config |
OS keyring (keyring 3.6) + zeroizing SecretString + env sanitization on every spawn |
| Discipline | Ad-hoc | Cross-cutting spec §0 disciplines + a documented Past Incidents ledger (each rule cites a commit + symptom) |
🛡️ A defense-in-depth safety stack — first-class crates, not afterthoughts
apohara-sandbox— seccomp-bpf, fail-closed, tiered. A real BPF program compiled viaseccompiler0.5, with three tiers (ReadOnly~45 pure-allow syscalls,WorkspaceWrite,DangerFullAccess). Unlisted syscalls returnEPERM(recoverable, not a silent allow).cloneis conditionally gated so allCLONE_NEW*namespace bits must be zero (blocks nested-userns escapes);clone3is denied outright. Tests actually fork a child, install the filter, and assertwrite()isEPERMunderReadOnly.- Unprivileged namespace isolation.
enter_isolated_namespaces()bundlesCLONE_NEWUSER|NEWNS|NEWPIDin oneunshare(2)— no root, noCAP_SYS_ADMIN— and writessetgroups=denybefore the uid/gid maps (kernel-required ordering). A sandboxed agent literally cannot resolve the orchestrator's PID to signal it. A double-fork PID-1 topology + 16 MiB output cap means a hostile child can't OOM the orchestrator. apohara-pathsafety— typed symlink-escape detection.canonicalize_recursivewalks one hop at a time (cap = 32) and distinguishes an attack (SymlinkEscape: looks-inside-but-resolves-outside) from a misconfig (EscapesRoot), plusSymlinkLoop,DanglingSymlink, and hard-rejected..traversal. Runs before every spawn.apohara-audit— race-free 0600 JSONL. Append-only audit log opened withmode(0o600)+O_NOFOLLOW, then re-tightened vialibc::fchmod(fd, …)on the live descriptor (not path-based) to close the TOCTOU swap-the-inode window. Daily + 64 MiB rotation, bounded queue, 13EventKindvariants includingPolicyViolationandSandboxBypassed.apohara-secrets— OS-native, zeroizing. Wrapskeyring3.6 (Keychain / Credential Manager / libsecret). Returns aSecretStringthat zeroizes its heap buffer onDropand whoseDebugprintsSecretString(***), so an accidentaltracing::info!(?secret)can never leak cleartext.- Env sanitization, two layers. The sandbox runner's
build_sanitized_env()uses an allowlist + a secret-stripping blocklist; the dispatch driver's spawn path doesenv_clear()then a strict 6-key allowlist (PATH/HOME/USER/LANG/TERM/TMPDIR) with forcedAPOHARA_*markers applied last so a malicious worktree.envcan't spoof orchestrator identity or smuggle an API key.
🧠 An in-process semantic indexer with no model download (`apohara-indexer`)
- Embeddings without a transformer. Deterministic blake3 feature-hashing into a 384-dim, L2-normalized vector — signed hashing with snake_case token-splitting (so
hello_worldmatcheshello world). Nocandle, no model file, no OOM hazard, ~0 resident RAM. The code explicitly traded away a prior Nomic-BERT + redb stack to kill that hazard. - Storage in one SQLite file. Code chunks + embeddings live in a
sqlite-vecvec0virtual table (embedding float[384]);rusqliteis staticallybundled. KNN is a single SQLWHERE embedding MATCH ?1 AND k = ?2 ORDER BY distance— no external vector DB, no network service. - Real structure extraction.
tree-sitter0.24 (Rust + TypeScript grammars) pulls function/method/trait signatures with params and return types, plus full import/export graphs.
Not something we found in any comparable local-first orchestrator we surveyed.
🧠 Cross-session episodic memory (`apohara-episodic`)
- Every run becomes an episode. At the end of each dispatch, Catalyst captures an
Episode { goal, providers, winning_diff_summary, gate_verdicts, outcome }into a durable cross-run store at~/.apohara/episodes/episodes.db— separate from, and never disturbing, the per-runorchestration.db. - Recall before the next run.
recall_for_goalpulls the closest past episodes and surfaces them as amemory:recallevent before dispatch. Retrieval is feature-similarity (NOT semantic) — the same deterministic blake3 feature-hashing the indexer uses (384-dim, sqlite-vec KNN), so it's keyword-ish over short goal strings. Zero tokens, zero model, fully local. - Reuses the indexer's primitives, adds no model.
apohara-episodicdepends onapohara-indexer'sfeature_hash_embed+ sqlite-vec — no transformer, no download, ~0 resident RAM. - Backs the MCP ledger. An
EpisodicLedger(read_events/search_events) replaces the old empty-stub ledger so past runs are queryable from the MCP surface.
🚦 An orchestration engine that verifies before it merges
apohara-dispatch— parallel CLI subprocess dispatch. Streams each child's stdout line-by-line over a boundedmpsc(1024)with non-blockingtry_send(drops + warns on backpressure rather than deadlocking the child); drains stderr concurrently.apohara-worktree— realgit worktree add -b apohara/<slug>with per-tree metadata + PID lock. Cleanup is preflight-gated: dirty/unpushed/live-agent trees are never force-removed — they're rescued onto a recovery branchapohara/task-<id>-failed-<ts>.apohara-coordinator— a semantic (file, symbol, kind) read/write/rename conflict matrix (only read-vs-read parallelizes), an indexer-confidence-gradient scheduler (Assign/Queue/Reject/Defer), and a deterministic tick loop with exponential backoff capped at 5 minutes.apohara-verification— a 7-gate mesh (Architecture, Security, Perf, CodeQuality, Frontend, SysadminSafety, BashScopeGate) that runs without short-circuit so the critic sees the full block list.SysadminSafetyblocks 5 dangerous shell patterns (rm -rf /,curl|sudo,chmod 777, rawddto disk, …);BashScopeGatedelegates to a proptest-verified compound-bash safety proof. Each block carries machine-actionablefeedback_to_agent. Bounded to 3 rounds, escalation wins over exhaustion.apohara-token-accounting— process-global counters using absolute (not delta) snapshots so replays/reconnects stay idempotent.
🖥️ A genuinely native desktop (`apohara-desktop-dioxus`, the largest crate at ~7.7k LOC)
- Dioxus 0.7 (
desktop), zero web stack. No Tauri, no wry, no React, no Electron — verified absent from itsCargo.toml. A single binary launched viaLaunchBuilder::desktop(), window titled "Apohara Catalyst". - A 3-pane CSS-grid shell (
280px / 1fr / 360px): an Objective pane (left), a center view that swaps Swarm Canvas → Kanban Board → Task Board off one signal, and a CodeDiffPane (right) with a provider-winner badge, files-changed list, and a line-classified unified diff. - Five effect-owner coroutines mounted once:
dispatch_loop(the heart — worktree → stream → capture diff → run gates → pick winner),git_apply_handler(Accept pipes the diff intogit apply -against your working tree, with success/error toasts),permission_arbitrator,reconciler_tick, andtoast_reaper. - Command palette (
Cmd/Ctrl+K, a real desktop global shortcut) with fuzzy-matched actions, plus a live token-usage statusline pollingapohara_token_accounting::api::current_totalsonce a second. - Provider cards are probed from
PATHat startup — only CLIs actually installed render; otherwise you get a "No providers enabled." empty state.
🧰 A CLI + a ratatui TUI sharing the same engine
The apohara binary ships three subcommands today:
apohara doctor— probes git + claude/codex/opencode onPATH, lists loaded crates (exit 1 on missing git, 2 on warnings).apohara verify-setup [--skip-real-providers]— 8 readiness probes across the dispatch state machine, verification mesh, safety grid, spec watcher, MCP bootstrap, hooks installer, decomposer, and projector.apohara run— single-prompt dispatch through the Rust path.
apohara-tui is a separate ratatui terminal UI. (Same crates, no JS anywhere.)
Apohara Catalyst v1.0.0-rc.5 targets Arch-family Linux with a one-command
cargo installflow. You need a Rust toolchain (rustup) andgit.
git clone https://github.com/SuarezPM/Apohara-Catalyst.git apohara-catalyst
cd apohara-catalyst
bash scripts/install-arch.shThe script runs cargo install --path crates/apohara-desktop-dioxus --force, symlinks the resulting binary to ~/.local/bin/apohara-catalyst, warns if ~/.local/bin isn't on your PATH, and installs a KDE/GNOME .desktop launcher (Apohara Catalyst, Development category) with a 256×256 icon.
Launch it:
apohara-catalyst # from the terminal…or pick "Apohara Catalyst" from your desktop application menu.
Other platforms (Linux non-Arch / macOS): the app itself is cross-platform — build it directly with cargo install --path crates/apohara-desktop-dioxus (only the .desktop launcher is Arch/freedesktop-specific). A binary-download scripts/install.sh (Linux/macOS, x86_64/aarch64) is wired and will work once a release is published — see Status & scope.
Check your environment first (optional):
apohara doctor # verifies git + claude/codex/opencode on PATHMake sure at least one of claude, codex, or opencode is installed and logged in — Catalyst dispatches whichever it finds on your PATH. It never sees your tokens: every spawn goes through env_clear() + a strict allowlist.
v1.0.0-rc.5 — codename "Integrated personal Arch desktop." This is an honest release candidate, not a 1.0.
What it is: a local-first, single-developer desktop orchestrator for Arch/CachyOS. One machine, one user, your CLIs, your worktrees.
Deliberately out of scope (by design, not omission):
- ❌ No cloud, no telemetry-by-default, no account. It runs entirely on your machine.
- ❌ No OAuth, ever — wrapping CLIs you're logged into is both a TOS and a billing-safety stance (it sidesteps the "wrong account billed" failure mode documented in our Past Incidents).
- ✅ 3 active providers by design:
claude-code-cli,codex-cli,opencode-go. Others are LEGACY behindAPOHARA_LEGACY_PROVIDERS=1. ⚠️ Cross-platform installers are wired but not yet published. Thescripts/install.shone-liner (Linux/macOS, x86_64/aarch64) and the AUR/Homebrew/Scoop manifests all target the sameapohara-<triple>.{tar.gz,zip}release assets the build matrix produces; assets publish on tag. No published release exists yet — the tag cut is a Pablo-gated manual step.⚠️ Daemon mode, SSH remote workers, smart router, and reactions ship OFF by default and are not production-validated.⚠️ The collaborative mesh (APOHARA_MESH) ships OFF by default. The bake-off is the validated default path; the mesh — zero-token planner, atomic cross-process claims, mailbox bus, single incremental integrator — is wired and hermetically tested, with real-CLI dogfooding in progress. The per-provider headless CLI dispatch it relies on was recently completed and verified; the next step is propagating each CLI's existing login into the per-blade run.⚠️ Opt-in model-judge tier (APOHARA_MODEL_JUDGE=1) is OFF by default and is the only token-spending path in the verification crate; the 7 quality gates stay pure-regex / zero-token. The flag currently assembles the judge prompt only — actual model dispatch is a deferred follow-up.⚠️ The seccomp/namespace sandbox is Linux-only and requireskernel.unprivileged_userns_clone=1; on macOS/Windows it falls back to no enforcement.
We'd rather under-promise here than ship marketing numbers we can't back with code. No TTFT speedup percentage is claimed because none was measured.
The workspace is 37 Rust crates / ~44,900 lines of Rust across 379 .rs files, with 1,136 #[test]/#[tokio::test] functions and zero non-Rust runtime packages (no package.json anywhere in the repo). Edition 2021, rust-version 1.95. Grouped by concern:
For contributors: before non-trivial changes, read the cross-cutting spec §0 disciplines and the Past Incidents ledger in
CLAUDE.md— each rule cites a commit and a symptom, so the why survives even when the original engineer doesn't.
Apohara Catalyst is the orchestrator of a three-part family:
- 🜂 Apohara Catalyst — multi-AI CLI orchestrator (this repo)
- ⚖️ Apohara Probant — cross-AI code verifier
- 🏛️ Apohara Consilium — agent governance OS
MIT © the Apohara authors.
Built in Rust, end to end. No webview. No OAuth. No extra tokens. Just your CLIs, racing.
