codeoid
v0.2.0
Published
Identity-first control plane for AI coding agents — multi-session, multi-frontend, with cross-session memory
Maintainers
Readme
Codeoid
Identity-first control plane for AI coding agents — multi-session, multi-frontend, with cross-session memory.
Run N parallel Claude Code sessions across repos. Switch between them from a terminal cockpit, a web UI, or Telegram. Every action auditable; every agent (and sub-agent) has a cryptographic identity via ZeroID. Memory persists across sessions so Claude inherits what it learned last time.
Terminal client lives in its own repo. The recommended cockpit is codeoid-tui — a native Rust/Ratatui client that speaks the daemon's WebSocket protocol. A built-in
codeoid tui(Ink/React) ships in this repo as a zero-install fallback. See Terminal client.
Contents
- Why Codeoid
- How Codeoid compares
- Quick start
- Architecture
- Features
- Permission scopes
- Configuration
- CLI reference
- Development
- Contributing & security
Why Codeoid
You're orchestrating AI coding agents. Codeoid solves the things Claude Code's single-terminal experience can't:
- Parallel sessions, shared workspace memory — Two sessions on two git worktrees building feature A and feature B. Both inherit the same workspace's history. Session B can
recall()what Session A learned yesterday, no re-read. - Never-lose-detail memory — Every tool call, result, and reasoning block persists as a retrievable episode. No lossy compaction. Recall returns the real bytes.
- Three-layer context reduction — Pre-entry compression of CLI output + auto-rotation of the backing context + verbatim recall. Turns that would have cost $0.30 drop to pennies; peak occupancy stays below compaction.
- Mid-turn streaming input (VSCode parity) — Send a follow-up message while Claude is already responding. Priority semantics (
now/next/later) let you interrupt-and-re-integrate or gracefully queue for the next turn. - Production-grade token instrumentation — Per-turn input/output/cache/cost persisted to SQLite. Live StatusBar shows cumulative + Δ this-turn + cache hit rate + current context occupancy + queue depth + rotation count.
- Autonomous runs with a budget — Flip a session to autonomous mode; it auto-approves safe operations until a write/exec budget is spent, then hands control back.
- Device handoff — Start a session on your laptop, attach from your phone. Scrollback replays. Same conversation.
- Identity-grade audit — Every tool call stamped with the SPIFFE URI of the agent that made it. Sub-agents get their own attenuated identities. Delegation chain traceable top to bottom.
- Multi-frontend — same session accessible from terminal TUI, browser, or Telegram bot. Share read-only tokens with a teammate.
How Codeoid compares
Codeoid is not a general-purpose IDE assistant — it's aimed at long-horizon multi-session agent work where context continuity and token economics matter more than inline code actions. Here's where it differs from the tools you're probably already using:
| Capability | Claude Code CLI | VSCode Extension | Cursor | Aider | Omnigent | Codeoid |
|---|:-:|:-:|:-:|:-:|:-:|:-:|
| Cross-session verbatim memory | ❌ /compact is lossy | ❌ session-scoped | ❌ | ❌ | ❌ state syncs, no episodic recall | ✅ SQLite + FTS5 + vectors, workspace-scoped |
| Parallel sessions, one control plane | ❌ one terminal | ❌ one window per repo | ~ tabs | ❌ | ✅ Polly delegates to parallel agents | ✅ N sessions, switch with Ctrl-G |
| Git-worktree-aware memory sharing | ❌ | ❌ | ❌ | ❌ | ~ worktrees for isolation, not shared memory | ✅ anchored on git-common-dir |
| Workspace memory index injected into system prompt | ❌ | ❌ | ❌ | ~ repo map | ❌ | ✅ hot files + topic clusters + recent sessions, auto-regenerated |
| Pre-entry CLI output compression (git diff, test runners, etc.) with recall recovery | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ declarative rules, 60-90% reduction with tee-cache |
| Auto-rotation of backing context near compaction ceiling | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ lossless via memory recall seed |
| Mid-turn user input (stream) | ❌ interactive CLI is turn-based | ✅ | ~ | ❌ | ~ real-time collab | ✅ with now/next/later priority |
| Per-turn token / cost / cache telemetry | ~ /cost total only | ❌ | ❌ | ~ | ~ spend caps + routing | ✅ persistent SQLite, StatusBar, Δ per turn |
| Current context occupancy visible | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ ctx 65k/1.0M (7%) live in StatusBar |
| Cryptographic identity per agent + sub-agent (SPIFFE) | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ ZeroID WIMSE URIs |
| Autonomous mode with write-action budget | ❌ | ~ | ~ | ❌ | ✅ stateful spend caps + risk escalation | ✅ budget tracked per session |
| Multi-frontend (terminal + web + mobile) | ❌ CLI only | ❌ IDE only | ❌ IDE only | ❌ | ✅ terminal → browser → phone | ✅ TUI + Web + Telegram, same session |
| Device handoff (start laptop, continue phone) | ❌ | ❌ | ❌ | ❌ | ✅ sessions follow you | ✅ WS re-attach with scrollback replay |
| Multi-harness (Claude + Codex + Cursor + Pi + custom) | ❌ Claude only | ❌ | ❌ | ❌ | ✅ swap/combine harnesses in one session | ❌ Claude Agent SDK only |
| OS-level sandbox (filesystem + network isolation) | ~ permission modes | ❌ | ❌ | ❌ | ✅ secure OS sandbox | ~ approval + autonomous budget, not OS-level |
| Credential brokering (hide secrets from the agent) | ❌ | ❌ | ❌ | ❌ | ✅ broker access, hide creds | ~ scoped ZeroID identity tokens |
| Inline IDE code actions | ❌ | ✅ | ✅ | ~ | ❌ orchestrates, not inline | ❌ not our niche |
| SWE-bench / automated coding benchmark score | — | — | ✅ | ✅ | — meta-harness | ❌ not yet benchmarked |
| Multi-model routing (Opus for plan, Haiku for cheap subtasks) | ~ recent | ~ | ✅ | ✅ | ✅ model routing across harnesses | ❌ roadmap |
Legend: ✅ first-class · ~ partial · ❌ not supported · — not a meaningful comparison
Where each tool fits: Omnigent (open-sourced by Databricks) is Codeoid's closest peer — a meta-harness that puts Claude Code, Codex, Cursor, and Pi behind one governance layer with an OS sandbox and credential brokering. Codeoid trades that multi-harness breadth for depth on a single harness: verbatim cross-session memory, a cryptographic identity per agent and sub-agent (ZeroID), pre-entry output compression, and per-turn token economics. So: if you need to orchestrate many different agents with OS-level isolation, reach for Omnigent; if you live in Claude Code across weeks and devices and care that it remembers rather than re-summarizes, Codeoid is the frontier; and if you just want "fix this function I'm looking at right now," Cursor is still sharper.
Quick start
Prerequisites
- Bun v1.0+
- Claude Code CLI logged in (
claude login) orANTHROPIC_API_KEYset - A ZeroID identity — either the hosted Highflame SaaS (no infra) or a self-hosted ZeroID
Install
From npm (recommended) — Codeoid runs on Bun, so install it with Bun (npm also works as long as Bun is on your PATH, since it's the runtime):
bun install -g codeoid # or: npm install -g codeoidThis puts a codeoid command on your PATH. Everywhere below you can run codeoid <cmd> directly — e.g. codeoid login, codeoid start, codeoid tui.
From source — to hack on it:
git clone https://github.com/saucam/codeoid.git
cd codeoid
bun installFrom a source checkout, run bun src/cli.ts <cmd> in place of codeoid <cmd> below.
Authenticate
Codeoid needs one thing to start: a ZeroID key. Two ways to get one.
Option A — Highflame SaaS (recommended, no infra)
Sign up at highflame.ai and open Studio → Code Agents.
Create a key (you'll get a
zid_sk_...).Log in — Codeoid ships pointing at the Highflame SaaS issuer, so there's nothing else to configure:
bun src/cli.ts login # prompts for the key (hidden), verifies it, saves to ~/.codeoid/config.json
Option B — Self-hosted ZeroID
Run your own ZeroID, register an agent to get a key, then point Codeoid at it. --zeroid accepts a preset (highflame, highflame-dev, local) or any URL:
bun src/cli.ts login --zeroid local # local ZeroID on :8899
bun src/cli.ts login --zeroid https://zeroid.mycorp.com # your deploymentThe issuer is pinned to whatever you log in against — a token minted by any other issuer is rejected. login exchanges the key on the spot and prints the subject + granted scopes so you know it works before the daemon ever starts.
Run
# Start the daemon — serves TUI/web/Telegram + mounts memory
bun src/cli.ts startThen connect a client:
# Recommended: the native Rust cockpit (separate repo).
# git clone https://github.com/saucam/codeoid-ui && cd codeoid-ui
# cargo run -p codeoid-tui --release
#
# Or the built-in fallback TUI (Ink/React, no extra install):
bun src/cli.ts tuiOr browse to http://localhost:7400/ui/ for the web UI.
Architecture
In one Bun process the daemon brokers everything between your clients and Claude, and owns three subsystems:
- Session Manager — per-session mode + write/exec budget, pinned files, the sub-agent tree, scrollback, and a JSONL transcript for crash-safe resume.
- Memory Engine — a chunker turns every tool call into a verbatim episode; a hybrid ranker (vectors + FTS5 BM25 + recency + path overlap) serves it back. Backed by SQLite (FTS5 + embeddings + file-read cache) and exposed to Claude as an in-process MCP server —
recall(),recall_file(),timeline(). - ZeroID client — registers the session's SPIFFE identity and mints attenuated tokens for each sub-agent.
Each session drives its own Claude Agent SDK query. The diagram at the top shows how the pieces fit.
Sessions are daemon-owned. Clients are stateless; they attach, receive scrollback replay, and stream live deltas. Detach and re-attach from anywhere.
Features
Terminal client
Codeoid has two terminal cockpits, both speaking the daemon's WebSocket protocol:
- codeoid-tui (recommended) — a native
Rust/Ratatui client in its own repo. A true cell-matrix
framebuffer, so it stays jitter-free under high-frequency streaming deltas.
Build once (
cargo run -p codeoid-tui --release) and point it at the daemon. - Built-in
codeoid tui(Ink/React) — ships in this repo as a zero-install fallback. Documented below. Same protocol, same daemon; codeoid-tui supersedes it.
bun src/cli.ts tui launches the built-in Ink cockpit with everything in one view:
[▾ studio2 @ /Workspace/codeoid]
You
use the Explore agent to survey src/daemon and summarize
Claude
I'll use the general-purpose agent to explore the structure...
[general-purpose] ✓ Glob **/*.ts
[general-purpose] ✓ Read src/daemon/session.ts
1 /**
2 * Session — wraps a single Claude Agent SDK query...
...
Claude
## Summary
The daemon module has 14 files. Key layers:
...
╭──────────────────────────────────────────────────────────────╮
│ ● test ▸● studio2 ᴀ⚡ 📌1 ● core │
│──────────────────────────────────────────────────────────────│
│ ⠋ Reasoning… 4s · Ctrl-X to interrupt │
│ acting as …/agent/codeoid-session-7838ee1d │
│ via general-purpose …/subagent/explorer-abc │
│──────────────────────────────────────────────────────────────│
│ ● connected · studio2 @ /Workspace/codeoid · working · mode: autonomous (42 actions left) │
│──────────────────────────────────────────────────────────────│
│ Enter to send · Ctrl-N new · Ctrl-G switch · ? help │
│ › _ │
╰──────────────────────────────────────────────────────────────╯Keybindings:
| Key | Action |
|---|---|
| Ctrl-N | New session |
| Ctrl-G | Switch session (fuzzy) |
| Ctrl-D | Destroy focused session |
| Ctrl-X | Interrupt focused session |
| Esc | Clear input draft — or interrupt if input is empty and session is working |
| Shift-Tab / Ctrl-M | Cycle execution mode |
| y / n | Approve / deny pending tool (when input empty) |
| ? | Show keybindings overlay |
| Ctrl-C | Quit the TUI (sessions keep running) |
Prompt editor:
| Key | Action |
|---|---|
| Enter | Send |
| Alt-Enter / Ctrl-J / \↵ | Insert newline |
| Up / Down | Cycle prompt history |
| Ctrl-A / Ctrl-E | Line start / end |
| Ctrl-U / Ctrl-K | Clear to start / end |
| Ctrl-W | Delete previous word |
| @<path> | Autocomplete a workspace file → attaches to send |
| / | Slash-command hint overlay |
Slash commands (built-in):
| Command | Action |
|---|---|
| /clear | Clear the visible transcript (memory kept) |
| /new | New session (opens modal) |
| /switch | Switch session (opens picker) |
| /destroy | Destroy focused session |
| /interrupt | Interrupt focused session |
| /mode [target] [budget] | Set execution mode. /mode autonomous 100 = 100 write actions budget; /mode autonomous 0 = unbounded |
| /pin <path> | Pin a file — prepended to every turn |
| /unpin <path> | Unpin a file |
| /context <path>… | Attach files to the NEXT turn only |
| /rotate | Roll over the Claude Code backing session — fresh context, memory preserved |
| /who | Print the identity chain (user → agent → sub-agents) |
| /help | Show keybindings |
Pass-through: any /command that isn't a Codeoid built-in or a workspace command is forwarded verbatim to Claude Code — /compact, /agent, custom subcommands all work without Codeoid knowing about them.
Workspace commands: any .claude/commands/*.md in your workspace auto-loads as a slash command on session focus. The file body becomes the prompt template; $ARGUMENTS is substituted with whatever the user typed after the command name. Zero Codeoid changes when you add new commands.
Cross-session memory
Codeoid records every tool call, result, and reasoning block as an episode — stored verbatim, never summarized, retrievable via hybrid search. Claude gains three tools:
recall(query)— semantic + keyword search across all prior sessions in this workspacerecall_file(path)— check if a file was read recently; skip the re-read if cachedtimeline(limit)— chronological list of recent activity across sessions
Memory is workspace-scoped via git rev-parse --git-common-dir, so all git worktrees of the same repo share one workspace — feature branches inherit the main line's knowledge.
What's in the store:
~/.codeoid/memory.db — SQLite (episodes + FTS5 + embeddings)
~/.codeoid/models/ — BGE-small-en-v1.5 (~50MB, downloaded once)Ranking:
| Signal | Weight | Purpose | |---|---|---| | Vector similarity (BGE-small cosine) | 0.55 | Semantic match | | FTS5 BM25 | 0.25 | Exact-string / function-name match | | Recency (48h half-life) | 0.12 | Prefer recent context | | Path overlap | 0.08 | Files touched in common |
No external spend — embeddings run in-process via @xenova/transformers (WASM). Configurable to Ollama, OpenAI, Voyage via the Embedder interface.
Disable with CODEOID_MEMORY=0 if you don't want it.
Parallel sessions + git worktrees
# Start two parallel features on separate worktrees, both sharing memory
bun src/cli.ts new featA --worktree feat/parser --repo /Workspace/codeoid
bun src/cli.ts new featB --worktree feat/ui --repo /Workspace/codeoid
# In the TUI: Ctrl-G between them--worktree <branch> spawns <repo>.wt-<branch> via git worktree add and points the session at it. The branch is created if it doesn't exist. Idempotent — re-running with the same args reuses the existing worktree.
Because workspaceId anchors on the shared .git/common-dir, both sessions live in the same workspace for memory purposes. Session A's Read auth.ts is available to Session B's recall("auth flow").
Execution modes
Each session has a mode. Only codeoid's internal memory-recall tools are ever
pre-approved at the SDK layer — every real tool (Read/Grep/Glob/Write/
Edit/Bash/Agent) is gated by the mode below:
| Mode | Behavior |
|---|---|
| guarded (default) | Read / Grep / Glob / memory auto-approve; Write / Edit / Bash / Agent prompt. ≈ Claude Code's default mode. |
| interactive | Every tool call prompts for approval — including reads. |
| autonomous | Everything auto-approves (no prompts) until the write/exec budget is spent, then reverts to guarded. ≈ Claude Code's bypass mode. |
Cycle with Shift-Tab (or Ctrl-M on terminals that swallow shift-tab). Set explicitly with /mode autonomous 100 for a 100-action budget. /mode autonomous 0 for unbounded (use with caution).
The StatusBar shows the current mode + remaining budget; SessionTabs shows ᴀ⚡ badge when autonomous.
Attachments
Three ways to add file context to a turn:
Inline @file mentions (TUI):
› what does @src/daemon/session.ts do?Tab-completes from your workspace, resolves the path, attaches the file's content to the turn. Multiple @paths per message work.
/context (one-shot attachments):
/context src/foo.ts src/bar.tsSends a minimal "review these" prompt with the files attached.
/pin (persistent across turns):
/pin SPEC.md
/pin acceptance-criteria.mdPinned files are re-read and prepended on every turn until you /unpin. Pins survive daemon restart (stored in session_pins SQLite table). The SessionRail shows 📌N for pinned count.
Web UI adds drag-drop and paste support: drop a file onto the chat area, it's read locally (up to 200 KB) and inlined as an attachment.
Size limits (enforced by the daemon):
- 100 KB per file, 500 KB total per turn
- Binary files (null-byte sniff) skipped with an inline error marker
- Missing files surface as
<file error="...">so Claude sees why a path didn't resolve
Identity chain
Every session gets a primary SPIFFE/WIMSE URI. Sub-agents (spawned via Claude's Agent tool) get their own attenuated identities. Every tool_call SessionMessage is stamped with the identity that made it — parent session OR sub-agent worker.
The TUI surfaces this:
WorkingIndicator shows the acting agent URI + any active sub-agents
Tool rows from sub-agents get a green
[name]prefix:[general-purpose] ✓ Read foo.ts/whoprints the full delegation chain:## Identity chain for studio2 You — user_xxx ↓ Session agent — spiffe://highflame.ai/personal/dev/agent/codeoid-session-7838ee1d ↓ ### Active sub-agents (1) - general-purpose (spiffe://highflame.ai/personal/dev/subagent/explorer-abc)
Why this matters: in audit/compliance terms, every tool call is cryptographically attributable to an exact delegated identity. Revoke the parent in ZeroID → the whole chain dies. Sub-agents get scope-attenuated tokens so they can't escalate.
Device handoff
Detach on laptop, attach from phone. The scrollback buffer (5000 entries / 20MB) replays what happened while you were away. Same session state, same memory, same pending approvals.
laptop$ codeoid attach oracle
> refactor the auth module
[agent working...]
Ctrl+C # detach — session keeps running
# Later, from your phone via Telegram:
/attach oracle
# → scrollback replays → continue the conversationContext reduction stack
Three orthogonal layers, each opt-in, each carrying its weight:
Layer C — workspace memory index (on by default with memory enabled)
A compact markdown block auto-injected into every turn's system prompt. Contains:
- Fingerprint:
294 episodes across 8 sessions · last activity 8m ago - Hot files: top 10 by touch count, each with a
recall_file(path)nudge - Topic clusters: k-means (k ≤ 8) over episode embeddings, labeled with dominant directory + content terms (optional LLM re-labeling via Haiku when
ANTHROPIC_API_KEYis set) - Recent sessions: one-liner per session with its first user-turn summary
- Recall shortcuts: compact tool reference
Rebuilt on a hybrid trigger: ≥ 5 new episodes OR ≥ 60s elapsed with ≥ 1 new episode, debounced to max one regen per 15 s so the prompt cache stays warm. Toggle: CODEOID_WORKSPACE_INDEX=0 disables, CODEOID_MEMORY_CLUSTERS=1 enables the cluster section.
Layer B — CLI output compression (opt-in: CODEOID_COMPRESS=1)
A homegrown RTK-style compressor that routes Bash tool invocations through a daemon-local wrapper before Claude sees them:
PreToolUsehook rewritesBash({ command: "git diff HEAD~5" })→Bash({ command: "bun …/wrapper.ts --b64 …" })- Wrapper runs the real command, captures stdout, applies declarative rules (
git-diffcollapses long unchanged context,git-statuselides huge untracked-files lists,test-runnerdrops passing-test noise,ls/cat/find/grepsummarize by extension/dir/file), 60–90% reduction on shell-heavy turns - Stderr never compressed (error fidelity matters)
- Raw output lives in our verbatim memory store — Claude can
recall(query)to retrieve the original bytes if the compressed version dropped something it needs
Rule format is declarative TypeScript:
export const myRule: CompressionRule = {
name: "my-rule",
description: "…",
match: (cmd) => /^mything\b/.test(cmd),
compress: (stdout, ctx) => ({ compressed, originalBytes: ctx.rawBytes, ruleName: "my-rule" }),
};Drop new rules in src/daemon/compress/rules/ — first match wins, generic head+tail truncator is the last-resort fallback.
Layer D — auto-rotation of the backing session (opt-in: CODEOID_AUTO_ROTATE=1)
When context occupancy creeps toward Claude Code's compaction ceiling, Codeoid rolls the underlying Claude Code session to a fresh backing id while keeping codeoid's public session id stable. The user never notices — same tab, same scrollback, same memory.
Seed strategy B: task-anchor (current default):
- Capture the most recent user turn from memory
- Inject a
<rotation_context>block into the first post-rotation prompt with: workspace, rotation count, last user message verbatim, reminder to callrecall/recall_file/timelinefor prior detail - No summarization — full verbatim memory is one tool call away
Thresholds (all configurable):
| Threshold | Default | Behavior |
|---|---|---|
| warnPct | 60% | (Reserved for UI nudging — currently unused) |
| rotatePct | 80% | Auto-rotate when enabled: true + over minTurnsBeforeRotate |
| hardRotatePct | 90% | Rotate even when enabled: false (safety net) |
| minTurnsBeforeRotate | 3 | Prevent rotation on fresh sessions where seed would be empty |
Each rotation emits a scrollback info message, bumps the rotation counter (persisted in SQLite), and shows 🔄 N in the StatusBar. Manual trigger via /rotate slash command.
Mid-turn streaming input (VSCode parity)
Claude Code's interactive CLI is turn-based — you wait for the response before sending the next message. The SDK underneath actually supports AsyncIterable<SDKUserMessage> streaming, which codeoid uses for mid-turn responsiveness.
When you hit Enter on an idle session: FIFO push, business as usual. When you hit Enter on a working session: codeoid auto-sets priority: "now" — the SDK aborts Claude's in-flight response and restarts with the new context included. You pay ~1-2 s for time-to-first-token on the restart; the alternative (priority: "next") gets two separate responses instead of one unified one.
Explicit control is exposed on the protocol — frontends can pass priority: "now" | "next" | "later" on any session.send message. Default is the smart auto-promotion above.
A live ⎆ N queued badge on the StatusBar shows queue depth. Turning-point feedback: the moment you queue a mid-turn message, an info row appears immediately so you know it was received, even though Claude's reply takes a second to start flowing.
Autonomous + stop conditions
Flip a session to autonomous mode and send it off:
/mode autonomous 50
› go finish the refactor; commit when tests passThe session auto-approves up to 50 write/exec actions. Reads + greps + memory recall don't count against the budget (they're classified as safe). When the budget runs out, the session reverts to interactive — the next write/bash will prompt you.
Status bar shows live budget: autonomous (37 actions left). You can interrupt anytime with Ctrl-X.
Web UI
Mobile-first SolidJS SPA at http://localhost:7400/ui/. Also works as a Telegram Mini App:
- Session switcher
- Approval buttons
- File browser + drag-drop attachments
- Markdown rendering for assistant replies
- Real-time thinking display
Telegram
Set TELEGRAM_BOT_TOKEN and TELEGRAM_ALLOWED_USER_IDS (put them in
~/.codeoid/.env so they survive restarts),
then bun src/cli.ts start. Commands:
| Command | Action |
|---|---|
| /auth <api_key> | Authenticate with ZeroID |
| /ls | List sessions |
| /new <name> <workdir> | Create session |
| /attach <name> | Start receiving output |
| /detach | Stop receiving (session keeps running) |
| /interrupt | Interrupt running agent |
| /destroy <name> | Destroy session |
| yes / no | Approve / deny tool calls |
| (any text) | Send to attached session |
While a turn is running, a one-tap ⏹ Stop button appears on the chat —
the mobile equivalent of Esc / /interrupt.
Thinking content and sub-agent tool calls surface as separate italic messages. Streamed assistant text is buffered per message and flushed when Claude finishes (Telegram's rate limits make per-token streaming infeasible).
Production resilience
| Pattern | What it does |
|---|---|
| Retry with fallback model | Exponential backoff with jitter, 429/529/5xx categorization, falls back to cheaper model after 3 capacity failures. |
| Graceful shutdown | LIFO cleanup registry with 30s grace. SIGTERM / SIGINT / SIGHUP handlers drain sessions before closing the store. |
| Session resume | JSONL transcripts per session; user prompts written BEFORE API calls. On daemon restart, sessions rebuild from transcript; #hasQueried flag ensures Claude Code's own session store is reused via resume instead of re-creating. |
| Rate limiting | Per-user sliding window: max 10 concurrent sessions, 30 creations/hour. |
| Tool approval correlation | Each approval request has a unique approvalId; first response wins; multiple concurrent approvals supported. |
| Subprocess stderr capture | Claude Code subprocess stderr is piped into the daemon log so SDK-level failures are debuggable. |
| Keep-warm interrupt | Interrupt (Esc / ⏹ / Ctrl-X) stops the in-flight turn via the SDK's Query.interrupt() — it reaps the running tool but keeps the backing session alive, so your next message continues on the same context. No re-query(), no resume handshake. Claude Code parity. |
| Never-lose user messages | A sent message is persisted to the transcript the instant it's accepted — before attachment resolution, rotation, or the SDK call. A failure afterward surfaces a visible ⚠️ row (never a silent drop), and frontends render send rejections instead of swallowing them. |
Permission scopes
| Scope | Description |
|---|---|
| session:create | Create sessions |
| session:destroy | Destroy sessions |
| session:list | List all sessions |
| session:attach | Attach (full interaction) |
| session:watch | Watch session output (read-only) |
| session:send | Send messages |
| session:interrupt | Interrupt running agents |
| session:approve | Approve / deny tool requests; also required for /mode, /pin, /unpin |
Share a read-only token with a teammate via ZeroID:
curl -X POST http://localhost:8899/oauth2/token -d '{
"grant_type": "token_exchange",
"subject_token": "YOUR_TOKEN",
"scope": "session:list session:watch"
}'
# → short-lived JWT, list + watch only, revocable anytimeConfiguration
Environment variables
# Auth
CODEOID_API_KEY=zid_sk_... # ZeroID API key (or use `codeoid login`)
ZEROID_URL=highflame # issuer: preset (highflame | highflame-dev | local) or URL
# default: highflame (the Highflame SaaS)
ZEROID_ISSUER= # expected `iss` claim; defaults to the resolved ZEROID_URL
ZEROID_ACCOUNT_ID=personal # Enable agent identities
ZEROID_PROJECT_ID=dev
# Daemon
CODEOID_DAEMON_URL=ws://127.0.0.1:7400 # (for CLI + TUI client)
CODEOID_DB_PATH=~/.codeoid/codeoid.db # SQLite path
CODEOID_TRANSCRIPT_DIR=~/.codeoid/transcripts
# Memory
CODEOID_MEMORY=1 # default: on; set to 0 to disable
CODEOID_MEMORY_DB_PATH=~/.codeoid/memory.db
CODEOID_MEMORY_MODEL=Xenova/bge-small-en-v1.5 # HF model id
CODEOID_MEMORY_CACHE_DIR=~/.codeoid/models
CODEOID_MEMORY_CLUSTERS=0 # k-means topic clusters in workspace index
# Workspace index (always-in-context memory pointer)
CODEOID_WORKSPACE_INDEX=1 # auto-injected into system prompt
CODEOID_WORKSPACE_INDEX_EPISODE_THRESHOLD=5
CODEOID_WORKSPACE_INDEX_TIME_MS=60000
CODEOID_WORKSPACE_INDEX_DEBOUNCE_MS=15000
# CLI output compression (Layer B)
CODEOID_COMPRESS=0 # opt-in: rewrites Bash output via rules
CODEOID_COMPRESS_EXCLUDE= # comma-separated cmd prefixes to skip
CODEOID_COMPRESS_PIPES=0 # allow compressing piped commands
CODEOID_COMPRESS_MIN_BYTES=1024 # skip compression below this size
# Auto-rotation (Layer D)
CODEOID_AUTO_ROTATE=0 # auto-rotate backing session near context ceiling
CODEOID_AUTO_ROTATE_PCT=0.8 # rotate at this occupancy (when enabled)
CODEOID_AUTO_ROTATE_HARD_PCT=0.9 # hard-rotate even when disabled
CODEOID_AUTO_ROTATE_MIN_TURNS=3 # skip rotation on fresh sessions
# Anthropic (optional, for Haiku cluster labeling)
ANTHROPIC_API_KEY=sk-ant-... # if set, clusters get LLM-quality labels
# OAuth (web UI PKCE)
CODEOID_HMAC_SECRET=... # enables OAuth authorization server
GOOGLE_CLIENT_ID=... # optional: Google IdP
GOOGLE_CLIENT_SECRET=...
# Telegram frontend
TELEGRAM_BOT_TOKEN=...
TELEGRAM_ALLOWED_USER_IDS=123,456Config file
Optional ~/.codeoid/config.json (env vars take precedence):
{
"daemonUrl": "ws://127.0.0.1:7400",
"zeroidUrl": "highflame",
"apiKey": "zid_sk_...",
"agentIdentity": {
"accountId": "personal",
"projectId": "dev"
},
"memory": {
"enabled": true,
"dbPath": "~/.codeoid/memory.db",
"model": "Xenova/bge-small-en-v1.5"
}
}~/.codeoid/.env — env-only secrets
Some daemon settings are env-only (notably the Telegram frontend). Rather than
exporting them in whatever shell happens to launch the daemon — where a restart
from a different terminal silently drops them — put them in ~/.codeoid/.env.
codeoid start loads this file before anything reads process.env, it's
co-located with config.json (cwd-independent), mode 600, and never in git.
A variable already set in the real environment still wins.
# ~/.codeoid/.env
TELEGRAM_BOT_TOKEN=123456:AA...
TELEGRAM_ALLOWED_USER_IDS=6714605885
# ANTHROPIC_API_KEY= # only if not logged in via `claude login`CLI reference
bun src/cli.ts start [--port 7400] [--host 127.0.0.1] [--no-telegram] [--no-web]
bun src/cli.ts tui # Launch the cockpit TUI
bun src/cli.ts ls # List sessions
bun src/cli.ts new <name> [workdir] # Create session
--worktree <branch> # auto-spawn a git worktree
--repo <path> # worktree source (default: cwd)
--worktree-dir <path> # override target dir
bun src/cli.ts attach <session> # Readline streaming attach
bun src/cli.ts send <session> <message...> # One-shot send
bun src/cli.ts interrupt <session> # Interrupt
bun src/cli.ts approve <session> [--deny] # Approve / deny pending tool
bun src/cli.ts destroy <session> # DestroyDevelopment
bun install # install deps
bun run dev # run daemon with --watch
bun run build # build to dist/
bun run typecheck # type check
bun run lint # lint with biome
bun test # run unit tests (memory, attachments, etc.)Key files
| Area | File |
|---|---|
| CLI + command routing | src/cli.ts |
| Daemon HTTP + WebSocket | src/daemon/server.ts |
| Session orchestration | src/daemon/session-manager.ts, src/daemon/session.ts |
| Memory engine | src/daemon/memory/ |
| Attachments + limits | src/daemon/attachments.ts |
| Git worktree helper | src/worktree.ts |
| Web UI server (serves web/dist at /ui) | src/frontends/web-ui/index.ts |
| Web UI app (SolidJS) | web/ |
| Telegram bot | src/frontends/telegram/index.ts |
| Built-in TUI (Ink, legacy fallback) | src/tui/ |
| Native TUI (Rust, recommended) | saucam/codeoid-ui |
| Protocol types | src/protocol/types.ts |
Contributing & security
PRs welcome — see CONTRIBUTING.md. For vulnerabilities, see SECURITY.md (please don't open public issues for security).
License
MIT © Codeoid
Powered by ZeroID + Claude Agent SDK. Terminal cockpit: codeoid-tui.
