winx-code-agent 0.2.343

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
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
# Streamable HTTP deployment

<p align="right">
  <b>English</b> • <a href="streamable-http.pt.md">Português</a> • <a href="streamable-http.zh.md">中文</a>
</p>

Winx exposes a configurable 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`, and the default tool profile remains `full`.

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.

## Workspace session coherence

Remote `Initialize` returns two values that form one session binding:

```text
thread_id + workspace_root
```

Copy both values unchanged into every later stateful Winx call. Before selecting a PTY or performing any tool operation,
Winx verifies that the thread affinity, supplied canonical root, and initialized session agree. Missing or mixed bindings
return a structured `needs_initialize`/`conflict` tool result and do not reach the shell or filesystem.

This check does **not** confine tool targets to `workspace_root`. It identifies which project context owns the terminal,
cwd, read history, and edit state. Path authority remains a separate policy: for example, `WINX_ALLOW_PATHS=/` still lets
a coherent session read or edit supporting paths outside the project when its active mode permits. This separation keeps
real monorepo/cross-directory work possible without allowing a chat to silently inherit another project's terminal.

For a different project, call `Initialize` with `type="first_call"` and the new path, then use the returned pair. Remote
`user_asked_change_workspace` calls fail closed so a durable key is never repointed to a different project in place.
If an adapter restart removed live state for the same project, a subsequent `reset_shell` or `user_asked_mode_change`
request is safely recovered as a first call with the supplied thread/workspace pair. The response sets
`initialize_recovered_missing_session=true`; workspace changes remain fail-closed.

## 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, and return a compact response instead
  of regenerating unchanged guidelines, repository context, and orchestration instructions;
- 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.

### Conversation affinity (parallel web conversations)

Use:

```bash
--session-affinity conversation
```

when parallel conversations from one principal must work in the same repository without sharing a shell. Winx derives the
key from:

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

Identity preference is:

1. `Mcp-Session-Id`, when the transport has a stable MCP session;
2. `X-Winx-Conversation-Id`, when a reviewed gateway injects a stable opaque value;
3. the supplied first-call `thread_id`, for modern stateless clients;
4. workspace affinity, when no conversation identity exists.

The identity is hashed into an external ID such as `cv_project_<hash>` and is never written raw to tool results or usage
logs. Repeating `Initialize(first_call)` with the same conversation identity reattaches even if the supplied model-generated
ID changes; another conversation identity gets a distinct guardian, cwd, command lock, and output journal.

The gateway header is a trust input. Only inject it at an authenticated edge, strip untrusted client copies, and keep its
value stable for the lifetime of the conversation.

### 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 shell replacement still uses reset, and mode changes remain explicit. A remote project change uses a new
`FirstCall` binding instead of mutating the old workspace identity. 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"
tool_profile = "coding"

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

[[principals]]
name = "ci"
token_env = "WINX_CI_MCP_TOKEN"
allowed_tools = ["Initialize", "BashCommand", "ReadFiles"]
```

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.
- `tool_profile` defaults to `full`; `allowed_tools`, when present, is an exact replacement and cannot be empty;
- allowlist names are case-sensitive and unknown tools prevent startup.

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.

### Tool catalog profiles

Profiles reduce the `tools/list` schema payload for clients that do not need every capability. Winx also checks the same
policy before dispatch, so a client cannot call a hidden tool by name.

| Profile     | Advertised tools                                                                                      |
|-------------|-------------------------------------------------------------------------------------------------------|
| `full`      | All eleven tools (backward-compatible default)                                                        |
| `coding`    | `Initialize`, `BashCommand`, `ReadFiles`, all edit tools, `VerifyEdit`, `UndoEdit`, and `CodeMap`     |
| `read-only` | `Initialize`, `ReadFiles`, `ReadImage`, and `CodeMap`                                                 |
| `terminal`  | `Initialize` and `BashCommand`                                                                        |

For a single-token server, select a profile on the command line:

```bash
winx-code-agent serve --http --token-file ~/.config/winx-http-token \
  --tool-profile coding
```

Or construct an exact catalog by repeating `--allow-tool`; explicit names replace `--tool-profile`:

```bash
winx-code-agent serve --http --token-file ~/.config/winx-http-token \
  --allow-tool Initialize --allow-tool BashCommand --allow-tool ReadFiles
```

Catalog policy is not a shell sandbox. Any profile containing `BashCommand` still grants the command capabilities of the
initialized Winx mode and operating-system user.

## LLM orchestration contract

The MCP handshake leads with a deterministic sequencing contract: initialize once, preserve the returned `thread_id`, use
`CodeMap` before broad reads, batch `ReadFiles`, read before editing, compose related fail-fast checks with `&&`, and never
repeat a rejected call unchanged. `Initialize` also returns a bounded `<workspace_root>/.winx/tmp/session-…/` directory
for independently useful derived helpers. Those helpers remain non-canonical, preserve source-path/line provenance, and
reuse stable names instead of materializing source or command output solely for `CodeMap`. Helper maps accept one existing
file, cap their navigation payload at 12 KiB, and allow 24 unique files / 64 calls per live session; canonical maps do not share that
aggregate budget. Temporary storage is capped at 64 MiB / 128 files per session (and 256 MiB per workspace), with stale
sessions pruned after 24 hours. Helpers never encode payload in filesystem names or pollute the project root with
`.winx-*`/`.winx_tmp` artifacts. Every foreground and background PTY exports the exact directory as `WINX_TEMP_DIR`;
statically visible shell writes that bypass it are rejected with `temporary_artifact_policy`. Winx audits the managed
directory after every Bash action as well, so dynamic writes are included in the byte/file budget. The result reports
actual usage; after an overage, ordinary commands are blocked until the agent explicitly inspects and removes obsolete
helpers. An overage never triggers automatic deletion; the 24-hour stale-session pruning remains unchanged. If `Initialize` returns `initialize_workspace_already_bound` or
`workspace_change_requires_new_session`, that call is terminal for the current conversation: keep its bound pair for
allowed absolute targets, or start a new conversation for a genuinely different project. A finite post-edit check can be
supplied as `verify_command` on either edit tool, saving one network/model round trip. Extra
`WINX_SERVER_INSTRUCTIONS` are appended after those stable rules and are also included in the `Initialize` response for
clients that do not expose handshake instructions to the model.

Every tool advertises an `outputSchema` and returns a common `structuredContent` envelope. Existing text and image content
remain unchanged for older clients. The main fields are:

```json
{
  "status": "needs_read",
  "tool": "FileWriteOrEdit",
  "message": "FileWriteOrEdit failed: ...",
  "errorCode": "read_required",
  "retryable": true,
  "retrySameCall": false,
  "nextAction": {
    "tool": "ReadFiles",
    "instruction": "Perform every required read before retrying the edit.",
    "arguments": {
      "file_paths": ["/workspace/README.md:231-301"],
      "thread_id": "ws_project_hash"
    }
  },
  "requiredReads": [
    { "path": "/workspace/README.md", "ranges": ["231-301"] }
  ]
}
```

Recoverable execution failures use HTTP/JSON-RPC success with MCP `isError: true`; malformed requests and failures that
prevent a valid tool result from being serialized remain JSON-RPC errors. `BashCommand` reports `running` with
`retryAfterMs` and a `status_check` next action, while interactive turns report `awaiting_input` or `awaiting_approval`.
The shell runtime produces a separate `BashCommandState` containing process status, cwd, exit code, background ID, elapsed
time, and optional interactive turn state. The MCP adapter and MCP Tasks consume only that typed value; rendered output,
child output, and unescaped-looking command metadata are presentation data and cannot spoof orchestration.
`BashCommand.wait_policy` selects generic execution behavior: `adaptive` (default) keeps short calls inline and promotes
an already-running foreground command only when Tasks and generation-bound runtime actions were negotiated; `until_complete` is accepted only for a foreground Command and creates a Task immediately or
uses a 60-second bounded synchronous fallback; `return_early` always stays inline and caps `wait_for_seconds` at 5 seconds.
Using `until_complete` with a background, status, input, screen, or wait action returns a normal recoverable tool result
(`errorCode: wait_policy_incompatible_with_action`) whose `nextAction.arguments` corrects the policy to `return_early`.
`wait_for_seconds` is bounded by the chosen policy. Task routing never depends on client
identity. A client that advertises the `io.winx/compact-bash-output` extension receives the runtime's trailer-free payload;
without that explicit capability, the historical text output is unchanged.

`ReadImage` validates content as JPEG, PNG, GIF, or WebP instead of trusting the filename. Sources are capped at 50 MiB
with independent decoded-dimension and allocation limits. Delivery is bounded to 2 MiB and a 2560 px long edge by
resizing/re-encoding oversized inputs. A bounded per-session content-fingerprint cache replaces unchanged repeats with a
compact structured reference; `force=true` is the explicit escape hatch when a client genuinely needs the bytes again.

`FileWriteOrEdit`, `ApplyPatch`, and `MultiFileEdit` accept optional `verify_command` and `verify_wait_for_seconds` (default `15`, maximum
`60`). Verification runs only after a successful commit, as a foreground command under the same mode allowlist as
`BashCommand`. Exit code zero completes the combined result. A non-zero exit keeps the committed edit result at
`isError: false` with `status: completed_with_issues`, `errorCode: verification_failed`, and `data.edit_applied: true`.
Its exact `VerifyEdit` next action reruns only the check after corrective changes. A check still running at the bounded
wait returns the normal `BashCommand` `status_check` next action. A principal must permit the edit tool and `BashCommand`;
custom allowlists cannot expose `VerifyEdit` without `BashCommand`.

Committed edits receive a compact persisted mutation receipt for 30 minutes. Exact duplicate calls are single-flight and
replayed without another write or verification command while target hashes match. If a target changed, Winx returns
`mutation_postcondition_changed` instead of overwriting newer state. Three SEARCH conflicts on the same session/target
escalate to `recovery_exhausted`, which removes the automatic retry action and tells the agent to change strategy.

Generation-bound routing is negotiated with the effective per-session guardian, not inferred from the control daemon's
version. The adapter keeps that guardian negotiation on an epoch-bound session channel, so ordinary daemon calls add no
repeated control hello round trips; generation-bound actions perform a final guardian check before relay. A closed channel forces renegotiation before another Task-bound launch.

A `ReadFiles` batch containing one or more failed paths returns `isError: true`; content from successful paths remains in the
same response, with `successful_files` and `failed_files` counts. Every successful file also returns a canonical path,
opaque SHA-256 revision, exact visible ranges, and continuation line. `ApplyPatch` consumes that revision as a
compare-and-swap token and refuses stale or unread ranges before writing. `MultiFileEdit` preserves planning failure semantics:
unread and stale files return `needs_read`, while missing or ambiguous SEARCH blocks return `conflict`, all without writing
any file and with a concrete `ReadFiles` recovery action. Batched reads run filesystem and tokenization work in a bounded
parallel pool (`WINX_READ_PARALLELISM`, default `4`, maximum `32`) but publish results and read coverage in request order.
MCP Task results retain the same envelope.

## 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

# Offline aggregation and recovery-contract audit; does not open the log writer
winx-code-agent report --last 10000 --since-minutes 120
```

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

## Persistent usage telemetry

Set a path to write only structured `winx::usage` events through a non-blocking JSONL writer:

```bash
WINX_USAGE_LOG="$HOME/.local/state/winx/usage.jsonl" \
WINX_USAGE_LOG_ROTATION=daily \
WINX_USAGE_LOG_KEEP_DAYS=7 \
winx-code-agent serve --http --token-file ~/.config/winx-http-token
```

Rotation accepts `daily` (default), `hourly`, or `never`; retention applies to daily/hourly files, and `0` disables pruning.
On Unix, every initial and rotated file is created/opened with `O_NOFOLLOW` and mode `0600`; an existing broader mode is
reduced to `0600`, and newly created log directories use `0700`. Each tool event contains the tool/action, fixed privacy-safe
command category, exact build identity, principal, scoped
thread, hashed request and MCP-session correlation, client name and version, negotiated protocol, outcome, result status,
duration, response size, batch item count, and the configured worker limit (`0` for non-batched tools). HTTP events contain
peer, method, status, and duration. Initialize events additionally record the transition (`created`, `attached_existing`,
or an explicit change), whether it reused or recovered a missing session, compact/full response mode, generated context/guideline sizes,
initial-file count, and effective code-writer policy strength. Command text, file contents, tool
arguments, URLs, output, bearer tokens, and raw conversation identities are never written to this sink. Ordinary warnings and diagnostics
continue to stderr according to `RUST_LOG` and `WINX_LOG_FORMAT`.

`winx-code-agent report` discovers rotated siblings automatically, supports build/time filters, correlates tool calls with
HTTP requests, reports latency/bytes/recovery behavior, and audits unsafe retry invariants. It disables the usage writer
while reading, so generating a report cannot append to, rotate, or prune the observed logs.

For a quick per-tool latency view across the active and rotated files:

```bash
jq -s '
  def pct($p): sort | .[((length - 1) * $p | floor)];
  [.[] | select(.fields.event == "tool_call") | .fields]
  | group_by(.tool)
  | map(. as $calls | {
      tool: $calls[0].tool,
      calls: ($calls | length),
      p50_ms: ([$calls[].duration_ms] | pct(0.50)),
      p95_ms: ([$calls[].duration_ms] | pct(0.95)),
      avg_response_bytes: (([$calls[].response_bytes] | add) / ($calls | length))
    })
' ~/.local/state/winx/usage.jsonl*
```

Use `request_id` to correlate a `tool_call` with its `http_request`; the difference between their durations approximates
transport/adapter overhead without exposing payloads.

Landlock is applied before the non-blocking logging worker is created, so the writer inherits the same domain as Tokio and
PTY children. With `WINX_SANDBOX=1`, the usage-log path must therefore be under the startup workspace, `/tmp`, or an
explicit `WINX_SANDBOX_RW_PATHS` root; startup fails rather than silently creating an unconstrained writer outside that
policy.

`winx-code-agent doctor` reports whether a usage log is configured and whether file tools are in `workspace`, `extended`,
or `unconfined` containment mode.

## 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 |
| `--tool-profile <PROFILE>`    | Single-principal catalog: `full`, `coding`, `read-only`, or `terminal` |
| `--allow-tool <NAME>`         | Build an exact single-principal catalog; repeatable |
| `--session-affinity <MODE>`   | Select `workspace`, `conversation`, or caller-owned `thread` 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 and later

- Protocol `1.6` identifies control and guardian roles and their exact package/revision/dirty build, and advertises
  cancellable action reservations. `winx-code-agent doctor` reports split control sockets and mixed guardian builds so
  an adapter never silently mistakes an old sibling binary for the executable the user just installed.
- Protocol `1.5` adds an optional trailer-free runtime payload and generation-bound actions. Protocol-1.4 guardians
  remain usable through bounded synchronous wait-policy fallback; adapters use legacy output when the compact field is
  absent. A newer control plane never substitutes its own capabilities for those of an older session guardian.
- Protocol `1.4` adds the `typed_action_result` capability and transports runtime-owned `BashCommandState` separately from
  terminal text. The control plane upgrades automatically when possible. Existing older guardians remain listable and
  attachable, but execution fails closed until the affected durable session is removed and initialized again; no
  text-parser fallback is used.
- 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, or mTLS. Those controls belong at a reviewed network edge when
required. Per-principal tool policy narrows MCP discovery and dispatch but is not a substitute for transport security or
an operating-system sandbox. 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;
- per-principal tool discovery and dispatch enforcement;
- 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
```