← Corpus / conversational-ui-and-native-shells / profile
open-vibe
- Path
- profiles/Profile__Open-Vibe.md
Open Vibe — Profile
A profile of Open Vibe as it lives in this study (studies/conversational-ui-and-native-shells/open-vibe/). LICENSE:1-3 confirms MIT, “Copyright (c) 2026 Thomas Ricouard / Copyright (c) 2026 citizenl”. Read alongside Profile__Routa.md (genuine per-session ACP process spawn, provider chosen per call) and Profile__Dive.md (one shared Python subprocess for the whole app) — Open Vibe sits between them: it genuinely spawns one real OS child process per workspace, with real per-workspace argv/env/cwd scoping, but the thing being spawned is always the same single binary (codex) — there is no adapter registry, no provider parameter, no swappable backend. It is a process-per-workspace supervisor around exactly one CLI, not a process-level adapter swap.
TL;DR
The repo’s own README says it plainly at README.md:35: “Start one codex app-server per workspace; list/resume/archive threads.” That is not marketing rounding — it is exactly what the code does. spawn_workspace_session (src-tauri/src/backend/app_server.rs:290-464) builds a tokio::process::Command, sets command.current_dir(&entry.path) (line 307), appends the literal argument command.arg("app-server") (line 308), sets CODEX_HOME from a per-workspace resolver if present (lines 309-311), and pipes stdin/stdout/stderr (lines 312-314) before command.spawn() (line 316). The resulting WorkspaceSession (app_server.rs:60-68) holds the live Child, its ChildStdin, and a pending: HashMap<u64, oneshot::Sender<Value>> for in-flight JSON-RPC-shaped requests keyed by an auto-incrementing next_id — a real line-delimited JSON-RPC-over-stdio protocol, confirmed by send_request/send_notification/send_response (app_server.rs:81-106) and the "initialize"/"initialized" handshake at lines 428-452.
Workspace scoping is real, not a config-row convenience: connect_workspace_inner (src-tauri/src/workspaces/commands.rs:1471-1520) looks up the WorkspaceEntry by id, resolves that workspace’s own codex_bin, codex_args (resolve_workspace_codex_args, commands.rs, worktree-inherits-from-parent then falls back to global app settings), and codex_home (resolve_workspace_codex_home, codex_home.rs:6-35, same inheritance chain plus a legacy .codexmonitor directory fallback), then spawns a session and inserts it into state.sessions: Mutex<HashMap<String, Arc<WorkspaceSession>>> (state.rs:15) keyed by workspace id. If a session already exists for that id and force_respawn isn’t set, connect_workspace_inner short-circuits and returns the existing one (commands.rs:1477-1481); if a stale session is being replaced, the old child process is explicitly killed (commands.rs:1515-1518). A dedicated GLOBAL_WORKSPACE_ID = "__global__" session exists for cross-workspace operations like listing all threads (codex.rs:31, 154-201, 415-449).
Verdict on the research summary’s claim: the “one codex app-server process per workspace over JSON-RPC/stdio” claim is fully confirmed and independently verified in app_server.rs and workspaces/commands.rs — this part is real, not shallow. But calling this a “genuine process-level adapter swap” (in the sense Routa’s AcpManager earns that label) overstates it: there is only one adapter. build_codex_command_with_bin (app_server.rs:173-196) always resolves to a binary literally named codex unless the user points codex_bin at a differently-named executable on disk — there is no provider parameter, no registry of alternative coding-agent CLIs (no Claude Code SDK, OpenCode, or Gemini CLI adapter anywhere in src-tauri/src/), and no protocol-normalization layer comparable to Routa’s ACP. What is swappable per-workspace is argv (codex_args), environment (CODEX_HOME), and cwd (entry.path) for the same binary — configuration-of-invocation, not adapter identity.
The workspace-to-process mapping — confirmed, with worktree inheritance
- One
HashMapentry, one live child, keyed by workspace id.AppState.sessions(state.rs:15) isMutex<HashMap<String, Arc<crate::codex::WorkspaceSession>>>— structurally identical in shape to what a “per-workspace process registry” should look like, and every read site (workspaces/commands.rs:125,135-136,271,332,468,584,708,733,776,916-955,1254-1449) treatscontains_key/get/removeon this map as the connectivity source of truth for a given workspace id. - Worktrees are a distinct
WorkspaceKindthat inherit config from their parent, not siblings that get their own default.WorkspaceKind::{Main, Worktree, Nanobot}(types.rs:223-227);resolve_workspace_codex_home(codex_home.rs:6-35) checks the worktree’s own override first, then falls back toparent_entry.settings.codex_home, then a legacyparent.path/.codexmonitordirectory, then the globalCODEX_HOME/~/.codexdefault — proven by three passing unit tests atcodex_home.rs:222-249(worktree_inherits_parent_codex_home_override,worktree_relative_override_uses_parent_path).resolve_workspace_codex_args(workspaces/commands.rs, mirrored logic) does the same for CLI args. - A
Nanobotworkspace kind exists as a third case (types.rs:226,240-242,is_nanobot()) — evidence the workspace concept is reused for the DingTalk/QQ bot bridge, not solely for git-backed coding workspaces; this is a routing distinction inside the samesessionsmap, not a different process-spawn mechanism. - A remote-backend escape hatch exists that is explicitly not process-per-workspace.
remote_backend.rs:53-63(RemoteBackend/RemoteBackendInner) opens oneTcpStreamto127.0.0.1:4732(DEFAULT_REMOTE_HOST, line 14) and multiplexes all calls (RemoteBackend::call, lines 65-80) through one connection with its ownpendingmap — every workspace command checksremote_backend::is_remote_mode(&state)first (e.g.codex.rs:158,337,365,393,419) and if remote mode is active, routes through this one shared TCP connection instead of spawning anything locally. This is the repo’s own version of Dive’s “one shared subprocess” pattern, coexisting with the per-workspace-process pattern as an alternate transport, not a fallback within it.
The CLI-invocation layer — one binary, real path/env resolution, zero adapter registry
build_codex_command_with_bin(app_server.rs:173-196) resolvescodex_bin(per-workspace override, falling back to a global default, falling back to the literal string"codex"), normalizes Windows path quoting, and decides whether to shell out viacmd /Cfor.cmd/.batscripts (should_run_via_cmd_on_windows, lines 221-238) — genuine cross-platform invocation hardening, but always for the same logical program.build_codex_path_env(app_server.rs:109-171) constructs a syntheticPATHby unioning the inherited environmentPATHwith platform-specific extra search locations (/opt/homebrew/bin,~/.local/bin,~/.local/share/mise/shims,~/.cargo/bin,~/.bun/bin, every~/.nvm/versions/node/*/bin, or%APPDATA%\npm/%USERPROFILE%\.cargo\binon Windows) plus the parent directory of an explicitcodex_binoverride — solving the “GUI apps don’t inherit shell rc-file PATH” problem that Tauri/Electron apps chronically hit, not multi-provider routing.check_codex_installation(app_server.rs:240-288) runscodex --versionwith a 5-second timeout before ever spawningapp-server, surfacing “Codex CLI not found” / “Codex CLI failed to start” errors with actionable remediation text — defensive plumbing around one CLI’s presence, not a provider-selection decision point.- No adapter registry, no provider enum, anywhere searched.
grepacrosssrc-tauri/src/*.rsforclaude-code,opencode,gemini-cli,amp, or any second agent-CLI binary name returns nothing; the only other “provider” concept in the codebase isnanobot’sLLMProvidertrait (nanobot_bridge_daemon.rs:12,68,124) — that’s the messaging-bot layer (DingTalk/QQ) picking an LLM to answer bridge chat, wired to the same already-running codex session via a request/response bridge (OpenVibeProvider, lines 68-124), not a second coding-agent backend competing with codex for workspace sessions.
What’s inside this submodule
| Path | What’s there |
|---|---|
README.md | Plain-language confirmation of the per-workspace codex app-server model (README.md:35); nanobot/DingTalk bridge, Happy mobile relay |
src-tauri/src/backend/app_server.rs | The actual spawn path — spawn_workspace_session, WorkspaceSession, build_codex_command_with_bin, build_codex_path_env, check_codex_installation |
src-tauri/src/codex.rs | Tauri commands wrapping the JSON-RPC session (start_thread, resume_thread, list_threads, list_threads_global), ensure_global_session, GLOBAL_WORKSPACE_ID |
src-tauri/src/state.rs | AppState — sessions: Mutex<HashMap<String, Arc<WorkspaceSession>>>, global_session: OnceCell<...>, plus dictation/happy/nanobot bridge state |
src-tauri/src/workspaces/commands.rs | connect_workspace_inner/connect_workspace/reconnect_workspace, resolve_workspace_codex_args, all sessions map read/write sites (1806 lines, largest file in the crate) |
src-tauri/src/codex_home.rs | resolve_workspace_codex_home — per-workspace/per-worktree CODEX_HOME resolution with tilde/env-var expansion, unit-tested |
src-tauri/src/codex_args.rs | parse_codex_args/apply_codex_args/resolve_workspace_codex_args — per-workspace CLI argument resolution |
src-tauri/src/remote_backend.rs | RemoteBackend — alternate single-TCP-connection transport, bypasses local process-per-workspace spawning entirely when active |
src-tauri/src/workspaces/worktree.rs, git.rs | Worktree creation/teardown and git plumbing backing the WorkspaceKind::Worktree case |
src-tauri/src/nanobot_bridge.rs, nanobot_bridge_daemon.rs, nanobot_integration.rs | DingTalk/QQ bot bridge — OpenVibeProvider: LLMProvider, routes bridge chat into an existing codex session; unrelated to backend/adapter selection |
src-tauri/src/happy_bridge.rs | Mobile relay bridge (Happy) — another consumer of an existing WorkspaceSession, not a new spawn path |
src-tauri/src/types.rs | WorkspaceEntry, WorkspaceKind::{Main, Worktree, Nanobot}, WorkspaceSettings (codexHome, codexArgs, launchScript, worktreeSetupScript) |
src-tauri/Cargo.toml | tauri = "2", tokio (process/net/io features), lib name codex_monitor_lib — the app’s prior name (CodexMonitor) still visible in the crate identifier |
src/features/** | React 19 frontend feature folders (threads, composer, workspaces, git, terminal, dictation, nanobot, happy) — no ADR/architecture docs directory exists in this repo |
If you read two files: src-tauri/src/backend/app_server.rs (the actual spawn mechanism — spawn_workspace_session, WorkspaceSession, the JSON-RPC framing) and src-tauri/src/workspaces/commands.rs (connect_workspace_inner — proof the spawn is keyed and deduplicated by workspace id, with worktree inheritance for config).
Mental model for using it well
- Read “workspace” here as “one codex app-server process,” full stop — there is no session-within-workspace subdivision the way Routa has
AcpManager::create_sessionspawning multiple independently-provider-selectable sessions per workspace. Open Vibe’s unit of process isolation and its unit of user-facing workspace addressing are the same thing. - The swappable axis is invocation, not backend identity.
codex_bin,codex_args,codex_homeall resolve per-workspace with worktree-to-parent inheritance — genuinely useful for pointing different workspaces at different Codex CLI versions, config homes, or extra flags — but every one of those workspaces still runs the same underlying agent (Codex). There is no code path where workspace A runs Codex and workspace B runs a different coding agent. - Treat the
remote_backendmodule as a second, mutually-exclusive transport mode, not a fallback layered under the per-workspace-process model — when active, it collapses everything to one shared TCP connection, structurally closer to Dive’s single-subprocess pattern than to its own default local mode. - Don’t confuse
nanobot’sLLMProvidertrait with a coding-agent adapter. It’s scoped to the DingTalk/QQ chat-bridge feature answering bridge messages through an already-running codex session — a different “provider” axis (which LLM answers bridge chat) layered on top of, not instead of, the one codex process per workspace. - No ADRs, no
docs/ARCHITECTURE.mdexist in this repo — unlike Routa, there is no maintained decision log; everything above is read directly from source, and the “why” for design choices (e.g., whyremote_backendexists) is not documented anywhere found in this checkout.
When NOT to reach for this
- You want evidence of genuine multi-backend/multi-provider adapter swapping. Open Vibe has exactly one coding-agent CLI wired in (
codex). For that question, Routa (AcpManager::create_session, provider chosen per session call) is the study’s positive result; Open Vibe is neither a positive nor really a comparable negative — it’s a different question (process-per-workspace supervision) answered well, for one backend. - You want a maintained decision log or architecture document. There is no
docs/ARCHITECTURE.mdordocs/adr/here — reasoning has to be reconstructed from source and the README alone. - You’re evaluating production maturity or community adoption.
git shortlogshows 118 of 119 commits from a single author (citizenl) at this pin, with no ADRs, no CONTRIBUTING doc found, and one co-credited license holder who does not otherwise appear in the commit log — this is a solo hobby-scale desktop app, not a broadly-adopted or heavily-reviewed project. Treat every architectural claim here as read directly from working code, not validated by a maintainer team or community usage at scale. - You need per-workspace concurrent, differently-configured sessions (multiple simultaneous threads against the same workspace with different models/configs). The
sessionsmap is one entry per workspace id — reconnecting or force-respawning replaces the old process outright (commands.rs:1508-1518) rather than layering a second concurrent session alongside it.
How this compares to the rest of the study
| Axis | Open Vibe | routa | dive |
|---|---|---|---|
| Shells/surfaces | Tauri 2 desktop only, single React 19 frontend | Tauri 2 desktop + Next.js web + VS Code extension, one api-contract.yaml | Tauri and Electron (dual, same React frontend) |
| Backend language(s) | Rust/Tokio (codex_monitor_lib), one implementation | Rust/Axum (desktop) and TypeScript/Next.js (web) — two independent implementations of one contract | Rust/TS shell + separate Python dive_httpd subprocess (submodule) |
| “Workspace” scope | Real: WorkspaceEntry keyed 1:1 to a live child process in state.sessions; worktrees inherit codex_home/codex_args from parent | Real schema-level scope for sessions, codebases, worktrees, tasks (workspace_id NOT NULL FKs); "default" explicitly flagged as transition scaffolding | N/A — no workspace concept, one MCP host process for the whole app |
| Adapter/backend swap unit | None — one CLI (codex) always; only argv/env/cwd vary per workspace via codex_bin/codex_args/codex_home | Per-session ACP process spawn (AcpManager::create_session) — provider/model/MCP-config chosen per call | One shared Python subprocess (dive_httpd) for the whole app — no per-context swap at all |
| Isolation mechanism | Real OS child process per workspace (WorkspaceSession.child), JSON-RPC-over-stdio, killed and replaced on reconnect | Real OS child process per session (ManagedProcess), workspace+session-scoped MCP endpoint URL | None — all tools/providers/conversations flow through the one dive_httpd process |
| Alternate transport | remote_backend.rs — one shared TCP connection, mutually exclusive with local per-workspace spawning | N/A in this checkout | N/A — Python subprocess is the only transport |
| Docs/decision trail | None found (no ADRs, no architecture doc); README is the only prose source | docs/ARCHITECTURE.md + 6 ADRs, actively maintained | Not profiled here in depth |
| Maturity/adoption | Single-contributor (118/119 commits), no CI beyond one bot commit, no CONTRIBUTING doc found — hobby-scale | Larger, more heavily tooled (fitness gates, .agents/skills/, CI parity tests) | Separate maturity profile |
| Best fit | Reference for “process-per-workspace supervision done cleanly around one CLI, with real per-workspace config/env/cwd resolution” — a genuine but narrower positive result than Routa’s | Reference for “genuine per-session/per-workspace adapter and process isolation across a protocol-rich, dual-backend product” — the study’s strongest positive result for adapter swapping specifically | Reference for “shell as thin process supervisor around one shared backend” — a different negative result (no per-context swap at all) |
Open Vibe’s load-bearing contribution to this study is a partial positive result, scoped narrowly: it proves that a Tauri desktop shell can cleanly spawn and supervise one real OS child process per workspace, with genuine per-workspace configuration inheritance (worktree-to-parent fallback chains for both CODEX_HOME and CLI args) — but it answers “is the process boundary real?” (yes) without answering “is the backend itself swappable?” (no; there is exactly one CLI wired in, codex). Where Routa demonstrates process isolation and backend-identity swapping together, Open Vibe demonstrates process isolation alone, cleanly, at hobby-project scale and with no adjacent decision-log documentation to lean on.
One-line summary
Open Vibe is a single-contributor Tauri 2 + Rust/Tokio desktop shell that spawns one genuine
codex app-serverchild process per workspace — confirmed directly inspawn_workspace_session(src-tauri/src/backend/app_server.rs:290-464) andconnect_workspace_inner(src-tauri/src/workspaces/commands.rs:1471-1520), with real per-workspace/per-worktree resolution ofcodex_bin,codex_args, andCODEX_HOME— but it is a process-per-workspace supervisor around exactly one coding-agent CLI, not a process-level adapter swap: there is no provider registry, no second backend, and no per-session multiplicity the way Routa’sAcpManagerhas, making it a genuine but narrower positive result than Routa and a clear step up from Dive’s single shared subprocess.