@kontextmind/kxm 0.7.96 → 0.7.98
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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.kxm/workflows/default.yaml +2 -0
- package/CHANGELOG.md +61 -2
- package/docs/concepts/architecture.md +1 -1
- package/docs/concepts/data-and-storage.md +1 -1
- package/docs/concepts/trust-model.md +3 -2
- package/docs/contracts/routing.md +1 -1
- package/docs/contributing/test-matrix.md +6 -5
- package/docs/glossary.md +1 -1
- package/docs/guides/peer-messaging.md +2 -2
- package/docs/guides/pi-workers.md +1 -1
- package/docs/guides/webhook-workflows.md +53 -18
- package/docs/operations/backup-and-restore.md +43 -24
- package/docs/operations/deploy.md +2 -2
- package/docs/operations/troubleshooting.md +3 -2
- package/docs/reference/cli-reference.md +65 -30
- package/docs/reference/config-reference.md +24 -13
- package/docs/reference/configuration.md +2 -2
- package/docs/reference/harness-routing.md +3 -3
- package/docs/reference/http-api.md +7 -7
- package/docs/reference/tools.md +1 -1
- package/docs/reference/workflow-definitions.md +2 -2
- package/docs/start/first-workflow.md +4 -4
- package/docs/start/quickstart-claude-code.md +2 -2
- package/docs/start/quickstart-pi.md +1 -1
- package/examples/workflow-signal.ts +4 -5
- package/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/claude-hook.js +11 -1
- package/plugins/kxm/dist/cli.js +552 -228
- package/plugins/kxm/dist/client.js +3 -1
- package/plugins/kxm/dist/core.js +16 -3
- package/plugins/kxm/dist/extension.js +45 -13
- package/plugins/kxm/dist/mcp-server.js +20 -4
- package/plugins/kxm/dist/runtime-supervisor.js +232 -51
- package/plugins/kxm/dist/runtime.js +432 -95
- package/plugins/kxm/dist/server.js +122 -20
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +4 -3
- package/plugins/kxm/skills/kxm-project-setup/SKILL.md +3 -2
- package/plugins/kxm/skills/kxm-routing-improve/SKILL.md +3 -0
- package/plugins/kxm/skills/kxm-runs/SKILL.md +8 -7
- package/plugins/kxm/src/cli/project.ts +22 -13
- package/plugins/kxm/src/cli/system.ts +22 -1
- package/plugins/kxm/src/cli/workflows.ts +12 -7
- package/plugins/kxm/src/cli.ts +30 -8
- package/plugins/kxm/src/client.ts +4 -0
- package/plugins/kxm/src/commands.ts +23 -1
- package/plugins/kxm/src/database.ts +210 -36
- package/plugins/kxm/src/engine.ts +117 -2
- package/plugins/kxm/src/extension.ts +20 -14
- package/plugins/kxm/src/github-watch.ts +8 -5
- package/plugins/kxm/src/harness.ts +29 -0
- package/plugins/kxm/src/hub-env.ts +19 -1
- package/plugins/kxm/src/hub.ts +105 -21
- package/plugins/kxm/src/improve-sources.ts +2 -7
- package/plugins/kxm/src/init-guide-setup.ts +43 -28
- package/plugins/kxm/src/mcp-server.ts +9 -2
- package/plugins/kxm/src/oneshot-producer.ts +16 -8
- package/plugins/kxm/src/prices.ts +33 -2
- package/plugins/kxm/src/routing.ts +13 -7
- package/plugins/kxm/src/runtime-store.ts +23 -0
- package/plugins/kxm/src/studio-layout.ts +5 -4
- package/plugins/kxm/src/template.ts +31 -0
- package/plugins/kxm/src/workflow.ts +70 -1
- package/plugins/kxm/src/worktree-witness.ts +71 -0
- package/schemas/backup-manifest.schema.json +33 -0
- package/scripts/smoke-multi-pi.mjs +5 -1
|
@@ -15,7 +15,7 @@ Output shown under examples was captured from a source checkout, inside a throwa
|
|
|
15
15
|
- Hub and sessions: [`hub`](#kxm-hub-view), [`session`](#kxm-session), [`dash`](#kxm-dash), [`studio`](#kxm-studio)
|
|
16
16
|
- Harnesses, models, and roles: [`harness`](#kxm-harness), [`auth`](#kxm-auth), [`update`](#kxm-update), [`models`](#kxm-models), [`routes`](#kxm-routes), [`role`](#kxm-role)
|
|
17
17
|
- Running work: [`run`](#kxm-run), [`runs`](#kxm-runs), [`runtime`](#kxm-runtime), [`agent`](#kxm-agent), [`workflow`](#kxm-workflow), [`gate`](#kxm-gate), [`peer`](#kxm-peer), [`task`](#kxm-task), [`goal`](#kxm-goal), [`suggest`](#kxm-suggest), [`explain`](#kxm-explain)
|
|
18
|
-
- Context and learning: [`context`](#kxm-context), [`memory`](#kxm-memory), [`skills`](#kxm-skills), [`improve`](#kxm-improve), [`routing`](#kxm-routing)
|
|
18
|
+
- Context and learning: [`context`](#kxm-context), [`memory`](#kxm-memory), [`skills`](#kxm-skills), [`improve`](#kxm-improve), [`routing`](#kxm-routing), [`prices`](#kxm-prices)
|
|
19
19
|
- Operations: [`backup`](#kxm-backup), [`restore`](#kxm-restore), [`tenant`](#kxm-tenant), [`ssh`](#kxm-ssh), [`help`](#kxm-help)
|
|
20
20
|
- [Known behavior gaps](#known-behavior-gaps)
|
|
21
21
|
|
|
@@ -102,7 +102,7 @@ kxm -V
|
|
|
102
102
|
- Plan with `planned`: `backup`, `restore`, `config set`, `role add|remove|modify|set-host|resume`, `workflow add|remove|modify`, `goal create`, `task create|run|sync`, `memory note|sync`, `skills evaluate|promote|reject`, `context promote`, `context wiki-compile --out`, `auth token`, `session token`, `session brief`, and `ssh run|file|close`. Each prints its normal result plus `dryRun: true` and `planned`, a list of `{action, target}` entries whose `action` is `write`, `delete`, `move`, `request`, or `ssh`. Text mode prints `dry run: <summary>` and one indented `would <action> <target>` line per entry (`session brief` appends `dry run: would write <file>` lines to the brief instead).
|
|
103
103
|
- Plan in their own shape (described in each section): `init`, `run`, `runs drive|cancel`, `runtime start|stop|sync-retry`, `hub start|stop|bind|unbind`, `session start|stop`, `agent worker`, `dash`, `studio serve`, `models inventory-refresh`, `routes admit|disable`, `update`, `completion install`, `workflow start|signal|export|checkpoint|record|wait`, every `peer` subcommand, `gate degrade|signal`, `skills create`, and `improve report`. `gate github watch --dry-run` still polls GitHub but does not post the signal.
|
|
104
104
|
- Read-only commands run as usual, without leaving a trace: a local SQLite store is opened without creating `-wal` or `-shm` files, and `runs status|list|receipt` only attach to a running Runtime supervisor. With no supervisor running they exit 2 with `dry_run_unsupported` instead of starting one. `context wiki-compile --dry-run` still asks the hub to compile, which is a read.
|
|
105
|
-
- Refused: `kxm models` (the interactive screen)
|
|
105
|
+
- Refused: `kxm models` (the interactive screen) and `kxm prices acknowledge` exit 2 with `dry_run_unsupported`. The CLI keeps one list of the commands that answer `--dry-run` and refuses every other command the same way before it runs, so a command added without dry-run support fails closed.
|
|
106
106
|
|
|
107
107
|
```bash
|
|
108
108
|
kxm config set user.theme light --dry-run
|
|
@@ -161,7 +161,7 @@ Exit 2 covers an unknown command or option, a missing argument or required optio
|
|
|
161
161
|
| Message peers | [`kxm peer list`](#kxm-peer-list), [`kxm peer send`](#kxm-peer-send), [`kxm peer await`](#kxm-peer-await), [`kxm peer fanout`](#kxm-peer-fanout) |
|
|
162
162
|
| Operate gates and evidence | [`kxm gate validate`](#kxm-gate-validate), [`kxm gate artifacts-exist`](#kxm-gate-artifacts-exist), [`kxm gate signal`](#kxm-gate-signal), [`kxm workflow checkpoint`](#kxm-workflow-checkpoint) |
|
|
163
163
|
| Query context and memory | [`kxm context get`](#kxm-context-get), [`kxm context recall`](#kxm-context-recall), [`kxm memory brief`](#kxm-memory-brief), [`kxm memory note`](#kxm-memory-note) |
|
|
164
|
-
| Read improvement and routing reports | [`kxm improve report`](#kxm-improve-report), [`kxm routing report`](#kxm-routing-report) |
|
|
164
|
+
| Read improvement and routing reports | [`kxm improve report`](#kxm-improve-report), [`kxm routing report`](#kxm-routing-report), [`kxm prices acknowledge`](#kxm-prices-acknowledge) |
|
|
165
165
|
| Back up and restore | [`kxm backup`](#kxm-backup), [`kxm restore`](#kxm-restore) |
|
|
166
166
|
| Update KXM and harnesses | [`kxm update --check`](#kxm-update), [`kxm update --kxm`](#kxm-update) |
|
|
167
167
|
|
|
@@ -183,7 +183,7 @@ Creates, validates, repairs, resumes, or joins a KXM project at the Git root tha
|
|
|
183
183
|
- `--project-id` must match `prj_` followed by 6 to 128 letters, digits, `_`, or `-`. `--repository` is repeatable; each value must be `<id>=<absolute path>`, and a repeated ID fails with `repository_binding_argument_duplicate`.
|
|
184
184
|
- Needs a Git repository. Does not need a hub or the Runtime.
|
|
185
185
|
- Writes `.kxm/project.yaml`, `.kxm/agents/coordinator.yaml`, `.kxm/agents/implementer.yaml`, `.kxm/gates.yaml`, `.kxm/repo/repo.yaml`, `.kxm/workflows/default.yaml`, and `.kxm/template-provenance.yaml`, using a `.kxm-init-transaction` directory at the Git root while a create or repair is in flight. Repository bindings are written under the user state root, never into Git. `--dry-run` writes nothing.
|
|
186
|
-
- On an interactive terminal without `--json` or `--dry-run`, a successful create or join offers to install shell completion (suppress with `KXM_SKIP_COMPLETION_PROMPT=1`) and to write workflow-guide agents
|
|
186
|
+
- On an interactive terminal without `--json` or `--dry-run`, a successful create or join offers to install shell completion (suppress with `KXM_SKIP_COMPLETION_PROMPT=1`) and to write workflow-guide agents (suppress with `KXM_SKIP_GUIDE_SETUP_PROMPT=1`). Guided setup keeps a role only when one of its guide candidates is on a fixed map of reviewed harness/model pairs and that harness is authenticated; it writes the agent and workflow files, appends those selectors to `.kxm/routes.yaml`, and skips every other candidate. Google candidates map to the Pi `antigravity` provider, which the Runtime's Pi one-shot cannot reach yet (see [Harness routing](harness-routing.md#google-through-the-antigravity-pi-provider)).
|
|
187
187
|
- JSON keys: `action` (`planned`, `created`, `joined`, `repaired`, `resumed`, or `validated`), `mode`, `inspectedFrom`, `projectRoot`, `changesRequired`, `legacyInputs`, `issues`, `configRevision`, `files`, `plannedOnly`, and, when relevant, `localBindingFile`, `bindingsChanged`, `repairPlan`, `resumePending`, `transactionKind`.
|
|
188
188
|
- Exit 0 for every completed action and every dry-run plan. Exit 1 when the result is planning-only (legacy state, blocked repair, partial state without provenance) or for `initialization_failed` (with `issues`) and `initialization_io_failed`. A planning-only text result prints the reason and then one `<file>: <code>: <message>` line per validation issue, for example `.kxm/workflows/first.yaml: gate_outcome_impossible: ...`.
|
|
189
189
|
|
|
@@ -846,7 +846,7 @@ kxm studio serve [-p <port>] [--host <host>] [--token <token>]
|
|
|
846
846
|
Serves the Web Studio on `http://127.0.0.1:4242` until interrupted. It serves `/`, `/health`, `GET /api/layout`, and `POST /api/mutate`. The plan comes from `.kxm/workflows/default.yaml` in the current directory, or the first YAML file in `.kxm/workflows/`.
|
|
847
847
|
|
|
848
848
|
> [!WARNING]
|
|
849
|
-
> Every response carries `Access-Control-Allow-Origin: *`, so any web page open in a browser on this machine can read `/api/layout` (your workflow plan). `POST /api/mutate` requires `Authorization: Bearer <session token>` only when a session token resolves (`--token`, `KXM_SESSION_TOKEN`, or the on-disk token); with none, it accepts any caller.
|
|
849
|
+
> Every response carries `Access-Control-Allow-Origin: *`, so any web page open in a browser on this machine can read `/api/layout` (your workflow plan). `POST /api/mutate` requires `Authorization: Bearer <session token>` only when a session token resolves (`--token`, `KXM_SESSION_TOKEN`, or the on-disk token); with none, it accepts any caller. An allowlisted command name gets HTTP 501 with `ok: false`, `executed: false`, `mappedToCli: false` and `error: "mutation_handler_missing"`, because `kxm studio serve` wires no mutation handler; it executes nothing. Keep the default loopback `--host`.
|
|
850
850
|
|
|
851
851
|
| Option | Argument | Default | Description |
|
|
852
852
|
|---|---|---|---|
|
|
@@ -1388,7 +1388,7 @@ Create a KXM run (offline-first; `kxm runs drive <runId> --simulated` executes i
|
|
|
1388
1388
|
- Needs a KXM project. Starts and uses the Runtime; no hub needed. Honors `--dry-run`, which validates the project and prints the plan without starting the supervisor.
|
|
1389
1389
|
- JSON keys: `phase`, `idempotent`, `run` (`runId`, `homeRuntimeId`, `status`, `configRevision`), `supervisor` (`runtimeId`, `port`, `started`). Dry run: `projectRoot`, `workflowId`, `configRevision`. The JSON result does not carry the drive command.
|
|
1390
1390
|
- Errors: `workflow_required` (exit 2), `project_required`, `run_workflow_unknown`, `run_failed` with `issues` (any invalid file in the project fails the load, for example `gate_outcome_impossible`), `run_io_failed` (exit 1).
|
|
1391
|
-
- The `default` workflow that `kxm init` writes
|
|
1391
|
+
- The `default` workflow that `kxm init` writes does not set `limits.maxAgentTimeMs`, so it can be driven. A workflow that sets that limit is created, but `kxm runs drive` refuses it with `run_handoff_required` (`limit_unsupported`); projects from older `kxm init` templates carry it on `default`.
|
|
1392
1392
|
|
|
1393
1393
|
```bash
|
|
1394
1394
|
kxm run default "Fix the flaky login test" --dry-run
|
|
@@ -1458,7 +1458,7 @@ kxm runs status --dry-run refused: the Runtime supervisor is not running and --d
|
|
|
1458
1458
|
kxm runs drive <runId> [--simulated] [--wait] [--timeout-ms <n>]
|
|
1459
1459
|
```
|
|
1460
1460
|
|
|
1461
|
-
Opens a drive of the run. With `--simulated`, a model-free producer reports every agent step as passed. Without `--simulated` the drive runs in live mode: each agent step invokes its harness through a one-shot producer, and the agent's model must be an admitted route (otherwise `producer_route_not_admitted`).
|
|
1461
|
+
Opens a drive of the run. With `--simulated`, a model-free producer reports every agent step as passed. Without `--simulated` the drive runs in live mode: each agent step invokes its harness through a one-shot producer, and the agent's model must be an admitted route (otherwise `producer_route_not_admitted`; there is no fallback model). A read-only step runs with the harness's read-only flags. A step with `write` access runs with an audited writer profile, which only `pi` and `grok` have; it must be a single assignment in a project whose `limits.maxConcurrentRuns` is 1, and, when `.kxm/roster.yaml` exists, its route must be on the roster's writer lineup. Otherwise the drive hands the run off with `step_unsupported`. Around each live attempt the Runtime fingerprints the checkout with `git status` and `git diff`: a write step settles `passed` only when the checkout changed (routing metadata `authored: true`), and a read-only step that changed it settles `failed` (`authoringWitness: readonly_mutated`).
|
|
1462
1462
|
|
|
1463
1463
|
| Option | Argument | Default | Description |
|
|
1464
1464
|
|---|---|---|---|
|
|
@@ -1480,7 +1480,7 @@ kxm runs drive run_0123456789abcdef0123456789abcdef --simulated --dry-run
|
|
|
1480
1480
|
drive plan: run run_0123456789abcdef0123456789abcdef in simulated mode (no events written)
|
|
1481
1481
|
```
|
|
1482
1482
|
|
|
1483
|
-
|
|
1483
|
+
A workflow that declares `limits.maxAgentTimeMs`, such as `default` in a project from an older `kxm init` template, is handed off (see [`kxm run`](#kxm-run)):
|
|
1484
1484
|
|
|
1485
1485
|
```bash
|
|
1486
1486
|
kxm runs drive run_a80e84c98f514299b82f0157f4537ea3 --simulated
|
|
@@ -1826,7 +1826,7 @@ kxm workflow record wf_123 lesson "Flaky test hid a race" --stage-id verify --ev
|
|
|
1826
1826
|
kxm workflow wait [runId] [stageId] [signalKey] [summary] [--evidence <json>] [--evidence-refs <json>] [--timeout-ms <ms>]
|
|
1827
1827
|
```
|
|
1828
1828
|
|
|
1829
|
-
Wait for a workflow signal callback: pauses the active stage until a signed external callback checkpoints it.
|
|
1829
|
+
Wait for a workflow signal callback: pauses the active stage until a signed external callback checkpoints it. Inside a project, a run that this project's Runtime store holds goes to the Runtime instead of the hub (the supervisor starts if needed); any other run ID, including a hub workflow run of the same `run_` + 32-hex shape, goes to the hub. The Runtime has no wait state yet: it checks that the run exists, answers `waiting: true`, and records nothing, so the command prints `waiting for signal on KXM run <id>` although the run is unchanged.
|
|
1830
1830
|
|
|
1831
1831
|
| Option | Argument | Default | Description |
|
|
1832
1832
|
|---|---|---|---|
|
|
@@ -1878,7 +1878,7 @@ KXM_WORKFLOW_ID=provenance-review KXM_WORKFLOW_SIGNAL_SECRET="$SIGNAL_SECRET" kx
|
|
|
1878
1878
|
kxm workflow start [definitionId] [--payload <json|@file>] [--delivery-id <id>] [--event <name>]
|
|
1879
1879
|
```
|
|
1880
1880
|
|
|
1881
|
-
POST a signed workflow-start webhook to `<hub>/v1/webhooks/<definitionId
|
|
1881
|
+
POST a signed workflow-start webhook to `<hub>/v1/webhooks/<definitionId>` under the [KXM sender contract](../guides/webhook-workflows.md#kxm-sender-contract): `x-kxm-signature` is an HMAC-SHA256 over the timestamp (`x-kxm-timestamp`), the delivery ID (`x-kxm-delivery-id`), the definition ID and the body, so a captured request cannot start a second run under another delivery ID. Repeating a delivery ID with the same body returns the existing run ID as a duplicate; with a different body the hub answers 409 `webhook_delivery_conflict`.
|
|
1882
1882
|
|
|
1883
1883
|
| Option | Argument | Default | Description |
|
|
1884
1884
|
|---|---|---|---|
|
|
@@ -2166,9 +2166,9 @@ Post a signed workflow callback that checkpoints a waiting stage.
|
|
|
2166
2166
|
| `--recovery-action` | `<action>` | none | KXM recovery action: retry, fail, cancel, unblock |
|
|
2167
2167
|
|
|
2168
2168
|
- Arguments: `<runId>` Workflow run ID; `<signalKey>` Wait signal key; `<status>` passed, warning, or failed; `<summary>` Callback summary; `[evidence...]` required-key=evidence pairs.
|
|
2169
|
-
-
|
|
2170
|
-
- Otherwise it is a hub webhook callback: `KXM_WORKFLOW_ID` names the definition, and the secret is the definition's `signalSecretEnv` (falling back to `secretEnv`) when a definition source is configured, else `KXM_WORKFLOW_SIGNAL_SECRET`. JSON keys: `duplicate`, `deliveryId`.
|
|
2171
|
-
- Reuse `--delivery-id` to retry one unchanged callback without a duplicate.
|
|
2169
|
+
- Inside a project, a run that this project's Runtime store holds is signaled in the Runtime (the supervisor starts if needed) and `--recovery-action` is passed through. JSON keys: `runId`, `signalKey`, `status`, `unblocked`, `deliveryId`. Hub workflow runs share the `run_` + 32-hex shape, so the store, not the ID, decides.
|
|
2170
|
+
- Otherwise it is a hub webhook callback: `KXM_WORKFLOW_ID` names the definition, and the secret is the definition's `signalSecretEnv` (falling back to `secretEnv`) when a definition source is configured, else `KXM_WORKFLOW_SIGNAL_SECRET`. The callback is signed under the [KXM sender contract](../guides/webhook-workflows.md#kxm-sender-contract), bound to its timestamp, delivery ID, definition, run and signal key. JSON keys: `duplicate`, `deliveryId`.
|
|
2171
|
+
- Reuse `--delivery-id` to retry one unchanged callback without a duplicate; each send re-signs with a fresh timestamp.
|
|
2172
2172
|
- Honors `--dry-run`. Exit 2 for a missing argument, an invalid status, malformed or duplicate evidence keys, or missing `KXM_WORKFLOW_ID` or secret; exit 1 for `signal_failed`.
|
|
2173
2173
|
|
|
2174
2174
|
```bash
|
|
@@ -2227,7 +2227,7 @@ Captured without a GitHub token. With `GITHUB_TOKEN` set, the same command polls
|
|
|
2227
2227
|
|
|
2228
2228
|
## `kxm peer`
|
|
2229
2229
|
|
|
2230
|
-
Peer agent messaging through the hub. Each invocation connects to the hub as a short-lived agent named `KXM_AGENT_NAME` (default `cli-<pid>`) in project `KXM_PROJECT` (default: the `package.json` name, else the directory name), using `KXM_AUTH_TOKEN`,
|
|
2230
|
+
Peer agent messaging through the hub. Each invocation connects to the hub as a short-lived agent named `KXM_AGENT_NAME` (default `cli-<pid>`) in project `KXM_PROJECT` (default: the `package.json` name, else the directory name), using `KXM_AUTH_TOKEN`, else this project's saved project token from `hub-env.json`. It never uses the persisted admin token: with neither, the command exits 2 with `project_token_missing` (`nextAction: "export_kxm_auth_token"`) before contacting the hub. The `kxm workflow` agent verbs (`checkpoint`, `record`, `wait`, `get`, `list`) connect the same way. That agent appears in `peer list` and the dashboard. Every subcommand accepts `--payload <json>` with the tool's fields as one object; explicit flags override it. Tool policy from `KXM_ATTEMPT_TOKEN`, `KXM_SESSION_TOKEN`, or the on-disk session token is enforced first (`tool_policy_denied`, `session_token_invalid`, `attempt_token_invalid`).
|
|
2231
2231
|
|
|
2232
2232
|
All subcommands need a hub, honor `--dry-run` (printing the parsed `args` without connecting), print the hub's result object on success, and print `{"ok":false,"error":"command_failed","detail":"..."}` with exit 1 on failure. Malformed `--payload` exits 2 with `invalid_payload`.
|
|
2233
2233
|
|
|
@@ -3245,10 +3245,10 @@ Without `--file` it reads the same sources as [`kxm improve report`](#kxm-improv
|
|
|
3245
3245
|
- Reads only. A price catalog that cannot be loaded is skipped silently.
|
|
3246
3246
|
- `--equivalent-list-cost` loads the catalog without the freshness check the producers apply, so it prices with a catalog of any date, including one the producers treat as stale. Check the catalog `date` before you rely on `ListEquiv($)`.
|
|
3247
3247
|
- A Runtime store that exists but cannot be read exits 1 with `improve_source_unreadable`.
|
|
3248
|
-
- Ranking: quality first (Pass%, then Rwk%), then cost per accepted attempt. Among routes of equal quality, any route with an unknown-cost attempt ranks after every route without one.
|
|
3248
|
+
- Ranking: quality first (Pass%, then Rwk%), then cost per accepted attempt. Among routes of equal quality, any route with an unknown-cost attempt ranks after every route without one. Such a route's cost is unknown, not a partial sum: `$/Acc` prints `-` (JSON `costPerAcceptedUsd: null`) and the `*` in `Unk` marks it.
|
|
3249
3249
|
- The `Quota` column counts attempts whose metadata looks quota-exhausted (a quota failure class, a `quota` flag, or text such as `rate limit` or `HTTP 429`). It is a count only: nothing fails over to another route.
|
|
3250
3250
|
- The text output does not list the sources, and prints `no routing records in telemetry` when no source holds a record. The Rwk% column counts records with `transitions` greater than 0, which Runtime records never set.
|
|
3251
|
-
- JSON keys: `file` (the telemetry path, also when the Runtime store was read), `sources` (without `--file`; the same shape as in `kxm improve`), `configurations` (per behavioral hash for v1 records), `report` (`schema`, `generatedAt`, `totalAttempts`, `rows`).
|
|
3251
|
+
- JSON keys: `file` (the telemetry path, also when the Runtime store was read), `sources` (without `--file`; the same shape as in `kxm improve`), `configurations` (per behavioral hash for v1 records; `totalCostUsd` is `null` when any record lacks a cost, and `missingCostRuns` counts those records), `report` (`schema`, `generatedAt`, `totalAttempts`, `rows`).
|
|
3252
3252
|
|
|
3253
3253
|
With no routing records yet:
|
|
3254
3254
|
|
|
@@ -3306,45 +3306,78 @@ claude fable 680 1200 450 $0.4
|
|
|
3306
3306
|
pi qwen3-coder-plus 560 1200 450 $0.12 passed
|
|
3307
3307
|
```
|
|
3308
3308
|
|
|
3309
|
+
## `kxm prices`
|
|
3310
|
+
|
|
3311
|
+
### `kxm prices acknowledge`
|
|
3312
|
+
|
|
3313
|
+
```text
|
|
3314
|
+
kxm prices acknowledge
|
|
3315
|
+
```
|
|
3316
|
+
|
|
3317
|
+
Stamps the existing `.kxm/prices.yaml` list as today's estimate. It rewrites the file with today's date and a new `sha256` over the same `models` rows, then reads it back and checks both. It does not fetch vendor rates or change any price. Cost estimates treat a catalog whose date is not today as stale and stay unknown, so run this only after you have checked the list against the vendors' current rates.
|
|
3318
|
+
|
|
3319
|
+
No command-specific options.
|
|
3320
|
+
|
|
3321
|
+
- Reads and rewrites `.kxm/prices.yaml` in the current directory. Needs no hub and no Runtime.
|
|
3322
|
+
- Refuses `--dry-run` with `dry_run_unsupported` (exit 2).
|
|
3323
|
+
- JSON keys: `date`, `sha256`, `note`.
|
|
3324
|
+
- Exit 1 with `prices_acknowledge_failed` and a `message`, for example `price catalog missing` when there is no `.kxm/prices.yaml`.
|
|
3325
|
+
|
|
3326
|
+
```bash
|
|
3327
|
+
kxm prices acknowledge
|
|
3328
|
+
```
|
|
3329
|
+
|
|
3330
|
+
```text
|
|
3331
|
+
price catalog stamped 2026-09-23 (list estimate only; vendor rates were not fetched)
|
|
3332
|
+
```
|
|
3333
|
+
|
|
3309
3334
|
## `kxm backup`
|
|
3310
3335
|
|
|
3311
3336
|
```text
|
|
3312
3337
|
kxm backup [--out <dir>]
|
|
3313
3338
|
```
|
|
3314
3339
|
|
|
3315
|
-
Creates a verified SQLite backup of the project hub store
|
|
3340
|
+
Creates a verified SQLite backup of the project hub store and the Runtime stores under the user state root, with a hashed `kxm.backup-manifest.v1` manifest. Relative to the current directory it discovers the hub store `.kxm/state/kxm.db`, and any `registry.db`, `bindings.db`, and `events/*.db` under `.kxm/runtime/`. Under the user state root (`KXM_STATE_HOME` or the platform default) it discovers `runtime/registry.db`, the `run-events.db` of every project under `runtime/projects/`, and each store's `run-events.db.run-prompts.json` prompt sidecar, which it copies as a plain file. Those Runtime stores belong to every project on the machine, not only this one. It ignores `--workspace` and `KXM_DATA_PATH`. Bindings, `update.yaml` and the other state roots are not included; see [Backup and restore](../operations/backup-and-restore.md).
|
|
3316
3341
|
|
|
3317
3342
|
| Option | Argument | Default | Description |
|
|
3318
3343
|
|---|---|---|---|
|
|
3319
3344
|
| `--out` | `<dir>` | `.kxm/backups/backup-<timestamp>` | Directory to write backup and manifest |
|
|
3320
3345
|
|
|
3321
|
-
- Writes a copy of each store and `manifest.json`. `--dry-run` lists the stores it found and the files it would write without opening any store, so no WAL is checkpointed (JSON: `outDir`, `stores` with `storeId`, `sourcePath`, `backupFile`, plus `dryRun` and `planned`).
|
|
3322
|
-
- JSON keys: `backupId`, `outDir`, `manifest` (`schema`, `backupId`, `createdAt`, `projectRoot`, `stores` with `storeId`, `sourcePath`, `backupFile`, `schemaVersion`, `sha256`, `bytes`, `integrity`; `manifestSha256`).
|
|
3323
|
-
-
|
|
3346
|
+
- Writes a copy of each store and sidecar and `manifest.json`. `--dry-run` lists the stores and files it found and the files it would write without opening any store, so no WAL is checkpointed (JSON: `outDir`, `stores` with `storeId`, `sourcePath`, `backupFile`, `files` with `id`, `sourcePath`, `backupFile`, plus `dryRun` and `planned`).
|
|
3347
|
+
- JSON keys: `ok`, `backupId`, `outDir`, `manifest` (`schema`, `backupId`, `createdAt`, `projectRoot`, `stores` with `storeId`, `sourcePath`, `backupFile`, `schemaVersion`, `sha256`, `bytes`, `integrity`; `files` with `id`, `sourcePath`, `backupFile`, `sha256`, `bytes`; `complete`; `omitted`; `manifestSha256`).
|
|
3348
|
+
- A store or sidecar that cannot be copied, or that appears while the backup runs, is listed in `omitted` and the manifest records `complete: false`. The command then prints `Backup is incomplete (<n> omitted); not ok:`, sets `ok: false`, and exits 1; `kxm restore` refuses that manifest.
|
|
3349
|
+
- Exit 1 with `backup_failed`; `issues` carry codes such as `backup_no_stores` (no store found, or none could be copied) and `database_corrupted`.
|
|
3324
3350
|
|
|
3325
3351
|
```bash
|
|
3326
3352
|
kxm backup --out ../bk
|
|
3327
3353
|
```
|
|
3328
3354
|
|
|
3329
3355
|
```text
|
|
3330
|
-
Created SQLite backup with
|
|
3356
|
+
Created SQLite backup with 3 store(s):
|
|
3331
3357
|
- hub-store: /work/proj/.kxm/state/kxm.db -> kxm.db (schema v5, 110592 bytes, sha256 sha256:56c6d...)
|
|
3358
|
+
- registry: /home/me/.local/state/kxm/runtime/registry.db -> registry.db (schema v1, 20480 bytes, sha256 sha256:a5bde...)
|
|
3359
|
+
- events:38ed26cb8eeaa297f3b0b452: /home/me/.local/state/kxm/runtime/projects/38ed26cb8eeaa297f3b0b452/run-events.db -> run-events.db (schema v7, 348160 bytes, sha256 sha256:27c13...)
|
|
3332
3360
|
Manifest: /work/bk/manifest.json
|
|
3333
3361
|
```
|
|
3334
3362
|
|
|
3335
|
-
|
|
3363
|
+
The summary lists stores; the prompt sidecar appears under `files` in the manifest.
|
|
3364
|
+
|
|
3365
|
+
In a project with a hub store and one Runtime project:
|
|
3336
3366
|
|
|
3337
3367
|
```bash
|
|
3338
3368
|
kxm backup --dry-run
|
|
3339
3369
|
```
|
|
3340
3370
|
|
|
3341
3371
|
```text
|
|
3342
|
-
dry run: back up
|
|
3372
|
+
dry run: back up 3 store(s) and 1 file(s) to /work/proj/.kxm/backups/backup-2026-09-23T17-44-51-277Z (sources are not opened, so their WAL is not checkpointed)
|
|
3343
3373
|
would write /work/proj/.kxm/backups/backup-2026-09-23T17-44-51-277Z/kxm.db
|
|
3374
|
+
would write /work/proj/.kxm/backups/backup-2026-09-23T17-44-51-277Z/registry.db
|
|
3375
|
+
would write /work/proj/.kxm/backups/backup-2026-09-23T17-44-51-277Z/run-events.db
|
|
3376
|
+
would write /work/proj/.kxm/backups/backup-2026-09-23T17-44-51-277Z/run-events.db.run-prompts.json
|
|
3344
3377
|
would write /work/proj/.kxm/backups/backup-2026-09-23T17-44-51-277Z/manifest.json
|
|
3345
3378
|
```
|
|
3346
3379
|
|
|
3347
|
-
|
|
3380
|
+
On a machine with no hub store and no Runtime stores yet:
|
|
3348
3381
|
|
|
3349
3382
|
```bash
|
|
3350
3383
|
kxm backup --json
|
|
@@ -3360,14 +3393,14 @@ kxm backup --json
|
|
|
3360
3393
|
kxm restore <manifest>
|
|
3361
3394
|
```
|
|
3362
3395
|
|
|
3363
|
-
Restores SQLite stores from a verified backup manifest. It checks the manifest schema, that every backup file is present
|
|
3396
|
+
Restores SQLite stores and prompt sidecars from a verified backup manifest. It checks the manifest schema, refuses a manifest that records `complete: false` (`restore_incomplete`), checks that every backup file is present and each file's digest, then restores each store to its recorded source path, then each sidecar. A path under the manifest's project root is rebased onto the current directory when the manifest came from another project root; Runtime stores under the user state root go back to their recorded absolute paths. A manifest without a `complete` field, from an older build, still restores.
|
|
3364
3397
|
|
|
3365
3398
|
- Arguments: `<manifest>`, path to `manifest.json`.
|
|
3366
3399
|
- No command-specific options.
|
|
3367
|
-
- Overwrites live stores
|
|
3368
|
-
- Before overwriting anything, a restore checks every store's recorded schema version against the ceiling for that store, so a store newer than this build is refused (`runtime_schema_newer`) before the first file is replaced. `--dry-run` runs the same manifest, file, digest, and schema checks and plans each target it would overwrite (and any `-wal` or `-shm` sidecar it would delete) without touching them. Dry-run JSON keys: `backupId`, `manifestPath`, `stores` (`storeId`, `targetPath`, `schemaVersion`), `dryRun`, `planned`.
|
|
3400
|
+
- Overwrites live stores, including the Runtime stores of every project the backup holds; it does not check whether the hub or the Runtime is running. Stop both before restoring.
|
|
3401
|
+
- Before overwriting anything, a restore checks every store's recorded schema version against the ceiling for that store, so a store newer than this build is refused (`runtime_schema_newer`) before the first file is replaced. `--dry-run` runs the same manifest, file, digest, and schema checks and plans each target it would overwrite (and any `-wal` or `-shm` sidecar it would delete) without touching them. Dry-run JSON keys: `backupId`, `manifestPath`, `stores` (`storeId`, `targetPath`, `schemaVersion`), `files` (`id`, `targetPath`), `dryRun`, `planned`.
|
|
3369
3402
|
- JSON keys: `backupId`, `manifestPath`, `restoredStores` (`storeId`, `sourcePath`, `backupFile`, `schemaVersion`, `integrity`).
|
|
3370
|
-
- Exit 1 with `restore_failed`; `issues` carry codes such as `runtime_path_invalid`, `restore_manifest_invalid`, `restore_file_missing`, `restore_manifest_digest_mismatch`, and `runtime_schema_newer`.
|
|
3403
|
+
- Exit 1 with `restore_failed`; `issues` carry codes such as `runtime_path_invalid`, `restore_manifest_invalid`, `restore_incomplete`, `restore_file_missing`, `restore_manifest_digest_mismatch`, and `runtime_schema_newer`.
|
|
3371
3404
|
- Two more checks run per store while restoring, after the plan checks: a backup file whose schema version differs from the version the manifest records is refused with `runtime_schema_mismatch`, and one that fails its SQLite integrity check with `database_corrupted`. In a multi-store restore, stores restored before the refused one stay restored.
|
|
3372
3405
|
|
|
3373
3406
|
```bash
|
|
@@ -3375,8 +3408,11 @@ kxm restore ../bk/manifest.json --dry-run
|
|
|
3375
3408
|
```
|
|
3376
3409
|
|
|
3377
3410
|
```text
|
|
3378
|
-
dry run: restore
|
|
3411
|
+
dry run: restore 3 store(s) and 1 file(s) from /work/bk/manifest.json; digests verified against the manifest
|
|
3379
3412
|
would write /work/proj/.kxm/state/kxm.db
|
|
3413
|
+
would write /home/me/.local/state/kxm/runtime/registry.db
|
|
3414
|
+
would write /home/me/.local/state/kxm/runtime/projects/38ed26cb8eeaa297f3b0b452/run-events.db
|
|
3415
|
+
would write /home/me/.local/state/kxm/runtime/projects/38ed26cb8eeaa297f3b0b452/run-events.db.run-prompts.json
|
|
3380
3416
|
```
|
|
3381
3417
|
|
|
3382
3418
|
A missing manifest:
|
|
@@ -3552,7 +3588,6 @@ Inspect KXM runs
|
|
|
3552
3588
|
|
|
3553
3589
|
These are behaviors of the current build that differ from what the help text or the flag names suggest. Each is also noted in the command's section.
|
|
3554
3590
|
|
|
3555
|
-
- The `default` workflow that `kxm init` writes sets `limits.maxAgentTimeMs`, so `kxm runs drive` hands every run of it off with `run_handoff_required` (`limit_unsupported`). Use a `kxm workflow add --template` workflow, or remove the limit, to drive a first run.
|
|
3556
3591
|
- `kxm routing benchmark` prints constant placeholder figures.
|
|
3557
3592
|
- `kxm task sync` does not contact GitHub or Jira.
|
|
3558
3593
|
- `kxm peer inbox` always returns an empty list from the CLI.
|
|
@@ -340,11 +340,15 @@ reference. Schema: `schemas/agent.schema.json`; semantic checks in
|
|
|
340
340
|
| `session.maxIdleMs` | Integer, 0 to 31,536,000,000 | Optional | Not read by any code path yet |
|
|
341
341
|
|
|
342
342
|
Tool presets are names checked against a registered list. The live one-shot
|
|
343
|
-
producer launches
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
343
|
+
producer launches each harness with a fixed argument set and does not
|
|
344
|
+
translate `tools` into harness flags. A read-only step uses the read-only set
|
|
345
|
+
(`READ_ONLY_ONESHOT_ARGS` in `plugins/kxm/src/harness.ts`). A step with `write`
|
|
346
|
+
access uses the audited writer set (`WRITER_ONESHOT_ARGS`), which exists only
|
|
347
|
+
for `pi` (`-a` with extensions, skills, prompt templates and sessions off) and
|
|
348
|
+
`grok` (`--always-approve` with subagents and web search off). Both approve
|
|
349
|
+
every tool call, shell commands included, and neither confines the process to
|
|
350
|
+
the checkout. A live write step on any other harness is handed off; see
|
|
351
|
+
[Steps the Runtime does not execute yet](#steps-the-runtime-does-not-execute-yet).
|
|
348
352
|
|
|
349
353
|
### Model selectors
|
|
350
354
|
|
|
@@ -451,9 +455,11 @@ Error codes: `executor_unknown`, `harness_unknown`, `tool_preset_unknown`,
|
|
|
451
455
|
[Rules shared by the project bundle](#rules-shared-by-the-project-bundle), and
|
|
452
456
|
`role_roster_conflicts_with_agent` (see [Roles](#kxmrolesroleyaml-kxmrolev1)).
|
|
453
457
|
|
|
454
|
-
Commands: `kxm init` creates `coordinator`
|
|
455
|
-
`
|
|
456
|
-
|
|
458
|
+
Commands: `kxm init` creates `coordinator` (`claude`, `anthropic/fable`) and
|
|
459
|
+
`implementer` (`grok`, `xai/grok-4.6`) and admits both selectors in
|
|
460
|
+
`.kxm/routes.yaml`; an interactive `kxm init` can add workflow-guide agents for
|
|
461
|
+
reviewed harness/model pairs whose harness is authenticated, and admits their
|
|
462
|
+
selectors too; `kxm run` and the Runtime read them; `kxm trust` diffs them.
|
|
457
463
|
|
|
458
464
|
## `.kxm/models/<id>.yaml` (`kxm.model.v1`)
|
|
459
465
|
|
|
@@ -672,16 +678,21 @@ these, the run is handed off (`step_unsupported`, `gate_unsupported`, or
|
|
|
672
678
|
`limit_unsupported`) instead of executing, and `kxm runs drive` reports
|
|
673
679
|
`run_handoff_required`:
|
|
674
680
|
|
|
675
|
-
- `limits.maxAgentTimeMs` in the workflow or project. The
|
|
676
|
-
|
|
677
|
-
as generated.
|
|
681
|
+
- `limits.maxAgentTimeMs` in the workflow or project. The current `kxm init`
|
|
682
|
+
template leaves it out; older templates set it on `default`.
|
|
678
683
|
- `tools`, `secrets`, or `safeSpeculation: true` on any step.
|
|
679
684
|
- `join.strategy` other than `all` or `all-settled`, `join.minimumPassed` with
|
|
680
685
|
`all`, and `join.cancelRemaining`.
|
|
681
686
|
- `assignments.maxAttemptsPerAssignment` above 2,
|
|
682
687
|
`assignments.maxWriteRepositories` above 1, and `distinctBy` other than
|
|
683
688
|
`provider`.
|
|
684
|
-
- A live (non-simulated) step with `write` access to any repository
|
|
689
|
+
- A live (non-simulated) step with `write` access to any repository when its
|
|
690
|
+
agent's harness has no audited writer profile (only `pi` and `grok` have
|
|
691
|
+
one), when `assignments.maximum` is above 1, when the project's
|
|
692
|
+
`limits.maxConcurrentRuns` is above 1, or when `.kxm/roster.yaml` exists and
|
|
693
|
+
is not a `kxm.developer-roster.v1` whose writer lineup admits that harness
|
|
694
|
+
and model with `edit` permission. The checkout witness can attribute a
|
|
695
|
+
change only to one writer at a time.
|
|
685
696
|
- Gate steps with `assignments.allowedAgents`, any assignment count or
|
|
686
697
|
`maxAttemptsPerAssignment` other than 1, `distinctBy`,
|
|
687
698
|
`maxWriteRepositories`, a join other than plain `all`, a `model`, no
|
|
@@ -1716,7 +1727,7 @@ Everything under `.kxm/` at the project root falls into one of three groups.
|
|
|
1716
1727
|
| `logs/` | Ignored runtime logs | The hub and workers |
|
|
1717
1728
|
| `state/` | Ignored restart state: the hub database `kxm.db`, Pi sessions, worker manifests | The hub and workers |
|
|
1718
1729
|
| `run/` | Ignored sockets (`run/ssh-sockets/`) | `kxm ssh` |
|
|
1719
|
-
| `backups/` | Ignored; each `backup-<time>/` holds
|
|
1730
|
+
| `backups/` | Ignored; each `backup-<time>/` holds copies of the hub database, the Runtime stores and their prompt sidecars, and `manifest.json` | `kxm backup` (without `--out`) |
|
|
1720
1731
|
| `config/` | Legacy: its JSON files make the project unloadable | Nothing current |
|
|
1721
1732
|
| `.kxm-init-transaction/` (sibling of `.kxm/` at the Git root) | Ignored; interrupted `kxm init` state | `kxm init` |
|
|
1722
1733
|
|
|
@@ -114,9 +114,9 @@ Every agent client (the Pi extension, the Claude Code MCP server, and the `kxm p
|
|
|
114
114
|
|
|
115
115
|
| Harness | Default name | Default purpose | Token when `KXM_AUTH_TOKEN` is unset |
|
|
116
116
|
|---|---|---|---|
|
|
117
|
-
| Pi extension | The Pi session name, else `pi-<pid>` | `General-purpose coding agent` |
|
|
117
|
+
| Pi extension | The Pi session name, else `pi-<pid>` | `General-purpose coding agent` | This project's saved project token only; never the admin token |
|
|
118
118
|
| Claude Code MCP server | `claude` from the plugin settings, else `claude-<pid>` | `Claude Code implementation and review agent` | This project's saved project token only; never the admin token |
|
|
119
|
-
| `kxm peer` commands | `cli-<pid>` | `CLI agent client` |
|
|
119
|
+
| `kxm peer` and `kxm workflow` agent commands | `cli-<pid>` | `CLI agent client` | This project's saved project token only; never the admin token (`project_token_missing`, exit 2) |
|
|
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
|
|
|
@@ -278,7 +278,7 @@ No product producer writes `metered` today. Subscription runs are `unmetered` be
|
|
|
278
278
|
- The catalog `date` is today.
|
|
279
279
|
- The harness is `claude` and the token counts are complete. The Runtime runs every harness, Pi included, through the one-shot producer, which estimates only for `claude`. The package also ships a long-lived Pi producer that estimates for any provider, but nothing in the Runtime calls it today.
|
|
280
280
|
|
|
281
|
-
`kxm routing report` groups records by harness, model, effort and role. It ranks routes by quality first (Pass%, then Rwk%), then by cost per accepted attempt. Among routes of equal quality, a route with any unknown-cost attempt ranks after every route without one, because its cost is
|
|
281
|
+
`kxm routing report` groups records by harness, model, effort and role. It ranks routes by quality first (Pass%, then Rwk%), then by cost per accepted attempt. Among routes of equal quality, a route with any unknown-cost attempt ranks after every route without one, because its cost is unknown. Its `$/Acc` prints `-` instead of a partial sum, and the `*` in the `Unk` column marks it. Read `Unm` and `Unk` before you trust `$/Acc`.
|
|
282
282
|
|
|
283
283
|
The `Quota` column counts attempts whose metadata looks quota-exhausted. Nothing fails over on it. The report has no provider column, so the `Harness` column is what tells you native from Pi.
|
|
284
284
|
|
|
@@ -407,7 +407,7 @@ The code today limits where that route can run:
|
|
|
407
407
|
- The KXM Pi extension registers the `antigravity` provider. The Runtime's Pi one-shot runs with `--no-extensions`, so it cannot reach `antigravity/…`.
|
|
408
408
|
- Only an interactive Pi with KXM loaded, or a long-lived Pi worker, can use it.
|
|
409
409
|
|
|
410
|
-
Admit an `antigravity/…` route only through a reviewed admission decision. Whether Google work should use `agy` or `antigravity` today is still an operator decision; see [Where the code is looser than the rules](#where-the-code-is-looser-than-the-rules).
|
|
410
|
+
Admit an `antigravity/…` route only through a reviewed admission decision. The guided setup in `kxm init` maps Google guide candidates to `antigravity/gemini-3.8-flash-high` on Pi and admits that selector, but a Runtime drive of such an agent still cannot reach the provider. Whether Google work should use `agy` or `antigravity` today is still an operator decision; see [Where the code is looser than the rules](#where-the-code-is-looser-than-the-rules).
|
|
411
411
|
|
|
412
412
|
### Claude bridge: experiment only
|
|
413
413
|
|
|
@@ -493,7 +493,7 @@ These are places where the code decides less than the [routing rules](#routing-r
|
|
|
493
493
|
- **Reseller ids without a vendor segment.** Pi also serves native vendors' models under a reseller's own provider id, for example `github-copilot/claude-…`, `amazon-bedrock/anthropic.…` or `azure-openai-responses/gpt-…`. The brake reads the provider and the vendor segment, not the model name, so it accepts these ids. Keep them out of `.kxm/routes.yaml`.
|
|
494
494
|
- **Open-weight models on a third-party plan.** A DeepSeek model billed through Alibaba's plan, such as `qwen-token-plan/deepseek-v4.1-flash`, passes the brake although DeepSeek is a braked vendor. The reverse also happens: an open-weight model filed under a native vendor's namespace, such as `groq/openai/gpt-oss-120b`, is refused. Whether a third-party plan bill counts as billing the vendor is not decided.
|
|
495
495
|
- **Selectors the brake cannot see.** A worker started without `--model` runs Pi's default model, and a bare model id lets Pi choose the provider. Neither names a vendor, so neither is checked. A model that a person selects inside an interactive Pi session is not checked either.
|
|
496
|
-
- **The Google route.** The rules name the `antigravity` Pi provider as Google's route,
|
|
496
|
+
- **The Google route.** The rules name the `antigravity` Pi provider as Google's route, and the guided setup in `kxm init` now writes Google roles on Pi with `antigravity/gemini-3.8-flash-high` and admits that selector. The Runtime's Pi one-shot runs with `--no-extensions`, so it cannot reach `antigravity/…` and a live drive of those roles fails at dispatch (section 4). A `google/gemini-…` selector still runs only through `agy`.
|
|
497
497
|
- **Two Pi provider allowlists.** `PI_ALLOWED_PROVIDERS` in `plugins/kxm/src/harness.ts` is exported but gates nothing; on the product path, the brake and admission in `.kxm/routes.yaml` decide. The developer helper keeps a different list of its own.
|
|
498
498
|
- **`limits.maxModelCost` cannot trip on a live run.** The engine counts only `metered` cost toward the cap, and no producer records `metered` today (section 2). The Runtime caps `unmetered` and `unknown` attempts at 100 per run instead; see [Cost basis and staleness](config-reference.md#cost-basis-and-staleness).
|
|
499
499
|
- **Config load checks harness/model pairs only for agents that declare `harness:` and select their model through a profile or tag.** An agent with a direct `{provider, model}` selector, or one that omits `harness:` and names `provider: xai`, passes `kxm init --dry-run` and fails only at dispatch.
|
|
@@ -26,7 +26,7 @@ Each route requires one of these credential classes. Send tokens as `Authorizati
|
|
|
26
26
|
| Project | The project's token, or the admin token for a project with no token of its own | Admission for agent registration and Runtime sync |
|
|
27
27
|
| Agent | The project token plus `x-kxm-agent-id` and `x-kxm-agent-key` from registration | The agent key rotates on every registration (401 `invalid_agent_identity` when stale) |
|
|
28
28
|
| Agent or admin | Agent headers for your own project, or the admin token with an explicit `project` | Admin calls may name a caller in `x-kxm-caller-id` |
|
|
29
|
-
| Signed |
|
|
29
|
+
| Signed | The [KXM sender contract](../guides/webhook-workflows.md#kxm-sender-contract): `x-kxm-signature` over the timestamp, delivery ID, route and body, within 300 seconds. A `jira` or `github` start may instead carry its provider's body HMAC | No bearer token; the secret is the workflow definition's |
|
|
30
30
|
|
|
31
31
|
A wrong or missing token is 401 `invalid_auth`. The [trust model](../concepts/trust-model.md) explains which person or process holds each credential.
|
|
32
32
|
|
|
@@ -75,7 +75,7 @@ sequenceDiagram
|
|
|
75
75
|
|
|
76
76
|
| Method | Path | Auth | Purpose |
|
|
77
77
|
|---|---|---|---|
|
|
78
|
-
| `POST` | `/v1/messages` | Agent | Send a request: `target`, `content`, optional `delivery`, `correlationId`, `idempotencyKey`, `workflowContext`, `ttlMs`, `maxHops`, `allowOffline`. 202 new; 200 with `idempotent: true` for an exact retry |
|
|
78
|
+
| `POST` | `/v1/messages` | Agent | Send a request: `target`, `content`, optional `delivery`, `correlationId`, `idempotencyKey`, `workflowContext`, `ttlMs`, `hops`, `maxHops`, `allowOffline`. `kxm_send` and `kxm_fanout` set `hops` one past the inbound request being handled. 202 new; 200 with `idempotent: true` for an exact retry |
|
|
79
79
|
| `GET` | `/v1/messages/<id>` | Agent | Read a message you sent or received |
|
|
80
80
|
| `POST` | `/v1/messages/<id>/ack` | Agent | Recipient marks a queued message `delivered` |
|
|
81
81
|
| `POST` | `/v1/messages/<id>/reply` | Agent | Recipient replies with `content`; the sender gets a `reply` event |
|
|
@@ -115,13 +115,13 @@ Key errors: `workflow_not_found` (404), `workflow_stage_out_of_order`, `workflow
|
|
|
115
115
|
| `POST` | `/v1/webhooks/<definitionId>` | Signed with the definition's start secret | Start a run and prompt the coordinator (status codes below) |
|
|
116
116
|
| `POST` | `/v1/webhooks/<definitionId>/runs/<runId>/signals/<signalKey>` | Signed with the signal secret, else the start secret | Report an external result for a waiting stage: `status` (`passed`, `warning`, `failed`), `summary`, `evidence`. 202 when the coordinator is resumed, 200 otherwise |
|
|
117
117
|
|
|
118
|
-
A start answers 202 for a new run, 200 with `duplicate: true` for a known delivery ID, and 204 when the event or filter does not match.
|
|
118
|
+
A start answers 202 for a new run, 200 with `duplicate: true`, `runId` and `status` for a known delivery ID and body, and 204 when the event or filter does not match.
|
|
119
119
|
|
|
120
|
-
|
|
120
|
+
Every signal, and every start of a `generic` definition, is signed under the KXM sender contract, and its delivery ID is the signed `x-kxm-delivery-id`. A `jira` or `github` start may instead carry the provider's body HMAC in `x-hub-signature-256` (or `x-hub-signature`); its delivery ID is then that provider's own header, `x-atlassian-webhook-identifier` or `x-github-delivery`, and the signed body starts at most one run. The event comes from `x-github-event`, else the payload's `webhookEvent` or `event` field.
|
|
121
121
|
|
|
122
|
-
A start with a known delivery ID
|
|
122
|
+
A start with a known delivery ID and a different body is refused with 409 `webhook_delivery_conflict`. A signal with a known delivery ID returns the original receipt when the body matches and 409 `workflow_signal_delivery_conflict` when it does not. Deduplication lasts as long as the run is retained.
|
|
123
123
|
|
|
124
|
-
Key errors: `webhook_not_found` (404), `webhook_signature_missing`, `webhook_signature_unsupported`, `webhook_signature_invalid` (401), `workflow_target_unavailable` (409, the coordinator never registered), `workflow_not_waiting`, `workflow_signal_mismatch` (409, the run waits for another key) and `workflow_signal_context_mismatch` (409, evidence names another run, stage or signal). See [Webhook workflows](../guides/webhook-workflows.md).
|
|
124
|
+
Key errors: `webhook_not_found` (404), `webhook_signature_missing`, `webhook_signature_unsupported`, `webhook_signature_invalid`, `webhook_timestamp_invalid`, `webhook_timestamp_expired` (401), `webhook_delivery_conflict`, `webhook_payload_replayed` (409, a provider body already started a run under another delivery ID), `workflow_target_unavailable` (409, the coordinator never registered), `workflow_not_waiting`, `workflow_signal_mismatch` (409, the run waits for another key) and `workflow_signal_context_mismatch` (409, evidence names another run, stage or signal). See [Webhook workflows](../guides/webhook-workflows.md).
|
|
125
125
|
|
|
126
126
|
## Context and state
|
|
127
127
|
|
|
@@ -191,7 +191,7 @@ A bad token is `runtime_auth_failed`; a missing parameter is `runtime_request_in
|
|
|
191
191
|
|
|
192
192
|
## Web Studio server
|
|
193
193
|
|
|
194
|
-
`kxm studio serve` runs a separate local server, on `127.0.0.1:4242` by default, with `/`, `/health`, `GET /api/layout` and `POST /api/mutate`. It answers every route with `Access-Control-Allow-Origin: *`, so any web page in a local browser can read the layout. The mutate route requires a bearer session token only when one resolves; with none it accepts any caller. It
|
|
194
|
+
`kxm studio serve` runs a separate local server, on `127.0.0.1:4242` by default, with `/`, `/health`, `GET /api/layout` and `POST /api/mutate`. It answers every route with `Access-Control-Allow-Origin: *`, so any web page in a local browser can read the layout. The mutate route requires a bearer session token only when one resolves; with none it accepts any caller. It answers an allowlisted command name with HTTP 501 and `error: "mutation_handler_missing"`, and executes nothing. See [`kxm studio serve`](cli-reference.md#kxm-studio-serve).
|
|
195
195
|
|
|
196
196
|
## Related
|
|
197
197
|
|
package/docs/reference/tools.md
CHANGED
|
@@ -44,7 +44,7 @@ The workflow tools act on hub [webhook workflow runs](workflow-definitions.md#we
|
|
|
44
44
|
|
|
45
45
|
### Identity and credentials
|
|
46
46
|
|
|
47
|
-
Each tool call acts as the calling session's registered agent in one hub project. The Claude Code MCP server authenticates with a project token only: `auth_token` from the plugin settings, else this project's token saved in `hub-env.json`, and never the admin token. The Pi extension uses `KXM_AUTH_TOKEN`,
|
|
47
|
+
Each tool call acts as the calling session's registered agent in one hub project. The Claude Code MCP server authenticates with a project token only: `auth_token` from the plugin settings, else this project's token saved in `hub-env.json`, and never the admin token. The Pi extension uses `KXM_AUTH_TOKEN`, else this project's saved project token, and never the admin token, including one that hub auto-start generated. `kxm_send` and `kxm_fanout` send one hop past the inbound request the session is handling, so the hub's hop limit stops a chain of forwarding agents. See [Agent settings](configuration.md#agent-settings).
|
|
48
48
|
|
|
49
49
|
### Tool policy
|
|
50
50
|
|
|
@@ -49,7 +49,7 @@ Keep the file out of `.kxm/config/workflows/`: any JSON there counts as legacy c
|
|
|
49
49
|
| Field | Type and limits | Default | Effect |
|
|
50
50
|
|---|---|---|---|
|
|
51
51
|
| `id` | String, 64 characters, unique | Required | The URL segment in `/v1/webhooks/<id>` and the name `kxm workflow start` takes |
|
|
52
|
-
| `source` | `jira`, `github` or `generic` | `generic` | Recorded on each run;
|
|
52
|
+
| `source` | `jira`, `github` or `generic` | `generic` | Recorded on each run. A `jira` or `github` definition also accepts its provider's body-only signature and delivery header; a `generic` one accepts only the KXM sender contract |
|
|
53
53
|
| `project` | String, 128 characters | Required | Hub project the run and its coordinator belong to |
|
|
54
54
|
| `target` | Agent name or ID, 80 characters | Required | The coordinator. It must have registered at least once before a run starts; it may be offline |
|
|
55
55
|
| `secretEnv` | Environment variable name | Use this or `secret` | Variable holding the start secret, read when the definitions are parsed |
|
|
@@ -148,7 +148,7 @@ Every transition taken is journaled as a `state-change` entry.
|
|
|
148
148
|
|
|
149
149
|
`kxm_workflow_wait` parks the active stage in `waiting` under a `signalKey` until a signed callback arrives or the wait expires (1 second to 30 days, default 24 hours; expiry fails the run). The coordinator can then reply to release its turn.
|
|
150
150
|
|
|
151
|
-
The callback is `POST /v1/webhooks/<id>/runs/<runId>/signals/<signalKey>`, signed
|
|
151
|
+
The callback is `POST /v1/webhooks/<id>/runs/<runId>/signals/<signalKey>`, signed under the [KXM sender contract](../guides/webhook-workflows.md#kxm-sender-contract) with the signal secret (or the start secret), bound to its timestamp, delivery ID, run and signal key. Its body carries `status`, `summary` and `evidence`. Evidence keys `workflow.run`, `workflow.stage` and `workflow.signal`, when present, must match the route (`workflow_signal_context_mismatch`). The signal checkpoints the stage with its status; if work remains, the hub sends the coordinator a resume message. See the [HTTP API](http-api.md#webhooks-and-signals) for headers and deduplication.
|
|
152
152
|
|
|
153
153
|
When `autoResumeLimit` is set and a stage's attempts reach it without passing, the stage escalates instead of retrying: it waits for signal key `audit_escalation` for 24 hours and records a `kxm.terminal-receipt.v1` receipt. A signed `audit_escalation` signal, or `kxm role resume`, resumes it. Anyone holding the signal secret can clear an escalation.
|
|
154
154
|
|
|
@@ -250,11 +250,11 @@ The file is a `kxm.workflow.v1` definition. `coordinator` names the agent that o
|
|
|
250
250
|
| Cost | None | Model usage on your accounts |
|
|
251
251
|
|
|
252
252
|
> [!WARNING]
|
|
253
|
-
> Without `--simulated`, `kxm runs drive` calls live harnesses. An agent whose model is not an admitted route fails with `producer_route_not_admitted`. Read [Harness routing](../reference/harness-routing.md) before your first live drive. A live
|
|
253
|
+
> Without `--simulated`, `kxm runs drive` calls live harnesses. An agent whose model is not an admitted route fails with `producer_route_not_admitted`. Read [Harness routing](../reference/harness-routing.md) before your first live drive. A live step with `write` access runs only on a harness with an audited writer profile (`pi` or `grok`), as a single assignment, in a project whose `limits.maxConcurrentRuns` is 1, and, when `.kxm/roster.yaml` exists, only on a route in its writer lineup; any other live write step is handed off. A live write step settles `passed` only when the checkout changed, and a read-only step that changes the checkout settles `failed`. Gate steps are exempt. The read-only `spec-and-plan` template needs none of this.
|
|
254
254
|
|
|
255
|
-
### Why
|
|
255
|
+
### Why start with `first` and not `default`
|
|
256
256
|
|
|
257
|
-
`kxm init` also writes a `default` workflow
|
|
257
|
+
`kxm init` also writes a `default` workflow. Its coordinator plans on `claude` / `anthropic/fable`, its implementer writes the checkout on `grok` / `xai/grok-4.6`, and a `test` gate runs `npm test`; `kxm init` admits exactly those two models in `.kxm/routes.yaml`. You can drive it, but even a simulated drive runs `npm test` in your checkout, and a live drive spends both subscriptions and passes the implement step only when Grok changes the checkout. `first` calls no model and runs no command, so it is the safer first run. [Steps the Runtime does not execute yet](../reference/config-reference.md#steps-the-runtime-does-not-execute-yet) lists every setting that makes a drive hand the run off.
|
|
258
258
|
|
|
259
259
|
### Two kinds of runs
|
|
260
260
|
|
|
@@ -264,7 +264,7 @@ The file is a `kxm.workflow.v1` definition. `coordinator` names the agent that o
|
|
|
264
264
|
|
|
265
265
|
| Message or symptom | Cause | Fix |
|
|
266
266
|
|---|---|---|
|
|
267
|
-
| `run_handoff_required` naming `limits.maxAgentTimeMs` |
|
|
267
|
+
| `run_handoff_required` naming `limits.maxAgentTimeMs` | The workflow declares an agent-time budget, which the Runtime does not enforce; a project from an older `kxm init` template has one on `default` | Cancel the run, remove the limit or use `first` |
|
|
268
268
|
| `trust check failed: review every expansion above before merging` | The workflow is not committed | Review it, then commit it |
|
|
269
269
|
| `workflow <id> does not exist in this project` | The ID is not a local workflow | Use an ID from `kxm workflow definitions` marked `[local]` |
|
|
270
270
|
| `gate_outcome_impossible` from `kxm init` | A gate step routes failure on `failed` | Declare `implementation-failure` on the gate step |
|
|
@@ -320,7 +320,7 @@ Continue with [step 4](#4-bind-this-machine-and-check-the-hub) through [step 7](
|
|
|
320
320
|
|
|
321
321
|
### Before you update
|
|
322
322
|
|
|
323
|
-
From the directory the hub runs in, back up its database, then stop the hub and the Runtime:
|
|
323
|
+
From the directory the hub runs in, back up its database and the Runtime stores, then stop the hub and the Runtime:
|
|
324
324
|
|
|
325
325
|
```bash
|
|
326
326
|
kxm backup
|
|
@@ -331,7 +331,7 @@ kxm runtime stop
|
|
|
331
331
|
`kxm backup` writes a verified copy and a hashed manifest to `.kxm/backups/backup-<timestamp>/`.
|
|
332
332
|
|
|
333
333
|
> [!WARNING]
|
|
334
|
-
> `kxm backup` copies the hub store
|
|
334
|
+
> `kxm backup` copies the hub store and the Runtime's registry and run event stores, and those Runtime stores belong to every project on this machine. A later `kxm restore` rolls all of them back. It does not copy bindings, `update.yaml` or the other state roots; [Back up everything else](../operations/backup-and-restore.md#back-up-everything-else) covers those.
|
|
335
335
|
|
|
336
336
|
[Back up and restore KXM](../operations/backup-and-restore.md) and [Upgrade KXM](../operations/upgrade.md) cover restores and rollback.
|
|
337
337
|
|
|
@@ -155,7 +155,7 @@ kxm hub view: health=ok; planner; 1 online agent(s)
|
|
|
155
155
|
```
|
|
156
156
|
|
|
157
157
|
> [!NOTE]
|
|
158
|
-
> Without `KXM_AUTH_TOKEN`, the extension signs in with
|
|
158
|
+
> Without `KXM_AUTH_TOKEN`, the extension signs in with the project token saved for this project on this machine, and never with the saved admin token. With neither, it reports the fix and stays offline. Set the project token so that each agent holds only its own project's credential.
|
|
159
159
|
|
|
160
160
|
## 7. Start the reviewer and send a request
|
|
161
161
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { canonicalWorkflowEvidenceKey } from "../plugins/kxm/src/workflow.ts";
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { canonicalWorkflowEvidenceKey, workflowWebhookHeaders } from "../plugins/kxm/src/workflow.ts";
|
|
3
3
|
|
|
4
4
|
const [runId, signalKey, status, summary, ...evidenceArgs] = process.argv.slice(2);
|
|
5
5
|
const serverUrl = process.env.KXM_SERVER_URL?.trim() || "http://127.0.0.1:7331";
|
|
@@ -31,7 +31,6 @@ if (!runId || !signalKey || !status || !summary || !definitionId || !secret) {
|
|
|
31
31
|
}
|
|
32
32
|
const evidence = Object.fromEntries(evidenceEntries);
|
|
33
33
|
const body = JSON.stringify({ status, summary, evidence });
|
|
34
|
-
const signature = `sha256=${createHmac("sha256", secret).update(body).digest("hex")}`;
|
|
35
34
|
const deliveryId = process.env.KXM_SIGNAL_DELIVERY_ID?.trim() || `example-${randomUUID()}`;
|
|
36
35
|
const endpoint = [
|
|
37
36
|
serverUrl.replace(/\/$/, ""),
|
|
@@ -46,8 +45,8 @@ if (!runId || !signalKey || !status || !summary || !definitionId || !secret) {
|
|
|
46
45
|
method: "POST",
|
|
47
46
|
headers: {
|
|
48
47
|
"content-type": "application/json",
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
// Signs the timestamp, delivery ID, definition, run, signal key, and body.
|
|
49
|
+
...workflowWebhookHeaders({ secret, scope: { definitionId, runId, signalKey }, deliveryId, body }),
|
|
51
50
|
},
|
|
52
51
|
body,
|
|
53
52
|
});
|
package/package.json
CHANGED
|
@@ -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.
|
|
5
|
+
"version": "0.7.98",
|
|
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",
|