winx-code-agent 0.2.333

Remote-first Rust MCP runtime with Streamable HTTP, durable PTYs, and guarded agent tools
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
# Streamable HTTP deployment

Winx exposes its full MCP toolset through an authenticated **Streamable HTTP** endpoint for ChatGPT, hosted agents,
remote automation, and clients that cannot launch a local stdio process. The endpoint is `/mcp`; the default listener is
`127.0.0.1:8000`.

The endpoint grants real shell and filesystem capabilities. Winx therefore fails closed: it requires a strong credential,
refuses non-loopback binding unless explicitly acknowledged, bounds request cost, and isolates authenticated principals.

## At a glance

| Property                 | Default                         |
|--------------------------|---------------------------------|
| MCP endpoint             | `/mcp`                          |
| MCP protocol             | `2026-07-28`                    |
| Listener                 | `127.0.0.1:8000`                |
| Authentication           | `Authorization: Bearer <token>` |
| Minimum token length     | 32 bytes                        |
| Remote session affinity  | `workspace`                     |
| Request body limit       | 64 MiB                          |
| Request timeout          | 120 seconds                     |
| Global concurrency       | 32 requests                     |
| Per-source-IP rate limit | 120 requests per minute         |
| Invalid-auth delay       | 100 ms                          |
| Never-used guardian TTL  | 1,800 seconds (30 minutes)      |
| Used guardian TTL        | 86,400 seconds (24 hours)       |
| Live guardian quota      | 32                              |

Winx supports modern stateless MCP calls and the legacy HTTP initialization/session flow. The same tools, prompts,
resources, structured content, and optional MCP Tasks are available over HTTP and stdio.

## Architecture

```text
Remote MCP client
       │ HTTPS + bearer token
       ▼
Private tunnel / VPN / authenticated HTTPS reverse proxy
       │ loopback HTTP
       ▼
127.0.0.1:8000/mcp
       │
       ├─ Host and body validation
       ├─ Timeout, rate, and concurrency limits
       ├─ Principal authentication
       ├─ Session-affinity resolution
       └─ thread_id / MCP Task scoping
              │
              ▼
        shared WinxService
              │
              ▼
            winxd
              │
              └─ winx-guardian per logical session
                         │
                         └─ real PTY / bash or zsh / foreground and background work
```

On Linux, macOS, and WSL2, `winx-code-agent` is only the MCP adapter. `winxd` owns the control plane, while each
`winx-guardian` owns one PTY. Dropping an HTTP connection or restarting the adapter does not terminate that PTY.

Native Windows uses the embedded runtime, so sessions last only as long as the server process. WSL2 is recommended when
durable remote sessions are required.

## Quick start

Install the three Unix binaries:

```bash
cargo install winx-code-agent
```

Create a strong token in a user-only file:

```bash
mkdir -p ~/.config
install -m 600 /dev/null ~/.config/winx-http-token
openssl rand -hex 32 > ~/.config/winx-http-token
```

Start Winx on loopback:

```bash
winx-code-agent serve --http \
  --bind 127.0.0.1:8000 \
  --token-file ~/.config/winx-http-token
```

Configure the client with:

```text
URL: http://127.0.0.1:8000/mcp
Authorization: Bearer <contents of ~/.config/winx-http-token>
```

Cloud clients need a reachable HTTPS URL. Keep Winx on loopback and put a private tunnel, VPN, or authenticated HTTPS
reverse proxy in front. Winx does not terminate TLS itself.

`--token-file` is preferred over `--token`: command-line secrets may appear in process listings, shell history, and
automation logs. `WINX_HTTP_TOKEN` remains available as a single-principal environment fallback.

## Session affinity

### Workspace affinity — default

The default is:

```bash
--session-affinity workspace
```

For each remote `Initialize(first_call)`, Winx derives the logical session from:

```text
(authenticated principal, canonical workspace)
```

The client-provided first-call `thread_id` is not trusted as the durable key. Variants such as:

```text
release_02333
release_0_2_333
```

resolve to the same internal guardian when they refer to the same principal and workspace. Winx returns a stable external
ID such as `ws_project_<hash>` and expects later tool calls to use that returned value.

Consequences:

- stateless reconnects attach to the existing session instead of creating another guardian;
- repeated first calls preserve the PTY, cwd, output journal, and running command;
- two different principals still receive different namespaces;
- parallel conversations from the **same principal in the same workspace share one shell** and its foreground-command
  lock;
- first calls without a workspace share one scratch-session key per principal;
- task resumption without a workspace is keyed by the saved task ID.

Workspace affinity is the recommended mode for ChatGPT and other clients whose generated IDs are not guaranteed to be
stable.

### Thread affinity — explicit opt-out

Use:

```bash
--session-affinity thread
```

when parallel conversations in one repository must own separate shells. In this mode the normalized external `thread_id`
is the durable key. The client is responsible for:

- generating a stable ID;
- reusing it after reconnects;
- avoiding cosmetic variants;
- pruning or killing abandoned sessions.

An empty first-call ID is still generated, so thread affinity should not be used with a client that cannot retain the
returned ID.

## Attach-or-create

Protocol `1.3` guardians implement attach-or-create for `FirstCall`:

1. a missing logical session creates a new PTY;
2. an existing logical session returns its authoritative snapshot;
3. the adapter updates its local state from that snapshot;
4. the guardian keeps its original PTY process, cwd, mode, journal, cursors, and running commands.

A deliberate replacement still uses the explicit reset or workspace/mode-change transitions. Repeating `FirstCall` is no
longer an implicit reset.

The same adapter also refreshes an existing guardian through a non-destructive mode transition. This keeps local and
embedded runtimes from resetting a session on duplicate first calls.

## Multiple authenticated principals

Use one credential per client or automation:

```bash
mkdir -p ~/.config
install -m 600 /dev/null ~/.config/winx-chatgpt-token
install -m 600 /dev/null ~/.config/winx-automation-token
openssl rand -hex 32 > ~/.config/winx-chatgpt-token
openssl rand -hex 32 > ~/.config/winx-automation-token
```

Create a TOML file:

```toml
# ~/.config/winx-principals.toml
[[principals]]
name = "chatgpt"
token_file = "/home/alice/.config/winx-chatgpt-token"

[[principals]]
name = "automation"
token_file = "/home/alice/.config/winx-automation-token"

[[principals]]
name = "ci"
token_env = "WINX_CI_MCP_TOKEN"
```

Then start:

```bash
chmod 600 ~/.config/winx-principals.toml
winx-code-agent serve --http \
  --principal-config ~/.config/winx-principals.toml
```

Principal rules:

- names may contain ASCII letters, digits, `_`, and `-`;
- each entry sets exactly one of `token_file` or `token_env`;
- names, derived IDs, and tokens must be unique;
- token files must be regular non-symlink files;
- Unix token-file permissions must exclude group and other access;
- tokens must contain at least 32 bytes unless `--allow-weak-token` is explicitly enabled for local testing.

Thread IDs and MCP Task IDs are scoped before they reach the shared service. Normal results, structured content, Task
results, and errors are translated back before leaving the server. A principal cannot get, update, or cancel another
principal's Task.

## Guardian lifecycle

`winxd` enforces lifecycle across every adapter connected to the same control socket.

### Authoritative activity clock

Protocol `1.3` guardians report:

- guardian creation time;
- last session activity;
- last command time;
- whether a command has ever been attempted.

The control-plane JSON next to each guardian socket is a cache for ownership and migration; it is not the source of truth.
This matters because runtime directories such as `/run/user/<uid>` are usually tmpfs and can lose metadata on reboot or
control-plane recreation.

For protocol-1.2 guardians without activity fields:

- used sessions conservatively use real control-plane request observations;
- never-used sessions prefer the guardian socket birth time;
- passive metadata reconstruction is distinguished from a real adapter request;
- recreating every metadata file at one instant does not make every old guardian appear newly active.

### Tiered TTL

Two independent defaults avoid treating disposable shells and valuable builds alike:

```text
WINX_UNUSED_SESSION_IDLE_TTL_SECS=1800   # never ran a command
WINX_SESSION_IDLE_TTL_SECS=86400         # ran a command
```

Set either value to `0` to disable that automatic tier. A foreground command or live background command is never removed,
even when its wall-clock TTL has elapsed. Output produced by a running command refreshes guardian activity, so retention
starts from meaningful terminal activity rather than only command submission.

### Quota pressure

Before refusing a new guardian, `winxd`:

1. removes stale sockets and expired sessions;
2. counts all live guardians;
3. if still full, reclaims the oldest inactive guardian that has never run a command;
4. repeats until one slot is available or no disposable guardian remains.

Used sessions and active foreground/background commands are never sacrificed to admit another session. If the quota is
still full, the error points to the effective manual command:

```bash
winx-code-agent prune --idle-seconds 0
```

That command removes every **idle** session while preserving active commands.

## Operational commands

```bash
# Inspect sessions; protocol 1.3 includes activity timestamps and ever_ran_command
winx-code-agent list

# Follow a guardian journal with an independent cursor
winx-code-agent attach <thread_id> --follow

# Apply the configured 30-minute / 24-hour tiers
winx-code-agent prune

# Override both tiers for one prune operation
winx-code-agent prune --idle-seconds 3600

# Remove every idle session; active commands remain
winx-code-agent prune --idle-seconds 0

# Explicit cleanup
winx-code-agent kill <thread_id>
winx-code-agent kill --all

# Replace only the control plane; guardians and PTYs survive
winx-code-agent restart-daemon

# Redacted runtime report
winx-code-agent doctor
```

Changing guardian limits or TTL environment variables requires `restart-daemon` because `winxd` reads them at startup.

## Network exposure

### Preferred

Keep the default loopback listener and place one of these in front:

- a private VPN such as WireGuard or Tailscale;
- an outbound-only/private MCP tunnel;
- an authenticated HTTPS reverse proxy;
- a tunnel operating inside the same trust boundary.

When a proxy forwards a public `Host`, add the exact authority:

```bash
winx-code-agent serve --http \
  --token-file ~/.config/winx-http-token \
  --allowed-host mcp.example.com
```

`--allowed-host` extends host validation; it does not create DNS, TLS, authentication, or a tunnel.

### Direct non-loopback binding

Winx refuses wildcard, LAN, and public listeners by default. A reviewed deployment can acknowledge the risk:

```bash
winx-code-agent serve --http \
  --bind 192.168.1.20:8000 \
  --allow-non-loopback \
  --token-file ~/.config/winx-http-token
```

This flag only permits the listener. It is not a substitute for HTTPS or firewall policy.

## Authentication modes

Winx accepts exactly one credential configuration:

1. `--principal-config` for named principals; or
2. `--token-file` for one file-backed principal; or
3. `--token` for one command-line principal; or
4. `WINX_HTTP_TOKEN` when no CLI credential source is supplied.

The preferred request header is:

```http
Authorization: Bearer <token>
```

Query tokens are disabled by default. Compatibility mode is explicit:

```bash
winx-code-agent serve --http \
  --token-file ~/.config/winx-http-token \
  --allow-query-token
```

Then a URL-only client can use `https://host/mcp?token=<token>`. Query strings often appear in browser, proxy, tunnel,
trace, and monitoring logs; prefer the bearer header.

Winx does not implement OAuth/OIDC discovery. Requests to `/.well-known/oauth-protected-resource`,
`/.well-known/openid-configuration`, and unrelated routes return ordinary `404 Not Found` responses without being forced
through bearer authentication. `/mcp` remains protected and returns `401` for a missing or invalid token.

## Resource limits and responses

| Condition                                       | Response                  | Notes                        |
|-------------------------------------------------|---------------------------|------------------------------|
| Missing or invalid token                        | `401 Unauthorized`        | Delayed by 100 ms            |
| Source IP exceeds the window                    | `429 Too Many Requests`   | Includes `Retry-After: 1`    |
| Global concurrency exhausted                    | `503 Service Unavailable` | Includes `Retry-After: 1`    |
| Request exceeds 120 seconds                     | `408 Request Timeout`     | Request is terminated        |
| Body exceeds 64 MiB                             | `413 Payload Too Large`   | Rejected before MCP dispatch |
| Public bind without acknowledgement             | Startup failure           | Prefer a private edge        |
| Missing, weak, duplicate, or unsafe credentials | Startup failure           | Fix the credential source    |

The rate limiter uses the TCP peer address seen by Winx. Several users behind one reverse proxy may share one window.
Winx intentionally does not trust forwarded-IP headers by default.

## CLI reference

| Option                        | Purpose                                            |
|-------------------------------|----------------------------------------------------|
| `serve --http`                | Select Streamable HTTP instead of stdio            |
| `--bind <IP:PORT>`            | Listener, default `127.0.0.1:8000`                 |
| `--token-file <PATH>`         | Preferred single-principal credential source       |
| `--principal-config <PATH>`   | Multi-principal TOML configuration                 |
| `--token <VALUE>`             | Compatibility source; visible in process arguments |
| `--session-affinity workspace | thread`                                            | Select reattach-by-workspace or caller-owned IDs |
| `--allow-weak-token`          | Permit a short token for local tests only          |
| `--allow-non-loopback`        | Explicitly permit a non-loopback listener          |
| `--allowed-host <HOST>`       | Extend accepted Host authorities; repeatable       |
| `--allow-query-token`         | Accept `?token=...`; disabled by default           |
| `--max-concurrency <N>`       | Global simultaneous-request cap, default `32`      |
| `--requests-per-minute <N>`   | Per-source-IP limit, default `120`                 |

## MCP request requirements

Modern clients normally send:

```http
Content-Type: application/json
Accept: application/json, text/event-stream
MCP-Protocol-Version: 2026-07-28
Authorization: Bearer <token>
```

Winx also supports the legacy MCP HTTP initialization/session flow and returns `Mcp-Session-Id` for that lifecycle.
Remote Roots bootstrap is disabled on the shared HTTP service; the explicit `Initialize` tool selects the workspace.

## Upgrade notes for 0.2.333

- Streamable HTTP now defaults to workspace affinity.
- Repeated first calls attach rather than reset protocol-1.3 guardians.
- `winxd` automatically upgrades from a control plane that supports planned restart; existing guardians keep running.
- Existing protocol-1.2 guardians remain readable through compatible `SessionInfo` defaults.
- Old never-used guardians are aged from their socket/activity evidence instead of bulk-recreated metadata timestamps.
- A protocol-1.2 guardian cannot return the new authoritative snapshot fields; workspace affinity normally creates a new
  canonical session while old sessions remain available for explicit cleanup.
- The no-argument `prune` command now applies the two TTL tiers rather than only the 24-hour used-session TTL.

## Troubleshooting

### Guardian quota reached

Inspect the fleet:

```bash
winx-code-agent list | jq '.[] | {
  thread_id,
  running,
  background_command_ids,
  ever_ran_command,
  created_at_unix_ms,
  last_activity_unix_ms
}'
```

Apply normal retention:

```bash
winx-code-agent prune
```

Force all idle sessions out while preserving active work:

```bash
winx-code-agent prune --idle-seconds 0
```

Raising `WINX_MAX_GUARDIANS` only changes capacity. It does not solve unstable session keys; use workspace affinity or a
stable thread-ID policy.

### Session unexpectedly shared

Workspace affinity deliberately shares one shell across conversations from the same principal and canonical workspace.
Use a distinct principal or start with:

```bash
--session-affinity thread
```

and provide stable unique IDs.

### Session unexpectedly disappeared

Run:

```bash
winx-code-agent doctor
winx-code-agent list
```

Check the selected runtime, the unused/used TTLs, explicit prune/kill operations, guardian quota pressure, and whether the
server is native Windows/embedded rather than Unix daemon mode.

### Token file rejected

On Unix, verify that it is a regular file, not a symlink, and has no group/other permissions:

```bash
chmod 600 ~/.config/winx-http-token
ls -l ~/.config/winx-http-token
```

### `401 Unauthorized`

Verify the exact bearer token, remove surrounding quotes, and ensure the credential belongs to the intended principal.
OAuth discovery probes are intentionally 404; Winx uses static bearer credentials unless an external edge adds another
identity system.

### `429` or `503`

Lower polling/parallelism first. Raise `--requests-per-minute` or `--max-concurrency` only after measuring machine and
client behavior. Remember that a reverse proxy may collapse many users into one source IP.

## Security boundary

A valid principal receives the capabilities of the selected Winx mode as the operating-system user running the server.
Authentication separates clients; it does not make arbitrary shell execution harmless.

- `wcgw` is full access and `BashCommand` is not workspace-confined;
- `architect` is intended for read-oriented exploration;
- `code_writer` constrains commands and writable globs;
- secret redaction is enabled by default;
- `WINX_SANDBOX=1` adds an experimental Landlock filesystem boundary on Linux;
- Winx should not run as root or another elevated account;
- each external client should receive its own principal and token;
- leaked tokens should be rotated immediately;
- remote access should be stopped when no longer needed.

Winx does not currently provide built-in TLS, OAuth/OIDC, mTLS, or per-principal tool policy. Those controls belong at a
reviewed network edge when required. Read [SECURITY.md](../SECURITY.md) before exposing a shell-capable principal.

## Verification coverage

Automated coverage includes:

- single-token and multi-principal HTTP startup;
- bearer and opt-in query-token authentication;
- public well-known probes returning 404 while `/mcp` remains authenticated;
- rejection of non-loopback binding without acknowledgement;
- rate limiting and duplicate-principal rejection;
- modern stateless discovery and tool listing;
- legacy HTTP session initialization;
- MCP Task completion over HTTP;
- two principals using the same external ID without crossing workspaces;
- one principal/workspace reusing a canonical session despite unstable first-call IDs;
- guardian quota reclaim for never-used sessions;
- active guardian preservation under quota pressure;
- tiered default pruning;
- guardian-owned activity clocks;
- attach-or-create preserving shell PID and cwd;
- legacy metadata fallback that prefers socket age over passive tmpfs reseeding.

The implementation lives primarily in:

```text
src/http_server.rs
src/config.rs
src/server/principal.rs
src/server/handler.rs
src/daemon/lifecycle.rs
src/daemon/server.rs
tests/mcp_2026_discovery_test.rs
tests/daemon_lifecycle_test.rs
```