- Status: early public product
- Package:
psyche-build - Command:
psyche - One-liner: Psyche Build is a project-scoped multiagent coding harness that runs parallel agent lanes in visible tmux/worktree sessions.
Parallel coding agents are useful only when the work stays visible, scoped, and recoverable.
Psyche Build gives developers one cockpit for launching agent lanes, watching terminals, keeping branches isolated, and bringing work back through explicit review. It focuses the proven terminal-cockpit primitives — tmux panes, git worktrees, agent launchers, rituals, file browsing, and merge flows — with clean Psyche Build branding and a source package that can also be validated as a package archive.
Branch, develop, inspect, and merge — all in parallel.
Developer
│
▼
Psyche Build cockpit
├─ projects
├─ panes
├─ git worktrees
├─ agent launchers
├─ rituals
├─ file browser
├─ merge / PR flows
└─ optional project-scoped sessions
The iOS codebase is a planned companion surface, not a released product and not an independent authority. The shipped source opens into a Now-first information architecture:
| Width class | Root navigation | Detail behavior |
|---|---|---|
| Compact | Tabs ordered Now, Projects, Settings | Now rows and project panes push PaneWorkspaceView on a navigation stack |
| Regular | Sidebar ordered Now, Projects, Settings, with project rows under Projects | A detail stack shows Now, all projects, one selected project, or Settings |
Now is a cross-project inbox generated from the authoritative workspace snapshot. It groups every published pane by state: needs attention, running, and recent. A row opens the pane; if the store is stale it shows "Showing last known state" with the last confirmation time instead of presenting the workspace as live. The root keeps tab state across size-class changes, and the Now route's pushed pane path is shared with the regular detail stack. The compact Projects route is hosted in its own unbound navigation stack, so rotating from a compact project or project pane reconstructs the regular shell at the Projects root.
iOS source currently has two composition roots:
- Production root:
MobileAppComposition.production()wiresURLSessionControlTransport,ControlRequestClient,WorkspaceStore,RemoteActionStore,PairedHostStore,HostReadinessMachine,WorkspaceCache, andTerminalSessionRegistry, then reconnects only to the stored host. - Fixture root:
-uiFixturelaunches deterministic in-memory state throughDemoStore,FixtureControlRequests, andFixtureTerminalClient. Fixture roots construct no transport and no Keychain-backed store. Fixture names, fixture roots,DemoStore,-uiFixture, and fixture-only debug controls such asfixture-deliver-live-snapshotare test scaffolding, not production behavior.
The bridge supports protocol versions 2 and 3
(SUPPORTED_PROTOCOL_VERSIONS = [2, 3]). A connection initially receives a
legacy v2 welcome. The client sends hello with one supported version; v3 gets
a v3 welcome and may use top-level control envelopes and workspaceChanged
events. v2 remains the legacy pane/project/ritual message set.
The v3 mobile control envelope currently accepts these production request families when the host has wired the corresponding executor:
| Area | Requests | Contract |
|---|---|---|
| Workspace | workspace.snapshot, workspaceChanged |
Complete snapshots carry a monotonic per-bridge sequence. Incremental changes are ignored across gaps until a full snapshot is accepted. |
| Terminal streams | panes.attach, panes.detach, panes.input, panes.resize |
Streams attach only to tmux-backed published panes; binary frames are ordered after attach metadata. |
| Pane lifecycle | panes.spawn, panes.kill, panes.meta |
Spawn targets must be the published project root or one of its worktrees. Kill stops the process but preserves branch and worktree. |
| Inspection | files.list, files.read, files.diff |
Inspection is scoped to the selected pane's published available worktree and rejects absolute, escaping, missing, bare, or prunable targets. |
| Actions | actions.start, actions.respond |
Action sessions are host-owned, one-shot for each reply, scoped to a published pane, and cleared on owner/session loss. |
| Rituals | rituals.launch / legacy launchRitual |
Publication is wired; production execution is not a supported mobile claim until #242 completes. PR #434 keeps published rituals unavailable for execution where the live launcher is not ready. |
Unsupported or unwired control commands return command_not_supported,
control_unavailable, or a correlated error; they must not be documented as
working production UI.
These limits are source constants, not product aspirations:
| Limit or guarantee | Exact value | Source |
|---|---|---|
| Bridge protocol versions | 2, 3 |
src/services/bridge/wireProtocol.ts |
| LAN WebSocket client frame cap | 1 MiB |
src/services/bridge/WSSListener.ts |
| Legacy pairing code | 6 digits |
src/services/bridge/PairingFlow.ts |
| Legacy pairing window | 5 minutes |
src/services/bridge/PairingFlow.ts |
| Legacy pairing wrong-code budget | 5 attempts |
src/services/bridge/PairingFlow.ts |
| Invite protocol profile | bridge.v3 only |
src/services/bridge/inviteAuth.ts |
| Invite lifetime | 10 minutes |
src/services/bridge/inviteAuth.ts |
| Invite secret entropy | 32 bytes |
src/services/bridge/inviteAuth.ts |
| Invite ID entropy | 16 bytes |
src/services/bridge/inviteAuth.ts |
| Invite wrong-presentation budget | 5 attempts |
src/services/bridge/inviteAuth.ts |
| Retained invite audit tail | 16 records |
src/services/bridge/inviteAuth.ts |
| Invite payload cap | 2,048 bytes |
src/services/bridge/inviteAuth.ts |
| Invite expiry clock skew tolerance | 60 seconds |
src/services/bridge/inviteAuth.ts |
| Pane output ring buffer on host | 256 KiB per pane |
src/services/bridge/PaneOutputBuffer.ts |
| v3 terminal streams per bridge connection | 4 |
src/services/bridge/BridgeDaemon.ts |
| Mobile attached terminal sessions | 2 |
native/ios/PsycheCore/Sources/PsycheCore/Terminal/TerminalSessionRegistry.swift |
| Mobile retained output per pane | 64 KiB |
native/ios/PsycheCore/Sources/PsycheCore/Terminal/TerminalSessionRegistry.swift |
| Mobile control request timeout | 15 seconds |
native/ios/PsycheCore/Sources/PsycheCore/Connection/ControlRequestClient.swift |
| Workspace cache encoded file cap | 256 KiB for the whole encoded PersistedWorkspaceCache file |
native/ios/PsycheCore/Sources/PsycheCore/State/WorkspaceCache.swift |
| Workspace cache draft count | 24 per cached state |
native/ios/PsycheCore/Sources/PsycheCore/State/WorkspaceCache.swift |
| Workspace cache draft length | 4,096 characters per draft |
native/ios/PsycheCore/Sources/PsycheCore/State/WorkspaceCache.swift |
| Mobile pane-spawn idempotency hot cache | 128 keys |
src/services/bridge/MobileControlGateway.ts |
| Pending remote action sessions | 64 |
src/actions/remoteActionSessions.ts |
| Remote action session TTL | 5 minutes |
src/services/bridge/MobileControlGateway.ts |
| Mobile file preview cap | 200,000 bytes |
src/utils/fileBrowser.ts |
| Git buffer for browser/diff inspection | 16 MiB |
src/utils/fileBrowser.ts |
| Published rituals per project | 50 |
src/workspace/ritualPublication.ts |
| Project ritual store read cap | 512 KiB store + 16 KiB manifest |
src/utils/rituals.ts |
| Published ritual metadata caps | ID 128 bytes, name 256 bytes, description 1,024 bytes |
src/utils/rituals.ts |
HostReadinessMachine is the authority for whether iOS may present a host as
ready. The legal spine is:
pairing → authenticating → host_committed → synchronizing → ready
Host identity must commit durably before any workspace snapshot can become authoritative. The machine treats transport, authentication, secure-store, decode/revision, workspace-apply, and revocation failures as named boundaries. Proven failures preserve prior authoritative state only as stale; indeterminate secure-store or workspace publication fails closed instead of guessing. Stored hosts may be adopted for reconnect, but adoption creates no new authority.
WorkspaceStore applies events only in order. Duplicate or old sequences are
ignored. A sequence gap marks the workspace stale and requires a full snapshot;
the store does not patch across holes. Restored cache state is immediately
stale and awaiting a connection snapshot. Live commands, file inspection, pane
creation, renames, stops, and ritual launches require a live workspace and a
published target.
WorkspaceCache stores host-keyed cached states containing the last confirmed
workspace, sequence, confirmation timestamp, selection, and drafts. The encoded
cache file is capped as a whole; draft count and draft length are enforced per
cached state. Corrupt or unreadable cache files surface explicit recovery errors
and preserve the original data where possible; they are not silently replaced
with an empty workspace.
RemoteActionStore consumes an action session before it dispatches the
response, so once a response is dispatched the host may already have acted. A
failure after that point is only reported as an ordinary failure when the
outcome is known: the transport refused the request before handing it over, or
the host answered with one of a closed set of error codes raised before the
action callback ran. The host answering is not by itself proof that it did
nothing — RemoteActionSessions.respond consumes the session and then awaits
the action, so a throw from inside a partially applied effect arrives here as
the daemon's generic internal_error. A timeout, a connection that ended while
a request was in flight, a cancellation, an unrecognised host error code, or a
reply that cannot be interpreted establish nothing, and a control-request
timeout does not cancel host execution.
ControlRequestError separates the two connection failures, because a caller
cannot classify an effect without them. notConnected means the request was
refused before registration and no bytes left the device; disconnected means
an already-registered request lost its connection, which cannot prove the host
saw nothing. Previously both were disconnected, so a response attempted with
no connection at all produced an unknown outcome an operator had to acknowledge
for an effect that never happened. For an action that can change repository,
worktree, pane, or agent state, those become an explicit unknown outcome: the
pane stays guarded, the presentation is not dismissable, and no fresh action is
permitted on that pane. The request, session, pane, and host scope are retained
so the outcome can be reconciled against the host; the operator's draft text is
deliberately not among them, because an unknown outcome persists until it is
resolved by hand and raw input must not sit in published state that long. A
resolution is reported as a reconciled outcome, not as a failure: an action the
host applied must never be dressed as one, because that is how an operator is
invited to retry it. Only an explicit operator
resolution stating what the host was observed to show releases the guard;
dismissing, reconnecting, and switching hosts do not.
Which actions count as consequential is a client-side safety policy, not a
claim of canonical identity — the host stays authoritative for what an action
does. Read-only actions (view, copyPath, openOutput, openInEditor,
openFileBrowser) are exempt so a lost reply for opening a file browser does
not strand a pane; every other action, including any added later, defaults to
consequential.
Bonjour parsing is currently a discovery adapter only. It validates TXT metadata, certificate fingerprint shape, supported protocol versions, and deduplicates on server ID, but no production caller has shipped the complete discovery/connect flow yet (#216 remains open).
Known open gaps: #435's lost-reply unknown-outcome guard is implemented on the
respond path described above; its physical-device reproduction and the
equivalent guard for a non-interactive start dispatch remain open. #241 still
requires physical-device acceptance and real-Keychain partial-write evidence;
#280 has only the invite protocol/fixture slice merged; #242 still owns ritual
execution; #216 owns discovery/connect; #213 owns the performance and full
acceptance matrix.
The public model uses four definitions everywhere:
- Task — one requested outcome.
- Lane — one agent or terminal working on that task.
- Isolation mode — an isolated worktree, a shared worktree, a plain terminal, or an optional provider-managed session.
- Integration — inspect, compare, merge, create a PR, archive, or clean up.
Psyche Build implements that model with these primitives:
- Project — an explicit repo/workspace launched into Psyche Build.
- Cockpit — the visible terminal control surface.
- Pane — one terminal workspace and the concrete surface a lane runs in, often backed by a worktree and agent process.
- Worktree — an isolated git checkout for a task or branch; the most common isolation mode.
- Agent — Coven CLI (stable config/internal ID
coven-code), Claude Code, Codex, OpenCode, Cline CLI, Gemini CLI, Qwen CLI, Amp CLI, pi CLI, Cursor CLI, Copilot CLI, Crush CLI, or another configured coding launcher. - Capability provider — registers a bounded optional execution or session integration with Psyche Build; an explicitly unavailable provider fails closed.
- Ritual — a reusable project setup recipe for opening a known pane layout.
- Operator — the person coordinating visible work and approving consequential actions.
- Optional session — a provider-managed session that Psyche Build can list, launch, or attach only when its canonical project scope is proven.
Psyche Build is for developers and maintainers who want multiple coding agents working at once without losing track of branches, terminals, tests, blockers, or handoffs.
The early user is comfortable with terminal tools and wants:
- parallel agent work without branch conflicts;
- terminal-level visibility;
- reusable setup rituals;
- explicit merge/PR/review control;
- project-scoped autonomy;
- a structured local control path for optional integrations.
Every agent lane should be able to work in its own branch and checkout. Parallelism should not trample the main tree.
Every worker should be inspectable as a terminal pane. No mysterious hidden jobs as the primary experience.
Psyche Build helps with merge, PR, and cleanup flows, but review remains explicit and understandable.
Rituals should make common project layouts fast without depending on brittle tmux snapshots.
Optional clients should use Psyche Build's structured, project-scoped control surface instead of blind terminal puppeteering.
The agent control surface is capability-leased and project-scoped. Agents may act only on registered pane/browser resources at exact generations. Risky effects pause for operator approval; resource replacement, provider disconnect, or owner restart fails closed. The product does not substitute whole-desktop, accessibility, coordinate, raw tmux, or shell control when a typed provider is unavailable. See Agent surface control.
Psyche Build should keep the core user promise sharp:
- tmux pane orchestration;
- git worktree isolation;
- agent launcher registry;
- multi-select launches;
- AI naming for branches, panes, and commits;
- project/pane metadata;
- file browser and pane visibility controls;
- attention/completion heuristics and notifications;
- rituals for reusable layouts;
- merge, PR, and cleanup workflows;
- lifecycle hooks.
Psyche Build-specific additions:
- source
psyche-buildpackage andpsychecommand, validated through the package archive; - cleaned public docs and branding;
- local bridge/daemon direction;
- optional local-session list/open/launch integration;
- project-scoped control APIs for trusted clients.
- Source/package-archive Node CLI
psyche; npm publication is not part of0.0.1. - TypeScript + Ink tmux cockpit.
- Project-scoped tmux session.
- Pane/worktree creation.
- Agent launcher registry.
- Multi-select agent launches.
- Built-in rituals and project rituals.
- Pane file browser and visibility controls.
- Merge/PR-oriented pane menu flows.
- Local daemon/control bridge.
- Optional local-session list/open/launch integration when a compatible provider is running; the macOS rail remains app-origin only.
- Smoke docs and contributor loop.
- Full native desktop cockpit as the primary public experience.
- Cloud terminals.
- Team collaboration.
- Hosted agent orchestration.
- Marketplace/plugin story.
- Broad public claims about stable automation policies.
The bridge must stay conservative:
- operate on explicitly launched project roots;
- reject out-of-project paths;
- prefer worktree-backed coding lanes;
- expose bounded pane capture/status APIs;
- avoid push, merge, publish, delete, or external actions without explicit approval;
- keep secrets and infrastructure URLs out of UI copy and logs.
Psyche Build accepts optional agent and session integrations only through bounded, project-scoped interfaces. It revalidates canonical project identity, resource ownership, lifecycle state, and requested capability before exposing or acting on provider-managed state. Missing, incompatible, or unavailable providers fail closed without disabling ordinary panes, worktrees, file browsing, merges, pull requests, rituals, settings, or cleanup. See Psyche Build integrations.
- Open a repo in Psyche Build.
- Press
nand describe a coding task. - Pick Codex, Claude, or another configured agent.
- Psyche Build creates an isolated worktree and terminal pane.
- The agent works visibly.
- Press
fto inspect files ormto open the pane menu. - Merge, create a PR, attach another agent, or close the pane explicitly.
- Press
uto open a reusable ritual when starting a known workflow. - If an optional local session provider is available, open or launch a project-scoped session from the bridge path.
If this loop is boringly reliable, Psyche Build is doing its job.
Psyche Build remains complete as a standalone tmux and git-worktree cockpit. Supported coding agents and a compatible local session provider may extend the workflow, but they do not own Psyche Build's project identity, pane lifecycle, merge decisions, or cleanup behavior. See Psyche Build integrations.