agentleFS
SuarezPMSuarezPM/Apohara-Catalyst/AGENTS.md

Apohara-Catalyst

AGENTS.md1 starsChanged 6 months agoMITView on GitHub

Concise navigation for AI coding agents working in this repository. For day-to-day engineering contract, see CLAUDE.md (symlinked here). Status: the workspace is now 37 Rust crates (Cargo.toml [workspace.members]) — the table below is a partial Stages 1-10 map; see Cargo.toml for the full list. There is no TypeScript side anymore: the former packages/ and src/core/ TS code was ported to Rust crates during the Dioxus migration (no packages/, no src/, no package.json in the repo). Per-crate AGENTS.md files are linked…

  • Reads credentials

The file

We split this AGENTS.md into the rules it gives an agent

7 rules. About 0 are common, 2 are distinctive, and 5 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 5

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  • could be automatic: a setting could enforce it
  1. Very specific:

    Before making changes, review the 33 disciplines in docs/superpowers/specs/2026-05-21-apohara-v1-design.md#0-disciplinas-transversales.

    line 80
  2. Distinctive:

    Do NOT: commit to main directly — open a PR (this is a public repo from Stage 11 onwards)

    line 93
  3. Distinctive:

    Do NOT: add OAuth flows for providers — CLI wrappers only (Pablo's hard rule: TOS prohibits programmatic OAuth for several providers.

    line 94
  4. Very specific:

    Do NOT: add providers to the active roster beyond claude-code-cli, codex-cli, opencode-go — others are LEGACY behind …

    line 95
  5. Very specific:

    The rule: ALL spawns route env through sanitizeEnv() from src/core/persistence/envSanitizer.ts.

    line 110
  6. Very specific:

    Why: When wiring T2.1 (PTY embedding), the tests using sh -c "sleep 0.05 && echo replay-test" exited cleanly with code 0 but produced ZERO …

    line 126
  7. Very specific:

    The rule: When you add #[derive(TS)] anywhere, run cargo run -p apohara-types --bin generate_types --features ts-export and commit the …

    line 140

≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
AGENTS.md

Apohara — Agent Navigation Guide

Concise navigation for AI coding agents working in this repository. For day-to-day engineering contract, see CLAUDE.md (symlinked here).

Build & test commands

TaskCommand
Build all cratescargo build --workspace
Run apohara-types testscargo test -p apohara-types --lib --tests
Run apohara-indexer testscargo test -p apohara-indexer
Start desktop devcargo run -p apohara-desktop-dioxus
Generate ts-rs bindings (Rust→TS)cargo run -p apohara-types --bin generate_types --features ts-export
Check bindings didn't driftcargo test -p apohara-types (codegen determinism test)
Doctor (full env check)apohara doctor
Setup verification end-to-endapohara verify-setup

Module map

Status: the workspace is now 37 Rust crates (Cargo.toml [workspace.members]) — the table below is a partial Stages 1-10 map; see Cargo.toml for the full list. There is no TypeScript side anymore: the former packages/ and src/core/ TS code was ported to Rust crates during the Dioxus migration (no packages/, no src/, no package.json in the repo). Per-crate AGENTS.md files are linked here as they land.

Rust crates (crates/)

CrateResponsibility
crates/apohara-typesShared types Rust↔TS (ts-rs SSoT, §0.7)
crates/apohara-secretsOS-native credential store (keyring-rs)
crates/apohara-pathsafetySymlink-escape detection
crates/apohara-auditJSONL audit sink + rotation + fchmod 0600
crates/apohara-notificationsCross-platform push notifications
crates/apohara-persistenceCross-platform service installer
crates/apohara-worktreeGit worktree lifecycle (consolidates the previous isolation-engine crate; not yet renamed in this branch)
crates/apohara-coordinatorSemantic conflict coordinator (slim, delegates state)
crates/apohara-hooks-serverAgent-hooks HTTP loopback (axum sidecar)
crates/apohara-attentionAttention bands state machine (HOT/WARM/COOL/IDLE)
crates/apohara-token-accountingToken accounting (absolutes > deltas, per-thread)
crates/apohara-mcp-bridgeCanonical MCP config + per-provider adapters (Claude/Codex/OpenCode dialects)
crates/apohara-event-humanizerProvider events → human-readable labels
crates/apohara-anti-thrashStrategy rotation tracker (anti-loop)
crates/apohara-indexertree-sitter + sqlite-vec storage + blake3 feature-hashing embeddings
crates/apohara-sandboxseccomp-bpf + namespaces sandbox (existing)

Surfaces (Rust crates)

The UI is native Rust — there is no packages/ directory or Node/Bun toolchain. ts-rs bindings emit per-crate to crates/<X>/bindings/*.ts (do not edit by hand — §0.7).

CrateResponsibility
crates/apohara-desktop-dioxusDioxus 0.7 native desktop UI (TaskBoard, Plans, Permissions, Verification timeline). No webview, no Electron.
crates/apohara-tuiratatui terminal UI (Dashboard, AgentList, CostTable, config wizard)
crates/apoharaapohara CLI (doctor, verify-setup, plan, …)

Former TypeScript domains (now Rust crates)

The orchestration / safety / spec / mcp / providers / hooks / decomposer / verification / persistence / anti-thrash / cli domains were TypeScript under src/core/ before the Dioxus migration. They now live as Rust crates — e.g. apohara-coordinator + apohara-dispatch (orchestration + provider drivers), apohara-safety, apohara-spec, apohara-mcp (+ apohara-mcp-bridge), apohara-hooks (+ apohara-hooks-server), apohara-decomposer, apohara-verification, apohara-persistence, apohara-anti-thrash, and the apohara CLI. See the crate tables above and Cargo.toml for the full set. No src/ TypeScript tree remains in the repo.

Spec source of truth

docs/superpowers/specs/2026-05-21-apohara-v1-design.md — read it before non-trivial changes.

Plan source of truth

docs/superpowers/plans/2026-05-22-apohara-v1.md — task-by-task implementation plan.

Indexer testing (post-Sprint-8)

cargo test -p apohara-indexer corre todos los binarios en paralelo sin OOM hazard. El indexer usa sqlite-vec para storage y feature-hashing (blake3) para embeddings — ambos in-process, ~0 RAM, deterministas.

Si en el futuro re-introduces un modelo transformer (e.g. fastembed-rs), documenta el footprint y vuelve al patrón "una test binary a la vez" si supera ~100MB en RAM total.

Cross-cutting disciplines (spec §0)

Before making changes, review the 33 disciplines in docs/superpowers/specs/2026-05-21-apohara-v1-design.md#0-disciplinas-transversales. They are NOT suggestions — they're guardrails that prevent entire bug classes.

Highlights:

  • §0.1 Centralized IPC listeners (never per-component)
  • §0.4 Env sanitization on all spawns (no API keys to subprocesses)
  • §0.7 ts-rs Single Source of Truth (never hand-edit the generated per-crate crates/<X>/bindings/*.ts; regenerate via cargo run -p apohara-types --bin generate_types --features ts-export)
  • §0.8 Atomic file writes (mkstemp + rename)
  • §0.14 Token accounting: absolutes > deltas
  • §0.16 enum_dispatch instead of Box<dyn> for providers

What NOT to do

  • Do NOT hand-edit the generated ts-rs bindings (crates/<X>/bindings/*.ts) — regenerate via cargo run -p apohara-types --bin generate_types --features ts-export
  • Do NOT commit to main directly — open a PR (this is a public repo from Stage 11 onwards)
  • Do NOT add OAuth flows for providers — CLI wrappers only (Pablo's hard rule: TOS prohibits programmatic OAuth for several providers; agents like Anthropic explicitly blocked from OAuth-based wrapping).
  • Do NOT add providers to the active roster beyond claude-code-cli, codex-cli, opencode-go — others are LEGACY behind APOHARA_LEGACY_PROVIDERS=1

Past incidents (rules earned the hard way)

Each rule below cost real time / money / trust to find. Treat them as load-bearing — breaking one regresses Apohara, and the audit history is recorded so the why survives even when the original engineer doesn't. (Nimbalyst's CLAUDE.md inspired this section — their published incident write-ups cited real session IDs and dollar costs; ours cite the commit + the symptom.)

NEVER pass process.env to a CLI subprocess unsanitized

Why: The pre-33d6901 src/providers/cli-driver.ts did env: { ...process.env } on every spawn. That leaked ANTHROPIC_API_KEY, OPENAI_API_KEY, AWS / GCP / Azure creds, GITHUB_TOKEN, etc. into every wrapped CLI. Two compound failures from this:

  1. The "wrong account billed" trap (nimbalyst's published incident): the CLI sees a key in its env and routes to that account instead of the user's logged-in CLI session — surprise billing on someone else's plan.
  2. The claude CLI hang at 120 s: two concurrent claude invocations from the same Bun process both inherited the same ~/.claude/ paths via env + got the same workspace state, then contended on the CLI's internal file locks. The second one hung until our 120 s SIGKILL.

The rule: ALL spawns route env through sanitizeEnv() from src/core/persistence/envSanitizer.ts. Allowlist only APOHARA_* and known-safe vars (PATH, HOME, USER, LANG, TERM, TMPDIR). The Rust sandbox runner does the equivalent in crates/apohara-sandbox/src/runner/imp.rs::build_sanitized_env(). Adding a new spawn site? Use sanitizeEnv or it gets caught in code review.

fs.watch on Linux only fires for the TEMP filename when the writer does atomic-rename

Why: The first cut of src/core/dispatch/result-watcher.ts listened for fs.watch events naming <taskId>.json. The dispatcher writes results via atomicWriteFile (tmp + rename). Linux's inotify reports the event for the tmp filename, not the renamed target. Result: the watcher heard the rename but matched on the wrong filename and ignored it. The UI saw "Done (0)" forever even though the result file existed on disk.

The rule: Watchers on directories where atomic writes land MUST treat every event as "something changed, rescan" — not "the named file appeared". Use readdir on every watch tick. Pair fs.watch with a 1-second polling backup so flaky inotify (NFS, FUSE, some Bun versions) never strands a result. See result-watcher.ts:80-110 for the pattern.

Every spawn site must serialize per-binary OR contend on CLI-internal locks

Why: Pablo hit claude-code-cli: CLI driver timed out after 120000 ms once per UI run. Root cause: claude keeps per-process state under ~/.claude/ (auth tokens, history, session locks). Two concurrent claude children from the same Bun process contended on those locks and the second one blocked until 120 s timeout SIGKILLed it.

The rule: src/providers/cli-driver.ts::runSerialized(binary, task) queues calls FIFO per binary name. Any new CLI you add via BUILTIN_CLI_DRIVERS inherits the queue automatically. Do not bypass it.

bash -c "echo X" does NOT reliably flush stdout through a PTY

Why: When wiring T2.1 (PTY embedding), the tests using sh -c "sleep 0.05 && echo replay-test" exited cleanly with code 0 but produced ZERO bytes in the replay buffer. Verified empirically against node-pty 1.1 + Bun 1.3: bash -c "echo X" exits before flushing; /bin/echo X is the stable case.

The rule: Tests that need to observe PTY output use /bin/echo directly or write to a long-running child via writePty. The PTY data path itself works (production /api/run flows through it just fine) — the issue is exclusively bash startup + immediate-exit timing.

opencode reads opencode.jsonc at the workspace root, NEVER .opencode/settings.json

Why: The pre-T2.4 mcpInjection.ts::injectOpenCode wrote <workspace>/.opencode/settings.json because that's where Apohara's agent-config.ts::hookConfigPath (also wrong) pointed. opencode 1.15+ doesn't look at that path. Our MCP injection landed in /dev/null for the entire opencode-go provider.

The rule: Provider config paths come from the UPSTREAM CLI's source, not from convention. Verify against each CLI's own published source every release (the opencode config loader for opencode; the trust-preset logic of comparable agent runners for cursor / copilot / codex). When the CLI changes its config discovery, our injection must follow.

Generated bindings MUST come back through cargo run -p apohara-types --bin generate_types --features ts-export after every Rust schema change

Why: Pre-dfad239, crates/apohara-types/src/bin/generate_types.rs was a stub that only wrote a header. Every bun run generate-types invocation silently overwrote packages/apohara-shared/types.ts with the stub — the §0.7 SSoT was a no-op, and Rust↔TS drift was undetectable. CI's generate-types:check only proved the stub matched itself.

The rule: When you add #[derive(TS)] anywhere, run cargo run -p apohara-types --bin generate_types --features ts-export and commit the regenerated crates/<X>/bindings/*.ts in the SAME commit. Do not hand-edit the generated bindings. The codegen determinism test (cargo test -p apohara-types, tests/codegen.rs) blocks drift.


This document is auto-loaded by Claude Code / Codex / OpenCode via CLAUDE.md symlink.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.