Connect Remote Nodes
Open Settings → Nodes → Add node. The wizard checks public Control Plane reachability, then asks about the target network and installation state. Prefer a connection initiated by whichever side can make outbound requests, minimizing public ingress.
Compare the Methods
| Method | Initiator | Best for | Public requirement |
|---|---|---|---|
| Reverse WSS | Node Agent | Remote node can reach the Control Plane | Expose only the Control Plane |
| Direct HTTPS | Control Plane | Control Plane can reach the Node Agent | Expose the Node Agent HTTPS API |
| Trusted Control Plane proxy | Current Control Plane | Another reachable Control Plane already manages the node | Current plane can reach the proxy plane |
Reverse WSS is normally preferred. It avoids public Node Agent listening, NAT, and TLS configuration, and the Node Agent persists the connection and reconnects after service restarts.
Reverse-connect to the Control Plane
Node Agent is not installed
- Ensure the target can reach the public Control Plane URL.
- In the wizard, select that the remote node can reach this Control Plane and that the agent is not installed.
- The wizard generates a one-time join token and a complete installation command.
- Run the command on the target Debian or Ubuntu host.
- Keep the wizard open until the node reports connected.
The command has this shape. Use the actual URL, token, and version generated by the UI:
curl -fsSL 'https://control.example.com/install-node-agent.sh' | sudo sh -s -- \
--control-plane 'https://control.example.com' \
--join-token 'ONE_TIME_JOIN_TOKEN' \
--npm-package @task-handoff/node-agent \
--controlled-instance-package @task-handoff/controlled-instance \
--version 'TARGET_VERSION'The installer creates and starts task-handoff-node-agent.service, completes pairing, stores a dedicated connection credential, and opens the reverse WebSocket. A fresh supported host does not need Node.js or npm preinstalled.
Node Agent is already installed
After the wizard generates the token, run this on the target:
sudo task-handoff-node-agent connect \
--control-plane 'https://control.example.com' \
--join-token 'ONE_TIME_JOIN_TOKEN'If another Control Plane already manages the target, you can instead open that plane's Node → Connections, select Add outbound connection, and enter the new Control Plane URL and join token.
Join tokens expire and are used only for initial pairing. Select Generate new invite when the wizard reports expiration. The persisted reverse connection uses a separate credential and does not need the token to remain online.
Connect Directly to a Public Node Agent
Use direct access only when the target cannot reach the Control Plane but the Control Plane can reach the Node Agent.
- On the Node Agent host, change the TCP listener from
127.0.0.1to the required interface. All IPv4 interfaces corresponds to0.0.0.0. - Configure firewall rules, DNS, NAT, and TLS termination so the Control Plane can reach the Node Agent over HTTPS.
- Generate a pairing token from the source Control Plane's node detail. For a standalone Node Agent, run:
sudo task-handoff-node-agent invite \
--ipc-path /run/task-handoff/node-agent.sock- Enter the Node Agent HTTPS endpoint and one-time pairing token in the new Control Plane's wizard.
- Verify the node ID, connection method, and health after connection.
Public Node Agent endpoints must use HTTPS. Changing the listener to 0.0.0.0 only expands the bind scope; it does not configure TLS, firewall rules, or authentication. A port change is rejected while running instances still reference the old callback port. Stop those instances, change the port, then restart them. Existing direct Control Planes must also update their endpoints manually.
Use a Trusted Control Plane Proxy
Let A be the current Control Plane, R the trusted proxy Control Plane, and B the target node already managed by R:
- On R, open B's Connections → Trusted Control Plane proxy and create a one-time invite bound to B.
- On A, select Neither side is publicly reachable → Use trusted Control Plane in the add-node wizard.
- Enter R's canonical HTTPS origin and the proxy invite token.
- Read and select the trust confirmation, then connect.
- On A, inspect the proxy path, binding state, target state, and observation time.
R can read, modify, and perform every proxied Node Agent operation on A's behalf. This is not end-to-end encrypted and must stay within one trusted administrative boundary. The origin must be an HTTPS origin with no userinfo, path, query, or fragment.
The proxy binding is fixed to the node selected when the invite was created. Disabling the proxy node on A stops A from initiating operations but leaves the binding on R. Normal deletion asks R to revoke first. Force deletion while R is unreachable can leave a remote credential behind, which must be revoked manually on R after recovery.
Account-based node relay
The add-node wizard describes a Thandoff account option, but account-based Node Agent relay is not yet supported by the protocol. Use a trusted Control Plane proxy here; Thandoff account remote access is a different client-to-Control Plane path.
State and Troubleshooting
| State or symptom | Meaning and action |
|---|---|
connecting / handshaking | Transport is being established; check initiator logs, URL, DNS, and certificates |
reconnecting | A configured connection was interrupted; fix the network or remote service without re-pairing |
| Node online, instance offline | Node transport works; inspect the instance process and runtime |
| Proxy reachable, target offline | A can reach R, but R does not currently observe B online |
| Invite expired or invalid | Generate another one-time token and ensure another request did not consume it |
| Port change rejected | Stop running instances that reference the old callback port and retry |
Stopping or restarting the Control Plane should not stop instances managed by a remote Node Agent. Preserve the node record and pairing while troubleshooting. Inspect the last error and diagnostics under Node → Overview/Connections instead of deleting the node to test connectivity.