# Remote SSH & Provider Neutrality

> Compares tmux's shell, job, and spawn portability with cmux's SSH workspace, remote PTY attach, loopback routing, and daemon bridge design. The portable lesson is to keep orchestration file, socket, and repository based so BYOC/BYOK workflows do not depend on one model provider or hosted connector.

- Repository: tmux/tmux-with-manaflow-ai-cmux
- GitHub: https://github.com/tmux/tmux
- Human wiki: https://grok-wiki.com/public/wiki/tmux-tmux-with-manaflow-ai-cmux-62db34dfaddc
- Complete Markdown: https://grok-wiki.com/public/wiki/tmux-tmux-with-manaflow-ai-cmux-62db34dfaddc/llms-full.txt

## Source Files

- `tmux-tmux:spawn.c`
- `tmux-tmux:job.c`
- `tmux-tmux:cmd-new-session.c`
- `tmux-tmux:environ.c`
- `manaflow-ai-cmux:Sources/WorkspaceRemoteConfiguration.swift`
- `manaflow-ai-cmux:Sources/WorkspaceRemoteSSHBatchCommandBuilder.swift`
- `manaflow-ai-cmux:Sources/RemoteLoopbackRuntimeBridge.swift`
- `manaflow-ai-cmux:Sources/RemoteRelayZshBootstrap.swift`

---

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [tmux-tmux:spawn.c](tmux-tmux/spawn.c)
- [tmux-tmux:job.c](tmux-tmux/job.c)
- [tmux-tmux:cmd-new-session.c](tmux-tmux/cmd-new-session.c)
- [tmux-tmux:environ.c](tmux-tmux/environ.c)
- [tmux-tmux:regress/new-session-size.sh](tmux-tmux/regress/new-session-size.sh)
- [manaflow-ai-cmux:Sources/WorkspaceRemoteConfiguration.swift](manaflow-ai-cmux/Sources/WorkspaceRemoteConfiguration.swift)
- [manaflow-ai-cmux:Sources/WorkspaceRemoteSSHBatchCommandBuilder.swift](manaflow-ai-cmux/Sources/WorkspaceRemoteSSHBatchCommandBuilder.swift)
- [manaflow-ai-cmux:Sources/RemoteLoopbackRuntimeBridge.swift](manaflow-ai-cmux/Sources/RemoteLoopbackRuntimeBridge.swift)
- [manaflow-ai-cmux:Sources/RemoteRelayZshBootstrap.swift](manaflow-ai-cmux/Sources/RemoteRelayZshBootstrap.swift)
- [manaflow-ai-cmux:Sources/RemoteLoopbackProxyAlias.swift](manaflow-ai-cmux/Sources/RemoteLoopbackProxyAlias.swift)
- [manaflow-ai-cmux:Sources/Workspace.swift](manaflow-ai-cmux/Sources/Workspace.swift)
- [manaflow-ai-cmux:Sources/CmuxSSHURLRequest.swift](manaflow-ai-cmux/Sources/CmuxSSHURLRequest.swift)
- [manaflow-ai-cmux:Sources/AppDelegate+CmuxSSHURL.swift](manaflow-ai-cmux/Sources/AppDelegate+CmuxSSHURL.swift)
- [manaflow-ai-cmux:cmuxTests/WorkspaceRemoteConnectionTests.swift](manaflow-ai-cmux/cmuxTests/WorkspaceRemoteConnectionTests.swift)
- [manaflow-ai-cmux:cmuxTests/CmuxSSHURLRequestTests.swift](manaflow-ai-cmux/cmuxTests/CmuxSSHURLRequestTests.swift)
</details>

# Remote SSH & Provider Neutrality

This page compares two different portability layers. `tmux` keeps terminal orchestration portable by treating shells, jobs, PTYs, working directories, and environment variables as local Unix process concerns. `cmux` builds a remote workspace layer around SSH, remote daemon transport, local socket forwarding, persistent PTY attach, loopback rewriting, and external-link guarding.

The shared architecture lesson is not “use one hosted connector.” It is to keep orchestration file-, socket-, process-, and repository-based so BYOC/BYOK workflows can move across machines, SSH configurations, keys, agents, and model providers without recoding the runtime boundary.

## Boundary Model

```text
tmux local process model
  client/session env + cwd + shell
        -> forkpty/fork
        -> execvp or $SHELL -c
        -> pane/job fd events

cmux remote workspace model
  workspace config + SSH options + socket path
        -> ssh stdio daemon OR ssh local-forwarded daemon socket OR websocket daemon
        -> daemon RPC/proxy/PTY bridge
        -> browser loopback alias + terminal attach/retry
```

`tmux` is narrower and lower-level: it launches local children correctly and portably. `cmux` is broader: it preserves local workspace UX while moving execution and PTY ownership behind SSH or another daemon transport. The provider-neutral takeaway is that neither boundary needs to be a model-provider API; both are organized around shells, sockets, process arguments, and runtime capabilities.

Sources: [tmux-tmux:spawn.c:30-48](tmux-tmux/spawn.c), [tmux-tmux:job.c:70-116](tmux-tmux/job.c), [manaflow-ai-cmux:Sources/WorkspaceRemoteConfiguration.swift:51-63](manaflow-ai-cmux/Sources/WorkspaceRemoteConfiguration.swift), [manaflow-ai-cmux:Sources/Workspace.swift:1757-1795](manaflow-ai-cmux/Sources/Workspace.swift)

## tmux: Shell, Job, and Spawn Portability

### Session launch is environment-first

`new-session` accepts a start directory, explicit environment entries, session naming, sizing, and an optional shell command. It creates a session environment, optionally updates it from the client environment, applies `-e` overrides, creates the session, and then passes a `spawn_context` into `spawn_window`.

Sources: [tmux-tmux:cmd-new-session.c:38-51](tmux-tmux/cmd-new-session.c), [tmux-tmux:cmd-new-session.c:262-296](tmux-tmux/cmd-new-session.c)

### Spawn normalizes cwd, PATH, shell, PTY, and exec form

`spawn_pane` builds a child environment from the session, adds pane identity, preserves useful client `PATH` behavior for unattached clients, falls back to `_PATH_DEFPATH`, validates `default-shell`, sets `SHELL`, then forks a PTY. In the child, it pushes the prepared environment and chooses between `execvp(argv)` for multi-argument commands, `$SHELL -c` for one command string, or a login shell when no command is supplied.

Sources: [tmux-tmux:spawn.c:207-240](tmux-tmux/spawn.c), [tmux-tmux:spawn.c:323-353](tmux-tmux/spawn.c), [tmux-tmux:spawn.c:393-489](tmux-tmux/spawn.c)

### Jobs reuse the same portability pattern

`job_run` also builds an environment via `environ_for_session`, chooses a shell from defaults when requested, forks either a PTY job or pipe-backed job, pushes the environment, and executes either shell command text or an argv vector. Job state is fd/event driven, with explicit PTY resize support and a closed/dead lifecycle.

Sources: [tmux-tmux:job.c:70-116](tmux-tmux/job.c), [tmux-tmux:job.c:120-190](tmux-tmux/job.c), [tmux-tmux:job.c:198-231](tmux-tmux/job.c), [tmux-tmux:job.c:291-307](tmux-tmux/job.c), [tmux-tmux:job.c:339-384](tmux-tmux/job.c)

### Environment is a controlled handoff, not global magic

`environ_update` copies only variables selected by `update-environment`; `environ_push` materializes the prepared environment after fork; `environ_for_session` merges global and session environment, sets terminal variables, clears inherited systemd socket activation variables, and writes `TMUX` with the socket path, server pid, and session id.

Sources: [tmux-tmux:environ.c:185-228](tmux-tmux/environ.c), [tmux-tmux:environ.c:250-282](tmux-tmux/environ.c)

## cmux: Remote SSH Workspace Layers

### Remote configuration is transport-neutral at the workspace boundary

`WorkspaceRemoteTransport` supports both `ssh` and `websocket`. The remote configuration records destination, port, identity file, SSH options, local proxy port, relay metadata, local socket path, terminal startup command, foreground auth token, daemon websocket endpoint, PTY preservation, and daemon bootstrap mode. That field set describes connection mechanics and capabilities, not a specific AI provider.

Sources: [manaflow-ai-cmux:Sources/WorkspaceRemoteConfiguration.swift:51-80](manaflow-ai-cmux/Sources/WorkspaceRemoteConfiguration.swift), [manaflow-ai-cmux:Sources/WorkspaceRemoteConfiguration.swift:276-327](manaflow-ai-cmux/Sources/WorkspaceRemoteConfiguration.swift)

### SSH startup supports both simple reconnect and persistent PTY attach

For normal SSH workspaces, `workspaceConfiguration()` normalizes destination, validates port range, normalizes SSH options, and chooses either a persistent PTY startup command or a plain reconnect command. Persistent PTY mode adds SSH control defaults, checks whether foreground auth is reusable, mints a foreground token, and uses `ssh-pty-attach` through the local cmux socket.

Sources: [manaflow-ai-cmux:Sources/WorkspaceRemoteConfiguration.swift:365-411](manaflow-ai-cmux/Sources/WorkspaceRemoteConfiguration.swift), [manaflow-ai-cmux:Sources/WorkspaceRemoteConfiguration.swift:83-150](manaflow-ai-cmux/Sources/WorkspaceRemoteConfiguration.swift), [manaflow-ai-cmux:Sources/WorkspaceRemoteConfiguration.swift:154-215](manaflow-ai-cmux/Sources/WorkspaceRemoteConfiguration.swift)

### Batch SSH commands isolate background transport from interactive sessions

`WorkspaceRemoteSSHBatchCommandBuilder` has three distinct command shapes:

| Command shape | Purpose | Portability detail |
| --- | --- | --- |
| `daemonTransportArguments` | Run remote daemon over stdio | Uses `ssh -T` and `sh -c 'exec remotePath serve --stdio'` |
| `daemonSocketForwardArguments` | Forward a local loopback port to a remote Unix socket | Uses `-N -T -S none` and `-L 127.0.0.1:localPort:remoteSocketPath` |
| `reverseRelayControlMasterArguments` | Ask an existing control master to add/cancel reverse forwarding | Requires a configured `ControlPath` |

The builder adds timeouts, keepalives, `BatchMode=yes`, default `StrictHostKeyChecking=accept-new` when absent, and `ControlMaster=no` so helpers may reuse configured control sockets without negotiating a new master.

Sources: [manaflow-ai-cmux:Sources/WorkspaceRemoteSSHBatchCommandBuilder.swift:3-76](manaflow-ai-cmux/Sources/WorkspaceRemoteSSHBatchCommandBuilder.swift), [manaflow-ai-cmux:Sources/WorkspaceRemoteSSHBatchCommandBuilder.swift:96-130](manaflow-ai-cmux/Sources/WorkspaceRemoteSSHBatchCommandBuilder.swift), [manaflow-ai-cmux:cmuxTests/WorkspaceRemoteConnectionTests.swift:1976-2064](manaflow-ai-cmux/cmuxTests/WorkspaceRemoteConnectionTests.swift)

### The daemon bridge can run through SSH stdio, socket forward, or websocket

`WorkspaceRemoteDaemonRPCClient.start()` chooses websocket, baked-VM socket forward, or SSH exec. SSH exec launches `/usr/bin/ssh` with daemon arguments and wires stdin/stdout/stderr pipes as the RPC transport. Baked VM mode launches SSH local forwarding to `/run/cmuxd-remote.sock`, then connects to the allocated loopback socket. Capability negotiation checks for proxy streaming and, when PTY preservation is requested, PTY session/token support.

Sources: [manaflow-ai-cmux:Sources/Workspace.swift:1757-1795](manaflow-ai-cmux/Sources/Workspace.swift), [manaflow-ai-cmux:Sources/Workspace.swift:1827-1877](manaflow-ai-cmux/Sources/Workspace.swift), [manaflow-ai-cmux:Sources/Workspace.swift:1879-1948](manaflow-ai-cmux/Sources/Workspace.swift), [manaflow-ai-cmux:Sources/Workspace.swift:2660-2680](manaflow-ai-cmux/Sources/Workspace.swift)

### PTY attach is routed through the daemon proxy broker

The remote proxy broker keys tunnels by `proxyBrokerTransportKey`, shares a tunnel across subscribers, exposes PTY list/close/resize/detach/start bridge operations, and reports a local browser proxy endpoint on `127.0.0.1`. This keeps terminal lifecycle and browser proxy lifecycle attached to a workspace transport key rather than a hosted provider session.

Sources: [manaflow-ai-cmux:Sources/WorkspaceRemoteConfiguration.swift:335-346](manaflow-ai-cmux/Sources/WorkspaceRemoteConfiguration.swift), [manaflow-ai-cmux:Sources/Workspace.swift:3840-3897](manaflow-ai-cmux/Sources/Workspace.swift), [manaflow-ai-cmux:Sources/Workspace.swift:3900-3975](manaflow-ai-cmux/Sources/Workspace.swift), [manaflow-ai-cmux:Sources/Workspace.swift:3978-4025](manaflow-ai-cmux/Sources/Workspace.swift)

## Loopback Routing and Remote Browser UX

Remote development servers often bind to `localhost`, `127.0.0.1`, `::1`, or `0.0.0.0` on the remote host, but browser code in a local web view needs a routable alias. `RemoteLoopbackProxyAlias` defines `cmux-loopback.localtest.me`, maps localhost-family names to that alias, and maps aliases back to localhost-family hosts. `RemoteLoopbackRuntimeBridge` injects a document-start script that rewrites `http:` and `ws:` loopback requests for `fetch`, `XMLHttpRequest`, and `WebSocket`, deliberately leaving `https:` and `wss:` untouched because TLS certificate/SNI expectations would change.

Sources: [manaflow-ai-cmux:Sources/RemoteLoopbackProxyAlias.swift:3-54](manaflow-ai-cmux/Sources/RemoteLoopbackProxyAlias.swift), [manaflow-ai-cmux:Sources/RemoteLoopbackRuntimeBridge.swift:3-72](manaflow-ai-cmux/Sources/RemoteLoopbackRuntimeBridge.swift), [manaflow-ai-cmux:Sources/RemoteLoopbackRuntimeBridge.swift:74-105](manaflow-ai-cmux/Sources/RemoteLoopbackRuntimeBridge.swift), [manaflow-ai-cmux:Sources/Panels/BrowserPanel.swift:3412-3418](manaflow-ai-cmux/Sources/Panels/BrowserPanel.swift)

## External SSH Entry Points and Guardrails

`CmuxSSHURLRequest` turns supported `cmux://ssh`-style URLs into CLI arguments such as `cmux ssh --port ... --name ... destination`. The parser only allows a narrow query set, rejects unsafe destination forms, validates ports, and converts selected knobs into SSH options. The app delegate launches the bundled CLI through the resolved cmux socket and warns that SSH may use local SSH config, keys, agent settings, `ProxyCommand`, `LocalCommand`, and forwarding rules.

Sources: [manaflow-ai-cmux:Sources/CmuxSSHURLRequest.swift:21-65](manaflow-ai-cmux/Sources/CmuxSSHURLRequest.swift), [manaflow-ai-cmux:Sources/CmuxSSHURLRequest.swift:80-198](manaflow-ai-cmux/Sources/CmuxSSHURLRequest.swift), [manaflow-ai-cmux:Sources/AppDelegate+CmuxSSHURL.swift:22-70](manaflow-ai-cmux/Sources/AppDelegate+CmuxSSHURL.swift), [manaflow-ai-cmux:Sources/AppDelegate+CmuxSSHURL.swift:216-275](manaflow-ai-cmux/Sources/AppDelegate+CmuxSSHURL.swift), [manaflow-ai-cmux:cmuxTests/CmuxSSHURLRequestTests.swift:17-70](manaflow-ai-cmux/cmuxTests/CmuxSSHURLRequestTests.swift), [manaflow-ai-cmux:cmuxTests/CmuxSSHURLRequestTests.swift:279-335](manaflow-ai-cmux/cmuxTests/CmuxSSHURLRequestTests.swift)

## Comparison Brief

| Concern | tmux approach | cmux approach | Portable lesson |
| --- | --- | --- | --- |
| Process launch | Local `forkpty`/`fork`, `execvp`, `$SHELL -c`, login shell | Local app launches `/usr/bin/ssh`, remote daemon, socket forward, or websocket | Keep launch mechanics explicit and argument-based |
| Environment | Session/client env merge, `PATH`, `SHELL`, `TERM`, `TMUX` socket | Workspace config, SSH options, socket path, foreground auth token | Treat environment and socket paths as durable orchestration inputs |
| PTY lifecycle | Pane/job owns PTY fd and resize | Remote daemon exposes PTY list/resize/detach/start bridge | Put PTY operations behind capability-checked boundaries |
| Remote routing | Out of scope in these files | Loopback alias and browser runtime rewriting | Route local dev servers without hardcoding a vendor tunnel |
| BYOC/BYOK fit | Uses user shell, cwd, env, and local socket | Uses user SSH destination, key path, SSH config/options, socket, daemon capabilities | Let users bring compute, SSH keys, config, and model keys independently |

Sources: [tmux-tmux:spawn.c:323-489](tmux-tmux/spawn.c), [tmux-tmux:environ.c:250-282](tmux-tmux/environ.c), [manaflow-ai-cmux:Sources/WorkspaceRemoteConfiguration.swift:276-327](manaflow-ai-cmux/Sources/WorkspaceRemoteConfiguration.swift), [manaflow-ai-cmux:Sources/WorkspaceRemoteSSHBatchCommandBuilder.swift:9-76](manaflow-ai-cmux/Sources/WorkspaceRemoteSSHBatchCommandBuilder.swift), [manaflow-ai-cmux:Sources/Workspace.swift:3900-3975](manaflow-ai-cmux/Sources/Workspace.swift)

## Design Implications for Provider-Neutral Workspaces

A provider-neutral remote workspace should keep these boundaries stable:

1. **Repository and filesystem first.** The durable unit is the workspace checkout plus its configuration, not a hosted model session.
2. **Sockets and stdio for orchestration.** Local socket paths, SSH stdio, SSH port forwards, and daemon RPC make the bridge portable across clouds and local machines.
3. **Keys remain user-controlled.** SSH identity files, agent behavior, and SSH config are local user inputs. Model API keys can follow the same BYOK pattern through environment or repo configuration without changing the transport.
4. **Capabilities over assumptions.** cmux checks daemon capabilities before relying on proxy streaming or persistent PTY features; this is the right pattern for optional remote features.
5. **External connectors are adapters.** A hosted connector can create or discover a workspace, but the runtime should still reduce to files, sockets, SSH/websocket transport, and repository state.

Sources: [manaflow-ai-cmux:Sources/Workspace.swift:1770-1795](manaflow-ai-cmux/Sources/Workspace.swift), [manaflow-ai-cmux:Sources/WorkspaceRemoteConfiguration.swift:462-473](manaflow-ai-cmux/Sources/WorkspaceRemoteConfiguration.swift), [manaflow-ai-cmux:cmuxTests/WorkspaceRemoteConnectionTests.swift:260-290](manaflow-ai-cmux/cmuxTests/WorkspaceRemoteConnectionTests.swift), [manaflow-ai-cmux:cmuxTests/WorkspaceRemoteConnectionTests.swift:383-430](manaflow-ai-cmux/cmuxTests/WorkspaceRemoteConnectionTests.swift)

## Closing Summary

`tmux` shows the durable Unix core: build the right environment, fork the right kind of child, attach it to a PTY or pipe, and keep sockets visible through environment. `cmux` extends that lesson to remote workspaces by making SSH, daemon RPC, loopback routing, and PTY attach explicit transport layers. The portable architecture is BYOC/BYOK friendly because compute, keys, sockets, repositories, and model-provider choices remain separable rather than being fused into one hosted connector. Sources: [tmux-tmux:environ.c:213-282](tmux-tmux/environ.c), [manaflow-ai-cmux:Sources/WorkspaceRemoteConfiguration.swift:276-327](manaflow-ai-cmux/Sources/WorkspaceRemoteConfiguration.swift), [manaflow-ai-cmux:Sources/Workspace.swift:11513-11650](manaflow-ai-cmux/Sources/Workspace.swift)
