@kontextmind/kxm 0.7.163 → 0.7.165

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 (38) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +22 -0
  3. package/README.md +2 -0
  4. package/docs/README.md +1 -0
  5. package/docs/architecture/inventory.md +4 -3
  6. package/docs/contributing/harness-routing-internals.md +28 -27
  7. package/docs/glossary.md +3 -3
  8. package/docs/operations/troubleshooting.md +3 -0
  9. package/docs/operations.md +154 -0
  10. package/docs/reference/cli-reference.md +4 -3
  11. package/docs/reference/configuration.md +11 -1
  12. package/docs/reference/harness-routing.md +17 -15
  13. package/package.json +1 -1
  14. package/plugins/kxm/.claude-plugin/plugin.json +2 -2
  15. package/plugins/kxm/README.md +2 -2
  16. package/plugins/kxm/dist/claude-hook.js +120 -16
  17. package/plugins/kxm/dist/cli.js +14929 -14542
  18. package/plugins/kxm/dist/extension.js +299 -122
  19. package/plugins/kxm/dist/mcp-server.js +7690 -106
  20. package/plugins/kxm/dist/runtime-supervisor.js +151 -4
  21. package/plugins/kxm/dist/runtime.js +158 -11
  22. package/plugins/kxm/package.json +1 -1
  23. package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +5 -1
  24. package/plugins/kxm/src/cli/context-skills.ts +64 -10
  25. package/plugins/kxm/src/cli/hub.ts +128 -17
  26. package/plugins/kxm/src/cli/project.ts +7 -3
  27. package/plugins/kxm/src/cli/types.ts +6 -0
  28. package/plugins/kxm/src/cli/workflows.ts +12 -1
  29. package/plugins/kxm/src/cli.ts +23 -5
  30. package/plugins/kxm/src/extension.ts +7 -3
  31. package/plugins/kxm/src/hub-autostart.ts +3 -0
  32. package/plugins/kxm/src/hub-binding.ts +182 -2
  33. package/plugins/kxm/src/hub-env.ts +71 -0
  34. package/plugins/kxm/src/mcp-server.ts +3 -2
  35. package/plugins/kxm/src/project-name.ts +34 -7
  36. package/plugins/kxm/src/runtime-supervisor.ts +9 -3
  37. package/plugins/kxm/src/tenant-status.ts +9 -2
  38. package/scripts/build-runtime.mjs +5 -0
@@ -11,7 +11,7 @@
11
11
  "name": "kxm",
12
12
  "source": "./plugins/kxm",
13
13
  "description": "Durable workflows, peer agents, and kxm tui",
14
- "version": "0.7.163",
14
+ "version": "0.7.165",
15
15
  "category": "development",
16
16
  "tags": ["kxm", "multi-agent", "workflows", "mcp"]
17
17
  }
package/CHANGELOG.md CHANGED
@@ -6,6 +6,15 @@ All notable user-facing changes are documented here. The project follows [Semant
6
6
 
7
7
  ### Added
8
8
 
9
+ - **`kxm hub bind --cloud` attaches a client to a remote hub reached through a loopback forward.**
10
+ The binding records `--token-env` and/or `--token-command` and never the token.
11
+ Later commands, including the environment for a Pi worker, use that source and
12
+ do not fall back to this machine's hub-env token. Peer commands and
13
+ `kxm tenant status` resolve one project id: flag or `KXM_PROJECT`, then
14
+ `.kxm/project.yaml`, then `package.json`, then the directory name. The
15
+ SSH-forward recipe is in
16
+ [Operate a KXM hub](docs/operations.md#cross-box-peer-attach).
17
+
9
18
  - **A live agent step uses a configurable one-shot timeout, and a cancelling run recovers when its child has already exited.**
10
19
  The bound is the step `timeoutMs`, or the project `limits.agentStepTimeoutMs`
11
20
  when the step omits it (minimum 60 seconds, default one hour). A wider step
@@ -156,6 +165,19 @@ All notable user-facing changes are documented here. The project follows [Semant
156
165
 
157
166
  ### Changed
158
167
 
168
+ - **The checked-in price catalog and model inventory match the live OpenRouter and Nous feeds.**
169
+ `.kxm/prices.yaml` is dated 2026-09-28. It adds `xai/grok-4.7` (alias
170
+ `x-ai/grok-4.7`) at $1.60 input and $4.80 output per 1M tokens, with cache
171
+ read $0.40. `openrouter/z-ai/glm-5.3-flash` moves from $0.09 / $0.30 to
172
+ $0.15 / $0.50 (cache read $0.03); the anchored `zai-coding-cn/glm-5.3-flash`
173
+ row moves with it. `qwen-token-plan/deepseek-v4.1-flash` (alias
174
+ `deepseek/deepseek-v4.1-flash`) moves from $0.15 / $0.60 to $0.014 / $0.40
175
+ (cache read $0.004) because the routing docs still name that selector.
176
+ `xai/grok-4.6` stays: its base rate still matches the live catalog, and code
177
+ and tests still name it. `.kxm/models/inventory.yaml` was refreshed with
178
+ `kxm models inventory-refresh`. Pi, Grok, and agy were not installed for that
179
+ fetch, so models that exist only in those CLIs remain the 2026-09-16 rows.
180
+
159
181
  - **Windows pull-request unit lanes finish inside the job limit.** The light
160
182
  lane on `windows-latest` was cancelled at 20 minutes on #361, #362, and
161
183
  #363 after it stopped reporting tests. `package-install.test.ts` (171s on
package/README.md CHANGED
@@ -105,6 +105,8 @@ You need Node.js 22.19 or newer on the 22.x line, or Node.js 24 or newer, plus G
105
105
  kxm hub view
106
106
  ```
107
107
 
108
+ A hub on another machine, reached through an SSH forward to loopback, uses `kxm hub bind --cloud` so this machine's hub-env token is not sent. The recipe is in [Operate a KXM hub](docs/operations.md#cross-box-peer-attach).
109
+
108
110
  Expected output:
109
111
 
110
112
  ```text
package/docs/README.md CHANGED
@@ -96,6 +96,7 @@ KXM connects coding agents through a durable, authenticated [hub](glossary.md#hu
96
96
 
97
97
  | Page | For | What you get |
98
98
  |---|---|---|
99
+ | [Operate a KXM hub](operations.md) | Hub operators | Index of the operations guides, and cross-box attach over an SSH forward |
99
100
  | [Deploy KXM](operations/deploy.md) | Hub operators | Deployment classes, supervision, and hosted per-tenant hubs behind a proxy |
100
101
  | [Monitor KXM](operations/monitoring.md) | Hub operators | `kxm dash`, health and readiness, metrics, logs and alerts |
101
102
  | [Back up and restore KXM](operations/backup-and-restore.md) | Hub operators | `kxm backup` and `kxm restore`, the state roots, and a stopped-hub recipe |
@@ -32,9 +32,10 @@ On macOS the user state root is `Library/Application Support/KXM` under the
32
32
  home directory (`kxmUserStateRoot` in `plugins/kxm/src/bindings.ts`). Windows
33
33
  uses `AppData/Local/KXM`. Other platforms use `$XDG_STATE_HOME/kxm` or
34
34
  `.local/state/kxm`. `KXM_STATE_HOME` overrides that root when it is absolute.
35
- The tracker mentions `docs/operations.md` for the six backup roots. That file
36
- is not in this checkout. The six roots are described in
37
- [Backup and restore](../operations/backup-and-restore.md).
35
+ The tracker mentions `docs/operations.md` for the six backup roots. That page
36
+ indexes the operations guides and links the six roots to
37
+ [Backup and restore](../operations/backup-and-restore.md). It also holds
38
+ [Cross-box peer attach](../operations.md#cross-box-peer-attach).
38
39
 
39
40
  ## Related
40
41
 
@@ -1,13 +1,13 @@
1
1
  # Harness routing internals
2
2
 
3
- This page records how the KXM repository applies [harness routing](../reference/harness-routing.md) to its own work: the routes this checkout admits, its writer roster, its price catalog, and the developer roster policy that `just assign` and the dev helper enforce. It is maintainer material. The snapshots were captured on 2026-09-23 on one operator machine, from a source checkout where `node scripts/kxm.mjs` is the same program as `kxm`; they change whenever an admission changes.
3
+ This page records how the KXM repository applies [harness routing](../reference/harness-routing.md) to its own work: the routes this checkout admits, its writer roster, its price catalog, and the developer roster policy that `just assign` and the dev helper enforce. It is maintainer material. The route and roster transcripts below were regenerated on 2026-09-28 from this checkout, where `node scripts/kxm.mjs` is the same program as `kxm`. Readiness notes that name a capture machine are from 2026-09-23 and were not re-probed. The transcripts change whenever an admission changes.
4
4
 
5
5
  > [!IMPORTANT]
6
6
  > The files are the authority, not this page: `.kxm/routes.yaml`, `.kxm/roles/*.yaml`, `.kxm/models/*.yaml`, and `.kxm/prices.yaml`. Product routing decisions are recorded under Tracking → Decided in `plans/implementation-plan.md`.
7
7
 
8
8
  ## Current ids (2026-09-27)
9
9
 
10
- The command transcripts below are the 2026-09-23 capture. The files now use one id shape per kind. A route id is `<harness>-<model-slug>[-<provider>]`, with `.` written as `-`. An agent id equals its role. A workflow id is `default`, `land`, or `<role>-only`. An agent step id equals that role.
10
+ The files use one id shape per kind. A route id is `<harness>-<model-slug>[-<provider>]`, with `.` written as `-`. An agent id equals its role. A workflow id is `default`, `land`, or `<role>-only`. An agent step id equals that role.
11
11
 
12
12
  This checkout's routes are `grok-grok-4-7`, `pi-qwen3-coder-plus-openrouter`, `agy-gemini-3-8-flash-high`, `agy-gemini-3-8-flash-medium`, `claude-fable`, `codex-gpt-5-6-sol`, `pi-qwen3-8-flash-openrouter`, and `pi-glm-5-3-flash-openrouter`. Agents are `planner`, `writer`, `reviewer-arch`, and `reviewer-cli`. `opus-claude` was removed because `opus` is not an admitted selector. `qwen-token-plan/*` and `zai-coding-cn/*` left the admitted list because those harnesses are not allowlisted; their `prices.yaml` rows stay. Old ids resolve as aliases. Every roster entry names an effort. `reviewer-arch` dispatches `claude-fable`. #343's architecture critic ran on opus because the justfile recipe review-arch hardcoded that model; the role file on that commit listed only `fable-claude`. The helper now refuses `opus`.
13
13
 
@@ -18,26 +18,22 @@ This checkout's routes are `grok-grok-4-7`, `pi-qwen3-coder-plus-openrouter`, `a
18
18
  ```text
19
19
  schema: kxm.role.v2
20
20
  id: writer
21
- description: ""
21
+ purpose: writer
22
+ permission: edit
23
+ description: Primary implementation agent.
22
24
  skills: []
23
25
  roster:
24
- - model: xai/grok-4.6
26
+ - route: grok-grok-4-7
25
27
  effort: medium
26
- enabled: true
27
- - model: openrouter/qwen/qwen3-coder-plus
28
+ - route: pi-qwen3-coder-plus-openrouter
28
29
  effort: medium
29
- enabled: true
30
- - model: zai-coding-cn/glm-5.3-flash
30
+ - route: agy-gemini-3-8-flash-high
31
31
  effort: medium
32
- enabled: true
33
- - model: qwen-token-plan/qwen3.8-flash
32
+ - route: agy-gemini-3-8-flash-medium
34
33
  effort: medium
35
- enabled: true
36
- - model: google/gemini-3.8-flash-high
37
- enabled: true
38
34
  ```
39
35
 
40
- The implementer agent declares `harness: grok`, so of this roster only `xai/grok-4.6` can run under it; the Pi selectors need an agent without `harness:`.
36
+ The writer role lists route ids, in order: `grok-grok-4-7`, then `pi-qwen3-coder-plus-openrouter`, then `agy-gemini-3-8-flash-high` and `agy-gemini-3-8-flash-medium`. `grok-grok-4-7` is harness `grok`, model `grok-4.7`, vendor `xai`. An agent file does not declare `harness:`.
41
37
 
42
38
  `node scripts/kxm.mjs routes list`:
43
39
 
@@ -49,12 +45,17 @@ admitted openai/gpt-5.6-sol
49
45
  admitted openrouter/qwen/qwen3-coder-plus
50
46
  admitted openrouter/qwen/qwen3.8-flash
51
47
  admitted openrouter/z-ai/glm-5.3-flash
52
- admitted qwen-token-plan/deepseek-v4.1-flash
53
- admitted qwen-token-plan/qwen3.8-flash
54
- admitted qwen-token-plan/qwen3.8-max
55
- admitted xai/grok-4.6
56
- admitted zai-coding-cn/glm-5.3
57
- admitted zai-coding-cn/glm-5.3-flash
48
+ admitted xai/grok-4.7
49
+ planner claude-fable
50
+ planner pi-qwen3-8-flash-openrouter
51
+ reviewer-arch claude-fable
52
+ reviewer-arch pi-glm-5-3-flash-openrouter
53
+ reviewer-cli codex-gpt-5-6-sol
54
+ reviewer-cli pi-qwen3-8-flash-openrouter
55
+ writer grok-grok-4-7
56
+ writer pi-qwen3-coder-plus-openrouter
57
+ writer agy-gemini-3-8-flash-high
58
+ writer agy-gemini-3-8-flash-medium
58
59
  ```
59
60
 
60
61
  `routes admit --model openrouter/qwen/qwen3-coder-plus --dry-run` exits 2 with `select a model from the refreshed inventory` even though that selector is already admitted, because the inventory never carries `openrouter/…` selectors. Selectors like these are admitted by a Git-reviewed edit of `.kxm/routes.yaml`.
@@ -63,7 +64,7 @@ On the capture machine, `kxm harness list` showed `claude` detected but logged o
63
64
 
64
65
  ### Price catalog and inventory
65
66
 
66
- `.kxm/prices.yaml` is dated `2026-09-16`, so every current run records `providerMetadata.priceCatalogStale: true` and no list estimate. The list prices below come from `.kxm/models/inventory.yaml`, fetched `2026-09-16T14:54:53Z`, in USD per 1M tokens. The checkout has no routing records yet, so none of the examples has recorded latency; for latency, run a bounded side-by-side experiment and compare p50 and p95 in `kxm routing report`.
67
+ `.kxm/prices.yaml` is dated `2026-09-28`. A run on that date can record a list estimate. Any other day records `providerMetadata.priceCatalogStale: true` and no list estimate. The list prices below come from `.kxm/models/inventory.yaml`. OpenRouter and Nous were fetched at `2026-09-28T14:02:09.052Z`. `pi`, `grok`, and `agy` were not installed for that refresh, so models that exist only in those CLIs are still the 2026-09-16 rows. Prices are USD per 1M tokens. The checkout has no routing records yet, so none of the examples has recorded latency; for latency, run a bounded side-by-side experiment and compare p50 and p95 in `kxm routing report`.
67
68
 
68
69
  ## The developer roster
69
70
 
@@ -108,15 +109,15 @@ The product brake, the dev helper and the roster policy now agree on the vendor
108
109
 
109
110
  These extend the generic examples on the reference page with this checkout's admissions, developer policy routes, readiness on the capture machine, and inventory prices.
110
111
 
111
- ### Grok 4.6
112
+ ### Grok 4.7
112
113
 
113
114
  | | Native `grok` | Pi + OpenRouter |
114
115
  |---|---|---|
115
- | Selector | `xai/grok-4.6`: admitted, and first in the writer roster | `openrouter/x-ai/grok-4.6`: not admitted |
116
+ | Selector | `xai/grok-4.7`: admitted, and first in the writer roster | `openrouter/x-ai/grok-4.7`: not admitted |
116
117
  | Developer roster | `grok-grok-4-7`, the writer route with `edit` | Refused: `native vendor cannot use Pi` |
117
- | Readiness | `grok` shows auth `yes` | `pi auth check --provider openrouter` returned `not_ready` |
118
- | Billing | grok.com subscription (OAuth) | $2.00 input, $6.00 output, $0.50 cached input |
119
- | Context | 500K (Pi's `xai` row; `grok models` does not print one) | 500,000 |
118
+ | Readiness | `grok` showed auth `yes` on the 2026-09-23 capture machine | `pi auth check --provider openrouter` returned `not_ready` on that capture |
119
+ | Billing | grok.com subscription (OAuth) | $1.60 input, $4.80 output, $0.40 cached input |
120
+ | Context | 500,000 (`x-ai/grok-4.7` in the refreshed inventory) | 500,000 |
120
121
 
121
122
  Pi's own `xai` provider reported `ready` (OAuth) on the capture machine. When the grok quota runs out, the next writer in the lineup is `pi-qwen3-coder-plus-openrouter`, a different vendor.
122
123
 
@@ -147,7 +148,7 @@ In the inventory, `openai/gpt-5.6-sol` has sources `openrouter+nous` and carries
147
148
 
148
149
  | | Native `agy` | `antigravity` Pi provider | Pi + OpenRouter |
149
150
  |---|---|---|---|
150
- | Selector | `google/gemini-3.8-flash-high`: admitted, last in the writer roster | `antigravity/gemini-3.8-flash`: not admitted | `openrouter/google/gemini-3.8-flash`: not admitted |
151
+ | Selector | `google/gemini-3.8-flash-high` and `google/gemini-3.8-flash-medium`: admitted, the last two writer routes | `antigravity/gemini-3.8-flash`: not admitted | `openrouter/google/gemini-3.8-flash`: not admitted |
151
152
  | Billing | Google subscription | Google subscription | $0.75 input, $3.75 output, $0.075 cached input |
152
153
  | Context | `agy` does not print it; the vendored catalog lists 1,048,576 | 1,048,576 | 1,048,576 |
153
154
 
package/docs/glossary.md CHANGED
@@ -38,7 +38,7 @@ The service that authenticates [agents](#agent), stores [messages](#message) and
38
38
 
39
39
  ### Hub binding
40
40
 
41
- The record that `kxm hub bind <url>` writes to `hub-binding.json` in the [user state root](#user-state-root), naming the hub this machine uses. `kxm hub view` checks it, and the Runtime syncs to it. Binding a remote hub requires a credential.
41
+ The record that `kxm hub bind <url>` writes to `hub-binding.json` in the [user state root](#user-state-root), naming the hub this machine uses. `kxm hub view` checks it, and the Runtime syncs to it. Binding a remote hub requires a credential. `kxm hub bind --cloud` marks a loopback forward as remote and records a token variable or command, never the token.
42
42
 
43
43
  ### KontextMind
44
44
 
@@ -104,11 +104,11 @@ Another [agent](#agent) in the same [project](#project). `kxm_list` and `kxm pee
104
104
 
105
105
  ### Project
106
106
 
107
- The authentication and discovery namespace on the hub. An agent names it with `KXM_PROJECT` (otherwise the `package.json` name, then the directory name), and its key in the hub's `KXM_PROJECT_TOKENS` holds its token. Agents see only peers in the same project. Compare [project ID](#project-id).
107
+ The authentication and discovery namespace on the hub. Every command resolves it the same way: `--project` or `KXM_PROJECT`, then the [project ID](#project-id) in `.kxm/project.yaml`, then the `package.json` name, then the directory name. Its key in the hub's `KXM_PROJECT_TOKENS` holds its token. Agents see only peers in the same project.
108
108
 
109
109
  ### Project ID
110
110
 
111
- The stable `prj_` identifier in `.kxm/project.yaml`. The [Runtime](#runtime) and hub sync use it. It is not the hub [project](#project) name, even though both describe the same body of work.
111
+ The stable `prj_` identifier in `.kxm/project.yaml`. It is the hub [project](#project) name unless `--project` or `KXM_PROJECT` overrides it. The [Runtime](#runtime) store keeps this id as the local key.
112
112
 
113
113
  ### Project token
114
114
 
@@ -87,6 +87,9 @@ If a malformed claim remains, read the exact `.pid` JSON in the state directory
87
87
  | `hub_url_invalid` | Use an `http` or `https` URL without user info, query or fragment |
88
88
  | `hub_bind_unauthenticated` | The URL is remote and this machine has no token for the project; export it and bind again |
89
89
  | `hub_credential_unreadable` | Repair or remove `hub-env.json` under the user state root |
90
+ | `cloud_token_missing` | A `--cloud` binding's variable is empty and it has no working command. Export the variable or fix `--token-command`. This machine's hub-env token is not sent |
91
+ | `cloud_flag_required` | `--token-env` or `--token-command` was passed without `--cloud` |
92
+ | 401 after binding a loopback forward | The bind was not `--cloud`, so the client sent the local hub-env token. See [Cross-box peer attach](../operations.md#cross-box-peer-attach) |
90
93
 
91
94
  ## Claude Code plugin and MCP
92
95
 
@@ -0,0 +1,154 @@
1
+ # Operate a KXM hub
2
+
3
+ This page is the operator index for a [hub](glossary.md#hub): where the other
4
+ operations guides live, and how to attach a client on another machine to a hub
5
+ that is not reachable from the internet. The client in that setup is a local
6
+ `kxm` CLI, plus the Pi and Claude Code agents that CLI starts.
7
+
8
+ ## Where each procedure lives
9
+
10
+ | Procedure | Page |
11
+ |---|---|
12
+ | Install the services on a tenant box, including loopback listeners | [Deploy KXM](operations/deploy.md) |
13
+ | Health, readiness, metrics, logs, and alerts | [Monitor KXM](operations/monitoring.md) |
14
+ | Stopped-state backup of all six state roots, and restore | [Back up and restore KXM](operations/backup-and-restore.md) |
15
+ | Package update, plugin reinstall, and schema ceilings | [Upgrade KXM](operations/upgrade.md) |
16
+ | Runtime outbox, sync refusals, and hub leases | [Operate Runtime sync and leases](operations/runtime-sync.md) |
17
+ | Symptoms, causes, and fixes | [Troubleshoot KXM](operations/troubleshooting.md) |
18
+
19
+ The six roots are the checkout, the workspace, workspace state, the user state
20
+ root, user config, and federated telemetry. The stopped-state copy of each one
21
+ is in [Back up and restore KXM](operations/backup-and-restore.md).
22
+
23
+ ## Before you begin
24
+
25
+ - The `kxm` CLI is installed on the client machine.
26
+ - You can open SSH to the hub host, and you already have that host's key.
27
+ - The hub's admin token is in a password manager. 1Password CLI (`op`) is the
28
+ usual source. The token is never written into `hub-binding.json`.
29
+
30
+ ## Cross-box peer attach
31
+
32
+ Use this when the hub listens on loopback on a private machine and your client
33
+ reaches it through an SSH local forward. A plain `kxm hub bind` of
34
+ `http://127.0.0.1:17331` is a loopback bind: it stores no credential, and later
35
+ commands send this machine's hub-env admin token. The remote hub rejects that
36
+ token with 401. `--cloud` marks the binding remote even though the URL is
37
+ loopback, and names where the token comes from without storing the token.
38
+
39
+ ### Open the forward with a pinned host key
40
+
41
+ Pin the host key before the first connection. Do not accept a key on first use.
42
+
43
+ ```bash
44
+ ssh-keyscan -H 10.31.0.21 >> ~/.ssh/known_hosts
45
+ ssh -N \
46
+ -o ExitOnForwardFailure=yes \
47
+ -o StrictHostKeyChecking=yes \
48
+ -o UserKnownHostsFile="$HOME/.ssh/known_hosts" \
49
+ -L 127.0.0.1:17331:127.0.0.1:7331 \
50
+ user@10.31.0.21
51
+ ```
52
+
53
+ Leave that SSH session open. Hub traffic then goes to `127.0.0.1:17331` on the
54
+ client and comes out on the hub host's loopback port 7331.
55
+
56
+ <details><summary>PowerShell</summary>
57
+
58
+ ```powershell
59
+ ssh-keyscan -H 10.31.0.21 >> $env:USERPROFILE\.ssh\known_hosts
60
+ ssh -N `
61
+ -o ExitOnForwardFailure=yes `
62
+ -o StrictHostKeyChecking=yes `
63
+ -o UserKnownHostsFile="$env:USERPROFILE\.ssh\known_hosts" `
64
+ -L 127.0.0.1:17331:127.0.0.1:7331 `
65
+ user@10.31.0.21
66
+ ```
67
+
68
+ </details>
69
+
70
+ ### Bind the forward as a cloud hub
71
+
72
+ Pick one token source. The environment variable is enough when the shell
73
+ already holds the token. The command form asks 1Password each time a command
74
+ needs the token. Do not put the token text in `--token-command`: that string
75
+ is stored in the binding.
76
+
77
+ ```bash
78
+ export KXMD_HUB_TOKEN="$(op read 'op://Private/kxmd/admin-token')"
79
+ kxm hub bind --cloud --token-env KXMD_HUB_TOKEN http://127.0.0.1:17331
80
+ kxm hub view
81
+ ```
82
+
83
+ Or name the 1Password command and skip the variable:
84
+
85
+ ```bash
86
+ kxm hub bind --cloud \
87
+ --token-command "op read op://Private/kxmd/admin-token" \
88
+ http://127.0.0.1:17331
89
+ ```
90
+
91
+ Expected output:
92
+
93
+ ```text
94
+ bound hub http://127.0.0.1:17331 · remote · health=on · token is not stored
95
+ hub health=true ready=true · remote hub
96
+ ```
97
+
98
+ `--token-env` and `--token-command` can be combined. A non-empty variable wins.
99
+ Otherwise the command runs. The command is a program and arguments, not a shell
100
+ pipeline (`|`, `&`, `;`, and redirection are refused).
101
+
102
+ <details><summary>PowerShell</summary>
103
+
104
+ ```powershell
105
+ $env:KXMD_HUB_TOKEN = op read "op://Private/kxmd/admin-token"
106
+ kxm hub bind --cloud --token-env KXMD_HUB_TOKEN http://127.0.0.1:17331
107
+ kxm hub view
108
+ ```
109
+
110
+ </details>
111
+
112
+ ### What --cloud changes
113
+
114
+ | | Local bind | `--cloud` bind |
115
+ |---|---|---|
116
+ | Loopback URL | Scope `loopback`. No credential required | Scope `remote`, including an SSH forward |
117
+ | Token stored in `hub-binding.json` | No | No. The file names `tokenEnv` and/or `tokenCommand` |
118
+ | Credential later commands send | `KXM_AUTH_TOKEN`, then this machine's hub-env | The named source only. Hub-env is not a fallback |
119
+ | Missing source | Agent commands report `project_token_missing` | `cloud_token_missing` or `cloud_token_command_failed`. The local admin token is not sent |
120
+ | `kxm hub start` from an extension | May start a local hub | Does not start a local hub when the forward is down (`cloud_hub_unreachable`) |
121
+
122
+ `kxm hub view`, `kxm tenant status`, `kxm peer`, `kxm workflow` agent commands,
123
+ `kxm context`, Runtime presence, and the environment exported for a Pi worker
124
+ all use that source. `kxm agent worker` puts `KXM_AUTH_TOKEN` in the child
125
+ process environment only. It does not write the token to disk.
126
+
127
+ The hub project id is the same value for peer commands and `kxm tenant status`:
128
+ `--project` or `KXM_PROJECT`, then the `id` in `.kxm/project.yaml`, then
129
+ `package.json` `name`, then the directory name.
130
+
131
+ ### Remove the binding
132
+
133
+ ```bash
134
+ kxm hub unbind
135
+ ```
136
+
137
+ Unbind deletes `hub-binding.json`. It does not delete hub-env, and it does not
138
+ close the SSH forward. Stop the forward by ending the `ssh -N` session.
139
+
140
+ ## Troubleshooting
141
+
142
+ | Symptom | Cause | Fix |
143
+ |---|---|---|
144
+ | 401 `invalid_auth` after binding `127.0.0.1:17331` | The bind was not `--cloud`, so the client sent this machine's hub-env admin token | Bind again with `--cloud` and a token source that holds the remote hub's token. Unset a stale `KXM_AUTH_TOKEN` that points at the local hub |
145
+ | `cloud_token_missing` | `KXMD_HUB_TOKEN` (or the variable you named) is unset or empty, and no command is configured | Export the variable or pass `--token-command`. The local hub-env token is intentionally unused |
146
+ | `cloud_token_command_failed` | `op` exited non-zero, timed out, or printed more than one line | Run the same command in a terminal and sign in to 1Password. Do not paste the token into the binding |
147
+ | Clients through one SSH host get locked out together | The hub rate-limits by agent id, otherwise by source address. Every forward from one SSH session shares `127.0.0.1` on the hub. `sshd` `MaxStartups` and `MaxSessions` can refuse new forwards first | Give each long-lived agent its own name. Raise the SSH limits on that host if many people share one jump host. Do not point two tenants at one hub |
148
+ | `kxm hub view` says `loopback` for the forward | `--cloud` was omitted | Bind again with `--cloud`. `KXM_SERVER_URL` overrides the binding URL and is labeled on its own |
149
+
150
+ ## Next steps
151
+
152
+ - Deploy the hub itself: [Deploy KXM](operations/deploy.md)
153
+ - Read health and metrics: [Monitor KXM](operations/monitoring.md)
154
+ - Command flags: [kxm hub bind](reference/cli-reference.md#kxm-hub-bind)
@@ -569,16 +569,17 @@ kxm hub stop --wait-ms 8000 --json
569
569
  ### `kxm hub bind`
570
570
 
571
571
  ```text
572
- kxm hub bind <url>
572
+ kxm hub bind <url> [--cloud] [--token-env <name>] [--token-command <command>]
573
573
  ```
574
574
 
575
575
  Binds this machine to a running hub by writing `hub-binding.json` under the user state root, then probes the hub's health for up to 300 ms. Every hub client uses the binding when `KXM_SERVER_URL` is unset.
576
576
 
577
577
  - Arguments: `<url>`, Hub base URL (http or https).
578
578
  - A URL with credentials, a query, a fragment, or a scheme other than http or https fails with `hub_url_invalid` (exit 2).
579
- - A remote (non-loopback) URL is refused with `hub_bind_unauthenticated` (exit 2, `nextAction: "export_kxm_auth_token"`) unless a credential for the current project resolves from `KXM_AUTH_TOKEN` or the persisted `hub-env.json`. A malformed record fails with `hub_credential_unreadable` (exit 2).
579
+ - A remote (non-loopback) URL is refused with `hub_bind_unauthenticated` (exit 2, `nextAction: "export_kxm_auth_token"`) unless a credential for the current project resolves from `KXM_AUTH_TOKEN` or the persisted `hub-env.json`. A malformed record fails with `hub_credential_unreadable` (exit 2). A loopback URL without `--cloud` still binds with no credential.
580
+ - `--cloud` marks the binding remote even when the URL is a loopback forward. It requires `--token-env`, `--token-command`, or both. The token is not written. A non-empty variable wins; otherwise the command runs. Source flags without `--cloud` fail with `cloud_flag_required`. A missing source later fails with `cloud_token_missing` and does not fall back to hub-env. See [Cross-box peer attach](../operations.md#cross-box-peer-attach).
580
581
  - Mutates the binding file. `--dry-run` validates and prints the plan without writing.
581
- - JSON keys: `url`, `scope`, `file`, `health` (`on`, `off`, or `unknown`), `probeMs`.
582
+ - JSON keys: `url`, `scope`, `file`, `health` (`on`, `off`, or `unknown`), `probeMs`. A cloud bind also returns `cloud`, and `tokenEnv` or `tokenCommand` when set.
582
583
 
583
584
  ```bash
584
585
  kxm hub bind http://127.0.0.1:7331 --dry-run
@@ -108,7 +108,7 @@ Every agent client (the Pi extension, the Claude Code MCP server, and the `kxm p
108
108
  |---|---|---|
109
109
  | `KXM_SERVER_URL` | `http://127.0.0.1:7331` | Hub base URL. For the CLI, the machine's `kxm hub bind` URL is used when this is unset |
110
110
  | `KXM_AUTH_TOKEN` | See below | The project token for this agent's project |
111
- | `KXM_PROJECT` | `name` in `package.json` in the working directory, else the directory name | Project for discovery and authentication |
111
+ | `KXM_PROJECT` | `id` in the nearest `.kxm/project.yaml`, else `name` in `package.json` in the working directory, else the directory name | Project for discovery and authentication. An explicit `--project` flag on `kxm peer list` and `kxm tenant status` wins over this variable. The same resolver is used by peer commands, tenant status, workers, the Pi extension, and the Claude Code MCP server |
112
112
  | `KXM_AGENT_NAME` | Harness-specific (see below) | Name peers see; unique among online agents in the project, compared case-insensitively |
113
113
  | `KXM_AGENT_PURPOSE` | Harness-specific (see below) | One line peers use to decide what to send this agent |
114
114
 
@@ -120,6 +120,16 @@ Every agent client (the Pi extension, the Claude Code MCP server, and the `kxm p
120
120
 
121
121
  A clean shutdown marks an identity offline. Reconnecting with the same project and name resumes its durable agent ID and rotates its agent key. If the Claude Code name is already online in the project, the MCP server registers once more as `<name>-<pid>`.
122
122
 
123
+ A `--cloud` hub binding replaces this table's token lookup for every client, including agents. The token comes from the binding's `--token-env` or `--token-command`, never from this machine's hub-env file, and it is not written to disk. `kxm agent worker` copies it into the child process environment as `KXM_AUTH_TOKEN`.
124
+
125
+ Also check plugin.json is valid JSON.
126
+
127
+ And configuration table cell might be very long. That's ok.
128
+
129
+ Look at harness table "never the admin token" - the new paragraph clarifies cloud. Good.
130
+
131
+ Plugin README add one sentence under which token.
132
+
123
133
  ## Claude Code plugin settings
124
134
 
125
135
  The plugin asks for `server_url`, `auth_token`, `agent_name`, `agent_purpose` and `project` when you install it, and passes them to its MCP server as `KXM_SERVER_URL`, `KXM_AUTH_TOKEN`, `KXM_AGENT_NAME`, `KXM_AGENT_PURPOSE` and `KXM_PROJECT`. It also sets `KXM_PROJECT_DIR` to the directory Claude Code started in. Change them with `/plugin configure kxm@kxm`. Every field, default and token rule is in [Claude Code plugin settings](config-reference.md#claude-code-plugin-settings) and the [plugin README](../../plugins/kxm/README.md).
@@ -131,11 +131,11 @@ This is the same vendor through Pi and OpenRouter, which the brake refuses:
131
131
 
132
132
  ```yaml
133
133
  harness: pi
134
- model: openrouter/x-ai/grok-4.6
134
+ model: openrouter/x-ai/grok-4.7
135
135
  vendor: xai
136
136
  ```
137
137
 
138
- The selector is `openrouter/x-ai/grok-4.6` and the harness is `pi`. The Pi brake refuses it before Pi starts, because the vendor segment `x-ai` is xAI, which has a native harness. A vendor with no native harness runs in the same shape: with `model: openrouter/qwen/qwen3-coder-plus`, Pi is spawned with `--model openrouter/qwen/qwen3-coder-plus`, its read-only flags (section 3) and `-p --mode json`. Write `model` exactly as the provider names it. For OpenRouter that is the vendor slug, so `x-ai`, not `xai`.
138
+ The selector is `openrouter/x-ai/grok-4.7` and the harness is `pi`. The Pi brake refuses it before Pi starts, because the vendor segment `x-ai` is xAI, which has a native harness. A vendor with no native harness runs in the same shape: with `model: openrouter/qwen/qwen3-coder-plus`, Pi is spawned with `--model openrouter/qwen/qwen3-coder-plus`, its read-only flags (section 3) and `-p --mode json`. Write `model` exactly as the provider names it. For OpenRouter that is the vendor slug, so `x-ai`, not `xai`.
139
139
 
140
140
  The harness and the selector have to agree. Pi refuses `provider: xai` and `openrouter/x-ai/…`. `grok` refuses `provider: openrouter`. [What the brake refuses](#what-the-brake-refuses) has the exact messages.
141
141
 
@@ -156,7 +156,9 @@ roster:
156
156
  - route: pi-qwen3-coder-plus-openrouter
157
157
  effort: medium
158
158
  - route: agy-gemini-3-8-flash-high
159
+ effort: medium
159
160
  - route: agy-gemini-3-8-flash-medium
161
+ effort: medium
160
162
  ```
161
163
 
162
164
  `grok-grok-4-7` resolves to `.kxm/models/grok-grok-4-7.yaml`: harness `grok`, model `grok-4.7`, vendor `xai`, status `admitted`, permission `edit`. `pi-qwen3-coder-plus-openrouter` is harness `pi`, model `openrouter/qwen/qwen3-coder-plus`, vendor `alibaba`. `agy-gemini-3-8-flash-high` is harness `agy`, model `gemini-3.8-flash-high`, vendor `google`. `kxm role list` prints the first route id as the primary, for example `(grok-grok-4-7)`. The previous ids (`grok-native`, `qwen-openrouter-pi`, `gemini-agy`) are aliases.
@@ -171,8 +173,8 @@ Role files are what `kxm role` and the Runtime membership check use.
171
173
 
172
174
  ```text
173
175
  admitted openrouter/qwen/qwen3-coder-plus
174
- admitted xai/grok-4.6
175
- disabled openrouter/x-ai/grok-4.6
176
+ admitted xai/grok-4.7
177
+ disabled openrouter/x-ai/grok-4.7
176
178
  ```
177
179
 
178
180
  The first segment of an id is the provider, and the rest is the model id. A route id does not name a harness. The first segment tells you which harness can run it. For an aggregator, the next segment names the vendor, and the Pi brake reads that too:
@@ -205,11 +207,11 @@ select a model from the refreshed inventory
205
207
  It exits with code 2. A bare OpenRouter slug is accepted, but it is not a dispatchable selector:
206
208
 
207
209
  ```bash
208
- kxm routes admit --model x-ai/grok-4.6 --dry-run
210
+ kxm routes admit --model x-ai/grok-4.7 --dry-run
209
211
  ```
210
212
 
211
213
  ```text
212
- would admit x-ai/grok-4.6
214
+ would admit x-ai/grok-4.7
213
215
  ```
214
216
 
215
217
  If that id were admitted, an agent would need `provider: x-ai`. Pi has no provider called `x-ai`, so the Pi auth probe would fail closed. When a route needs a provider prefix that the inventory does not carry, admit it by a Git-reviewed edit of `.kxm/routes.yaml`, after the admission decision is reviewed.
@@ -218,13 +220,13 @@ If that id were admitted, an agent would need `provider: x-ai`. Pi has no provid
218
220
 
219
221
  The same model shows up under different ids depending on the file:
220
222
 
221
- | Where | Id for Grok 4.6 | What that id means |
223
+ | Where | Id for Grok 4.7 | What that id means |
222
224
  |---|---|---|
223
- | `.kxm/routes.yaml`, agent selector | `xai/grok-4.6` | Provider `xai`. Needs `harness: grok`. |
224
- | `.kxm/routes.yaml`, agent selector | `openrouter/x-ai/grok-4.6` | OpenRouter through Pi. The brake refuses it. |
225
- | `.kxm/models/inventory.yaml`, source `pi` | `xai/grok-4.6` | Pi's own `xai` provider. The brake refuses it. |
226
- | `.kxm/models/inventory.yaml`, source `openrouter` | `x-ai/grok-4.6` | The OpenRouter catalog slug. The inventory stores OpenRouter ids without an `openrouter/` prefix. |
227
- | `.kxm/prices.yaml` | `id: xai/grok-4.6`, `provider: xai`, alias `x-ai/grok-4.6` | A list price that applies only to provider `xai`, because price lookup filters by provider. |
225
+ | `.kxm/routes.yaml`, agent selector | `xai/grok-4.7` | Provider `xai`. Needs `harness: grok`. |
226
+ | `.kxm/routes.yaml`, agent selector | `openrouter/x-ai/grok-4.7` | OpenRouter through Pi. The brake refuses it. |
227
+ | `.kxm/models/inventory.yaml`, source `pi` | `xai/grok-4.6` | Pi's own `xai` provider from the last `pi --list-models` capture. The brake refuses it. This refresh did not re-run Pi, and that capture has no `xai/grok-4.7` row. |
228
+ | `.kxm/models/inventory.yaml`, source `openrouter` | `x-ai/grok-4.7` | The OpenRouter catalog slug. The inventory stores OpenRouter ids without an `openrouter/` prefix. |
229
+ | `.kxm/prices.yaml` | `id: xai/grok-4.7`, `provider: xai`, alias `x-ai/grok-4.7` | A list price that applies only to provider `xai`, because price lookup filters by provider. `xai/grok-4.6` remains a separate row. |
228
230
 
229
231
  An inventory id can also mean something else in `.kxm/routes.yaml`. For example, the inventory can list `openai/gpt-5.6-sol` from the OpenRouter feed with OpenRouter prices, while in `.kxm/routes.yaml` the same string means provider `openai` through native `codex`.
230
232
 
@@ -341,12 +343,12 @@ The live one-shot producer runs every harness read-only, with a 120-second proce
341
343
 
342
344
  The Runtime does not run a step live when it has `write` access to a repository; it hands the run off with `step_unsupported`. Only Pi runs as a long-lived worker.
343
345
 
344
- ### Grok 4.6
346
+ ### Grok 4.7
345
347
 
346
348
  | | Native `grok` | Pi + OpenRouter |
347
349
  |---|---|---|
348
- | Model file | `harness: grok`, `model: grok-4.6`, `vendor: xai` | `harness: pi`, `model: openrouter/x-ai/grok-4.6`, `vendor: xai` |
349
- | Selector | `xai/grok-4.6` | `openrouter/x-ai/grok-4.6` |
350
+ | Model file | `harness: grok`, `model: grok-4.7`, `vendor: xai` | `harness: pi`, `model: openrouter/x-ai/grok-4.7`, `vendor: xai` |
351
+ | Selector | `xai/grok-4.7` | `openrouter/x-ai/grok-4.7` |
350
352
  | Billing | grok.com subscription (OAuth) | OpenRouter credit |
351
353
  | Recorded `costBasis` | `unknown` | None: the brake refuses it before dispatch |
352
354
  | Mode | One-shot `grok --single`. Not a worker. | Refused as a one-shot and as a worker model |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kontextmind/kxm",
3
- "version": "0.7.163",
3
+ "version": "0.7.165",
4
4
  "description": "KXM local-first multi-agent orchestration and operator dashboard",
5
5
  "type": "module",
6
6
  "author": "KontextMind",
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "kxm",
4
4
  "displayName": "KXM",
5
- "version": "0.7.163",
5
+ "version": "0.7.165",
6
6
  "description": "Headless multi-agent orchestration, durable workflows, and a live operator dashboard for Pi and Claude Code",
7
7
  "author": {
8
8
  "name": "KontextMind",
@@ -65,7 +65,7 @@
65
65
  "project": {
66
66
  "type": "string",
67
67
  "title": "Project",
68
- "description": "Hub project key; must match a key in the hub KXM_PROJECT_TOKENS. Blank uses the package.json name, then the directory name",
68
+ "description": "Hub project key; must match a key in the hub KXM_PROJECT_TOKENS. Blank uses the .kxm/project.yaml id, then the package.json name, then the directory name",
69
69
  "required": false
70
70
  }
71
71
  }
@@ -58,7 +58,7 @@ Claude Code asks for these options when you install the plugin. Change them late
58
58
  | `auth_token` | `KXM_AUTH_TOKEN` | Blank | This project's token from the hub's `KXM_PROJECT_TOKENS`. Leave it blank on the machine that runs the hub. Never the hub admin token. Marked sensitive. |
59
59
  | `agent_name` | `KXM_AGENT_NAME` | `claude` | Name other agents see. The first active session in a project keeps it; a later concurrent session registers as `<name>-<pid>`. |
60
60
  | `agent_purpose` | `KXM_AGENT_PURPOSE` | `Claude Code implementation and review agent` | One line that peers use to decide what to send this agent. |
61
- | `project` | `KXM_PROJECT` | Blank | Hub project key; must match a key in the hub's `KXM_PROJECT_TOKENS`. Blank uses `name` from the project's `package.json`, then the directory name. |
61
+ | `project` | `KXM_PROJECT` | Blank | Hub project key; must match a key in the hub's `KXM_PROJECT_TOKENS`. Blank uses the `id` in `.kxm/project.yaml`, then `name` from `package.json`, then the directory name. |
62
62
 
63
63
  The MCP server also receives `KXM_PROJECT_DIR`, set to the directory Claude Code was started in (`CLAUDE_PROJECT_DIR`). It decides the default project key and whether this is a KXM project (one with a `.kxm/` directory).
64
64
 
@@ -67,7 +67,7 @@ The MCP server also receives `KXM_PROJECT_DIR`, set to the directory Claude Code
67
67
  The plugin acts as an agent of one hub project and authenticates with that project's token. It never uses the hub admin token.
68
68
 
69
69
  - On the machine that runs the hub, leave `auth_token` blank. The MCP server then uses the project token the hub saved for this project in `hub-env.json` under the user state root (`KXM_STATE_HOME`, or the platform default listed in [State outside the project](../../docs/reference/config-reference.md#state-outside-the-project)). It uses only that entry, never the admin token saved beside it.
70
- - On any other machine, enter this project's token at `/plugin configure kxm@kxm`. Get it from whoever runs the hub, through your password manager.
70
+ - On any other machine, enter this project's token at `/plugin configure kxm@kxm`. Get it from whoever runs the hub, through your password manager. When the machine is bound with `kxm hub bind --cloud`, leave `auth_token` blank: the MCP server reads the binding's token source and does not store the token. The recipe is [Cross-box peer attach](../../docs/operations.md#cross-box-peer-attach).
71
71
  - Never enter the hub admin token. It is the operator's credential. The hub accepts it for any project that has no token of its own, so an agent holding it could act in projects it was never given.
72
72
  - Without a project token, every `kxm_*` tool fails with `KXM has no project token for project <p> on this machine`, and the MCP server does not contact the hub.
73
73