Issue #4697 Product and Architecture Memo

Making VMs, remote servers, and SSH workspaces first class in Orca

Users want one desktop Orca client to control local work plus multiple remote hosts concurrently, without losing terminals or mentally switching into a different app.

Recommended model One app window, many first-class execution hosts, grouped in the workspace sidebar.
Core architecture move Replace global server switching with host-scoped routing and per-host session slices.
Settings principle Client settings stay local; host settings are explicit and contextual.

Executive Recommendation

Build a single-window, host-aware workbench. Local, SSH targets, VMs, and paired Orca runtime servers should appear as execution hosts. Selecting a workspace changes focus; it must not tear down other hosts' terminals.

The current destructive behavior comes from treating activeRuntimeEnvironmentId as a global app mode. That model does not satisfy concurrent remote work. It should become connection management and default selection, not the identity of the whole workbench.

Multiple app windows can be a later power-user feature, but should not be the first design. A window-per-host approach fights the existing single-renderer architecture and weakens the product goal of one client coordinating work across machines.

The product should feel configurable, not split into separate modes. A local-only user sees today's Orca. A remote-heavy user can choose All hosts to operate like a command center, or choose one host so the client feels almost entirely like that machine. Those are two views over the same host registry and session model.

Do not design around a minimal MVP boundary here. The intended destination is the full end-to-end model: concurrent local, SSH, VM-like, and paired-runtime hosts with safe host-owned routing. Implementation can still be phased for review and risk control, but the design should describe the complete product.

Vocabulary

Term Meaning Examples
Host The machine or backend that owns execution, files, terminals, ports, and agents. Local Mac, Windows VM over SSH, Linux builder, paired Orca runtime server.
VM A loose product term for a remote machine. It may be backed by SSH today, a paired runtime server, or future Orca-provided cloud infrastructure. Windows dev VM, Linux cloud builder, future Orca-hosted environment.
Visible host filter Which hosts the sidebar shows. This is presentation/filtering, not teardown. All hosts, Local Mac + Windows VM, Linux Builder only.
Default host The host preselected for new workspace creation when a flow needs a target. Local Mac remains the default while Windows VM is also visible.
Execution owner The host identity attached to a repo/worktree/tab/PTY operation. ssh:target-1, runtime:env-a, local.

What We Learned

Current behavior

Core product requirement

A user must be able to start work on one host, switch attention to another host, then come back without losing terminals, browser tabs, agent status, file context, or workspace ordering. Host focus is a viewing and default-target choice. It is not permission to close work on the old host.

Agent feedback

Agent Main recommendation Useful nuance
Cursor Unify local, SSH, runtime, and VMs under an ExecutionHostId. Partition session state by host and phase the refactor around routing keys.
Claude Make runtime environments per-worktree, like SSH is already per-target. Keep local/runtime/SSH transport types distinct internally for now, but converge them in the product model.

Product Model

Orca should present every machine or remote backend as a host. A host can be local, an SSH target, a paired Orca runtime server, or a VM profile backed by either SSH or runtime RPC.

"VM" should remain a user-facing label, not a required separate transport in v1. Today a VM can be an SSH target or a remote runtime server. Later, if Orca offers managed cloud machines as a revenue source, those machines should slot into the same host model instead of creating a parallel workspace concept.

local ssh:windows-vm runtime:prod-builder vm:win-only-product

Sidebar behavior

Host actions

Two Valid User Modes

Mode User intent Experience
All hosts "I want my local and remote work visible together." Sidebar shows host sections. Cmd+J/search can cross hosts. New work defaults to the last focused host or an explicit host picker.
One visible host "Make this Orca client feel like the server." Sidebar shows only that host. Terminals/files/agents route to that host. Other hosts keep running in the background and can be restored by changing scope.
Selected hosts "Show my Mac plus the remote machines relevant to this task." Sidebar shows only checked host sections, with host health still visible. This is a persistent presentation filter, not an ownership or teardown operation.

Primary user journeys

Journey Expected behavior Implementation implication
Add an SSH VM User saves a target, Orca shows it as a host, and new work can be created there. Saved SSH targets become host registry entries with ssh:<targetId> execution identity.
Switch to Windows VM Sidebar narrows to Windows workspaces. Existing local terminals and agents keep running. Host visibility updates view/default state only. It must not call terminal/browser close paths.
Create a workspace from All hosts Orca asks for or infers a target host, then binds the repo/worktree to that host forever unless explicitly moved. Creation flows use focused/default host as input. Later operations use owner host.
Open Settings while scoped to a remote host Settings still clearly says whether the user is editing client settings or that host's details. Host focus must never silently reinterpret local client settings as remote settings.
Reconnect after SSH auth expires Remote workspaces remain visible as unavailable/deferred until the user reauths. Session hydration and sidebar rendering cannot require an active SSH tunnel.

Sidebar Mock

Hosts should behave as a visibility filter above the existing organization controls. The existing Group by, Sort by, filters, and project filter still apply inside the visible host set.

There is no separate "Focused Host Mode" toggle. Showing only Local Mac, Windows VM, or any other single host is the focused experience. Showing All hosts or a checked subset is the command-center experience. New workspace creation should still expose the target host explicitly when more than one host is visible.
feature-control-multi... Cursor opinion... Claude opinion...
$ visible hosts: Local Mac, Windows VM, Linux Builder
Local Mac and remote workspaces stay alive while the sidebar changes focus.
Selecting Windows VM would hide local workspaces without closing them.

How this maps to the real sidebar

Settings Model

The Settings page should be explicit: users are either editing the local client, a host, or a repo/worktree. Do not let host focus silently change what a setting means.
Scope Examples Storage / routing
Client settings Theme, keybindings, language, window layout, telemetry, global UI preferences. Existing local settings store.
Host settings Host label, connection details, auth/account selection, detected agents, shell capability, server version, health, default clone location. Local host registry plus host RPC for server-owned facts.
Repo/worktree settings Base ref, source-control AI recipe, tabs, layouts, per-worktree state. Repo/worktree records tagged with host identity.

Host Details surface

Do not build a second giant Settings app. Add a focused Host Details panel reachable from each host header and from Settings > Servers.

Effective settings rule

For ambiguous preferences, use inheritance: effective = host override ?? client default. Show when a host is overriding the client default, and provide a clear reset action.

In the settings navigation, keep client settings as the default top-level destination. Add a Servers / Hosts area for saved local, SSH, runtime, and VM hosts. From the sidebar, Manage Host should deep-link directly to the selected host details, not change the meaning of the entire Settings page.

For settings that can vary by host, a scoped selector is enough: a dropdown such as Apply to: Client default / Local Mac / Windows VM / Linux Builder. This is similar to Windows settings surfaces that let users choose whether a setting applies to Windows or WSL. The key rule is that the selected settings scope must be visible in the control itself, not inferred from sidebar focus.

How SSH Fits

SSH should be treated as a first-class host type. It is not a side feature. It is the closest existing implementation to the desired multi-host model.

SSH already has many of the properties this design needs: target-keyed connection state, repo-level connectionId, per-target remote workspace snapshots, SSH-aware PTY IDs, remote file operations, and port forwarding. The new model should give those pieces a clearer product wrapper rather than replacing them.

Host identity

Conceptually, host IDs should look like this:

An SSH connection is both a saved connection profile and an execution host. The connection profile stores how to reach the machine. The execution host identity is what repos, worktrees, PTYs, agents, browser sessions, and file operations route through.

A VM is not a special case here. A Windows VM reached over SSH is ssh:<targetId>. A cloud VM running a paired Orca runtime is runtime:<environmentId>. A future Orca-managed VM should still expose a host ID and adapter rather than leaking cloud-provider details into workspace routing.

SSH lifecycle

State Sidebar representation User action
Saved, never connected Host appears as available to connect, with no live workspaces yet. Connect, edit details, or create workspace on host.
Connected Host section shows health, active workspaces, live ports, and agent count. Create/open work, launch terminals, manage host, disconnect.
Needs auth Host section remains visible with a clear auth-needed status. Enter passphrase/password or edit credentials.
Offline/reconnecting Previously known workspaces remain visible but disabled where necessary. Retry, inspect error, edit connection, or remove host.
Removed Host disappears after confirmation; cached host sessions are cleaned safely. Explicit destructive action only.

SSH in the sidebar

A saved SSH target appears as a host section, not merely as a small badge on a project. The transport can still be visible in secondary text:

Host Details for SSH

Keep adapters distinct

The UI and routing language should converge, but the implementation should not force SSH and paired runtime servers into one transport too early. Use an adapter shape:

Features ask "which host owns this worktree?" and dispatch through the right adapter. That preserves SSH-specific filesystem, PTY, relay, and port-forward behavior while making the product feel coherent.

SSH-specific risks

Architecture Direction

Introduce execution host identity

Add a normalized host/routing key that can represent local, SSH, runtime, and future VM hosts.

Bind work to hosts

Data ownership rules

Data Owner Rule
Theme, keybindings, window/sidebar layout Client Always local. Host focus must not change what these settings mean.
Host registry and labels Client Local list of known hosts, with runtime/SSH-specific connection fields.
Paired runtime server configuration Client The desktop client owns the saved server entry, connection policy, and user-facing settings for that server. The server reports execution facts and capabilities.
Repos and worktrees Host Every repo/worktree must carry or derive host identity.
Terminal/browser handles Host IDs must be host-scoped and routed through the owner host.
Agent detection and account availability Host or client depending on runtime Show source clearly. Installed tools on a VM are not the same as local tools.

Source of truth rules

Partition session state

Move from one global session blob toward host-partitioned session slices. The renderer can still render one unified workbench, but persistence and routing should be host-aware.

Terminal and session identity direction

Existing SSH PTY IDs already point toward the right model: src/shared/ssh-pty-id.ts embeds the SSH connection ID into app-visible PTY IDs so relay-local IDs cannot collide after restore. Runtime and local sessions should converge on the same principle.

Stop using global active runtime for mutations

activeRuntimeEnvironmentId should not decide where a terminal, file operation, git action, browser page, or agent launch goes. The owning worktree should decide.

Compatibility bridge

During migration, keep a compatibility layer where activeRuntimeEnvironmentId can still provide a default host for legacy flows. New code should ask for host identity from the repo/worktree/tab owner first, then fall back to the focused/default host only for creation flows.

Existing remote-server users should not experience a jarring migration. Their current active runtime becomes the focused/default host. The main visible change is additive: they may now also see local-machine workspaces when using All hosts, and can narrow back to the remote server by selecting only that host.

Version skew and capability gating

Multi-host Orca must assume hosts can run different Orca builds. A new desktop client may connect to an old runtime server, an old desktop client may connect to a newer server, and two remote hosts may expose different feature sets at the same time.

The current runtime compatibility foundation is already the right shape: src/shared/protocol-version.ts declares protocol versions and capabilities, src/shared/protocol-compat.ts evaluates whether client/server protocol ranges overlap, and runtime.status.get returns optional protocol and capability fields. The host model should promote that from a hidden preflight into visible per-host state.

The important rule is that Orca app versions are diagnostic labels, not the compatibility contract. A desktop client at app version 1.8.0 should be allowed to use a server at app version 1.7.2 if their runtime protocol ranges overlap. Exact app version equality would make remote hosts unnecessarily brittle, especially for SSH boxes, long-lived VMs, and future cloud-hosted machines that may update on a different cadence from the user's laptop.

Compatibility should be modeled as host state, not app state. Each host registry entry needs a verdict such as compatible, server-too-old, client-too-old, unknown, or degraded, plus the capabilities that were actually advertised by that host. The sidebar, Settings, worktree actions, terminal launch, browser actions, hosted review polling, and background agents should all ask the owning host for this verdict before mutating anything.

Handshake model

  1. When a host connects, call its cheapest status endpoint first, such as runtime.status.get for runtime servers or the SSH relay status equivalent for SSH hosts.
  2. Record appVersion, runtimeProtocolVersion, minCompatibleRuntimeClientVersion, capabilities, platform, relay version, health, and last successful check time on the host registry entry.
  3. Evaluate protocol compatibility per host. A host is hard-blocked only when the client is below the server's minimum client protocol, or the server is below the client's minimum server protocol.
  4. Evaluate optional features from capabilities. Missing capabilities disable only the specific feature, not the whole host.
  5. Cache the verdict per host and re-check on reconnect, explicit refresh, server update, relay reinstall, or status-version change.
Scenario Expected behavior UI treatment
New client, old compatible server Connect normally. Older servers may omit newer fields; clients must treat missing optional fields as unavailable, not corrupt. Enable only features advertised by the server's capabilities. Host is healthy. Newer unsupported features are hidden or disabled with a short "requires newer server" explanation.
New client, too-old server Block runtime RPCs after the status handshake reports server-too-old. Keep cached host sidebar/session state visible where possible, but do not launch terminals, agents, git mutations, browser actions, or file writes against that host. Host header shows Update server. Host Details shows server protocol, required protocol, and update guidance.
Old client, newer server Server should allow the client if the client's protocol is within the server's supported range. Otherwise the server reports client-too-old before accepting mutating calls. Host header shows Update client when blocked. Do not attempt partial writes after a hard incompatibility verdict.
Mixed compatible hosts Each host gets its own status and capability set. One old host must not downgrade the UI for another newer host. Disable unsupported actions only inside the owning host/workspace. Keep other hosts fully functional.
SSH relay or agent-hook skew Treat relay/agent-hook versions as host capabilities. Terminal/file basics can work while newer agent attribution, port detection, orchestration, or browser features are unavailable. Host Details lists relay version, detected agents, missing capabilities, and repair actions.

Operational contract for mismatched versions

The client should treat every remote host as an independent compatibility island. A new client connected to an old server, an old client connected to a new server, and a fully current local host can all coexist in the same sidebar. Compatibility must be checked at the host boundary before the host is allowed to receive mutating commands.

  1. Status-only bootstrap: the first call to a remote host must be a backwards-compatible status handshake. It should not depend on the full runtime API being compatible. If even the status endpoint is unavailable, classify the host as unknown or needs update and keep cached read-only state visible.
  2. Hard compatibility verdict: compare the client's protocol range with the host's reported protocol range. If they do not overlap, the host remains visible but disabled for terminals, agents, git writes, file writes, browser actions, and settings mutations.
  3. Soft capability verdict: if the protocol range is compatible, use the host's advertised capabilities to enable or disable individual features. This is how a new desktop client can safely work with an older but still compatible server.
  4. Per-command guard: every host-owned mutation should check the host compatibility/capability verdict at dispatch time, not only when rendering the button. UI hiding prevents confusion; dispatch guards prevent stale UI or background workflows from writing to an incompatible host.
  5. Update direction: when blocked, the host registry should preserve a reason code such as server-too-old, client-too-old, or missing-capability. The sidebar badge and Host Details page can then say exactly whether the user should update the desktop client, update/reinstall the remote server or relay, or simply accept that one optional feature is unavailable on that host.
  6. Owner-scoped fallback: if an older server cannot answer a newer API, the client may fall back only for the host that owns the request, and only to a known compatible older endpoint. It must not silently reroute the request to the focused host or to the local machine.
A mismatched remote host should never force the whole Orca client into a degraded mode. In All hosts, incompatible hosts are visible with blocked actions while compatible hosts continue working normally. When the visible host filter contains only an incompatible host, the main view should show the blocked state for that host and offer repair/update actions instead of silently falling back to local execution.

Implementation inventory to start from

Area What to inspect Desired direction
Runtime switching activeRuntimeEnvironmentId, getActiveRuntimeTarget Use only as focused/default host compatibility, not mutation routing.
SSH ownership connectionId, SSH target maps, remote workspace projections Promote existing target-keyed concepts into host registry entries.
PTY and browser sessions Terminal IDs, browser page IDs, relay/session IDs Namespace or validate by execution host before attach, close, or mutate.
Sidebar state Workspace filters, grouping, project filters, selected workspace Add the Hosts visibility filter above existing organization controls; keep other filters inside the visible host set.
Settings Local settings store, runtime settings, SSH target editing Separate client settings from Host Details and inherited host overrides.

Phased Plan

Phase 0

Design the host identity layer

Define ExecutionHostId, host helpers, migration notes, and a grep map of call sites that currently read activeRuntimeEnvironmentId.

Phase 1

Separate connection management from workbench focus

Keep saved servers in Settings, but stop presenting one exclusive active server as the whole app mode. Add focused/default host state for new work only.

Phase 2

Keep terminals alive across local/server focus changes

Preserve runtime terminal/browser handles when changing focus. Do not close remote resources unless the user disconnects or removes a host.

Phase 3

Support concurrent local plus runtime and SSH hosts

Route repo/worktree/terminal operations by owner host. The full destination is grouped local, SSH, and runtime hosts in one sidebar, with the visible host set as a filter.

Phase 4

Generalize to many runtime servers

Use host-keyed connection maps, per-host health, and per-host session persistence. Mirror the per-target discipline SSH already has.

Phase 5

Unify local, SSH, runtime, and VM UI

Show host-grouped workspaces in the sidebar. Make SSH targets and paired runtime servers feel like peers while preserving transport-specific implementation.

Phase 6

Add optional detached host windows

Only after the host registry and session partitioning are solid, allow a host to be opened in another window for power users.

Risks

The main engineering risk is not UI. It is preventing work from being routed to the wrong machine once multiple hosts are alive at the same time.

Immediate Follow-ups

  1. Write a design doc for ExecutionHostId, host registry, session partitioning, and migration.
  2. Audit every activeRuntimeEnvironmentId read and classify it as default host, owner host, or client setting.
  3. Prototype a host-grouped sidebar with local plus one remote server.
  4. Add a Host Details panel for saved servers without duplicating the full Settings page.
  5. Preserve remote terminals when changing focus between local and remote contexts.
  6. Fix the terminal-read bug where Claude Code's visible full-screen TUI output can appear as blank lines through orca terminal read.
  7. Add operator safeguards: do not close live agent terminals solely because transcript output is quiet or blank.

Acceptance Criteria

Agent Implementation Notes

A future agent picking this up should treat this as a multi-step architecture change, not a sidebar-only feature. The UI can ship incrementally, but the safety boundary is routing.

Resolved Decisions and Remaining Unknowns

Resolved decisions

Remaining unknowns