This file is the operating manual for any agent (subagent, orchestrator, or future-Claude-session)
working in this codebase. It supplements CLAUDE.md (architecture) and Roadmap.md
(current priorities). Read this before starting any task.
bun run dev # server on :4097, Vite on :5173
bun run build # tsc --noEmit + vite build → web/dist
bun run start # production server on :4097
bun run typecheck # server tsc --noEmit
cd web && bunx tsc --noEmit # client tscFor browser testing: use agent-browser at iPhone 15 Pro Max viewport (430x932).
bun run buildbun run typecheck && cd web && bunx tsc --noEmitkill $(lsof -nP -iTCP:4097 -sTCP:LISTEN -t)to stop the running serverbun run startto start fresh (your new code is in web/dist)agent-browserverification
For auth-gated browser testing: the server has loopbackBypass: true by default.
For full auth bypass, temporarily switch ~/.config/opzero-claude/config.json to
plaintext password ("password": "testpass" instead of bcrypt hash), restart server,
log in via curl, extract JWT cookie, load into agent-browser state, restore bcrypt
config, restart server.
See Roadmap.md. The orchestrator works in waves:
- Shipped: marked
[SHIPPED]inRoadmap.md - Rejected: marked
[REJECTED]with reason inRoadmap.md - In progress: ask orchestrator or check active task
- Next: top of the open list
Each agent gets exclusive ownership of a file set. No two agents touch the same file unless explicitly coordinated. Before dispatching, the orchestrator scouts each item to identify conflicts.
- Own files only
- New files in their owned directories
- No changes to
server/types.ts/web/src/lib/types.tswithout the orchestrator coordinating both sides (types must stay in sync)
git add .orgit commit- Touch files under
/Users/opz/opz/opzero-sh/OpZero.sh/ - Remove emojis or add them
- Change the package name
opzero-claude - Add new npm dependencies without checking
package.jsonfirst
After implementing, agents must run:
bun run typecheckat repo rootcd web && bunx tsc --noEmitbun run build(if web files changed)
Never mutate state directly. Always rebuild the top-level object:
// WRONG — React won't re-render
state.foo = bar;
emit();
// RIGHT — immutable update
setState({ ...state, foo: bar });- Add variant to
SSEEventin bothserver/types.tsANDweb/src/lib/types.ts - Emit from server (usually
SessionProcessorSessionTailer) - Handle in
web/src/lib/store.ts::dispatch - If it mutates messages, add a reducer in
web/src/lib/parts.ts
- Add handler in
server/routes/*.ts - Wire into
server/index.ts::fetch - Add path to public-paths in
server/auth.tsif unauthed access is needed - Add client wrapper in
web/src/lib/api.ts - Add types to
server/types.tsandweb/src/lib/types.ts
- Create
web/src/components/parts/XyzToolView.tsx - Add case to
web/src/components/parts/ToolUsePart.tsxdispatcher - Follow existing patterns:
border-l-2 border-l-accentfor subagent tools,border-l-2 border-l-primaryfor regular tools. Cyan pulse for running, violet accent for results.
The CLI flag for permission behavior is conditional:
permissionMode === "bypassPermissions"→--dangerously-skip-permissionspermissionModeis set →--permission-mode <val>permissionModeis blank → omit both (claude default)
Never add --dangerously-skip-permissions alongside --permission-mode.
Use encodeProjectSlug(cwd) from server/claude/paths.ts. Never hardcode or
manually construct slugs. The encoder handles encoding all special chars.
Stream-json = stdout from claude -p --output-format stream-json. Used by
SessionProcess for live sessions. Emits result/cost records.
JSONL = ~/.claude/projects/<slug>/<id>.jsonl. Appended by Claude Code
for all sessions. Used by SessionTailer for mirror sessions and history.ts
for historical reads. Does NOT carry cost/result records.
Never mix these formats.
Two providers are shipped:
createCookieAuthProvider(config)— default, JWT cookiecreateCloudflareAccessAuthProvider(config)— set"authProvider": "cf-access"in config
To add a new provider: implement AuthProvider interface in server/auth.ts, wire
in server/index.ts. The interface is:
interface AuthProvider {
name: string;
verify(req: Request): Promise<{ok: true; user: {sub: string}} | {ok: false}>;
loginUrl?: string;
logoutUrl?: string;
}The orchestrator maintains .agent-log/ with per-wave entries covering:
- Decisions and why
- Challenges encountered
- Files changed
- Shipped items
Read the most recent log before starting new work to avoid repeating mistakes.
docs/research/ contains backgrounders on features under investigation:
kairos.md— not shipped, compile-time strippedultraplan.md— internal-only, not wrapablevoice_mode.md— no native Claude Code primitive, implemented via Web Speech APIdaemon.md— KAIROS rename, not shippedagent_triggers.md— cron scheduling, undocumentedbridge_monitor.md— BRIDGE_MODE is Remote Control (not applicable), MONITOR_TOOL shipped v2.1.98+buddy.md— real Tamagotchi, not externally scriptableauto_dream.md— memory consolidation, read-access straightforward
- Browser test auth: loopback bypass is on by default; for full auth testing, see the testing pattern in "How to test a change" above.
- JSONL has no cost data: mirror sessions show $0 cost. This is correct — the JSONL format doesn't carry result records.
- Fork session: blocked.
claude --fork-sessioncan return a new session ID atsystem.inittime. The current architecture keys on the spawn-time ID. - Subagent streaming: blocked. Requires channels in subagent subprocesses.
- Bun.serve idleTimeout: must be
idleTimeout: 0for SSE streams. Don't change.
Owner: Jeff Cameron. Repo: https://github.com/OpZero-sh/CodeZero (private). Live: https://claude.opzero.sh