@kontextmind/kxm 0.7.95 → 0.7.97

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.kxm/README.md +39 -9
  3. package/CHANGELOG.md +23 -1
  4. package/README.md +147 -257
  5. package/SECURITY.md +21 -12
  6. package/docs/README.md +133 -54
  7. package/docs/adr/ADR-0002-browser-automation-steel-doks.md +24 -18
  8. package/docs/adr/ADR-0003-sqlite-only-store.md +100 -0
  9. package/docs/adr/ADR-0004-edge-identity-authentik.md +99 -0
  10. package/docs/adr/README.md +33 -0
  11. package/docs/concepts/architecture.md +262 -0
  12. package/docs/concepts/data-and-storage.md +194 -0
  13. package/docs/concepts/trust-model.md +153 -0
  14. package/docs/contracts/README.md +22 -14
  15. package/docs/contracts/effects-and-recovery.md +3 -0
  16. package/docs/contracts/migration.md +2 -2
  17. package/docs/contracts/routing.md +6 -5
  18. package/docs/contributing/assignment-runner.md +388 -0
  19. package/docs/contributing/ci-and-release.md +231 -0
  20. package/docs/contributing/development.md +362 -0
  21. package/docs/contributing/harness-routing-internals.md +192 -0
  22. package/docs/{packages.md → contributing/packages.md} +13 -15
  23. package/docs/{skills → contributing}/repo-work-delivery.md +20 -21
  24. package/docs/contributing/test-matrix.md +208 -0
  25. package/docs/{tui-components.md → contributing/tui-components.md} +30 -22
  26. package/docs/contributing/writing-docs.md +340 -0
  27. package/docs/glossary.md +471 -0
  28. package/docs/guides/agent-skills.md +137 -0
  29. package/docs/guides/browser-automation.md +160 -0
  30. package/docs/guides/context-and-memory.md +352 -0
  31. package/docs/guides/continuous-improvement.md +228 -0
  32. package/docs/guides/governed-skills.md +173 -0
  33. package/docs/guides/nous-providers.md +186 -0
  34. package/docs/guides/peer-messaging.md +304 -0
  35. package/docs/guides/pi-workers.md +219 -0
  36. package/docs/guides/provenance-gates.md +313 -0
  37. package/docs/guides/webhook-workflows.md +399 -0
  38. package/docs/kb/how-credentials-retrieved-safely.md +38 -12
  39. package/docs/kb/how-to-capture-and-annotate-section.md +15 -13
  40. package/docs/kb/how-to-connect-playwright-to-steel.md +16 -11
  41. package/docs/kb/how-to-recover-expired-session-or-orphan.md +26 -16
  42. package/docs/kb/how-to-resume-after-mfa.md +19 -11
  43. package/docs/kb/how-to-take-over-session.md +17 -13
  44. package/docs/kb/why-authentication-disappeared.md +22 -14
  45. package/docs/kb/why-automation-opened-different-browser.md +23 -14
  46. package/docs/kb/why-session-viewer-cannot-control.md +13 -12
  47. package/docs/operations/backup-and-restore.md +248 -0
  48. package/docs/operations/deploy.md +307 -0
  49. package/docs/operations/monitoring.md +209 -0
  50. package/docs/operations/runtime-sync.md +192 -0
  51. package/docs/operations/troubleshooting.md +266 -0
  52. package/docs/operations/upgrade.md +124 -0
  53. package/docs/prompts/browser-annotate-feedback.md +7 -7
  54. package/docs/prompts/browser-diagnose-recover.md +11 -10
  55. package/docs/prompts/browser-explore.md +7 -7
  56. package/docs/prompts/browser-repro-fix.md +7 -7
  57. package/docs/prompts/browser-start.md +12 -11
  58. package/docs/prompts/browser-takeover.md +8 -8
  59. package/docs/{cli-reference.md → reference/cli-reference.md} +88 -46
  60. package/docs/{config-reference.md → reference/config-reference.md} +159 -148
  61. package/docs/reference/configuration.md +299 -0
  62. package/docs/reference/harness-routing.md +508 -0
  63. package/docs/reference/http-api.md +203 -0
  64. package/docs/reference/tools.md +370 -0
  65. package/docs/{workflow-guide.md → reference/workflow-catalog.md} +92 -153
  66. package/docs/reference/workflow-definitions.md +286 -0
  67. package/docs/start/first-workflow.md +287 -0
  68. package/docs/start/install.md +146 -0
  69. package/docs/start/quickstart-claude-code.md +405 -0
  70. package/docs/start/quickstart-pi.md +213 -0
  71. package/docs/templates/README.md +78 -73
  72. package/docs/templates/adr.md +13 -13
  73. package/docs/templates/architecture.md +55 -71
  74. package/docs/templates/bug-fix.md +13 -16
  75. package/docs/templates/feature.md +14 -19
  76. package/docs/templates/handoff.md +44 -46
  77. package/docs/templates/postmortem.md +30 -43
  78. package/docs/templates/research.md +15 -20
  79. package/docs/templates/review.md +49 -50
  80. package/docs/templates/runbook.md +38 -30
  81. package/docs/templates/test-plan.md +16 -23
  82. package/docs/templates/test-report.md +14 -17
  83. package/examples/README.md +9 -5
  84. package/examples/provenance-workflow.json +1 -1
  85. package/examples/webhook-workflows/jira-development.json +59 -0
  86. package/examples/webhook-workflows/jira-issue-updated.json +12 -0
  87. package/examples/workflow-signal.ts +4 -5
  88. package/package.json +1 -1
  89. package/packages/core/tui/README.md +1 -1
  90. package/plugins/kxm/.claude-plugin/plugin.json +1 -1
  91. package/plugins/kxm/README.md +31 -32
  92. package/plugins/kxm/dist/claude-hook.js +11 -1
  93. package/plugins/kxm/dist/cli.js +164 -79
  94. package/plugins/kxm/dist/client.js +3 -1
  95. package/plugins/kxm/dist/core.js +11 -1
  96. package/plugins/kxm/dist/extension.js +45 -13
  97. package/plugins/kxm/dist/mcp-server.js +20 -4
  98. package/plugins/kxm/dist/runtime-supervisor.js +1 -3
  99. package/plugins/kxm/dist/runtime.js +18 -4
  100. package/plugins/kxm/dist/server.js +115 -20
  101. package/plugins/kxm/package.json +1 -1
  102. package/plugins/kxm/skills/kxm/references/protocol.md +3 -1
  103. package/plugins/kxm/skills/kxm-browser-auth/SKILL.md +1 -1
  104. package/plugins/kxm/skills/kxm-browser-diagnostics/SKILL.md +5 -5
  105. package/plugins/kxm/skills/kxm-browser-explore/SKILL.md +2 -2
  106. package/plugins/kxm/skills/kxm-browser-session/SKILL.md +10 -13
  107. package/plugins/kxm/skills/kxm-browser-takeover/SKILL.md +1 -1
  108. package/plugins/kxm/skills/kxm-browser-verify/SKILL.md +1 -1
  109. package/plugins/kxm/skills/kxm-context-memory/SKILL.md +13 -4
  110. package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +3 -1
  111. package/plugins/kxm/skills/kxm-mind-setup/SKILL.md +2 -1
  112. package/plugins/kxm/skills/kxm-project-setup/SKILL.md +31 -54
  113. package/plugins/kxm/skills/kxm-projects/SKILL.md +1 -1
  114. package/plugins/kxm/skills/kxm-protocol/SKILL.md +1 -1
  115. package/plugins/kxm/skills/kxm-routing-improve/SKILL.md +15 -7
  116. package/plugins/kxm/skills/kxm-runs/SKILL.md +11 -5
  117. package/plugins/kxm/skills/kxm-session/SKILL.md +1 -1
  118. package/plugins/kxm/skills/kxm-tasks/SKILL.md +9 -7
  119. package/plugins/kxm/skills/kxm-workflow/SKILL.md +10 -2
  120. package/plugins/kxm/src/cli/system.ts +1 -1
  121. package/plugins/kxm/src/cli/workflows.ts +12 -7
  122. package/plugins/kxm/src/cli.ts +22 -8
  123. package/plugins/kxm/src/client.ts +4 -0
  124. package/plugins/kxm/src/commands.ts +23 -1
  125. package/plugins/kxm/src/extension.ts +20 -14
  126. package/plugins/kxm/src/github-watch.ts +8 -5
  127. package/plugins/kxm/src/hub-env.ts +19 -1
  128. package/plugins/kxm/src/hub.ts +105 -21
  129. package/plugins/kxm/src/improve-sources.ts +2 -7
  130. package/plugins/kxm/src/init-guide-setup.ts +1 -1
  131. package/plugins/kxm/src/mcp-server.ts +9 -2
  132. package/plugins/kxm/src/modes.ts +1 -1
  133. package/plugins/kxm/src/runtime-store.ts +23 -0
  134. package/plugins/kxm/src/workflow.ts +70 -1
  135. package/schemas/README.md +1 -1
  136. package/scripts/smoke-multi-pi.mjs +5 -1
  137. package/docs/agent-communication-envelopes-and-gates.md +0 -553
  138. package/docs/agent-skills.md +0 -198
  139. package/docs/architecture.md +0 -245
  140. package/docs/assignment-runner.md +0 -264
  141. package/docs/browser-automation.md +0 -139
  142. package/docs/configuration.md +0 -437
  143. package/docs/continuous-improvement.md +0 -226
  144. package/docs/getting-started.md +0 -277
  145. package/docs/harness-routing.md +0 -616
  146. package/docs/kb/qa-authentik-authentication.md +0 -97
  147. package/docs/kb/qa-extension-install-and-hub-bootstrap.md +0 -85
  148. package/docs/kb/qa-hub-on-a-public-host.md +0 -48
  149. package/docs/kb/qa-sqlite-vs-duckdb.md +0 -35
  150. package/docs/kb/qa-what-the-hub-stores.md +0 -64
  151. package/docs/kxm-handbook.md +0 -1181
  152. package/docs/operations.md +0 -510
  153. package/docs/operator-pi-packages.md +0 -67
  154. package/docs/provenance-gates.md +0 -295
  155. package/docs/skills.md +0 -47
  156. package/docs/test-matrix.md +0 -132
  157. package/docs/troubleshooting.md +0 -322
  158. package/docs/webhook-workflows.md +0 -240
@@ -0,0 +1,307 @@
1
+ # Deploy KXM
2
+
3
+ Run one KXM [hub](../glossary.md#hub) and one [Runtime](../glossary.md#runtime) supervisor as a supervised service, on loopback or behind a trusted proxy. This page is for operators who take KXM past a single developer terminal. You end with a hub that restarts cleanly, clients bound to it with the right credential, and, for hosted use, one isolated box per tenant.
4
+
5
+ ## Before you begin
6
+
7
+ - Install the `kxm` CLI on the host ([Install KXM](../start/install.md)) and run `kxm init` in the project checkout.
8
+ - Read the [trust model](../concepts/trust-model.md) if anyone other than you will hold a KXM credential.
9
+ - Have a service manager (systemd, launchd, or a Windows service wrapper) and a place to keep secrets outside Git.
10
+
11
+ ## Know the deployment envelope
12
+
13
+ KXM is a single-node service. One Node.js process serves the hub over HTTP and server-sent events, and it is the only writer of one SQLite database. The Runtime supervisor is a second local process that owns run event stores and pushes summaries to the hub.
14
+
15
+ | KXM provides | KXM does not provide |
16
+ |---|---|
17
+ | Durable restart recovery of agents, messages and workflow runs | Clustering, leader election or shared-state failover |
18
+ | Project-scoped tokens and per-agent keys | Per-user identity, roles or an identity provider |
19
+ | Signed webhook workflows and callbacks | Exactly-once delivery (it is at-least-once) |
20
+ | Health, readiness, metrics and structured logs | TLS termination (the hub speaks plain HTTP) |
21
+ | Graceful shutdown and PID-claim recovery | Encryption at rest (SQLite files are plain) |
22
+
23
+ Do not put a load balancer in front of several hubs. Each hub has its own presence, queue and database, so agents on different hubs never see each other.
24
+
25
+ ## Choose loopback or a network bind
26
+
27
+ The hub listens on `KXM_HOST`, which defaults to `127.0.0.1`, and on `KXM_PORT`, which defaults to `7331`. Keep loopback unless a client on another machine must reach the hub.
28
+
29
+ The hub process refuses a non-loopback bind without an admin token:
30
+
31
+ ```text
32
+ KXM_AUTH_TOKEN is required when binding beyond localhost
33
+ ```
34
+
35
+ `kxm hub start` always supplies an admin token, generating one if needed, so that check alone protects nothing. Before you bind anywhere else, put all of the following in place:
36
+
37
+ 1. An admin token and a distinct project token for every project (see [Manage credentials](#manage-credentials)).
38
+ 2. A TLS-terminating reverse proxy that is the only public listener.
39
+ 3. Network restriction in front of the proxy: an IP allowlist or a VPN.
40
+ 4. Buffering disabled, and read timeouts above 15 seconds, for the event streams `/v1/events` and `/v1/ops/events`. The hub sends a heartbeat every 15 seconds.
41
+ 5. Encrypted storage for the state directory, because message bodies are stored as sent.
42
+
43
+ > [!WARNING]
44
+ > Never expose the hub port directly to the internet. The built-in rate limit keys on a client-supplied agent header or the socket address, which is the proxy's address behind a proxy, and it resets on restart. It is a courtesy limit, not abuse protection.
45
+
46
+ ## Manage credentials
47
+
48
+ The hub checks two kinds of bearer token. Give each holder only the one it needs.
49
+
50
+ | Credential | Who holds it | What it opens |
51
+ |---|---|---|
52
+ | Admin token (`KXM_AUTH_TOKEN` on the hub) | The hub and the operator terminal | `/metrics`, `/v1/ops/*`, admin context calls, state promotion, quorum degradation, and any project missing from the project-token map |
53
+ | Project token (an entry in `KXM_PROJECT_TOKENS`) | Agents and the Runtime for that project | Registration, discovery, messaging, context, Runtime sync and assigned workflow operations for that project only |
54
+
55
+ List every project in `KXM_PROJECT_TOKENS`. A project that is not in the map falls back to the admin token, so an admin-token holder can register agents in any unlisted project name. Webhook start and signal secrets are separate HMAC secrets; see [Run webhook workflows](../guides/webhook-workflows.md).
56
+
57
+ ### How the hub finds its token
58
+
59
+ `kxm hub start` resolves credentials in this order and never prints a token:
60
+
61
+ 1. `KXM_AUTH_TOKEN` and `KXM_PROJECT_TOKENS` from the environment. Explicit values are also saved to the credential file so restarts keep them.
62
+ 2. The credential file `hub-env.json` (schema `kxm.hub-env.v1`, mode `0600`) under the user state root. The root is `KXM_STATE_HOME`, or the platform default listed in [State outside the project](../reference/config-reference.md#state-outside-the-project).
63
+ 3. A newly generated admin token, saved to that file. The hub therefore never starts without an admin token by accident.
64
+
65
+ Expected output on first start:
66
+
67
+ ```text
68
+ kxm hub: using newly generated KXM_AUTH_TOKEN from /home/kxm/.local/state/kxm/hub-env.json
69
+ ```
70
+
71
+ Operator clients on the same machine (`kxm dash`, the Runtime) read the same file. They use `KXM_AUTH_TOKEN` from their environment first, then the saved project token for their project, then the saved admin token. Agent clients (the Pi extension, the Claude Code MCP server, and the `kxm peer` and `kxm workflow` agent commands) never fall back to the admin token.
72
+
73
+ To rotate the admin token, restart the hub with a new `KXM_AUTH_TOKEN`; the hub saves it in place of the old one and keeps the saved project tokens. Rotate a project token by restarting with an updated full `KXM_PROJECT_TOKENS` map. Then restart every client that held the old value. Deleting `hub-env.json` also works, but it drops the saved project tokens too.
74
+
75
+ ### Generate and inject tokens
76
+
77
+ Create tokens once, store them with your secret manager or in files readable only by the service account, and inject them through the environment. Never pass a token as a command-line argument.
78
+
79
+ ```bash
80
+ umask 077
81
+ mkdir -p /etc/kxm
82
+ openssl rand -hex 32 > /etc/kxm/admin-token
83
+ # One entry per project the hub serves
84
+ node -e 'const t = () => require("node:crypto").randomBytes(32).toString("hex");
85
+ process.stdout.write(JSON.stringify({ product: t(), api: t() }))' > /etc/kxm/project-tokens.json
86
+ ```
87
+
88
+ The map must list every project the hub serves.
89
+
90
+ `/etc/kxm/project-tokens.json`:
91
+
92
+ ```json
93
+ {"product": "replace-with-product-token", "api": "replace-with-api-token"}
94
+ ```
95
+
96
+ ## Start the hub
97
+
98
+ `kxm hub start` runs in the foreground from the project checkout. It writes the PID claim `.kxm/state/hub.pid`, the database `.kxm/state/kxm.db`, and the log `.kxm/logs/kxm-hub.jsonl` unless you relocate them.
99
+
100
+ ```bash
101
+ cd /srv/kxm/product
102
+ export KXM_HOST=127.0.0.1
103
+ export KXM_PORT=7331
104
+ export KXM_AUTH_TOKEN="$(cat /etc/kxm/admin-token)"
105
+ export KXM_PROJECT_TOKENS="$(cat /etc/kxm/project-tokens.json)" # every project, not only this one
106
+ kxm hub start
107
+ ```
108
+
109
+ <details><summary>PowerShell</summary>
110
+
111
+ ```powershell
112
+ Set-Location C:\srv\kxm\product
113
+ $env:KXM_HOST = "127.0.0.1"
114
+ $env:KXM_PORT = "7331"
115
+ $env:KXM_AUTH_TOKEN = Get-Content C:\kxm\secrets\admin-token
116
+ $env:KXM_PROJECT_TOKENS = Get-Content C:\kxm\secrets\project-tokens.json -Raw
117
+ kxm hub start
118
+ ```
119
+
120
+ </details>
121
+
122
+ > [!WARNING]
123
+ > `KXM_PROJECT_TOKENS` replaces the hub's saved project map instead of adding to it, and the hub saves the replacement. A one-project value silently removes every other project, and their agents then fail with `invalid_auth`. Always pass the full map. To add a project to a hub started by hand, use the merge command in [Start the hub](../start/quickstart-claude-code.md#3-start-the-hub) and [Give the project a token on the running hub](../start/quickstart-claude-code.md#give-the-project-a-token-on-the-running-hub).
124
+
125
+ Expected output:
126
+
127
+ ```text
128
+ kxm hub listening at http://127.0.0.1:7331; storage=/srv/kxm/product/.kxm/state/kxm.db; auth=token
129
+ ```
130
+
131
+ Confirm from another terminal:
132
+
133
+ ```bash
134
+ kxm hub view
135
+ ```
136
+
137
+ Expected output:
138
+
139
+ ```text
140
+ hub health=true ready=true · loopback hub
141
+ ```
142
+
143
+ ## Bind clients to the hub
144
+
145
+ Clients pick their hub in this order: `KXM_SERVER_URL`, then the machine binding written by `kxm hub bind`, then `http://127.0.0.1:7331`. The binding lives in `hub-binding.json` under the user state root and is labelled `loopback` or `remote`.
146
+
147
+ ```bash
148
+ # Same machine as the hub
149
+ kxm hub bind http://127.0.0.1:7331
150
+ ```
151
+
152
+ Expected output:
153
+
154
+ ```text
155
+ bound hub http://127.0.0.1:7331 · loopback · health=on
156
+ ```
157
+
158
+ A remote URL puts a bearer token on the network, so `kxm hub bind` refuses it when this machine has no credential for the project:
159
+
160
+ ```text
161
+ refusing to bind remote hub https://hub.example.com with no credential for project product; export KXM_AUTH_TOKEN (or point KXM_STATE_HOME at the hub-env record that already holds one), then re-run; the hub itself requires a token beyond loopback
162
+ ```
163
+
164
+ Export the project token for this machine, then bind again. A successful remote bind ends with `· token leaves this machine`. Only `localhost`, names ending in `.localhost`, `::1` and `127.x.x.x` count as loopback; `0.0.0.0`, a LAN address or any other host name is remote. Remove a binding with `kxm hub unbind`.
165
+
166
+ ## Supervise the hub and Runtime
167
+
168
+ ### The hub
169
+
170
+ Run `kxm hub start` under your service manager with a stable working directory, secrets injected from protected files, restart on failure, and a stop timeout of at least 15 seconds. On `SIGTERM` the hub stops accepting connections, closes event streams, waits up to 5 seconds for active requests, and closes SQLite; its wrapper force-stops the server after 10 seconds.
171
+
172
+ Example `/etc/systemd/system/kxm.service` (illustrative, adjust paths and names):
173
+
174
+ ```ini
175
+ [Unit]
176
+ Description=KXM hub
177
+ After=network.target
178
+
179
+ [Service]
180
+ User=kxm
181
+ WorkingDirectory=/srv/kxm/product
182
+ Environment=KXM_HOST=127.0.0.1
183
+ Environment=KXM_PORT=7331
184
+ Environment=KXM_STATE_HOME=/srv/kxm/state
185
+ # Lines KXM_AUTH_TOKEN=... and KXM_PROJECT_TOKENS='{...every project...}', mode 0600
186
+ EnvironmentFile=/etc/kxm/hub.env
187
+ # Path from: command -v kxm
188
+ ExecStart=/usr/local/bin/kxm hub start
189
+ Restart=on-failure
190
+ RestartSec=5
191
+ TimeoutStopSec=20
192
+
193
+ [Install]
194
+ WantedBy=multi-user.target
195
+ ```
196
+
197
+ The PID claim prevents a second hub on the same state directory. A claim whose wrapper died is reclaimed on the next start, and an orphaned server child is stopped first. `kxm hub stop` also stops long-lived workers that recorded claims. Set `KXM_DAEMON=1` to stop mirroring log lines to stdout when your service manager already reads the log file.
198
+
199
+ The Pi extension starts a hub in the background when none is running (`hub.autoStart: background`). On a service host, set `hub.autoStart: off` in [personalization settings](../reference/config-reference.md#personalization-settings-kxmconfigv1) so only the service manager starts the hub.
200
+
201
+ ### The Runtime supervisor
202
+
203
+ The supervisor is a singleton per user state root. It always detaches from the command that starts it and has no foreground mode, so a service manager cannot own its process directly. Runtime commands such as `kxm run`, `kxm runs list` and `kxm runs cancel` start it on demand.
204
+
205
+ 1. Start it after the hub with `kxm runtime start`.
206
+ 2. Check it from a timer with `kxm runtime status`, which exits `1` when it is not running.
207
+ 3. Stop it before the hub with `kxm runtime stop`. The command returns once shutdown starts; running drives get up to `KXM_RUNTIME_STOP_GRACE_MS` (default 30 seconds, maximum 10 minutes) to finish.
208
+
209
+ > [!NOTE]
210
+ > The supervisor inherits the environment of the command that started it, including `KXM_SERVER_URL` and `KXM_AUTH_TOKEN`. After you change either, stop and start the supervisor. A `kxm hub bind` change is picked up on the next sync tick.
211
+
212
+ Long-lived Pi workers need one service unit each; see [Run supervised Pi workers](../guides/pi-workers.md).
213
+
214
+ ## Host one tenant per box
215
+
216
+ For a hosted service, a tenant is a machine, not a row. Each box runs one hub, one Runtime supervisor and one state set, and a separate multi-user web application (the portal) is the only thing users reach. The hub has no tenant column and no user accounts, so do not serve unrelated teams from one hub: the admin token and the shared database collapse all isolation.
217
+
218
+ The following diagram shows that browsers stop at the portal, which calls the hub and Runtime over loopback.
219
+
220
+ ```mermaid
221
+ flowchart LR
222
+ B[Browser]
223
+ subgraph Box["Tenant box"]
224
+ P["Reverse proxy<br/>TLS + Authentik forward auth"]
225
+ PB["Portal backend<br/>users and sessions"]
226
+ HUB[("KXM hub<br/>127.0.0.1")]
227
+ RT["Runtime supervisor<br/>127.0.0.1"]
228
+ P -->|"strips Authorization and x-kxm-* headers"| PB
229
+ PB -->|"machine token over loopback"| HUB
230
+ PB -->|"kxm tenant status"| RT
231
+ RT -->|"sync events"| HUB
232
+ end
233
+ B -->|HTTPS| P
234
+ B -.->|"blocked: no public hub or Runtime port"| HUB
235
+ ```
236
+
237
+ Hold these invariants on every tenant box:
238
+
239
+ 1. **A service account, not root.** Session isolation routes model context; it is not a sandbox against a hostile process under the same account. Give untrusted workers separate accounts or containers.
240
+ 2. **Explicit, stable paths.** Set `KXM_WORKSPACE_DIR`, `KXM_STATE_DIR`, `KXM_DATA_PATH`, `KXM_LOG_PATH` and `KXM_STATE_HOME` instead of inheriting a home directory, and pin `KXM_HOST=127.0.0.1`. `kxm backup` ignores `KXM_STATE_DIR` and `KXM_DATA_PATH`, so on such a box it finds no hub store and fails with `backup_no_stores`; use the [stopped-state backup](backup-and-restore.md#back-up-everything-else) instead.
241
+ 3. **Loopback listeners only.** Neither the hub nor the supervisor has a public port, and nothing is load-balanced across hubs.
242
+ 4. **Edge authentication.** The proxy authenticates browsers (for example with Authentik forward auth) on the portal's routes only. The hub never interprets browser identity; see [ADR-0004](../adr/ADR-0004-edge-identity-authentik.md).
243
+ 5. **Server-side machine credentials.** The portal backend keeps the hub credentials and calls the hub itself. `kxm tenant status`, which uses the admin token, gives it one labelled view of hub metadata and Runtime runs.
244
+ 6. **One restart path.** Use the service manager and PID-claim recovery above. Do not add a second manager for the hub.
245
+
246
+ ### Reverse-proxy contract
247
+
248
+ KXM ships no proxy configuration, because a generated file reads as authoritative while one missing directive can re-open header forgery. Whatever proxy you use, it must hold these properties:
249
+
250
+ - The hub and supervisor ports are not reachable from outside the box.
251
+ - The proxy strips client-supplied `Authorization`, `x-kxm-agent-id`, `x-kxm-agent-key`, `x-kxm-caller-id` and any identity or tenant headers before it adds its own validated values.
252
+ - The proxy never injects the hub admin token on a user's behalf. That makes every authenticated user a hub admin and destroys attribution.
253
+ - A browser never holds, echoes or is redirected with a hub bearer.
254
+ - If outside systems must deliver signed webhooks, forward only `POST /v1/webhooks/...` paths to the hub. They authenticate with their HMAC signature; the rest of `/v1/*` stays private.
255
+
256
+ Example (illustrative nginx shape; not generated, and not tested by KXM):
257
+
258
+ ```nginx
259
+ server {
260
+ listen 443 ssl;
261
+ server_name kxm.example.com;
262
+
263
+ location /outpost.goauthentik.io {
264
+ proxy_pass http://127.0.0.1:9000/outpost.goauthentik.io;
265
+ proxy_pass_request_body off;
266
+ proxy_set_header Content-Length "";
267
+ }
268
+
269
+ location / {
270
+ auth_request /outpost.goauthentik.io/auth/nginx;
271
+ # Drop headers a browser could forge before they reach the portal.
272
+ proxy_set_header Authorization "";
273
+ proxy_set_header X-Kxm-Agent-Id "";
274
+ proxy_set_header X-Kxm-Agent-Key "";
275
+ proxy_set_header X-Kxm-Caller-Id "";
276
+ # The portal backend, never the hub.
277
+ proxy_pass http://127.0.0.1:8080;
278
+ }
279
+ }
280
+ ```
281
+
282
+ ## Plan for scale
283
+
284
+ Measure concurrent agents, request rate, event-loop delay, database size, disk latency and reconnect frequency for your workload. Terminal messages are purged after `KXM_MESSAGE_RETENTION_MS` (7 days by default), and finished workflow runs and their journals after 7 days. Keep free disk space ahead of the database's growth, and include workflow data in privacy reviews: verified evidence snapshots stay in a run after its source messages are purged.
285
+
286
+ Durable transport does not make peer work exactly-once. Use idempotent tasks and stable idempotency keys, and keep important artifacts in Git or another system of record.
287
+
288
+ ## Troubleshooting
289
+
290
+ | Symptom | Cause | Fix |
291
+ |---|---|---|
292
+ | `KXM_AUTH_TOKEN is required when binding beyond localhost` | The hub process was started directly with a non-loopback `KXM_HOST` and no admin token | Set `KXM_HOST=127.0.0.1`, or finish the network-bind checklist and set `KXM_AUTH_TOKEN` |
293
+ | `KXM hub is already managed by PID <pid>` | A live hub already owns this state directory | Run `kxm hub stop`, or use a different workspace |
294
+ | `hub_bind_unauthenticated` from `kxm hub bind` | Remote URL and no credential for the project | Export the project token, then bind again |
295
+ | Agents fail with `invalid_auth` after a restart | `KXM_PROJECT_TOKENS` changed or dropped a project | Pass the full map again and restart the hub |
296
+ | Event streams stall behind the proxy | Response buffering or a short read timeout | Disable buffering on `/v1/events` and `/v1/ops/events`; raise the read timeout |
297
+
298
+ See [Troubleshoot KXM](troubleshooting.md) for more.
299
+
300
+ ## Next steps
301
+
302
+ - Watch the service: [Monitor KXM](monitoring.md)
303
+ - Protect its state: [Back up and restore KXM](backup-and-restore.md)
304
+ - Move to a new release: [Upgrade KXM](upgrade.md)
305
+ - Keep run facts flowing to the hub: [Operate Runtime sync and leases](runtime-sync.md)
306
+ - Understand who can do what: [Trust model](../concepts/trust-model.md)
307
+ - Every hub route: [Hub HTTP API reference](../reference/http-api.md)
@@ -0,0 +1,209 @@
1
+ # Monitor KXM
2
+
3
+ Check that the [hub](../glossary.md#hub) and the [Runtime](../glossary.md#runtime) supervisor are healthy, watch live work in the terminal dashboard, scrape Prometheus metrics, and read the structured logs. This page is for operators running KXM as a service. You end with a health check, a metrics scrape, log shipping, and a short list of alerts worth setting.
4
+
5
+ ## Before you begin
6
+
7
+ - A hub started with `kxm hub start` (see [Deploy KXM](deploy.md)).
8
+ - The admin token for `/metrics`, the operations endpoints and `kxm tenant status`. Health probes need no token, and a project token is enough for the dashboard's fallback mode.
9
+ - For Runtime checks, a KXM project (`kxm init`) on the machine that runs the supervisor.
10
+
11
+ ## Check health from the command line
12
+
13
+ Three read-only commands cover the whole machine:
14
+
15
+ ```bash
16
+ kxm hub view # hub /health and /ready, and whether the URL is loopback or remote
17
+ kxm runtime status # supervisor liveness plus per-project sync state
18
+ kxm tenant status # hub metadata and Runtime runs as one labelled view
19
+ ```
20
+
21
+ Expected output:
22
+
23
+ ```text
24
+ hub health=true ready=true · loopback hub
25
+ runtime supervisor running: rtm_74573b9df67705d8f6596518 pid 41437 on 127.0.0.1:50724
26
+ sync prj_a17d607765e144cd95b93a8715d360b6: ok (pending 0, acked 1, refused 0)
27
+ tenant prj_a17d607765e144cd95b93a8715d360b6 @ http://127.0.0.1:7331 (loopback)
28
+ hub 0/0 agents online, 0 hub runs, 0 open messages
29
+ runtime 1 runs (authoritative, on this box)
30
+ cross-check: unavailable — no run id appears in both sources (independent id spaces)
31
+ ```
32
+
33
+ `kxm hub view` exits `1` when either probe fails, and `kxm runtime status` exits `1` when the supervisor is not running, so both work in a health timer. Add `--json` for machine-readable results. The sync states are explained in [Operate Runtime sync and leases](runtime-sync.md#check-sync-status).
34
+
35
+ ### Probe the hub directly
36
+
37
+ The hub serves two unauthenticated probes. Neither counts against the rate limit.
38
+
39
+ | Endpoint | Use it for | Success |
40
+ |---|---|---|
41
+ | `GET /health` | Liveness: the process answers HTTP | `200 {"ok":true,"agents":<online count>}` |
42
+ | `GET /ready` | Readiness: SQLite answers | `200 {"ok":true,"storage":"sqlite"}`; `503` when storage fails |
43
+
44
+ ```bash
45
+ curl --fail --silent http://127.0.0.1:7331/ready
46
+ ```
47
+
48
+ Route traffic on `/ready` and restart on `/health`.
49
+
50
+ ## Watch live work with `kxm dash`
51
+
52
+ `kxm dash` opens a full-screen terminal dashboard for the current project. It never loads or renders message bodies.
53
+
54
+ ```bash
55
+ kxm dash # opens on the Agents screen
56
+ kxm dash --screen workflows # open on another screen
57
+ ```
58
+
59
+ Without a terminal, for example in a pipe, it prints one plain snapshot and exits:
60
+
61
+ ```text
62
+ kxm dash ● hub ok ● ready live ops 0/0 online updated 18:29:32 UTC · http://127.0.0.1:7331
63
+ [1 Agents 0/0] 2 Tasks 0 3 Workflows 0 4 Plans 0 5 Inbox 0 6 Procs 1/1 7 Spend 0
64
+ list detail
65
+ Nothing here yet No agent selected
66
+ tab Agents · list · 1–7 tabs · h help · a/r/d/s/c · q quit · live
67
+ ```
68
+
69
+ `--json` is refused; use `kxm hub view --json` for automation.
70
+
71
+ ### Screens
72
+
73
+ | Key | Screen | What it lists |
74
+ |---|---|---|
75
+ | `1` | Agents | Registered agents with hub-clocked presence (online, stale, offline), host, model and last seen; the detail pane adds lease expiry and the agent's open messages |
76
+ | `2` | Tasks | Running or waiting runs with a stage checklist, progress bar and attempt counts |
77
+ | `3` | Workflows | Recent runs with state, current stage and progress |
78
+ | `4` | Plans | Plan entries from workflow journals: age, run, stage and summary |
79
+ | `5` | Inbox | Open messages: state, sender, recipient, delivery mode and age |
80
+ | `6` | Procs | PID claims in the workspace state directory (hub and workers), live or dead |
81
+ | `7` | Spend | Always empty: it reads `telemetry.jsonl` from the workspace state directory, but KXM writes it under `.kxm/logs/`. Use `kxm routing report` |
82
+
83
+ ### Where the data comes from
84
+
85
+ - **With the admin token** (header `live ops`), agents, open messages, runs and plans come from the hub's `/v1/ops/snapshot`, refreshed on every `/v1/ops/events` wake-up. The Tasks and Workflows screens then show the hub's webhook workflow runs only. Use `kxm runs list` or `kxm tenant status` for Runtime runs.
86
+ - **With a project token** (header `presence/local`), the operations endpoints refuse, so the dashboard registers a hidden observer, opens a presence-only event stream, and reads the rest from the local hub database and Runtime run stores. It refuses a hub that cannot guarantee a presence-only stream.
87
+
88
+ The dashboard uses `KXM_AUTH_TOKEN`, then the saved project token for its project, then the saved admin token. When the project has a saved project token, the dashboard therefore starts in fallback mode; export the admin token as `KXM_AUTH_TOKEN` in that terminal for admin mode.
89
+
90
+ ### Keys
91
+
92
+ | Key | Action |
93
+ |---|---|
94
+ | `1`–`7` | Jump to a screen |
95
+ | `Tab` or `]`, `Shift+Tab` or `[` | Next or previous screen |
96
+ | `←`, `→` (also `Enter`, `Space`) | List pane or detail pane |
97
+ | `↑`, `↓` | Select a row |
98
+ | `PgUp`, `PgDn` (also `Ctrl+U`, `Ctrl+D`) | Scroll |
99
+ | `h` or `?` | Toggle help |
100
+ | `q`, `Ctrl+C`, `Esc` | Quit (`Esc` closes help first) |
101
+
102
+ > [!WARNING]
103
+ > The dashboard is not read-only. The keys `a`, `r`, `s` and `c` post approve, reject, signal and cancel requests to a hub route that does not exist, so they change nothing, although the status line reports the action. The `d` key does the same and then creates a Git branch and worktree under `.kxm/worktrees/` in the current directory. Act on runs with `kxm runs cancel`, `kxm gate signal` or `kxm gate degrade` instead.
104
+
105
+ ## Scrape metrics
106
+
107
+ `GET /metrics` returns Prometheus text. It needs the admin token whenever the hub has one, including on loopback, and `kxm hub start` always creates one. Counters live in memory and restart from zero with the hub.
108
+
109
+ Example Prometheus job:
110
+
111
+ ```yaml
112
+ scrape_configs:
113
+ - job_name: kxm
114
+ metrics_path: /metrics
115
+ authorization:
116
+ type: Bearer
117
+ credentials_file: /etc/kxm/admin-token
118
+ static_configs:
119
+ - targets: ["127.0.0.1:7331"]
120
+ ```
121
+
122
+ For a one-off look, keep the token out of the process list by reading the header from a private file:
123
+
124
+ ```bash
125
+ umask 077
126
+ printf 'Authorization: Bearer %s\n' "$KXM_AUTH_TOKEN" > "$HOME/.kxm-metrics-header"
127
+ curl --silent --header @"$HOME/.kxm-metrics-header" http://127.0.0.1:7331/metrics
128
+ ```
129
+
130
+ | Area | Metrics |
131
+ |---|---|
132
+ | Presence and load | `kxm_online_agents` (gauge), `kxm_messages` (retained messages, gauge), `kxm_requests_total`, `kxm_errors_total`, `kxm_registrations_total` |
133
+ | Messages | `kxm_messages_sent_total`, `kxm_messages_replied_total`, `kxm_messages_cancelled_total`, `kxm_messages_expired_total`, `kxm_messages_purged_total` |
134
+ | Workflows | `kxm_webhooks_accepted_total`, `kxm_workflow_checkpoints_total`, `kxm_workflow_waits_total`, `kxm_workflow_signals_total`, `kxm_workflow_wait_timeouts_total`, `kxm_workflow_degradations_total`, `kxm_workflow_journal_entries_total` |
135
+ | Context | `kxm_context_requests_total` |
136
+ | Leases | `kxm_leases_granted_total`, `kxm_leases_refused_total`, `kxm_leases_released_total` |
137
+ | Runtime sync | `kxm_sync_events_accepted_total`, `kxm_sync_events_duplicate_total`, `kxm_sync_events_refused_total`, `kxm_sync_conflicts_total`, `kxm_runtime_heartbeats_total` |
138
+ | Reserved | `kxm_attempt_latency_seconds_total`, `kxm_metered_cost_usd_total`: exported, but the hub does not increment them yet, so they stay `0` |
139
+
140
+ The admin-only operations endpoints `GET /v1/ops/snapshot?project=<name>` and `GET /v1/ops/events?project=<name>` return project metadata and metadata-only wake-ups; they answer `503 admin_auth_not_configured` when the hub has no admin token. See the [Hub HTTP API reference](../reference/http-api.md).
141
+
142
+ ## Read the logs
143
+
144
+ KXM writes JSON lines. Structured logs carry actors, project, state and request IDs, never prompt, request or reply bodies.
145
+
146
+ | Log | Default location | Rotation |
147
+ |---|---|---|
148
+ | Hub | `.kxm/logs/kxm-hub.jsonl` (`KXM_LOG_PATH`); also mirrored to stdout unless `KXM_DAEMON=1` | 10 MB, three rotated files |
149
+ | Runtime supervisor | `runtime/logs/kxm-runtime.jsonl` under the user state root | 2 MiB, three rotated files |
150
+ | Worker lifecycle | `.kxm/logs/kxm-worker-<key>.jsonl` (`KXM_WORKER_LOG_PATH`) | None; rotate externally |
151
+ | Raw Pi output | `.kxm/logs/pi-agent-<key>.log` (`KXM_AGENT_LOG_PATH`) | None; rotate externally |
152
+
153
+ The supervisor runs detached with no terminal, so its log and `kxm runtime status` are its only voice.
154
+
155
+ ### What is redacted
156
+
157
+ - Log fields named like `token`, `secret`, `password`, `apiKey` or `authorization` are replaced with `[redacted]`.
158
+ - Every string value is scanned for credential shapes (API keys, GitHub tokens, bearer headers, 64-character hex strings) and those are replaced too.
159
+ - Context requests log sizes, not text: `context_packet_assembled` records `taskChars` and `taskTokens`, and `context_recall` records `queryChars` and `queryTokens`.
160
+ - Sync failures in the Runtime log are cut to 300 characters with token-like strings removed.
161
+
162
+ > [!WARNING]
163
+ > Raw `pi-agent-*.log` files hold model and tool output and can contain anything the model saw. Restrict them like secrets, and never copy them into journals, retrospectives or bug reports.
164
+
165
+ ### Useful events
166
+
167
+ | Area | Hub events |
168
+ |---|---|
169
+ | Lifecycle | `hub_started`, `hub_stopping`, `request_error`, `security_alert` |
170
+ | Agents | `agent_registered`, `agent_resumed`, `agent_stale`, `agent_unregistered` |
171
+ | Messages | `message_sent`, `message_replied`, `message_cancelled`, `message_expired`, `message_purged` |
172
+ | Workflows | `webhook_workflow_started`, `workflow_checkpoint`, `workflow_wait_started`, `workflow_signal_received`, `workflow_wait_timed_out`, `workflow_degradation_approved`, `workflow_journal_recorded`, `workflow_run_purged` |
173
+ | Context | `context_packet_assembled`, `context_recall`, `context_state_proposed`, `context_state_promoted` |
174
+ | Leases and sync | `lease_acquired`, `lease_renewed`, `lease_released`, `lease_denied`, `lease_purged`, `runtime_registered` |
175
+
176
+ The Runtime log records `runtime_sync_state` (info) and `runtime_sync_stalled` (warning) once per change, not once per tick, plus `runtime_sync_retry` and `runtime_sync_context_unavailable`. Worker lifecycle events such as `worker_process_error`, `worker_exited`, `worker_tool_timeout` and `worker_session_routed` go to the worker log; see [Run supervised Pi workers](../guides/pi-workers.md).
177
+
178
+ Ship logs to an approved collector, restrict file access, and never commit them as workflow evidence; cite a protected location or a sanitized asset instead.
179
+
180
+ ## Set alerts
181
+
182
+ Alert on these conditions:
183
+
184
+ - `/ready` fails for more than one minute, or the hub restarts repeatedly.
185
+ - `kxm runtime status` exits `1`, or any project reports `blocked` or `refusing`; also alert on `runtime_sync_stalled` in the Runtime log.
186
+ - `kxm_errors_total` or HTTP 429 responses rise faster than usual.
187
+ - `kxm_messages` keeps growing although `kxm_messages_purged_total` advances.
188
+ - `security_alert` appears, or `kxm_sync_conflicts_total` increases.
189
+ - `kxm_workflow_wait_timeouts_total` rises, or waiting runs outlast the expected external latency.
190
+ - `kxm_workflow_degradations_total` increases outside a declared incident or change window.
191
+ - Webhook signature rejections (HTTP 401 on `/v1/webhooks/...`) or provider retries climb.
192
+ - Free disk space nears the database's expected growth margin.
193
+
194
+ ## Troubleshooting
195
+
196
+ | Symptom | Cause | Fix |
197
+ |---|---|---|
198
+ | `/metrics` returns 401 on loopback | The hub has an admin token, so loopback is no longer open | Send the admin token |
199
+ | The dashboard header shows `presence/local` instead of `live ops` | It found a project token before the admin token | Export the admin token as `KXM_AUTH_TOKEN` in that terminal |
200
+ | Tasks and Workflows are empty while Runtime runs exist | Admin mode lists hub workflow runs only | Use `kxm runs list` or `kxm tenant status` |
201
+ | Spend is always empty | The screen reads `telemetry.jsonl` from the workspace state directory, but KXM writes it under `.kxm/logs/` | Use `kxm routing report` for Runtime attempt costs |
202
+ | No Runtime log file | The supervisor writes its log on the first sync state change | Wait one sync tick (10 seconds), then check again |
203
+
204
+ ## Next steps
205
+
206
+ - Diagnose a failing check: [Troubleshoot KXM](troubleshooting.md)
207
+ - Fix sync problems: [Operate Runtime sync and leases](runtime-sync.md)
208
+ - Protect the state you are watching: [Back up and restore KXM](backup-and-restore.md)
209
+ - Every command used here: [CLI reference](../reference/cli-reference.md)