English lecture · source-traced

CodexMonitor source analysis: a command stand for Codex—how a Tauri desktop app orchestrates many agents over the official protocol

**Subject**: CodexMonitor (`github.com/Dimillian/CodexMonitor`), MIT, this analysis is based on **main branch 2026-03-26** (commit dd61b9a, PR #584 "Refactor tray thread menu"). Author **Dimillian** (Thomas Ricouard)—a well-known developer in the SwiftUI open-source circle (ACarousel, MovieSwiftUI). **Code size**: 860 files / 28MB. Rust backend 92 files / 31,171 lines (`src-tauri/`); TS frontend `src/` 557 ts/tsx fil

EN
lecture
source
file:line
no
embellishment
中文
sibling

Subject: CodexMonitor (github.com/Dimillian/CodexMonitor), MIT, this analysis is based on main branch 2026-03-26 (commit dd61b9a, PR #584 "Refactor tray thread menu"). Author Dimillian (Thomas Ricouard)—a well-known developer in the SwiftUI open-source circle (ACarousel, MovieSwiftUI). Code size: 860 files / 28MB. Rust backend 92 files / 31,171 lines (src-tauri/); TS frontend src/ 557 ts/tsx files / 112,428 lines (counting css, 603 files / 127,161 lines); also docs/, scripts/ (iOS build chain). Runtime depends on the official codex CLI (codex app-server protocol). One-line position: a desktop command stand for the OpenAI Codex CLI—a Tauri 2 app. Each workspace spawns a codex app-server subprocess and drives multiple Codex agent threads through the official stdio JSON-RPC protocol (not PTY screen scraping); it ships workspace/worktree management, streaming chat, diff/PR review, usage stats, dictation, and a full iOS remote daemon. Audience: readers who have already read at least one analysis in this series (especially the OpenAI Codex source analysis and the Kanban source analysis—this lecture is a “deeper companion” of the Codex analysis, and a contrast sample against kanban’s “PTY+hooks scheme”). The whole text cites file:line so you can check the source.


Part 2

Chapter 1 · Project overview: a command stand for the Codex CLI

What is CodexMonitor? The first sentence of the README: A Tauri app for orchestrating multiple Codex agents across local workspaces. It is not another coding agent—the agent is codex; it is the agent’s command stand:

  • Multi-workspace: the sidebar manages many projects; each workspace spawns a codex app-server, with an independent CODEX_HOME.
  • Multi-thread: many threads (chat sessions) inside one workspace; resume/fork/archive/copy, pin/rename, per-thread drafts, interrupt/steer an in-flight turn.
  • worktree isolation: give the agent an independent git worktree (independent branch + independent codex home), many agents in parallel with zero conflict—the same spirit as kanban’s one worktree per card.
  • Composer: image attachments, Queue vs Steer dual follow-up modes, $ skills / /prompts: / /review / @ file autocomplete, model selection, context ring.
  • git/GitHub full flow: diff panel, stage/revert, commit log, branches, GitHub issues/PR (list/diff/comments), “Ask PR” sending PR context into a new agent thread.
  • Usage dashboard: home usage snapshot (parse Codex’s rollout JSONL) + sidebar credits meter.
  • Dictation: local Whisper (whisper-rs), hold-to-talk + waveform.
  • iOS remote mode: the desktop runs a daemon (TCP JSON-RPC); the iPhone connects over Tailscale as a thin client.

How it relates to codex / kanban (two neighbors in this series)

codex (official CLI) kanban (orchestration board) CodexMonitor (desktop command stand)
Who is the Agent itself 7 CLI agents only codex
Integration — PTY process + hooks reverse injection official app-server protocol (stdio JSON-RPC)
Shape terminal CLI browser board Tauri desktop + iOS
Orchestration grain single session card (task) workspace × thread
Relation to codex the body codex is one of 7 (PTY) codex is the only core, over the official socket

The most interesting contrast: kanban and CodexMonitor do the same job (multi-agent orchestration) but take two sharply different technical routes—black-box adaptation (PTY screen scrape + hooks) vs official protocol (app-server JSON-RPC). That is the through-line of this lecture.

Digitized project shape

Dimension Number Note
Backend src-tauri/ 92 files / 31,171 lines Tauri commands + shared domain core + daemon
Frontend src/ 557 ts/tsx / 112,428 lines (with css, 603 files / 127,161 lines) React + Vite + feature-sliced, 25 features
Tauri commands generate_handler! registers 125 (#[tauri::command] appears 138 times in source; the gap is dictation/terminal/menu cfg dual implementations) lib.rs invoke_handler registration; each command is dual-track “local or forward to daemon”
app-server methods ~22 called from codex_core.rs; all official protocol methods (no private/experimental)
Dependencies (frontend) 19 runtime deps; UI libraries only 6 families lucide / react-markdown(+remark-gfm) / prismjs / xterm / react-virtual / Sentry, plus @pierre/diffs, vscode-material-icons, and 6 @tauri-apps plugins—no Tailwind/Radix/state library
Platforms macOS primary + Windows/iOS tray macOS only; Windows opt-in; iOS remote-daemon mode
License MIT extremely permissive

Part 3

Chapter 2 · Panorama: Tauri dual-process + app-server protocol + event stream

Layering convention (AGENTS.md hard rule)

┌──────────────────────────────────────────────────────────────┐
│  React frontend (feature-sliced, src/features/ 25 features)   │
│  Orchestration in hooks/* and bootstrap/*; event fanout only in services/ │
└───────────────┬──────────────────────────────────────────────┘
                │ Tauri IPC (125 #[tauri::command] registered)
┌───────────────▼──────────────────────────────────────────────┐
│  Rust backend (src-tauri/)                                    │
│  ┌──────────────┐  ┌─────────────┐  ┌──────────────────────┐ │
│  │ lib.rs cmd   │→ │ shared/*    │← │ bin/codex_monitor_  │ │
│  │ workspaces/  │  │ domain core │  │ daemon (remote mode) │ │
│  │ files/ ...   │  │ (source of  │  │ #[path] include same │ │
│  │ thin adapter │  │  truth)     │  │                      │ │
│  └──────┬───────┘  └──────┬──────┘  └──────────────────────┘ │
│         ▼                 ▼                                   │
│  codex app-server subprocess (one per workspace, stdio JSON-RPC) │
└──────────────────────────────────────────────────────────────┘
  • Domain logic lives in src-tauri/src/shared/*—app and remote daemon compile into the same binary and share the same *_core functions, so logic cannot drift.
  • codex/, workspaces/, git/ etc. are thin Tauri-command adapters; frontend IPC only goes through src/services/tauri.ts.
  • The daemon (src-tauri/src/bin/codex_monitor_daemon.rs, TCP newline-delimited JSON-RPC) reuses the shared core with the app; method names/payloads align.
  • Frontend feature-sliced: src/features/{app,threads,workspaces,...}; event fanout only in src/services/events.ts.

Command registry (src-tauri/src/lib.rs, 337 lines)

invoke_handler registers 125 commands (#[tauri::command] appears 138 times in source; the extras are dictation/terminal/menu dual desktop vs mobile implementations under cfg). Every codex command has a uniform pattern: first check remote_backend::is_remote_mode; if so, call_remote (forward to daemon); else go to shared::codex_core::*_core. State is managed via state::AppState:

  • workspaces: Mutex<HashMap<String, WorkspaceEntry>>
  • sessions: Mutex<HashMap<String, Arc<WorkspaceSession>>>—one app-server process per workspace
  • app_settings, codex_login_cancels, tcp_daemon, etc.

Event stream (the through-line of the whole lecture)

codex app-server stdout (JSON lines)
  → backend/app_server.rs classify (request response / thread/list cwd attribution / pure notification)
  → AppServerEvent{workspace_id, message}
  → event_sink.rs → app.emit("app-server-event")
  → frontend services/events.ts createEventHub lazy-subscribe fanout
  → useAppServerEvents (30+ method allowlist + approval suffix match)
  → useThreadEventHandlers → each reducer slice dispatch
  → React UI

Part 4

Chapter 3 · app-server attach: one codex process per workspace, LSP-style handshake

spawn (src-tauri/src/backend/app_server.rs, L749 spawn_workspace_session)

  1. check_codex_installation (codex --version).
  2. build_codex_command_with_bin builds the command: configurable codexBin/codexArgs; Windows special-cases cmd/bat.
  3. current_dir(entry.path) sets the working directory, sets CODEX_HOME, args ["app-server"]—one independent codex process + independent codex home per workspace (no mutual pollution; config/session/usage data are naturally isolated).
  4. stdio fully piped.

Lifecycle (src-tauri/src/shared/workspaces_core/connect.rs)

  • connect_workspace_core holds a global spawn lock: if a live session already exists, reuse it (take_live_shared_session walks existing sessions with try_wait() to decide liveness; many workspaces in the same process share one codex process—app-server itself supports multiple clients).
  • Delete: kill_session_by_id → kill_child_process_tree.
  • Reconnect is “lazy”: no background auto-restart. On frontend start / window focus it calls connect_workspace per workspace (the README is explicit). Failed sessions are cleared from the map and reborn on the next call.
  • Handshake: initialize (clientInfo + experimentalApi, 15s timeout then kill) → initialized notification → emit a synthetic codex/connected event—LSP-style two-step handshake.

stdio read/write and JSON-RPC codec

  • Write side: write_message (JSON single line \n-delimited).
  • Read side two tasks: stdout uses BufReader::lines() then serde_json::from_str per line; stderr is forwarded as codex/stderr events.
  • send_request_for_workspace (L494): increment next_id → register pending: oneshot and request_context(workspace, method) → timeout 300s → response fills back by id.
  • send_notification / send_response (answers to server requests such as approval).

Event classification (the key to intelligent routing)

Each stdout line is first judged as one of three classes:

  1. Has id + result/error = request response → look up request_context to restore workspace/method, and extract_related_thread_ids backfills the thread_workspace map.
  2. thread/list response: extra parse of cwd → resolve_workspace_for_cwd (longest-prefix match) establishes thread→workspace attribution; memory_consolidation sub-agent threads join hidden_thread_ids and instead emit a synthetic codex/backgroundThread(hide) event (memory-consolidation sub-agents do not pollute the main UI).
  3. Pure notification: route workspace by thread_workspace; global notifications (account/*) broadcast to all workspaces under the session.

Essential difference from kanban’s PTY+hooks scheme

This is the most important contrast point of the analysis:

kanban CodexMonitor
Data source PTY byte stream + hooks text callback structured JSON-RPC notifications
Status semantics parse hook payload / poll rollout jsonl typed events (thread/status/changed, item/started/completed, delta streams)
Control direction one-way (receive only) two-way (send_response answers permissions, turn/steer interrupts)
Reliability depends on each agent’s hook implementation; Codex even has to poll guaranteed by the protocol itself
Cost an adapter matrix for 7 agents bound to Codex’s official protocol (single ecology)

One sentence: kanban treats the agent as a “black box” (screen scrape + text stream); CodexMonitor treats codex as a “service” (official socket, direct). The former is wide; the latter is deep.


Part 5

Chapter 4 · Protocol cross-check: CodexMonitor × Codex’s official app-server protocol

Every Codex-side citation in this chapter comes from the official Codex source checked out in this repository (参考项目/codex/codex-rs/). This is a “contrast experiment” aligning CodexMonitor’s code with Codex itself.

4.1 Full protocol method surface (Codex side)

Codex-side codex-rs/app-server-protocol/src/protocol/common.rs (4072 lines) defines the full protocol surface (every common.rs: line number in this section refers to that file):

  • Client requests (ClientRequestPayload, common.rs:482–1235): initialize:482, thread/start:491, thread/resume:497, thread/fork:503, thread/archive:509, thread/list:630 (cursor/limit/sortKey/sourceKinds), turn/start:836, turn/steer:842, turn/interrupt:848, review/start:889, model/list:895, collaborationMode/list:964, skills/list:684, account/rateLimits/read:1058, account/read:1192, etc.
  • Experimental methods (#[experimental]): thread/search, environment/*, process/spawn|kill, thread/increment_elicitation, memory/reset.
  • Server→client requests (ServerRequestPayload, i.e. approval requests, common.rs:1505–1554): item/commandExecution/requestApproval, item/fileChange/requestApproval, item/tool/requestUserInput, item/permissions/requestApproval, item/tool/call, attestation/generate.
  • Server notifications (common.rs:1658–1731): thread/started, thread/status/changed, turn/started, turn/completed, item/started, item/completed, item/agentMessage/delta, item/plan/delta, command/exec/outputDelta, account/updated, model/rerouted, etc.

4.2 CodexMonitor’s coverage (src-tauri/src/shared/codex_core.rs, 1033 lines)

It calls about 22 methods, all official protocol methods, not one private/experimental method. It does declare experimentalApi: true, but uses no experimental methods (a protocol-capability switch; see below).

  • Approval requests use send_response(request_id, result) as a generic forward (codex_core.rs:848–856), not the typed *ApprovalResponse structured handling in official codex-rs/app-server/src/bespoke_event_handling.rs:1872–2018.
  • Not covered: multi-agent orchestration, plugin/marketplace management (skills is read-only list), fs/* RPC, command/exec dual mode, process/spawn, config RPC (it instead reads/writes config.toml directly), thread/realtime, remoteControl.

Conclusion: CodexMonitor is not a “full-coverage protocol client”—it covers one depth: thread/turn lifecycle, queries, and account. The rest of the surface it deliberately does not touch. This is “enough is enough” taste: deep-integrate the core path; the periphery uses simpler direct means (write config.toml directly).

4.3 Official design intent of app-server (Codex-side AGENTS.md)

  • AGENTS.md:104–106: lists app-server APIs as a breaking-change must-search surface for external integrations (clients like CodexMonitor must watch versions).
  • :270: new APIs only go into v2; v1 is frozen; :273 <resource>/<method> singular naming; :274–276 on-the-wire camelCase; :286–287 experimental surface gated by #[experimental]; :294–296 list methods default to cursor/limit paging.
  • Official README position: app-server = the “socket” for editors/IDEs—so an IDE does not have to parse terminal ANSI or hack hooks, and can drive Codex with an LSP-style protocol.

CodexMonitor is walking the officially designated path: src-tauri/src/backend/app_server.rs:760–771 spawn args, build_initialize_params (:419–430) clientInfo{name:"codex_monitor"} + capabilities.experimentalApi:true, initialized notification (:1091)—item-by-item match with official Codex codex-rs/app-server/src/request_processors/initialize_processor.rs:70–101 handshake contract.

4.4 Rollout JSONL usage-stats cross-check

  • Official format: ~/.codex/sessions/YYYY/MM/DD/rollout-<ts>-<uuid>.jsonl, line structure RolloutLine{timestamp, ordinal, item} (Codex codex-rs/protocol/src/protocol.rs:3380); item is an 8-variant enum RolloutItem: SessionMeta | ResponseItem | InterAgentCommunication | InterAgentCommunicationMetadata | Compacted | TurnContext | WorldState | EventMsg (same file :3186–3199). TokenCountEvent contains info (input/cached/output of total/last_token_usage) + rate_limits (same file :2140–2143).
  • CodexMonitor (src-tauri/src/shared/local_usage_core.rs, 840 lines): spawn_blocking parses JSONL—session_meta/turn_context cwd filters the workspace (:220–229), take model (:232–237), token_count delta-dedup counts input/cached/output (:281–380), agent_message/agent_reasoning count duration (:260–279), response_item assistant counts runs (:383–409).
  • Took: token usage, cache, model, cwd, active duration/count. Missed: rate_limits (credits/plan_type—the most valuable field already on disk officially), originator/source (CLI vs VSCode vs appServer), sub-agent lineage, exec_command/file_change call counts.

Taste observation: using already-on-disk official data for local stats (no API call, no hooks) is the smart move of “same frequency as the official data format”; but 840 lines of handwritten parse also expose “enough is enough”—the credits stats that should actually be done were not read.

4.5 Three small cross-checks: config.toml / auth.json / rules

  • [features] (Codex codex-rs/config/src/config_toml.rs:455 → FeaturesToml in codex-rs/features/src/lib.rs:656): keys defined in the same file steer:1286 (always on), collaboration_modes:1340 (always on), unified_exec:856, apps:1106 (needs chatgpt auth). personality top-level enum None|Friendly|Pragmatic (Codex codex-rs/protocol/src/config_types.rs:311–317). The 5 fields CodexMonitor syncs match official key names exactly, and it correctly rejects the deprecated collab key (CodexMonitor src-tauri/src/codex/config.rs:50–53).
  • auth.json: {OPENAI_API_KEY, tokens:{id_token, access_token, refresh_token, account_id}, last_refresh}; idToken JWT payload contains chatgpt_plan_type/email. CodexMonitor src-tauri/src/shared/account.rs:63–99 URL_SAFE_NO_PAD decode → take chatgpt_plan_type/email corresponds exactly, and is the fallback when account/read fails (account_read_core in src-tauri/src/shared/codex_core.rs:627–650).
  • prefix_rule is an execpolicy Starlark builtin (defined in execpolicy/src/parser.rs; config/src/requirements_exec_policy.rs:51–58 is its mirrored structure in requirements.toml, and the source comments say so), decision="allow" lets the agent skip approval. CodexMonitor src-tauri/src/rules.rs:97–100 appends prefix_rule(pattern=[...], decision="allow") to ~/.codex/rules/default.rules—semantics correct.

These four cross-checks say one thing: when Dimillian wrote CodexMonitor he aligned field-by-field with Codex’s official source and config formats—protocol method names, handshake params, config keys, auth JWT, rules syntax, all aligned. This is the integration taste of “stay in contract-level sync with upstream.”


Part 6

Chapter 5 · Thread model and event-driven state machine

5.1 Thread lifecycle (src-tauri/src/shared/codex_core.rs)

Operation Codex method Key params
New thread/start {cwd, approvalPolicy:"on-request"}
List thread/list sourceKinds exclude generic subAgent (prevent memory-consolidation leak) paging
Resume thread/resume called as soon as the frontend selects; refresh messages from disk
fork/archive/compact thread/fork/archive/compact/start —
Rename thread/name/set —
Send turn/start input items (text/image/localImage/mention), accessMode→sandboxPolicy (dangerFullAccess/readOnly/workspaceWrite) + approvalPolicy
Interject turn/steer with expectedTurnId
Interrupt turn/interrupt —

5.2 The backend does not maintain a state machine

Key design: CodexMonitor’s Rust side does not maintain a thread state machine—state is an event-driven frontend reducer. thread/status/changed, turn/started/completed, item/started/completed, approval requests (*requestApproval suffix) are routed by METHODS_ROUTED_IN_USE_APP_SERVER_EVENTS in useAppServerEvents.ts into useThreadsReducer slices; threadNormalize.ts is the camelCase/snake_case fallback. running/idle/needs_approval are entirely derived on the frontend from events.

Taste observation: this is the corollary of “the protocol is the truth”—since app-server will push authoritative events, the backend does not need a mirrored state (one less layer of state-sync bugs). Contrast kanban’s backend state machine (session-state-machine.ts 78 lines): CodexMonitor put state transition into the frontend reducer (1179 lines).


Part 7

Chapter 6 · Rust shared layer: domain core shared by app and daemon

src-tauri/src/shared/ has 17 modules / 31 files / 11,197 lines—a pure-logic layer, no Tauri/IPC dependency, only types, codex::config/home, backend::app_server::WorkspaceSession, storage. App and daemon compile into the same binary and each call the same *_core functions.

6.1 worktree core (workspaces_core/worktree.rs, 642 lines)

add_worktree_core is a highly generic function (9 closures injected: FSpawn/FGit/FSanitize etc.). The core is simply git worktree add, three branch cases: branch already exists → worktree add <path> <branch>; not local but has a remote tracking → worktree add -b <branch> <path> <remote_ref>; else worktree add -b <branch> <path>.

  • Root-directory priority: workspace-level worktrees_folder > global global_worktrees_folder/<parent_id> > data_dir/worktrees/<parent_id>.
  • After create, generate WorkspaceEntry{kind: Worktree, parent_id, worktree:{branch}}; can copy_agents_md inherit the parent repo’s agents.md; a marker file records whether a one-shot worktree_setup_script has already run.
  • remove_worktree_core: kill session → git worktree remove --force → git worktree prune --expire now.
  • rename_worktree_core: git branch -m + git worktree move (with full rollback).

6.2 git / GitHub (git_ui_core/*)

Hybrid implementation (a taste highlight of this analysis): - Reads go through libgit2 (git2 crate): status/diff (diff.rs StatusOptions/Status bit-enum mapping A/M/D/R/T, DiffOptions patch, 10MB image base64, 2MB text cap, has_ignored_parent_directory); log (log.rs revwalk + graph_ahead_behind). Ignored-file recognition is the only place that uses git check-ignore --stdin -z CLI. - Writes go through git CLI (commands.rs, tokio_command): commit/push/pull (--autostash→--no-rebase fallback chain) / fetch/stage/revert/checkout/git init. - GitHub all through gh CLI (github.rs): gh issue list --json, gh pr list --json, gh pr diff --color never (parse_pr_diff text parser splits files), gh api /repos/.../issues/N/comments --jq, gh pr checkout, gh repo create.

6.3 Account and usage

  • account.rs (221 lines): account/rateLimits/read + account/read; on failure fall back to parsing $CODEX_HOME/auth.json → idToken → JWT → chatgpt_plan_type/email (Chapter 4 already cross-checked the official format).
  • local_usage_core.rs (840 lines): scan $CODEX_HOME/sessions/<YYYY-MM-DD>/*.jsonl (including each workspace’s independent codex_home), aggregate daily input/cached/output tokens, agent_time_ms, agent_runs, compute last7/last30, cache hit rate, peak day, top4 models; MAX_ACTIVITY_GAP_MS=2min merges runs.

6.4 Multi-agent config (agents_config_core.rs, 1026 lines)

Read/write [features] multi_agent, [agents] max_threads/max_depth (defaults 6/1, clamped 1-12/1-4); agent definitions are [agents.<name>] tables; managed agent config lands in $CODEX_HOME/agents/<name>.toml. Full CRUD (create generates from a template, update rename also changes the filename, delete), including path-traversal protection and config rollback.

6.5 Design taste

  • Why shared is independent: app and daemon dual-process share the same crate’s pure logic; all state is injected via Mutex<HashMap>; side effects (git/fs/RPC) via closures or type injection—naturally unit-testable (tests.rs 437 lines).
  • RPC reuse: git_rpc.rs/workspace_rpc.rs only declare method-name constants + DTOs (to_params/from_params); implementation is all in *_core—the daemon’s handler and the app’s client share the same signatures; one thin map is enough.
  • Hybrid git strategy: read with libgit2 (fast, in-memory, type-safe), write with CLI (behavior-faithful), worktree orchestration also with CLI—pragmatic, not dogmatic.

Part 8

Chapter 7 · worktree and workspace: first-class objects

7.1 Workspace model (workspaces_core)

WorkspaceEntry: id/name/path/kind/parent_id/worktree/settings. kind has ordinary repo, worktree, clone. Persisted in workspaces.json (app data directory).

  • add_workspace: local path or git clone.
  • add_worktree: build a worktree from the parent repo (Chapter 6.1).
  • add_clone: URL clone a new workspace.
  • Each workspace can have independent settings (codexBin, codex_home, worktrees_folder).

7.2 worktree contrast with kanban

kanban CodexMonitor
Grain one worktree per card (task-level) one workspace can build many worktrees (agent-level)
Identity worktree is the card’s “shadow” worktree is a kind of workspace (first-class object)
Lifecycle trash the card and it is deleted CRUD, rename (with rollback), change upstream
Purpose resource isolation for parallel tasks independent branch + independent codex home for the agent

Taste observation: kanban treats worktree as a by-product of task execution; CodexMonitor treats worktree as a manageable entity (WorktreeEntry can rename/pin/archive)—it is a “workspace-management product,” not a “task board”; product intent decided the data model.


Part 9

Chapter 8 · git / GitHub: libgit2 read + CLI write + gh integration

(The core mechanism is already covered in Chapter 6.2; this chapter supplements the frontend face.)

  • diff panel: render with @pierre/diffs + homemade diffsWorker (WorkerPool) + @tanstack/react-virtual virtual list; theme in design-system/diffViewerTheme.
  • stage/unstage/revert: stage_git_file / unstage_git_file / revert_git_file (useGitActions).
  • Inline comments: GitHub PR review—usePullRequestLineSelection selects lines, get_github_pull_request_comments fetches, submit as an independent review thread.
  • “Ask PR”: when a PR is selected, prefillDraft a PR summary into composer; runPullRequestReview: ensure the workspace is connected → reuse the current thread or create new → buildPullRequestReviewPrompt (PR metadata + diffs ≤6 files×40 lines + comments ≤8 + intent instruction, safety fence) → sendUserMessageToThread into a new agent thread and activate.

Taste observation: “Ask PR” is the clever stroke of “hand the GitHub flow to the agent”—the agent can read PR context and work by intent. That is CodexMonitor’s differentiated value against a traditional git GUI.


Part 10

Chapter 9 · Remote daemon and iOS: one core, two shapes

src-tauri/src/bin/ is 4,902 lines in all: daemon entry (1966) + daemonctl (1414) + rpc domain handling (codex 510 / workspace 273 / rpc.rs 201 / git 196 / prompts 149 / daemon 60) + transport (103) + dispatcher (30).

9.1 Daemon architecture

  • Protocol: TCP + newline-delimited JSON-RPC (JSONL, one JSON object per line), not HTTP. Default 127.0.0.1:4732 (daemon internal) / 0.0.0.0:4732 (daemonctl external).
  • Frame format: request {"id":n,"method","params"}; response {"id":n,"result"|"error"}; events have no id: {"method":"app-server-event","params":...} (broadcast::channel(2048) pushes to all authenticated connections).
  • Auth: auth handshake. Token from --token or CODEX_MONITOR_DAEMON_TOKEN; when unauthenticated, non-auth requests all return unauthorized; --insecure-no-auth skips. Concurrency limit: Semaphore(32) per connection.

9.2 RPC dispatch and reuse

dispatcher.rs try_handle in order: daemon → workspace → codex → git → prompts. Reuse is extremely high: the daemon entry uses #[path=] to include the main-program modules (backend/shared/files/workspaces_core etc.) directly; DaemonState methods almost 1:1 wrap xxx_core—the same batch of core as local Tauri commands. The only difference is AppState → DaemonState + DaemonEventSink; menu_set_accelerators degrades to no-op (“remote parity”).

9.3 daemonctl lifecycle control (most of the 1414 lines)

  • status: probe_daemon three-step state machine—ping (if unauthenticated, receive unauthorized) → auth → daemon_info; produce NotReachable / Running{auth_ok} / NotDaemon.
  • start: first probe; if running but identity/version/mode mismatch (should_restart_daemon) → RPC daemon_shutdown graceful stop → poll 20×100ms → if still not gone, lsof -iTCP:<port> parse PID → SIGTERM → SIGKILL. Force-kill is allowed only when auth passed and the process name is codex-monitor-daemon (can_force_stop_daemon).
  • No log file, no PID file—PID is entirely reverse-looked-up from the port (minimalism, but depends on lsof).
  • command-preview: output a full start-command template (token placeholder <remote-backend-token>).

9.4 Why iOS needs remote mode

iOS has none of the desktop resources: cannot spawn a codex app-server subprocess, cannot run git, no local workspace filesystem—so it is a thin client connecting to the desktop daemon. terminal_mobile.rs and dictation/stub.rs all commands directly return "Terminal/Dictation is not available on mobile builds." (terminal needs a local PTY; dictation needs a local Whisper model).

9.5 Tailscale integration

tailscale_status: scan candidate binaries (PATH → homebrew → Tailscale.app bundle) → tailscale version verify (looks_like_tailscale_version against GUI error output) → tailscale status --json. macOS uses launchctl asuser <uid> to run as the GUI user. suggested_remote_host parses DNSName/TailscaleIPs/CurrentTailnet to produce macbook.tailnet.ts.net:4732. The iOS side fills the suggested host + token into Settings > Server.

9.6 Taste observation

~5000 lines looks like a lot; the core logic is all a reused thin shell. What is genuinely new is three layers of glue: #[path] module splicing, JSON param-parse boilerplate, daemonctl lifecycle management. Why it is worth it: mobile cannot run codex locally; without this you would rewrite the entire backend for iOS—“one core + one protocol” serving desktop and iOS at once, behavior naturally consistent. The cost: the desktop side gains a class of “process + port + version drift” ops complexity; TCP plaintext token depends on Tailscale’s private network rather than its own encryption.


Part 11

Chapter 10 · Frontend architecture: feature-sliced + zero state libraries

10.1 Scale and organization

src/ has 603 ts/tsx/css files, about 127K lines; src/features/ 496 files / 103K lines, 25 features, using feature-sliced components/hooks/utils subdirectory convention:

feature files/lines Duty
app 141 / 27,205 startup, layout, sidebar, controllers (heaviest)
threads 70 / 20,636 chat-thread state machine
settings 42 / 12,111 settings page
git 59 / 9,099 diff/log/branch/GitHub
workspaces 36 / 6,931 workspace management
composer 32 / 6,270 input box + autocomplete

10.2 Entry and routing

  • No router library, purely state-driven. App.tsx (64 lines) uses useWindowLabel() (read Tauri window label) to split—about window lazy-loads <AboutView>, else <MainApp />.
  • main.tsx: Sentry init + app_open count + mobile gesture/viewport compatibility.
  • MainApp.tsx (1882 lines) giant composition root: no Context; ~70 hooks’ return objects are passed as props layer by layer. View switching relies on activeTab state (home|projects|codex|git|log) + centerMode (chat/diff) + showHome.

10.3 State-management choice (the taste core)

Zero external state libraries: no redux/zustand/jotai/mobx (grep-verified). Pure useReducer + useState + useRef, explicit props drill-down.

  • threadReducer (1179 lines, 6 slices, no third party): threadLifecycleSlice (493 lines) / threadItemsSlice (333 lines) / threadConfigSlice / threadQueueSlice / threadSnapshotSlice / common. About 40 actions (ensureThread/upsertItem/appendAgentDelta/appendToolOutput/appendReasoningSummary/markProcessing…).
  • useThreads.ts (938 lines, main aggregator) internally uses ref-mirrored state (itemsByThreadRef etc.) for event callbacks to read synchronously—solving the classic “closure captured stale state” problem.
  • Bridge from events to reducer: useThreadEventHandlers → useThreadTurnEvents / useThreadItemEvents / useThreadApprovalEvents → each callback dispatch.

Why no external store (taste judgment): 1. A single-workspace desktop app; the state graph converges to “one activeWorkspace + a pile of threads”; a reducer is enough; 2. Event-dense streaming updates (delta-level) need ref mirror + dispatch; an external store’s benefit is small; 3. feature-sliced itself provides module boundaries, avoiding global-store coupling; 4. The cost is MainApp’s giant props passing and a lot of useMemo/useCallback glue.

10.4 Component library and styles

No Tailwind, no Radix, no shadcn. Dependencies only lucide-react (icons), react-markdown+remark-gfm, prismjs, @xterm/xterm, @tanstack/react-virtual, Sentry. UI is all handwritten: CSS-variable tokens (ds-tokens.css) + semantic class names; components are ordinary function components assembled into ModalShell/ToastShell/PanelShell.

10.5 Frontend contrast with kanban

The architecture is isomorphic (backend process + Tauri/browser frontend + event-stream fanout + reducer-driven UI), but CodexMonitor’s state management is clearly heavier—reducer 1179 lines/40 actions, event-parse hooks thousands of lines, ref-mirror sync, multi-workspace keyed state. The root of the difference: kanban card state is a coarse-grained snapshot (fetch then wholesale replace); CodexMonitor has to carry streaming delta rendering (agentMessage append per delta, tool output stream, reasoning stream) + approval queue + token/rate-limit live panel + subagent cascade archive. But the architecture layer is actually lighter: no store middleware, no selector library, no async actions; complexity is suppressed by feature-sliced directories and a huge unit-test count.


Part 12

Chapter 11 · Frontend features: Composer / dictation / diff / usage

11.1 Composer autocomplete (fully homemade)

useComposerAutocompleteState.ts assembles three trigger groups: $ (skills+apps), / (8 hardcoded slash commands + prompts), @ (files). All data goes through RPC: skills→skills_list, apps→apps_list, prompts→prompts_list (all forwarded via daemon to Codex app-server); files→list_workspace_files (Rust scans disk directly, cap 500). Trigger decision / subsequence scoring / highlight are all handwritten; UI is ComposerSuggestionsPopover positioned above the textarea; no third-party autocomplete library. Selecting $ an app generates an app://id mention binding; /prompts: supports findNextPromptArgCursor Tab-jump of argument slots.

11.2 Queue vs Steer (a model of interaction polish)

ComposerSendIntent = "default"|"queue"|"steer". resolveSendMessageOptions: canSteerCurrentTurn = isProcessing && steerEnabled && activeTurnId. steer → RPC turn_steer; queue/unsupported → send_user_message (turn/start; message enqueue is managed by app-side ComposerQueue). Default mode from setting followUpMessageBehavior; Shift+Cmd/Ctrl+Enter sends the opposite mode (oppositeSubmitIntent). Failures distinguish steer_failed and handle stale-steer errors; runtime button copy switches between Steer/Queue.

11.3 Image attachments

Three entries all land as a local-path array (useComposerImages stored by thread draftKey): picker uses tauri open; drag-drop uses window-level onDragDropEvent + local DOM drop (web side FileReader to data URL); paste by useComposerImageDrop.handlePaste reading clipboardData. What is sent to Codex is a data-URL string array: normalizeImagesForRpc in remote/mobile mode calls Rust read_image_as_data_url one by one.

11.4 Dictation (Whisper)

Rust side whisper-rs local inference; real.rs defines ggml-tiny/base/small/medium/large-v3 download from HuggingFace; download/cancel/remove/status go through daemon RPC; progress events push to the frontend. waveform is frontend-drawn 36 dynamic bars (DictationWaveform, subscribe level events).

11.5 Usage and notifications

  • Usage: Home usage = local_usage_snapshot(30 days) (Rust reads Codex home data, useLocalUsage 5-minute poll); sidebar credits meter = account/rateLimits/updated AppServerEvent → RateLimitSnapshot → getUsageLabels.
  • Notifications: purely frontend-driven, subscribe AppServerEvents (turn complete / approval request); system notifications use @tauri-apps/plugin-notification; Rust only send_notification_fallback—on macOS debug, osascript AppleScript (not notify_rust). Sound uses local mp3 assets + HTMLAudio. All have min-interval / window-focus suppression.

11.6 Taste observation

Bias toward homemade: autocomplete engine, prompt history, rank scoring, waveform, usage view model all handwritten; only the heavy work is given to libraries (@pierre/diffs, xterm, whisper-rs, tauri plugins, react-virtual). Interaction polish: stale-steer tolerance, drag-position devicePixelRatio normalize, code-fence expand, /prompts: Tab-jump args, hold-to-talk, follow-up hint bar dynamically explaining Steer availability, mobile touch layout branch, every RPC writes a DebugEntry for tracing.


Part 13

Chapter 12 · Config tripartite: settings.json / workspaces.json / config.toml

File Contents Ownership
settings.json (app data directory) AppSettings: codexBin/codexArgs, backendMode, remote backend host/token, shortcuts, defaultAccessMode, uiScale/theme, global_worktrees_folder CodexMonitor itself
workspaces.json WorkspaceEntry list (id/name/path/kind/worktree/settings) CodexMonitor itself
config.toml ($CODEX_HOME) Codex itself: features (steer/collaboration_modes/unified_exec/apps), personality, model codex is the source of truth

Sync strategy (settings_core.rs, 70 lines + codex/config.rs): 5 fields synced with config.toml (collaboration_modes_enabled/steer_enabled/unified_exec_enabled/experimental_apps_enabled + personality) are bidirectionally synced in both read and write—get_app_settings_core overwrites in-memory values from config.toml (codex is authoritative); update_app_settings_core writes back to config.toml (via toml_edit in config_toml_core.rs) + writes local storage.

Taste observation: respect “Codex’s config belongs to Codex”—the App only syncs the few switches its own UI exposes, does not full-mirror, avoiding two configs fighting. Chapter 4 already confirmed key names match official exactly.


Part 14

Chapter 13 · Engineering and tests: AGENTS.md layered discipline + unit-test density

13.1 AGENTS.md (6KB of tribal knowledge)

  • Domain logic → shared/*; commands → thin adapter layer; frontend IPC only through services/tauri.ts; event fanout only in services/events.ts.
  • Frontend feature-sliced: src/features/{app,threads,...}, orchestration in hooks/ and bootstrap/.
  • Daemon and app reuse shared core; method names/payloads align (remote parity).
  • docs/codebase-map.md: task-oriented file finding (“if you need X, edit Y”)—this is a navigation document written for agents.

13.2 Test density

  • Rust side: git_ui_core/tests.rs 437 lines, tray.rs 6 unit tests, files io.rs 6 symlink-escape tests, worktree/workspaces each have tests—the pure-logic layer is testable because of closure injection.
  • Frontend: every hook has *.test.ts(x) (useSettingsDefaultModels, useThreads, composer, etc.).
  • Integration: main.test.tsx, smoke, etc.

13.3 Engineering-culture contrast with Codex / kanban

  • codex: bazel monorepo, 126 crates, AGENTS.md long and complete (including app-server API best practices).
  • kanban: GritQL static checks + husky + dogfood (use kanban to develop kanban).
  • CodexMonitor: layered discipline written into AGENTS.md (agent-readable, agent-executable), docs/codebase-map.md navigation, dual-platform (desktop + daemon) shared-core architecture tests, .codex/ directory (the project itself is developed with Codex).

Commonality: all three projects are shaped by an agent-first development paradigm—AGENTS.md is not documentation but “an engineering contract for the agent”; the codebase structure has to serve the agent’s navigability.


Part 15

Chapter 14 · Development taste: Dimillian’s ten choices

This chapter is the center of gravity of the lecture. Dimillian (Thomas Ricouard) is known for SwiftUI open-source components (ACarousel etc.), yet for a “tool that orchestrates AI agents” he chose Tauri (Rust + Web frontend). The choice itself leaks a lot of information. Each of the ten choices below is annotated with source evidence.

Choice 1: connect over the official protocol, not black-box scrape

CodexMonitor did not take the “spawn Codex’s TUI then parse the screen” path (that is what kanban does to Codex). It uses codex app-server + stdio JSON-RPC. This shows respect for the “upstream ecology”: the official side provided a socket (the app-server protocol is designed for IDEs), so use the socket; rather bind to the Codex ecology than do a fragile screen adapter. Chapter 4 confirmed all 22 methods are official protocol methods, handshake params item-by-item match, config key names consistent, auth JWT parse corresponds, rules syntax correct—this is “contract-level alignment” code, behind it the patience of reading official source field by field.

Choice 2: shared-core + thin commands + closure injection

Domain logic (git, worktree, Codex, settings, usage) is all collected into the shared/* pure-logic layer; the Tauri command layer is only a thin shell of “local or forward to daemon”; platform side effects (git commands, fs, spawn) are all injected as closures. This is a very Rust architectural view: typed dependency injection + pure functions that are testable. Effect: app and daemon dual-process share one core, zero logic drift; unit tests can construct in-memory state to test the core (git_ui_core/tests.rs 437 lines).

Choice 3: read with libgit2, write with CLI, GitHub with gh

Not dogmatic—reads want “fast and type-safe” (libgit2 pure memory); writes want “behavior fully faithful to git” (CLI); GitHub directly reuses gh (do not rebuild an API client). Pick the most suitable tool for each scene, rather than a unified abstraction. This is seasoned engineering judgment: libgit2’s write path has many pits (config differences, hooks); the read path is stable.

Choice 4: worktree is a first-class object, not a task by-product

Contrast kanban (worktree is the card’s shadow; trash and it is gone): CodexMonitor made worktree a complete entity of WorkspaceEntry{kind: Worktree}: CRUD, rename (with rollback), change upstream, inherit agents.md. Product intent decides the data model—it is a “workspace-management product”; worktree is an independent execution environment for the agent (independent branch + independent codex home).

Choice 5: frontend zero state libraries + feature-sliced

No redux/zustand; pure useReducer + ref mirror + explicit props drill-down; no Tailwind/Radix; CSS-variable tokens + handwritten components; UI side uses only 6 families of libraries. “Complexity is suppressed by directory structure and discipline, not by libraries.” Streaming delta rendering needs ref mirror + dispatch (useThreads.ts 938 lines); an external store’s benefit here is small; feature-sliced’s 25 feature directories provide a strong enough module boundary.

Choice 6: if you can write it yourself, write it yourself

Autocomplete engine (trigger decision / subsequence scoring / highlight), prompt history, rank scoring, waveform (36 dynamic bars), usage aggregation (840-line JSONL parse), frontmatter parse, rules dedup, lock files—all handwritten; only the heavy work is given to libraries (xterm, whisper-rs, @pierre/diffs, react-virtual). Dependency minimalism: 600+ frontend files, only 19 runtime deps, of which UI-related only 6 families.

Choice 7: macOS first, but not only macOS

Tray macOS only (tray.rs 701 lines; non-macOS empty implementation), window appearance uses objc2-app-kit calling NSAppearance directly, icon resolve calls defaults/sips—native platform APIs called directly, not abstracted; meanwhile Windows opt-in (separate tauri config to avoid macOS-only window effects), iOS remote mode. This is the strategy of “take the primary platform to the extreme; other platforms follow pragmatically.”

Choice 8: iOS remote daemon—one core, two shapes

iOS cannot run Codex locally, so start a desktop daemon (TCP JSONL JSON-RPC) and let the iPhone use Tailscale. #[path] include reuses main-program modules; daemonctl uses port reverse-lookup of PID + version compare + force-kill safety gate to manage lifecycle. This is “architectural investment for multi-end shapes”—one core + one protocol, desktop/iOS behavior naturally consistent; the cost is daemon process/port/version-drift ops complexity (most of daemonctl’s 1414 lines is preventing “killing the wrong process”).

Choice 9: agent-first development

AGENTS.md is “an engineering contract for the agent” (layering rules, remote parity, feature-sliced conventions); docs/codebase-map.md is “if you need X, edit Y” task navigation; the repo ships .codex/ config—this project itself is developed with Codex (isomorphic with kanban’s dogfood). Codebase structure serves the agent’s navigability; this is a common trait of the 2025-2026 generation of AI-native project clusters.

Choice 10: defensive programming + interaction polish

canonicalize + symlink-escape checks (6 tests), 400KB read truncation, strict UTF-8, Arc::ptr_eq against races, stale-steer tolerance, disconnect DISCONNECTED_MESSAGE—engineering conservatism; meanwhile Shift+Cmd+Enter reverses Queue/Steer, hold-to-talk waveform, /prompts: Tab-jump args, follow-up hint bar dynamically explaining—product aggressiveness. Conservative engineering + aggressive interaction is the typical temperament of a mature independent developer.

Taste in one sentence

With the fewest dependencies and the cleanest architectural layering, put the official protocol to work, and make a “command stand for Codex”—restrained and conservative in engineering, polished in product, and architecturally investing early for multi-end shapes.


Part 16

Chapter 15 · Summary: three most distinctive designs and their trade-offs

Three things (if you remember only three designs)

1. Official app-server protocol direct connect, not PTY screen scrape This is the most essential split between CodexMonitor and kanban. kanban has to be compatible with 7 agents, so it must black-box-adapt; CodexMonitor only serves Codex, so it takes the official socket (LSP-style handshake + typed events + two-way control). Trade-off: deep not wide—give up “any agent can plug in,” in exchange for “every layer of Codex capability is in hand” (steer interrupt, approval response, sandboxPolicy passed precisely, account/rateLimits live).

2. shared-core one core, desktop/iOS two shapes Domain logic collected into a pure-logic layer (closures inject side effects); app and remote daemon use #[path] include to share the same batch of *_core. This is architectural investment for iOS remote mode—without it you would rewrite the entire backend for mobile. The cost is daemon ops complexity (process/port/version drift).

3. Frontend zero state libraries + streaming-delta state machine No redux/zustand/Tailwind/Radix; pure useReducer + ref mirror + handwritten CSS tokens; 600+ frontend files, 19 runtime deps (UI libraries only 6 families). Event-driven state machine (app-server events → hub fanout → reducer slice) carries streaming render. Trade-off: MainApp’s giant props glue in exchange for zero middleware complexity—a wise choice for a single-workspace desktop app.

One-page contrast with Codex / kanban

Dimension Codex (official) kanban CodexMonitor
Role the Agent itself multi-agent orchestration board Codex-dedicated desktop command stand
Integration path — PTY + hooks (black box) app-server protocol (official socket)
State model rollout file assets card 4-column state machine workspace × thread event stream
worktree none one worktree per card worktree as first-class workspace
Frontend TUI React board React feature-sliced
Philosophy local-first CLI browser control plane desktop + iOS remote

Who it is for / who it is not for

  • For: heavy Codex users (desktop experience far better than the terminal), developers who want multi-agent parallel with worktree isolation, iOS + Tailscale mobile-office scenes, readers studying the “official-protocol integration” paradigm.
  • Not for: non-Codex users (bound ecology), teams that need browser access (no web edition), light users (installing a Rust toolchain + Codex + gh is not a low bar).
  • Connection to this series: it is a “deeper practical confirmation” of the OpenAI Codex source analysis (protocol, rollout, config, auth all match), and the “official-protocol contrast group” of the Kanban source analysis.

🔍 Source pointers (go check the source)

Want to know Look here
app-server spawn and handshake src-tauri/src/backend/app_server.rs (L749 spawn, :419 handshake)
Full set of Codex method calls src-tauri/src/shared/codex_core.rs (1033 lines)
Event classify-and-route src-tauri/src/backend/app_server.rs (extract_related_thread_ids / resolve_workspace_for_cwd)
shared domain core src-tauri/src/shared/ (31 files 11,197 lines)
worktree core src-tauri/src/shared/workspaces_core/worktree.rs (642 lines)
git hybrid strategy src-tauri/src/shared/git_ui_core/ (libgit2 read + CLI write)
rollout usage stats src-tauri/src/shared/local_usage_core.rs (840 lines)
Remote daemon src-tauri/src/bin/codex_monitor_daemon.rs + codex_monitor_daemonctl.rs
Frontend event hub src/services/events.ts (createEventHub lazy subscribe)
Frontend state machine src/features/threads/hooks/threadReducer/ (1179 lines 6 slices)
Composer autocomplete src/features/composer/ (useComposerAutocompleteState.ts)
Codex protocol definition 参考项目/codex/codex-rs/app-server-protocol/src/protocol/common.rs
Codex handshake implementation 参考项目/codex/codex-rs/app-server/src/request_processors/initialize_processor.rs (:70–101)
Codex best practices 参考项目/codex/AGENTS.md (:104 app-server breaking-change surface)
↑