Architecture
April 2, 2026 ยท View on GitHub
Components
- iOS app (SwiftUI): UI for projects, chats, conversation, approvals, pairing, dictation, and session controls.
- Mac companion (Node/TypeScript): Local service that connects to
codex app-server, reads local Codex/Git state, exposes mobile-safe APIs, and triggers a best-effort sync of the visible Codex Desktop app. - Codex app-server: Local Codex runtime bridge using JSON-RPC over stdio.
Runtime flow
- iOS app pairs with companion using QR payload (
codexremote://pair?...). - Companion validates pairing nonce and asks user confirmation on Mac.
- Companion issues a device token plus the active transport scheme (
httporhttps). - iOS app stores that scheme with the paired host and calls REST APIs with bearer token over the same transport.
- iOS app derives
wsorwssfor the live stream from that stored scheme. - iOS app activates a selected chat before follow-up work.
- Companion remembers the selected chat's project/chat labels and nudges Codex Desktop to select that visible sidebar entry.
- iOS app loads persisted history from local Codex rollout files, including saved assistant commentary plus tool/search activity rows that mirror the desktop transcript more closely.
- iOS app also re-hydrates any still-open approval for the selected chat so desktop-first approval prompts are not lost when mobile connects later.
- iOS app receives streaming updates from
/v1/stream, including approval-open and approval-cleared events.
Local state sources
- Threads/projects:
codex app-serverthread/*APIs - Persisted history: local rollout files in
~/.codex/sessions - Runtime config:
~/.codex/config.toml - Git context: direct
gitcommands in the selected project working tree
Desktop sync bridge
The visible Codex Desktop app does not currently expose a direct "open this exact chat in the running UI" API to the companion.
Because of that, the companion now does the best clean thing it can once a real visible chat context exists:
- remember which workspace belongs to the chat
- activate the running Codex Desktop app
- try to select the known project/chat in the visible sidebar, then reveal the latest content
Recommendation: yes, keep this bridge for now, because it reduces restart-only behavior without pretending we already have a true live desktop-session API. The bridge should not open a workspace over CLI during live sync, because that can create a new empty chat in the visible desktop app instead of keeping the current chat context.
Mobile control surface
The iPhone app now has four separate control areas:
- Drawer: projects plus chats
- Conversation: full-screen chat view with live stream
- Composer dock: text input, send, and dictation
- Session sheet: runtime settings, Git branch, changed files, diffs, and staged commit flow
Git write actions are intentionally narrow:
- checkout only switches to an existing branch
- commit only uses already staged changes
- diff output is read from the real repository state
Runtime setting writes are also narrow:
- only top-level
approval_policy - only top-level
sandbox_mode - all other
config.tomlcontent is preserved
Security model
- Private network assumption: Tailscale tailnet.
- Device token required for all non-pairing routes.
- Pairing requires local GUI confirmation on Mac.
- Token can be revoked.
- Pairing now carries the companion transport scheme so the iPhone can use
https/wssautomatically when the companion is configured with TLS instead of forcing plaintexthttp/ws. - Release iPhone builds now reject plaintext companion pairings and rely on ATS-default transport rules, while Debug builds keep a separate local-development plist that still permits plaintext HTTP for non-TLS companion work.
Session allow approvals
When user chooses allow_for_session, companion marks the chat session as allowed and auto-accepts future approvals for that chat session.
For MCP approvals, the companion now scopes that session allow to the same MCP server/tool fingerprint instead of opening the whole chat to every approval type.
Always allow approvals
When user chooses allow_always for an MCP approval, the companion persists that MCP server/tool fingerprint in a local JSON file next to the device token store and auto-accepts future matching approvals after restart.
Closed-loop verification
The project now has an end-to-end debug loop that proves the main production path with real artifacts:
- issue token
- create chat
- load context
- exercise Git routes in a temporary repository
- round-trip runtime config writes
- send a real message
- collect stream events
- try visible desktop verification through screenshot + OCR
The remaining hard external dependency is the visible desktop proof. That step only passes when the loop runs inside a real logged-in macOS desktop session with Screen Recording and Accessibility/Automation permissions.