> ## Documentation Index
> Fetch the complete documentation index at: https://panopticon-cli.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Linked machines

> Link two machines' dashboards over an encrypted channel, learn routes, and watch each other's health

# Linked machines

Each machine runs its own Overdeck and owns its own conversations, agents and
credentials. A **machine link** lets one machine's dashboard reach another machine's
dashboard: the two servers talk to each other over an end-to-end encrypted channel, so
this machine can show and control what runs on the other one. Your browser still talks
only to the machine it opened. Released in v0.73.0.

A link is one direction. **Link both ways** (the default) creates the mirror link in the
same step, so either dashboard can show both machines.

## Link a machine

On the machine you want to reach, create a pairing link, exactly as for a device:

```bash theme={null}
pan pair
```

or **Settings → Anywhere → Pair a device**. The link looks like
`https://desk.tailnet.ts.net/#pair=odp_…` and works once, for ten minutes.

On the machine that should reach it, paste the link:

```bash theme={null}
pan machines link https://desk.tailnet.ts.net/#pair=odp_…
```

or **Settings → Anywhere → Machines → Link a machine**. The dialog takes the pairing
link, the access level and the **Link both ways** checkbox. Linking takes a few seconds:
the two machines prove their keys to each other through the pairing code, exchange
tokens, and the new link shows up as **Online** on both sides.

Both servers on one host (for a test) need `--allow-loopback`, because a `localhost`
pairing link only works on the machine that printed it.

## Link a fresh server

To link a server you reach over SSH, one command on the server does everything: it
installs Overdeck as a service that survives logout and reboot, starts it, and prints the
link.

```bash theme={null}
# on the server, over SSH
npx @overdeck/core connect
```

Then run the `pan machines link '…'` line it prints on your laptop. See
[`pan connect`](https://panopticon-cli.com/cli/machines#connect) for lingering, macOS, firewalls and removal.

## Access levels

| Level | What this machine may do on the other | Flag |
| - | - | - |
| **Operator-level** (default) | Read and control the other machine's conversations, agents and terminals as the operator. | none |
| **Agent-fenced** | Only what an agent on this machine is allowed to do, within that agent's own scopes; requests are stamped with the agent and are never operator-level. | `--agent-fenced` |

Neither level can pair devices, mint tokens or change the other machine's settings. The
other machine refuses those routes to any linked machine.

## What is encrypted

Everything, on every route. The channel is a Noise protocol session (X25519,
ChaCha20-Poly1305) between the two machine keys; the pairing code only introduces the
keys. Every request, the event stream and every terminal that crosses a link runs inside
the channel. A machine's key lives in `~/.overdeck/machine-key.json`, mode 0600, and its
fingerprint (five groups of four hex characters) is shown next to each link so you can
compare it with the other machine's **This machine** line.

## Routes

A link keeps a list of ways to reach the other machine, best first: a LAN address, a
Tailscale address, then public or relayed ones. Where they come from:

* The **pairing** address is the one in the pairing link.
* **Advertised** addresses are learned from the other machine itself: its LAN and
  Tailscale addresses, and its Tailscale Serve name when it has one. When a better one
  answers, the link moves to it within a minute. Addresses the other machine stops
  advertising are dropped.
* **Add route** (in the expanded route list) adds an address you know works, for
  example an SSH-forwarded port: `tcp://127.0.0.1:3041`.

Expand a link's **Routes** to see each address, its kind, where it came from and whether
it answered last time.

## States

| State | Meaning |
| - | - |
| **Online** | Heartbeats arrive every 15 s over the channel. |
| **Connecting** | A heartbeat is late (17.5 s); every route is being tried while the old channel stays open. |
| **Offline** | No heartbeat for 20 s and no route answered. Retries back off from 1 s to 45 s; the link comes back within a minute of the other machine returning. |
| **Unsupported** | The other machine runs an Overdeck without machine links, or another link protocol. The state names the exact version, where to install it, and the `npm install -g @overdeck/core@<version>` command. It is not retried until **Reconnect** or a restart. |
| **Revoked** | The other machine removed this machine's access. Link again with a new pairing link. |

When a link drops, the row says why. **froze** means the other dashboard's event loop
stalled (it reports its own stalls in the next heartbeat, so a frozen machine is told
apart from a dead network); **network** means nothing arrived.

## Fleet versions and Update all

**Settings → Anywhere → Fleet versions** (or `pan machines versions`) shows one row per
machine, this machine first: the Overdeck version and the version of each harness CLI
installed there (Claude Code, Codex CLI, Kimi Code CLI, OpenCode, Muse Code, Prime Agent,
OhMyPi). A linked machine that is not online shows its last known Overdeck version. A
linked machine on an Overdeck from before fleet updates shows the version to install and
its command.

**Update all** (or `pan machines update`) asks every online machine to update itself:

1. **Wait for running turns.** Each machine waits until no agent or conversation is in the
   middle of a turn there, checking every 5 s. It never starts on its own while a turn
   works. After 30 minutes the row says it is still waiting. **Update now** on that row
   (or `pan machines update --now`) starts it at once; **Cancel** (or
   `pan machines update --cancel`) stops a waiting update.
2. **Update each harness CLI** with its own command. A CLI installed with npm is updated
   with npm into the same prefix. A failure in one CLI does not stop the next; the row
   shows the command's output.
3. **Update Overdeck last** on a machine where it was installed with
   `npm install -g @overdeck/core`, then restart that machine's dashboard. A repository
   checkout (`pan reload` deployments) or the desktop app keeps its Overdeck and still
   updates the CLIs.

Each version shows `before → after` once it changed. Update all always installs the
latest version; it never lets anyone pick a version.

| CLI | Update command | Notes |
| - | - | - |
| Claude Code | `claude update` | npm installs: `npm install -g --prefix <prefix> @anthropic-ai/claude-code@latest` |
| Codex CLI | `codex update` | npm installs: `npm install -g --prefix <prefix> @openai/codex@latest` |
| Kimi Code CLI | `kimi upgrade --yes` | |
| OpenCode | `opencode upgrade` | npm installs: `npm install -g --prefix <prefix> opencode-ai@latest` |
| Muse Code | none | Listed with its version; Overdeck does not update it. |
| Prime Agent | none | Overdeck pins it to 0.8, so it is not updated to latest. |
| OhMyPi | none | Listed with its version; Overdeck does not update it. |

Homebrew installs, Windows, and an npm prefix Overdeck cannot write show the command to
run yourself instead of running it. Offline, connecting, unsupported and revoked machines
are skipped, and so is a machine reached over an agent-fenced link: Update all needs an
operator-level link.

## Unlink

**Unlink** on a link (or `pan machines unlink <label>`) removes it here and tells the
other machine to drop its access token for this machine. If the other machine is
unreachable, it sees the link as **Revoked** when it next connects. The **Inbound** list
shows machines that hold access to this one; **Remove** revokes one and closes its open
connections.

## Troubleshooting

* **Unsupported**: update Overdeck on the machine the state names, then **Reconnect**.
* **Revoked**: the other side unlinked. Create a new pairing link there and link again.
* **Update all skipped a machine**: the toast and `pan machines update` name the reason.
  An offline or unsupported machine is never updated; bring it online (or install the
  named Overdeck version) and run Update all again. "agent-fenced" means this machine's
  link to it is agent-fenced; link again at operator level to update it from here.
* **A CLI update failed**: the row shows `Failed: <CLI>`, and the Update cell's tooltip
  (or `pan machines versions --json`) holds the command's output. Fix the cause on that
  machine (often permissions on the npm prefix) and run Update all again; the other CLIs
  were still updated.
* **Offline with "ECONNREFUSED"**: the other dashboard is not running, or no advertised
  address reaches it from here. Add a route you know works.
* **Offline with "closed the connection before proving the pinned machine key"**: every
  route answered but none proved the expected key. Either the other machine's key changed
  (its `machine-key.json` was replaced) or a proxy answered for a dead peer. Unlink and
  link again if the key really changed.
* The link exists but `require_token_mint` turned itself on: that is expected. Any remote
  access turns it on; see [Remote access](https://panopticon-cli.com/configuration/remote-access.md#local-reverse-proxies-require_token_mint).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.