Coodra Docs
v 0.2.0-beta.3 Contribute GitHub ↗
Coodra · Documentation · v 0.2.0-beta.3 · 2026

Master the context.

Coodra is a local-first MCP server platform that gives AI coding agents project context, memory across sessions, and policy enforcement — so they stop coding blind. This is the complete reference, from your first coodra init to merging your first PR.

Audience
Engineers · Architects · Contributors
Read time
Quick start in 5 min · Full read ~45 min
License
MIT · open source · local-first
/01 — Foundation

What Coodra is, in one breath.

Read first
~3 min

A coordination layer between human architects and AI coding agents — Claude Code, Codex. It runs as a local-first MCP (Model Context Protocol) server and feeds every agent session three things, automatically.

/ pack

Feature Packs

The architectural blueprint for the module the agent is touching. Injected at session start so the agent reads spec → tech-stack → implementation order before writing code.

Injected via SessionStart hook
/ memory

Context Packs

The durable record of every decision, file change, and policy event from past sessions. One pack per run, queryable in natural language. Cross-session memory the agent can pull on.

14 SQLite tables · sqlite-vec
/ guardrail

Policy enforcement

Pre-tool-use checks recorded in an append-only audit trail. deny writes to .env.production, ask before merging, fail-open on engine fault.

Cockatiel breaker · 100ms timeout

/01.1Design principles

Five things that are true everywhere in the codebase, and that any contribution must respect.

Local-first
Everything runs on your laptop with zero network. SQLite is the primary store in both solo and team mode. apps/mcp-server/src/lib/db.ts — only call site uses createDb({ kind: 'local' })
Fail-open
If the policy engine throws, times out, or the breaker opens — the verdict is ALLOW. Coodra never blocks the agent because of its own faults. packages/policy/src/policy.ts — FAIL_OPEN_RESULT
Append-only audit
Policy decisions and decisions records are never updated or deleted. Idempotency keys gate writes. Resumed kill-switches keep the row, just stamp resumed_at. ADR-007
Production-grade
Every module ships complete or it doesn't merge. No placeholder surfaces. No mocked endpoints to make the UI render. Linear build, start to finish. CLAUDE.md:8-10
Bind to 127.0.0.1
Every long-running process listens on loopback by default. Hooks, MCP HTTP transport, web UI. Nothing exits the laptop unless the operator sets DATABASE_URL.
/02 — Onboarding

Five minutes to your first run.

Solo mode
Zero network

If you have Node 22, pnpm, and a Claude Code / Codex install — you're three commands away. Solo mode is the default after coodra init.

/00

Confirm the prereqs.

You need Node 22.16.0 (pinned in .nvmrc), pnpm 10.33.0 via corepack, git, and a supported agent installed locally. macOS uses launchd; Linux uses a fallback daemon manager with PID files.

terminal · prereq check
# Versions Coodra pins to
nvm use            # 22.16.0 per .nvmrc
corepack enable    # activates pnpm 10.33.0
node --version    # → v22.16.0
pnpm --version    # → 10.33.0
/01

Install the CLI.

The only published artefact is @coodra/cli. It ships as a self-contained bundle — bring-your-own SQLite native module is rebuilt on install.

terminal · install
npm i -g @coodra/cli
# or, in a workspace:
pnpm add -D @coodra/cli
/02

Initialise the project.

From your repo root, run coodra init. It registers the project under .coodra/, seeds the default policy, and leaves repo-root .mcp.json to the user. Native Coodra plugins carry the MCP server and hook wiring.

repo root
coodra init
# picks the slug from git remote, asks which IDE,
# defaults to --solo. Use --team to wire Clerk.

# What it creates:
.coodra/config.json   {"projectSlug":"my-app"}
.coodra/manifest.json project registration ledger
~/.coodra/data.db     SQLite primary store
~/.coodra/.env        solo-mode env, ready to grow into team
/03

Start the daemons.

One command brings up the MCP server (3100), hooks bridge (3101), and the web UI on 3001. Sync-daemon stays asleep in solo mode. Add --no-web to skip the dashboard, --foreground to tail logs.

terminal · launch
coodra start
# → mcp-server   listening on 127.0.0.1:3100
# → hooks-bridge listening on 127.0.0.1:3101
# → web-v2       http://localhost:3001

# Confirm everything's alive
coodra status
coodra doctor          # 11 essential checks (38 with --full)
/04

Open the agent & watch.

Launch Claude Code in your repo. The SessionStart hook fires POST /v1/hooks/claude-code, opens a runs row, captures base_sha, and returns a Feature Pack body in the additionalContext field. Every tool use after that is gated by policy and logged in run_events.

Visit http://localhost:3001/runs to watch the trace populate live.

→ Next

Now skim Core concepts to understand what's happening under the hood, or jump to the CLI reference for every subcommand.

/03 — Deployment

Two modes. Same primitives.

Mode is a COODRA_MODE
env flag

Solo and Team mode share schema, code, and CLI. The difference is who sees what, and whether the sync-daemon boots.

/ solo · default

Everything on your laptop.

SQLite at ~/.coodra/data.db. Loopback only. Solo-bypass auth — no Clerk, no JWT, just SOLO_ACTOR stamped on every write. The sync-daemon does not boot. Zero network egress.

Best for: individual contributors, OSS hacking, air-gapped envs, contractors with a strict no-cloud policy.

Set: COODRA_MODE=solo
/ team · opt-in

Cloud-synced memory.

Same local SQLite — plus a Postgres + pgvector mirror you operate, plus Clerk for identity. The sync-daemon pushes sync_to_cloud outbox rows and pulls teammates' runs, decisions, and packs back into local SQLite.

Best for: small teams sharing context, multi-laptop developers, BYO-cloud self-host.

Set: COODRA_MODE=team · DATABASE_URL · CLERK_*

/03.1Mode matrix

Capability Solo Team
Local SQLite primary storeYESYES
MCP server + hooks bridge (loopback)YESYES
Web UI on :3001YESYES
Sync-daemon bootsNOYES
Clerk auth · JWT verificationBYPASSREQUIRED
Cross-teammate visibilityNOYES
Cross-machine (same user)NOVIA team install
Network egress requiredNONEPOSTGRES + CLERK

Promote a project from solo to team without losing history with coodra team setupcoodra cloud-migrate. The migration is tracked in _migration_attempts with per-row mapping in _migration_map for surgical rollback.

/04 — Concepts

The vocabulary.

Read this once.
It pays back.

Six nouns carry the whole system. Internalise these and the rest of the docs read themselves.

session
An agent-side identifier. One per IDE/agent invocation. Coodra never invents one — it accepts whatever the agent sends. UNIQUE (project_id, session_id) in runs
run
A row in the runs table — agent type, mode, status, base_sha, created_by_user_id, project_id. Bound to a session by get_run_id; reused across get_run_id calls within the same session. One run per session per project
context pack
A markdown summary of one run, written at session end. Stored in context_packs (one row per run, UNIQUE constraint), mirrored to ~/.coodra/packs/<date>-<runId>.md for grep. source: 'agent' | 'bridge_auto'
feature pack
Reusable task guidance, authored by humans or agents in .coodra/recipes/<slug>/. Pulled on demand via list_recipes / get_recipe when a task matches a recipe's trigger — not auto-injected.
decision
A structured architectural choice — description, rationale, alternatives, reversibility — recorded by the agent mid-run. Idempotent on sha256(description)[:32]. Surfaced to the next session via recent-decisions on UserPromptSubmit
policy decision
An append-only audit row: who asked, what tool, what input, what verdict. Idempotency key pd:{sessionId}:{toolUseId}:{toolName}:{eventType}. ADR-007

/04.1Runs & sessions

A session is what the agent calls itself. A run is what Coodra calls that work. The first call to get_run_id for a session either opens a new runs row, or — if the agent crashed and restarted — finds and resumes the existing one.

session → run binding
// MCP tool: get_run_id  ·  apps/mcp-server/src/tools/get-run-id/
const { runId } = await mcp.call("get_run_id", {
  sessionId: "cc-sess-2026-05-16-8a3f2c",
  projectSlug: "my-app",
  agentType: "claude_code",
});

// First call:  inserts a runs row, returns runId.
// Repeat call: returns the same runId (UNIQUE on project_id, session_id).

Every run_events row, every policy_decisions row, and the eventual context_packs row hang off this runId. run_events.run_id uses ON DELETE SET NULL so PreToolUse events that fire before the run exists still land — they just attach later when the row appears.

/04.2Feature Packs & Context Packs

The two are easy to confuse. They sit on opposite ends of the run.

→ before

Feature Pack

Human- or agent-authored. Lives in .coodra/recipes/<slug>/. Reusable guidance the agent pulls on demand via list_recipes / get_recipe when a task matches its trigger.

Direction: human → agent.

← after

Context Pack

Agent-authored (or bridge-auto on session end). One row per run in context_packs with title, content, content_excerpt, meta. Queryable cross-session via search_packs_nl, list_context_packs, read_context_pack.

Direction: agent → future agents.

Pattern

If a saved pack has source: 'agent' and a new save_context_pack call arrives with the same runId, the response is status: 'idempotent_hit'. If the existing pack was bridge-auto, it gets 'upgraded_from_bridge_auto' in place.

/04.3Decisions

An ADR-grade record the agent emits when it makes a non-trivial choice: pick an ORM, change a public API shape, defer a refactor. They survive across sessions and surface to the next agent via the UserPromptSubmit hook.

MCP tool · record_decision
await mcp.call("record_decision", {
  runId,
  description:   "Use better-sqlite3 instead of sql.js for primary store",
  rationale:     "Synchronous API matches MCP request lifecycle; ~3x faster on writes.",
  alternatives:  ["sql.js", "libsql/Turso"],
  confidence:    "high",
  reversible:    false,
});
// Idempotent on  dec:{runId}:{sha256(description)[:32]}
// Resurfaces on next  UserPromptSubmit  via  recent-decisions.ts

/04.4Policy & kill switches

Two layers of guardrail. Kill switches sit above policy — they're operator-level pauses, evaluated first.

/01
kill_switch_check
Polymorphic (scope, target) — global, project, tool, agent_type. 5s in-process cache. Soft-resume keeps the row.
FIRST
/02
policy.check(event)
Cache-first DB read inside a Cockatiel timeout(100ms) + circuitBreaker(5) fuse. Picomatch path globs. First match wins by priority ASC.
EVALUATE
/03
recordPolicyDecision
Writes to policy_decisions with ON CONFLICT (idempotency_key) DO NOTHING. Async; the hook returns before the write.
AUDIT
/04
return verdict
ALLOW · ASK · DENY — only an explicit rule match returns DENY.
VERDICT
/--
FAIL_OPEN
On breaker open · isolation · timeout · any thrown DB error, returns { decision: 'allow', reason: 'policy_check_unavailable' }.
SAFETY NET

Policy rules match on five axes — event type, tool name, path glob, agent type, and priority. Decisions are append-only. The default policy seed lives in packages/db/src/ensure-default-policy.ts.

/05 — Architecture

Process topology.

5 layers
3 daemons

Four long-running processes plus a one-shot CLI. The agent enters at the top; the cloud (when in team mode) sits at the bottom.

Layer model
L0 · Agent
Claude Code stdio + hooks
Codex stdio + hooks
L1 · Protocol
MCP Server :3100 · 16 tools · stdio + HTTP
Hooks Bridge :3101 · Hono · 3 routes
L2 · Core
Policy engine cockatiel · 100ms timeout
Pack service read · write · search
Run recorder events + diff
OutboxWorker SQLite-backed queue
L3 · Storage
SQLite ~/.coodra/data.db · 14 tables
sqlite-vec context_packs_vec
Postgres + pgvector team mode only
L4 · Clients
Web v2 :3001 · Next.js 15
CLI 32 commands · 38 doctor checks
Ink TUI coodra ui

/05.1Long-running processes

Process Port Bind DB Started by
mcp-server
16 MCP tools · stdio + HTTP transport
3100 127.0.0.1 SQLite (local) coodra start · native plugin subprocess
hooks-bridge
Hono HTTP · 5 Claude Code events
3101 127.0.0.1 SQLite (local) coodra start
sync-daemon
Push + pull · kill-switch puller
none n/a SQLite + Postgres coodra start · team mode only
web-v2
Next.js 15 · 46 page/route files
3001 0.0.0.0 SQLite (server actions) coodra start · skip with --no-web

/05.2Hook lifecycle

Exactly what happens between you typing into Claude Code and an edit landing on disk.

/01
SessionStart
Hooks bridge opens or resumes a runs row, captures base_sha, returns Feature Pack body via additionalContext.
RUN OPENED
/02
UserPromptSubmit
Injects recent decisions from the project's history into the prompt context.
MEMORY
/03
PreToolUse
Kill-switch check → policy.check → return allow/ask/deny. Outbox writes policy_decisions.
GATE
/04
PostToolUse
Inserts a run_events row with phase, tool name, tool input, outcome.
LOG
/05
Stop · SessionEnd
Marks runs.status = completed, runs run-diff-runner to populate run_diffs, writes auto-Context Pack.
CLOSE
/06 — Storage

Data model.

14 SQLite tables
17 + 3 cloud-only

Drizzle dual-dialect schemas. Postgres mirrors SQLite column-for-column, enforced by __tests__/unit/schema-parity.test.ts. The only intentional drift: context_packs.summary_embedding is vector(384) in Postgres, text in SQLite.

SQLite primary store · 14 tables
Table Purpose Key constraints
projectsProject registry. One per repo (by slug).slug UNIQUE · org_id
runsOne per agent session. Bound to project.UNIQUE (project_id, session_id)
run_eventsAppend-only PostToolUse log.FK→runs · ON DELETE SET NULL
context_packsOne markdown summary per run.UNIQUE (run_id)
decisionsCross-session architectural choices.UNIQUE idempotency_key
policies · policy_rulesActive policies and their rule axes.UNIQUE (policy_id, priority, …)
policy_decisionsAppend-only audit of every PreToolUse.UNIQUE idempotency_key
kill_switchesPolymorphic operator pauses.idx (resumed_at, scope, target)
feature_packs · featuresAuthored blueprints + on-demand skills.UNIQUE (project_id, slug)
run_diffsUnified diff captured at session end.PK = run_id · CASCADE
pending_jobsDurable outbox. Module 03.1.idx (queue, status, run_after)
team_invitesHMAC-signed onboarding tokens.UNIQUE jti
context_packs_vecsqlite-vec virtual table for semantic search.virtual

/06.1ER summary

foreign-key topology
projects ─1───n─ runs ─1───n─ run_events
   │         │           ├─0/1─ context_packs   (UNIQUE run_id)
   │         │           ├─0/1─ run_diffs       (PK run_id, CASCADE)
   │         │           └─0/n─ decisions       (FK ON DELETE SET NULL)
   │         │
   │         └──1───n─ policies ─1───n─ policy_rules
   │                                          │
   │                                          └─ policy_decisions.matched_rule_id
   │
   ├─ context_packs.project_id
   ├─ policy_decisions.project_id
   └─ features.project_id (UNIQUE project_id, slug)

# Top-level (not FK'd to projects):
feature_packs, kill_switches, team_invites, pending_jobs

# Sentinel projects (seeded at every boot):
'__global__'   ensureGlobalProject()   # events before a real project
'__solo__'     SOLO_ACTOR.orgId         # solo-mode org

# Cloud-only mirrors (Postgres):
_migration_attempts ─1───n─ _migration_map   # migrate resume + rollback
knowledge_audit                              # Phase F.3.c audit

/06.2What lives in ~/.coodra/

data.db
Primary SQLite store. 14 tables. Override with COODRA_SQLITE_PATH. Managed by coodra db backup / restore
packs/
Per-pack markdown mirror of context_packs rows. Filename: {date}-{sanitised-runId}.md. Gitignored. Useful for grep + AI agent file-side reads
graphify/<slug>/
Codebase graph index produced by an external graphify scan. Read-only consumer in apps/mcp-server/src/lib/graphify.ts. No in-repo producer
logs/<service>.log
Per-service stderr captured by the daemon manager. Tail with coodra logs <service> --follow
pids/<service>.pid
Fallback daemon manager PID files. On macOS (launchd) this dir stays empty — PIDs come from launchctl print.
config.json
Per-machine identity + mode. Clerk user_id, org_id, deployment mode. Written by init, login, team setup.
clerk-token.json
Verified Clerk JWT claims after coodra login. Consumed by the MCP server identity guard.
.env
Single source of truth for env vars on a laptop. Merged from coodra team setup output.

/06.3Sync & multi-tenancy

In team mode, the sync-daemon pushes outbox rows to cloud Postgres and pulls teammates' rows back into local SQLite — both directions on a 1-second tick.

push
OutboxWorker
Filtered to ['sync_to_cloud']. Every record_decision, save_context_pack, etc. enqueues a paired job that the daemon drains.
LOCAL → CLOUD
pull
team-rows-puller
Pulls runs · decisions · context_packs · run_events cloud → local via ON CONFLICT DO NOTHING. Skips __global__ + __solo__.
CLOUD → LOCAL
pull
kill-switch-puller
Propagates org-scoped pauses to every teammate so a hard stop is global.
CLOUD → LOCAL

Every shared table carries Clerk user attribution: created_by_user_id on runs, decisions, context_packs, policies; paused_by_user_id / resumed_by_user_id on kill_switches. Web-v2 enforces single-tenant via COODRA_EXPECTED_ORG_ID — a deployment refuses to boot without it.

/07 — Reference

CLI commands.

32 source files
38 doctor checks

Every command is a thin shell over a typed core module. Pass --json almost anywhere for machine output. Pass --help for the up-to-date flag list.

/07.1Lifecycle commands

Command What it does Key flags
coodra initBootstrap a project. Writes .coodra/config.json + .coodra/manifest.json, seeds the default policy, and leaves repo-root .mcp.json user-owned.--solo · --team · --ide · --feature-pack · --force · --dry-run
coodra startBring up all daemons. Picks launchd / fallback manager based on platform.--no-mcp · --no-hooks · --no-sync · --no-web · --foreground · --tunnel
coodra stopTear down daemons. --uninstall removes launchd plists.--service · --uninstall
coodra statusHealth snapshot of each daemon + DB.--json
coodra doctor11 essential checks by default. Add --full for all 38.--full · --fix · --timeout-ms · --json
coodra agentsDetect installed IDEs and confirm each is wired to the bridge.--json
coodra logs <svc>Tail ~/.coodra/logs/<svc>.log across platforms.--follow · --lines · --since
coodra upgradeCompares installed version against npm view. Restarts daemons unless --no-restart.--check-only · --no-restart
coodra uninstallRemoves the CLI. --purge deletes ~/.coodra/.--purge · --dry-run

/07.2Data & memory commands

Command Subcommands Use case
coodra projectlist · show · reset · promote · demoteManage which projects Coodra tracks; promote a transient project to active.
coodra runlist · show · cancelInspect or cancel in-flight runs.
coodra packnew · list · show · regenerate · deleteAuthor or regenerate Context Packs.
coodra featureadd · list · show · edit · index · removeManage on-demand skill rows (pull-on-trigger features).
coodra policylist · show · add · enable · disableAuthor and toggle policy rules per project.
coodra templatelist · install <source>Install community feature-pack templates.
coodra export <runId>Export a run as markdown · json · html · slack. With --include-audit for the full policy trail.
coodra dbmigrate · backup · restoreDrizzle migrations + snapshot management.

/07.3Team & auth commands

Command Subcommands What it does
coodra loginBrowser handoff to Clerk hosted sign-in. Captures JWT via loopback listener.
coodra logoutClears ~/.coodra/clerk-token.json. --force skips confirmation.
coodra invite <email>Mints an HMAC-signed invite URL + writes team_invites row in cloud.
coodra orgstatus · switch <slug>Inspect or switch the active Clerk org for this machine.
coodra teamsetup · init · install · join · leave · migrate · login · logoutFull team lifecycle. setup provisions Clerk + DB; install redeems an invite on a second laptop.
coodra pauseTrip a kill switch. Scope = global · project · tool · agent_type; mode = hard · soft.
coodra resumeUn-pause. --id, --all, or scope+target.
coodra cloud-migrateOne-shot SQLite → Postgres migration. Tracked in _migration_attempts with surgical rollback.
coodra uiForeground Ink TUI for live triage.
Tip

The coodra team namespace re-exports login and logout as aliases — both coodra login and coodra team login work. The doctor (--full) runs 38 numbered checks under packages/cli/src/doctor/checks/; the README's claim of "20-check health report" is divergent and being updated.

/08 — Reference

MCP tools — all 16.

Source of truth
tools/index.ts

Order matches registration order in apps/mcp-server/src/tools/index.ts. There are no jira_* or github_* tools — older docs that mention them are stale.

# Tool Kind What it does
01pingreadHealth check. Returns { pong, serverTime, sessionId, idempotencyKey }.
02get_run_idwriteIdempotently opens or returns a runs row for this (project, session).
03get_feature_packreadReturns the module blueprint — spec, implementation, techstack, meta.
04save_context_packwritePersists the agent-authored Context Pack. Returns created · idempotent_hit · upgraded_from_bridge_auto.
05search_packs_nlreadKeyword LIKE search. Agent ranks. Ordered by recency, not relevance.
06record_decisionwriteIdempotent on (runId, sha256(description)[:32]). Surfaces on next session.
07query_run_historyreadRecent runs + attached pack title, chronological.
08check_policywriteReturns allow · ask · deny. Fail-open on evaluator faults.
09query_codebase_graphreadReads ~/.coodra/graphify/<slug>/graph.json. Requires external graphify scan.
10query_decisionsreadCross-session decision history.
11list_context_packsreadPaginated, newest-first.
12read_context_packreadFull body + decisions for one pack.
13list_featuresreadSkill discovery (filtered to status='published').
14get_featurereadPull-on-trigger; returns frontmatter + body + file list.
15get_feature_filereadReads one supporting file from a feature. 256 KB hard cap. Path-escape guard.
16query_run_diffreadUnified diff + per-file numstat. Soft failures: analysis_pending, no_base_sha, git_diff_failed.

/08.1Hook events

Wired by native Coodra plugins. Body validated by ClaudeCodeHookPayloadSchema; fail-open on invalid JSON.

Event Handler Side effects
SessionStarthandlers/session-start.tsOpens / resumes run · captures base_sha · returns Feature Pack body
UserPromptSubmithandlers/user-prompt-submit.tsInjects recent decisions
PreToolUsehandlers/pre-tool-use.tsKill-switch · policy.check · writes policy_decisions
PostToolUsehandlers/post-tool-use.tsInserts run_events row
Stop · SessionEndhandlers/session-end.tsMark completed · run-diff-runner · auto-Context Pack
/09 — Reference

Configuration.

single source
~/.coodra/.env

Defaults are sensible. You usually only need to touch COODRA_MODE, DATABASE_URL, and the Clerk pair. Everything else has a documented fallback.

Required in team mode
Variable Default Purpose
COODRA_MODEsolosolo | team. Controls auth strategy and whether sync-daemon boots.
DATABASE_URLnonePostgres connection string. Required in team mode.
CLERK_SECRET_KEYsk_test_replace_meSentinel value triggers solo bypass; real key required in team mode.
CLERK_PUBLISHABLE_KEYpk_test_replace_meSame sentinel pattern.
CLERK_JWT_ISSUERnoneTenant JWT issuer URL for Clerk JWT verification.
LOCAL_HOOK_SECRETnoneShared secret for X-Local-Hook-Secret header. Required outside solo bypass.
COODRA_EXPECTED_ORG_IDnoneSingle-tenant pin. Web-v2 refuses to boot without it in team mode.
COODRA_INVITE_HMAC_SECRETnoneHMAC signing key for invite tokens.
Tunables
Variable Default Purpose
MCP_SERVER_PORT3100HTTP transport port.
MCP_SERVER_HOST127.0.0.1HTTP bind.
MCP_SERVER_TRANSPORTbothstdio · http · both.
HOOKS_BRIDGE_PORT3101Hono listener.
HOOKS_BRIDGE_HOST127.0.0.1Hono bind.
WEB_APP_PORT3001Next.js port for web-v2.
COODRA_SQLITE_PATH~/.coodra/data.dbOverride the SQLite file location.
COODRA_HOME~/.coodraOverride the whole data dir. Used in compose.
COODRA_CONTEXT_PACKS_ROOT<cwd>/docs/context-packsWhere pack markdown is materialised.
COODRA_GRAPHIFY_ROOT~/.coodra/graphifyWhere to look up graph.json.
COODRA_SYNC_TICK_MS1000Outbox + puller cadence.
COODRA_SYNC_LEASE_MS30000Worker lease duration.
COODRA_WEB_URLhttp://localhost:3001Used by CLI login for browser handoff.
LOG_LEVELinfopino level.
Stale

Some keys in .env.example are no longer consumed by live code: NL_ASSEMBLY_PORT, SEMANTIC_DIFF_PORT, ANTHROPIC_API_KEY, GEMINI_API_KEY, OPENAI_API_KEY, GITHUB_APP_ID, ATLASSIAN_*. They reference modules superseded by ADR-012 + ADR-013 (M05 / M06). REDIS_URL is provisioned in compose but has no live consumer either.

/10 — Operate

Self-host the team stack.

5 services
docker compose

Team mode runs on your own Postgres. deploy/compose.yaml ships a 5-service stack — postgres, a one-shot migrator, the MCP server, the hooks bridge, and the sync-daemon — that any operator can lift end-to-end.

Service Image Depends on Port
postgrespgvector/pgvector:pg16${POSTGRES_PORT:-5432}:5432
cloud-migratedeploy/Dockerfile.cloud-migratepostgres healthyone-shot
mcp-serverdeploy/Dockerfile.mcp-servercloud-migrate done${MCP_SERVER_PORT:-3100}:3100
hooks-bridgedeploy/Dockerfile.hooks-bridgecloud-migrate done${HOOKS_BRIDGE_PORT:-3101}:3101
sync-daemondeploy/Dockerfile.sync-daemoncloud-migrate done
operator path · first deploy
# 1. Clone & build images locally
git clone https://github.com/your-org/coodra
cd coodra
cp deploy/.env.example deploy/.env
# Fill in: DATABASE_URL, CLERK_*, COODRA_EXPECTED_ORG_ID, LOCAL_HOOK_SECRET

# 2. Bring it up
docker compose -f deploy/compose.yaml up -d

# 3. Point laptops at it
#    Each developer runs:
coodra team install <invite-url>
coodra start

/10.1Auth chain & RBAC

The hooks-bridge and MCP server share the same auth chain. First match wins.

/01
Solo bypass
CLERK_SECRET_KEY === 'sk_test_replace_me' OR COODRA_MODE === 'solo'. Attaches SOLO_IDENTITY.
solo
/02
X-Local-Hook-Secret
Compared against LOCAL_HOOK_SECRET. Attaches { source: 'local-hook' }. Used by the native plugin subprocess.
local-hook
/03
Bearer JWT
Authorization: Bearer <token>verifyClerkJwt. Required in team mode.
clerk
/--
No match
Returns 401 { ok: false, error: 'unauthorized' }. No further processing.
unauthorized

RBAC has three roles — viewer, member, admin — parsed from Clerk's org:admin/org:viewer (anything else → member). The shared assertCanEdit helper enforces admin-OR-(member + ownership); viewer never edits. Knowledge mutations have their own gate, assertCanAuthorKnowledge.

/11 — Operate

When things break.

First, run
coodra doctor

Every problem in Coodra leaves a trail in ~/.coodra/logs/, in policy_decisions, or in the doctor output. Start there.

/ symptom

Agent never sees the Feature Pack

SessionStart isn't reaching the bridge. Confirm the native Coodra plugin is installed, then:

coodra status shows hooks-bridge listening on 3101?
curl 127.0.0.1:3101/healthz returns ok: true?
tail ~/.coodra/logs/hooks-bridge.log shows the inbound POST?

If yes to all three but the agent still doesn't see it — check X-Local-Hook-Secret against LOCAL_HOOK_SECRET.

/ symptom

Policy denies something it shouldn't

Look up the audit row by idempotency_key = pd:{sessionId}:{toolUseId}:{toolName}:{eventType}. The matched_rule_id tells you exactly which rule fired.

If reason: 'policy_check_unavailable' — the engine fail-opened, never a deny. Check the breaker state in logs.

/ symptom

Sync-daemon can't connect

Symptoms: queue depth climbs in doctor (check 26), nothing appears on teammates' laptops.

Confirm DATABASE_URL reachable from this machine. coodra doctor --full runs cloud-reachability check 24. Look at ~/.coodra/logs/sync-daemon.log for connection errors. Restart with coodra stop --service sync-daemon && coodra start.

/ symptom

web-v2 refuses to boot

Almost always COODRA_EXPECTED_ORG_ID missing in team mode. The middleware refuses to start without it — by design, to prevent any Clerk-authed account in this deployment's Clerk app from reading your team's data.

If set but you land on /forbidden?reason=org_mismatch — your Clerk session is in a different org. Sign out and back in, or run coodra org switch <slug>.

/ symptom

SQLite locked or corrupted

Stop all daemons, snapshot the file, repair:

coodra stop && coodra db backup
sqlite3 ~/.coodra/data.db "PRAGMA integrity_check;"
coodra db restore <snapshot> if needed.

Open an issue with the doctor JSON + log tail.

/ symptom

No run_diff for completed run

query_run_diff returns analysis_pending, no_base_sha, or git_diff_failed. Common cause: the agent operated outside a git repo, or base_sha was unreachable when SessionEnd fired.

Re-trigger with coodra run show <id> --regen-diff after checking out the right commit.

/12 — Contribute

Build & test.

Turbo · Vitest
Biome

A monorepo with Turbo for orchestration, Biome for lint, and Vitest for everything from unit to e2e. CI runs four jobs in dependency order.

/12.1Install & build

workspace bootstrap
nvm use            # 22.16.0 per .nvmrc
corepack enable    # pnpm 10.33.0
pnpm install
pnpm rebuild       # native modules: better-sqlite3 + sqlite-vec

# Day-to-day:
pnpm build         # turbo run build
pnpm typecheck     # turbo run typecheck
pnpm lint          # biome check .
pnpm lint:fix
pnpm --filter @coodra/cli build  # tsc + esbuild bundle

/12.2Test tiers

Command Runner Needs
pnpm test:unitVitestnothing — runs entirely in-process
pnpm test:integrationVitestPostgres + Redis service containers
pnpm test:e2eVitest · testcontainersDocker · fileParallelism: false · 120s hook timeout
pnpm test:functionalbash__tests__/functional/run-all.sh · 11 shell scripts

/12.3CI jobs

/01
verify
migration-lock check (packages/db), lint, typecheck, unit. No services.
ubuntu
/02
integration
Builds @coodra/* in dependency order, then runs pnpm test:integration against postgres + redis containers.
depends → verify
/03
hook-adapter-smoke
Runs scripts/hook-adapters/__tests__/smoke.sh on both ubuntu and macos.
parallel
/04
e2e
Testcontainers-driven full-stack scenarios.
depends → integration
/13 — Contribute

Contribution guide.

MIT licensed
PRs welcome

Coodra is built linearly, start to finish, as a production-grade system. Contributions are welcome — and held to the same bar.

/13.1Core principles (read first)

  1. Ship complete or don't merge. No placeholder surfaces, no mocked endpoints to make the UI render, no "we'll come back to this later." Every module ships end-to-end.
  2. Local-first remains the default. Any new feature must work offline. If it requires the cloud, it must degrade gracefully back to local + offer a clear path to enable.
  3. Fail-open in the hot path. Hooks return ALLOW on engine fault. Hot-path code paths catch and log async — never throw at the agent.
  4. Append-only audit. Don't add an UPDATE on policy_decisions, decisions, or run_events. Resurface via a new row with the same idempotency key.
  5. Document divergence. If your PR contradicts an ADR, README claim, or essentials doc — update the source-of-truth doc in the same commit.

/13.2Workflow

/01

Pick or file the issue.

Look for good-first-issue or help-wanted. For non-trivial work, open an issue first so we can align on shape before code lands.

/02

Branch off main.

Naming: feat/<short-slug>, fix/<short-slug>, chore/<short-slug>, docs/<short-slug>. Squash-merge expected at the end.

/03

Write the tests.

Unit tests live next to source as *.test.ts. New tables/migrations require a schema-parity test. New MCP tools require a happy-path + soft-failure test at minimum.

/04

Update docs.

If you touched the public surface — env vars, CLI flags, MCP tools, hook payloads — update the relevant table in this docs page in the same PR. Also update essentialsforclaude/ if the agent contract changes.

/05

Pre-commit & push.

The .githooks/pre-commit hook runs the migration-lock check automatically when packages/db/* is staged. Before pushing: pnpm lint:fix && pnpm typecheck && pnpm test:unit.

/06

Open the PR.

Title in conventional-commit form (feat:, fix:, chore:, docs:). Body should state: what changed, why, how to verify locally, and any ADR/docs it touches. CI will run verify → integration → hook-adapter-smoke → e2e.

/13.3Code conventions

Biome is the source of truth. Rules ratcheted up over time — these are the ones most likely to flag a PR.

useConst
error — Prefer const. Reach for let only when re-assigning.
useImportType
error — Type-only imports must be marked. import type { Foo } from '...'
noExplicitAny
errorany is a code smell. Prefer unknown + narrowing, or a real type.
noNonNullAssertion
warn — Avoid x!. Prefer explicit guard + early return.
noUnusedImports / Variables
error — Lint-fix will strip them automatically.
noImportCycles
error — Scoped to apps/mcp-server/src/lib/**. If your import cycles, refactor the shared bits up a layer.
noConsole
warn — Use the shared logger (pino-backed). console.log only for CLI user output.

/13.4Where things live

repo map
Coodra/
├── apps/
│   ├── mcp-server/      # 16 MCP tools · stdio + HTTP transport
│   ├── hooks-bridge/    # Hono HTTP · 5 Claude Code hook events
│   ├── sync-daemon/     # push + pull workers · team mode only
│   ├── web/             # DEPRECATED · do not extend
│   └── web-v2/          # Next.js 15 dashboard · 46 routes
├── packages/
│   ├── cli/             # @coodra/cli npm package · only published artefact
│   ├── db/              # Drizzle dual-dialect schema + migrations
│   ├── policy/          # Cockatiel-wrapped evaluator + types
│   └── shared/          # auth, roles, hook adapters, logger, env
├── docs/
│   ├── feature-packs/   # 16 module blueprints
│   ├── context-packs/   # template + module/phase closeouts
│   ├── DEVELOPMENT.md
│   └── deploy/self-host.md
├── essentialsforclaude/ # 12 numbered docs auto-imported by CLAUDE.md
├── deploy/              # 4 Dockerfiles + compose.yaml
├── scripts/             # hook-adapter installers + maintenance
├── __tests__/           # e2e + functional + manual harnesses
└── .github/workflows/   # ci.yml (4 jobs)

Hot tip: when in doubt about whether a behaviour is correct, search docs/audit/ and docs/verification/. Those reports cite exact file:line ranges for every claim made elsewhere.

/14 — Design

Decisions of record.

essentialsforclaude
11-adrs.md

Architectural decisions, locked once accepted, superseded only by another ADR. The ones below are the most load-bearing — if you're new to the codebase, these explain why things look the way they do.

ADR-007

Append-only audit

Policy decisions, decisions records, and kill-switch state changes are append-only. Idempotency keys gate writes. Resumes don't delete — they stamp resumed_at on the existing row. Recovery and forensics become trivial.

ADR-010

Graphify is a reader

Coodra reads ~/.coodra/graphify/<slug>/graph.json produced by an external graphify scan. No in-repo producer ships — separation of concerns and faster iteration on the graph format.

ADR-012

Agent-driven NL assembly

Module 05 originally proposed a Python FastAPI service with sentence-transformers. Replaced with MCP tools (search_packs_nl, list_context_packs, read_context_pack) — the agent does the ranking. No external service. No model dependency.

ADR-013

Run Diff in process

Module 06 was renamed from "Semantic Diff" to "Run Diff". The Python tree-sitter service is gone. Replaced by an in-process git-diff runner in the hooks-bridge that captures base_sha → head_sha at session end.

ADR-014

Tier 2.5 RBAC

Three roles — viewer, member, admin — parsed from Clerk org roles. assertCanEdit centralises the gate; ownership unlocks member-level edits on rows they created. Knowledge mutations are their own narrower gate.

ADR-014 · caveat

Bridge stays local

Hooks-bridge always runs on the laptop. No cloud bridge ships, even in team mode. The sync-daemon is the only process that opens the cloud connection — preserves the "fail-open & loopback" invariant.

/15 — Reference

External dependencies.

Material
libraries

Only the libraries that shape behaviour. If something in the runtime surprises you, the explanation is usually here.

Package Where What it gives us
@modelcontextprotocol/sdkmcp-serverMCP protocol server with stdio + HTTP transports.
hono · @hono/node-serverhooks-bridge · mcp-server HTTPTiny HTTP framework. Validates payloads via @hono/zod-validator.
drizzle-ormpackages/dbDual-dialect schemas + migrations.
better-sqlite3everywhere localSynchronous SQLite driver. Native module — rebuild after install.
sqlite-vecpackages/db extensionVector search for the context_packs_vec virtual table.
postgresweb-v2 · packages/dbPostgres driver.
next 15 · react 19web-v2Server framework + UI.
@clerk/nextjs · @clerk/backendweb-v2 · cliIdentity, org membership, JWT.
cockatielpackages/policyCircuit breaker + timeout fuse around DB reads.
picomatchpolicy · mcp-serverPath glob matching for policy rules.
zodeverywhereSchema validation at every untrusted boundary.
commander · execa · inkcliCLI parser, subprocess management, React-for-terminal TUI.
vitest · testcontainerstestsUnit + integration + e2e runners.
biomerepoLint + format.
turborepoWorkspace orchestration.

Not present, in case you're checking: no @anthropic-ai/sdk, no openai, no @google/generative-ai, no web-tree-sitter, no bullmq, no @octokit/*, no JIRA SDK. The MCP server is the only LLM-facing surface — and it talks to the agent, not the model.

/16 — Help

Frequently asked.

Short answers
linked deep dives
Is Coodra a code editor?
No. It runs alongside your editor — Claude Code, Codex. Coodra is the layer between the agent and your project: blueprints in, audit out.
Does it call out to OpenAI / Anthropic?
Coodra itself does not. Your agent (Claude Code, etc.) talks to its model. Coodra only ever talks to the agent through MCP, locally.
Can I use Coodra without Claude Code?
Yes. Codex is fully supported alongside Claude Code — both get the native plugin, hooks, and MCP wiring. Any MCP-speaking client can talk to mcp-server.
What happens if the hooks bridge goes down?
The agent's hook calls fail-open with a default ALLOW. You lose audit + memory injection for the affected tool calls, but you don't lose the ability to code.
Where is my data stored?
In solo mode: only at ~/.coodra/data.db. In team mode: also in the Postgres you operate. Coodra ships no hosted backend.
Can I delete my run history?
Yes — scripts/wipe-disposable-projects.sh or scripts/wipe-all.sh. Append-only doesn't mean undeletable; it means we don't update rows in place.
Why does .env.example mention services that don't exist?
Historical. Modules 05 (NL Assembly) and 06 (Semantic Diff) were superseded by ADR-012 and ADR-013. The example file is being trimmed.
Is the web UI required?
No — it's a convenience. coodra start --no-web skips it. coodra ui gives you a TUI alternative.
/17 — Help

Glossary.

Short
definitions
Actor
The authenticated entity behind a request — userId, orgId, role, source. Solo bypass attaches SOLO_ACTOR.
Bridge
Short for hooks-bridge — the Hono HTTP service on 3101 that the agent's hooks POST to.
Cockatiel
The resilience library wrapping policy DB reads. Provides timeout + circuit breaker primitives.
Doctor
A self-diagnostic CLI command. 11 essential checks by default, 38 with --full.
Drizzle
The TypeScript ORM. Dual-dialect schemas in packages/db/src/schema/{sqlite,postgres}.ts.
Fail-open
When the engine can't decide, the verdict is ALLOW. Never block the agent because of our own faults.
graphify
An external CLI that scans your codebase and emits graph.json. Coodra reads it; doesn't produce it.
Idempotency key
A deterministic string used to gate writes. Same key → at most one row.
Ink
React for the terminal. Powers coodra ui.
launchd
macOS's service manager. Coodra writes per-service plists into ~/Library/LaunchAgents/ during start.
MCP
Model Context Protocol. The standard Coodra speaks to AI agents.
Outbox
The durable SQLite-backed queue (pending_jobs table) used for async writes and cloud sync.
picomatch
Path glob library. Used to match policy_rules.match_path_glob.
Run diff
Unified diff between runs.base_sha and runs.head_sha, captured at session end.
Sentinel project
__global__ for events before a real project exists; __solo__ for the solo-mode org.