Skip to content

Commit 911bdec

Browse files
committed
docs(federation): add operator setup guide
1 parent 9186a0d commit 911bdec

2 files changed

Lines changed: 221 additions & 13 deletions

File tree

‎docs/federation-architecture.md‎

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -48,19 +48,18 @@ protocol version, source instance, optional target instance, request IDs, and
4848
deadlines. Gateways may relay envelopes when a client targets another enrolled
4949
instance.
5050

51-
The first RPC surface intentionally maps read-oriented backend operations:
52-
53-
- `backend.listThreads`
54-
- `backend.readThread`
55-
- `backend.listSkills`
56-
57-
This is enough to bootstrap remote windows, thread discovery, and federated
58-
search without prematurely exposing turn-control or environment mutation.
51+
The RPC surface maps navigation and thread reads plus the remote operation
52+
surface used by desktop windows and messaging. It includes thread creation,
53+
fork/review, turn start/steer/interrupt/compact, pending-request submission,
54+
model and execution settings, environment actions, environment setup progress,
55+
and workspace handoff. Capability checks remain attached to every method, so an
56+
older or restricted peer fails closed instead of silently executing locally.
5957

6058
## Diagnostics
6159

6260
`federation:get-health` returns a sanitized health snapshot for settings and
63-
future support tooling:
61+
support tooling. `federation:get-diagnostics` adds bounded, redacted session
62+
audit events:
6463

6564
- configured role and enabled status
6665
- local listener URL when enabled
@@ -111,7 +110,10 @@ In a client profile:
111110
3. Click Open next to the gateway to work against the gateway, or next to a
112111
sibling client to route through the gateway.
113112

114-
Remote backend events stream back into the matching remote window, so prompt
115-
submission should show live turn status, tool activity, and assistant deltas
116-
without reopening the thread. The MVP still expects you to test against an
117-
existing client thread; remote new-thread creation is not wired yet.
113+
Remote backend events and environment setup progress stream back into the
114+
matching remote window. Remote navigation includes the target instance's
115+
directories and launchpads, so new threads, forks, and reviews execute on the
116+
remote machine as well as operations on existing threads.
117+
118+
See `docs/federation.md` for direct-mode and Cloudflare Tunnel setup, Access
119+
service-token and mTLS credential handling, diagnostics, and revocation.

‎docs/federation.md‎

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
# PwrAgent Federation Operator Guide
2+
3+
PwrAgent federation connects multiple PwrAgent profiles through an authenticated
4+
WebSocket control plane. A gateway accepts connections, clients connect to that
5+
gateway, and the gateway relays authorized traffic between clients.
6+
7+
Federation is disabled by default. The first supported remote posture is a
8+
loopback gateway listener published through Cloudflare Tunnel. Direct
9+
localhost/LAN WebSocket URLs use the same PwrAgent enrollment and peer
10+
authentication.
11+
12+
## Security Boundaries
13+
14+
Federation uses two independent admission layers:
15+
16+
1. Cloudflare can restrict which traffic reaches the tunnel hostname with IP,
17+
Access service-token, or mTLS policy.
18+
2. PwrAgent always requires an enrolled instance identity and a signed
19+
challenge. Cloudflare headers or certificates never replace PwrAgent peer
20+
authentication.
21+
22+
Each PwrAgent profile owns an Ed25519 identity key in encrypted desktop secret
23+
storage. Enrollment invites are short-lived and single-use. The gateway stores
24+
the enrolled public identity and can revoke it. Revocation closes the active
25+
gateway connection and blocks reconnects with that identity.
26+
27+
Keep the gateway listener on `127.0.0.1` when using `cloudflared`. Do not expose
28+
the listener port through a router or host firewall.
29+
30+
## Local Dogfood Setup
31+
32+
Build the desktop app once, then launch isolated profiles:
33+
34+
```sh
35+
pnpm --filter @pwragent/desktop build
36+
PWRAGENT_PROFILE=gateway pnpm --filter @pwragent/desktop preview
37+
PWRAGENT_PROFILE=laptop pnpm --filter @pwragent/desktop preview
38+
```
39+
40+
On the gateway profile, open Settings -> Federation and set:
41+
42+
- Mode: `gateway`
43+
- Listen host: `127.0.0.1`
44+
- Listen port: `47830`
45+
- Public URL: `ws://127.0.0.1:47830`
46+
47+
Save, generate an invite, and import it on the laptop profile. The imported
48+
profile switches to client mode and connects immediately.
49+
50+
Both profiles should list the other instance as Connected. Open the remote
51+
instance from either profile. A client can also open the gateway or another
52+
connected client; sibling traffic is relayed by the gateway.
53+
54+
## Cloudflare Tunnel
55+
56+
Cloudflare Tunnel supports WebSockets and connects to the origin without an
57+
inbound public port. See Cloudflare's
58+
[Tunnel overview](https://developers.cloudflare.com/tunnel/) and
59+
[Tunnel WebSocket support](https://developers.cloudflare.com/cloudflare-one/faq/cloudflare-tunnels-faq/).
60+
61+
Create a named tunnel and DNS route:
62+
63+
```sh
64+
cloudflared tunnel login
65+
cloudflared tunnel create pwragent-federation
66+
cloudflared tunnel route dns pwragent-federation pwragent.example.com
67+
```
68+
69+
Use a `cloudflared` configuration like:
70+
71+
```yaml
72+
tunnel: YOUR_TUNNEL_ID
73+
credentials-file: /absolute/path/to/YOUR_TUNNEL_ID.json
74+
75+
ingress:
76+
- hostname: pwragent.example.com
77+
service: http://127.0.0.1:47830
78+
- service: http_status:404
79+
```
80+
81+
Start the tunnel:
82+
83+
```sh
84+
cloudflared tunnel run pwragent-federation
85+
```
86+
87+
On the gateway, keep the listener at `127.0.0.1:47830` and set Public URL to
88+
`wss://pwragent.example.com`. Generate new invites after changing the public
89+
URL because the URL is embedded in each invite.
90+
91+
### Source IP Restriction
92+
93+
An IP allow rule at Cloudflare is a useful outer gate while the gateway is at a
94+
stable location. Verify the rule against the hostname before relying on it and
95+
remove or change it deliberately when the connecting laptop moves networks.
96+
PwrAgent enrollment remains mandatory whether or not the IP rule is active.
97+
98+
### Cloudflare Access Service Token
99+
100+
Cloudflare Access service tokens use the
101+
`CF-Access-Client-Id` and `CF-Access-Client-Secret` headers. Configure a
102+
Self-hosted Access application and a Service Auth policy for the federation
103+
hostname. Cloudflare documents the current flow in
104+
[Service tokens](https://developers.cloudflare.com/cloudflare-one/access-controls/service-credentials/service-tokens/)
105+
and [Access policies](https://developers.cloudflare.com/cloudflare-one/access-controls/policies/).
106+
107+
On every client profile, before importing its federation invite:
108+
109+
1. Open Settings -> Federation -> Cloudflare.
110+
2. Enable Access service auth.
111+
3. Enter the Access client ID and client secret.
112+
4. Select Save edge policy.
113+
5. Import the federation invite.
114+
115+
PwrAgent stores both values in encrypted desktop secret storage and sends them
116+
only in the WebSocket upgrade request. If either value is missing while the
117+
mode is enabled, the connector fails closed with a Settings diagnostic.
118+
119+
### Cloudflare Access mTLS
120+
121+
Cloudflare Access can enforce a Service Auth policy with a Valid Certificate or
122+
Common Name selector. Its current setup and certificate requirements are in
123+
[Cloudflare Access mTLS](https://developers.cloudflare.com/cloudflare-one/access-controls/service-credentials/mutual-tls-authentication/).
124+
Availability depends on the Cloudflare account and product configuration; check
125+
the dashboard before treating mTLS as an available outer gate.
126+
127+
Issue each client a certificate and private key from the CA associated with the
128+
federation hostname. On the client profile, before importing its invite:
129+
130+
1. Open Settings -> Federation -> Cloudflare.
131+
2. Enable mTLS.
132+
3. Paste the PEM client certificate and matching PEM private key.
133+
4. Select Save edge policy.
134+
5. Import the federation invite.
135+
136+
The private key stays in encrypted desktop secret storage. PwrAgent supplies
137+
the certificate and key only during the TLS handshake. Missing certificate
138+
material causes a closed failure before a WebSocket is opened.
139+
140+
Access service tokens and mTLS can be enabled together when the Access policy
141+
requires both.
142+
143+
## Remote Operation
144+
145+
A remote window routes its navigation, thread reads, prompt submission,
146+
steering, interruption, compaction, approvals, model and execution settings,
147+
environment selection/actions, environment action stop, reviews, forks,
148+
launchpad materialization, and workspace handoff to the selected instance.
149+
Backend events and environment setup output stream back with the source
150+
instance identity.
151+
152+
Global thread search fans out metadata queries to connected peers. Remote
153+
results carry their instance label and open directly in a window scoped to that
154+
instance.
155+
156+
Messaging thread browse includes connected remote threads with an instance
157+
label. Selecting one persists its full federated identity, so prompts, steering,
158+
interrupts, compaction, settings changes, approvals, status reads, and streamed
159+
events continue to route to the owning instance. A disconnected remote binding
160+
remains stored and reports unavailable thread state rather than rebinding to a
161+
same-named local thread.
162+
163+
Disconnected or revoked peers remain visible for diagnosis, but Open is
164+
disabled. PwrAgent does not fall back to local execution when a remote target
165+
is unavailable.
166+
167+
Remote filesystem paths describe the remote machine. Do not assume a path is
168+
present on the machine displaying the remote window.
169+
170+
## Diagnostics And Revocation
171+
172+
Settings -> Federation shows:
173+
174+
- local mode, listener, public URL, and connector status
175+
- peer role, status, protocol version, negotiated capabilities, and activity
176+
- recent connection attempts, accepts, rejects, disconnects, relays, and errors
177+
- redacted failure details without invites, private keys, or raw credentials
178+
179+
Select Revoke next to a peer to close its active gateway connection and prevent
180+
that identity from reconnecting. Re-enrollment requires a fresh invite and a
181+
new instance identity.
182+
183+
Common failures:
184+
185+
- `unknown_peer`: the client identity was never enrolled or local state was
186+
replaced; generate and import a new invite.
187+
- `revoked_peer`: the gateway has revoked this identity.
188+
- `bad_signature`: the stored private key does not match the enrolled public
189+
identity.
190+
- HTTP `401` or `403` before a PwrAgent audit event: Cloudflare rejected the
191+
WebSocket upgrade. Check IP, Access token, or mTLS policy.
192+
- Missing Cloudflare credential error: enable the edge mode only after storing
193+
all required fields.
194+
195+
## Threat Model
196+
197+
The design protects against arbitrary Internet clients reaching a tunnel,
198+
unenrolled PwrAgent instances, replay of used enrollment invites, reconnect by
199+
revoked identities, and accidental routing of remote commands to the local
200+
profile.
201+
202+
It does not protect against a compromised enrolled machine, an attacker who can
203+
use that profile's unlocked desktop secret storage, or commands explicitly
204+
approved on a remote coding-agent thread. Treat every enrolled peer as able to
205+
exercise the capabilities shown in Settings and revoke peers that are lost or
206+
retired.

0 commit comments

Comments
 (0)