@kontextmind/kxm 0.7.95 → 0.7.97
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.kxm/README.md +39 -9
- package/CHANGELOG.md +23 -1
- package/README.md +147 -257
- package/SECURITY.md +21 -12
- package/docs/README.md +133 -54
- package/docs/adr/ADR-0002-browser-automation-steel-doks.md +24 -18
- package/docs/adr/ADR-0003-sqlite-only-store.md +100 -0
- package/docs/adr/ADR-0004-edge-identity-authentik.md +99 -0
- package/docs/adr/README.md +33 -0
- package/docs/concepts/architecture.md +262 -0
- package/docs/concepts/data-and-storage.md +194 -0
- package/docs/concepts/trust-model.md +153 -0
- package/docs/contracts/README.md +22 -14
- package/docs/contracts/effects-and-recovery.md +3 -0
- package/docs/contracts/migration.md +2 -2
- package/docs/contracts/routing.md +6 -5
- package/docs/contributing/assignment-runner.md +388 -0
- package/docs/contributing/ci-and-release.md +231 -0
- package/docs/contributing/development.md +362 -0
- package/docs/contributing/harness-routing-internals.md +192 -0
- package/docs/{packages.md → contributing/packages.md} +13 -15
- package/docs/{skills → contributing}/repo-work-delivery.md +20 -21
- package/docs/contributing/test-matrix.md +208 -0
- package/docs/{tui-components.md → contributing/tui-components.md} +30 -22
- package/docs/contributing/writing-docs.md +340 -0
- package/docs/glossary.md +471 -0
- package/docs/guides/agent-skills.md +137 -0
- package/docs/guides/browser-automation.md +160 -0
- package/docs/guides/context-and-memory.md +352 -0
- package/docs/guides/continuous-improvement.md +228 -0
- package/docs/guides/governed-skills.md +173 -0
- package/docs/guides/nous-providers.md +186 -0
- package/docs/guides/peer-messaging.md +304 -0
- package/docs/guides/pi-workers.md +219 -0
- package/docs/guides/provenance-gates.md +313 -0
- package/docs/guides/webhook-workflows.md +399 -0
- package/docs/kb/how-credentials-retrieved-safely.md +38 -12
- package/docs/kb/how-to-capture-and-annotate-section.md +15 -13
- package/docs/kb/how-to-connect-playwright-to-steel.md +16 -11
- package/docs/kb/how-to-recover-expired-session-or-orphan.md +26 -16
- package/docs/kb/how-to-resume-after-mfa.md +19 -11
- package/docs/kb/how-to-take-over-session.md +17 -13
- package/docs/kb/why-authentication-disappeared.md +22 -14
- package/docs/kb/why-automation-opened-different-browser.md +23 -14
- package/docs/kb/why-session-viewer-cannot-control.md +13 -12
- package/docs/operations/backup-and-restore.md +248 -0
- package/docs/operations/deploy.md +307 -0
- package/docs/operations/monitoring.md +209 -0
- package/docs/operations/runtime-sync.md +192 -0
- package/docs/operations/troubleshooting.md +266 -0
- package/docs/operations/upgrade.md +124 -0
- package/docs/prompts/browser-annotate-feedback.md +7 -7
- package/docs/prompts/browser-diagnose-recover.md +11 -10
- package/docs/prompts/browser-explore.md +7 -7
- package/docs/prompts/browser-repro-fix.md +7 -7
- package/docs/prompts/browser-start.md +12 -11
- package/docs/prompts/browser-takeover.md +8 -8
- package/docs/{cli-reference.md → reference/cli-reference.md} +88 -46
- package/docs/{config-reference.md → reference/config-reference.md} +159 -148
- package/docs/reference/configuration.md +299 -0
- package/docs/reference/harness-routing.md +508 -0
- package/docs/reference/http-api.md +203 -0
- package/docs/reference/tools.md +370 -0
- package/docs/{workflow-guide.md → reference/workflow-catalog.md} +92 -153
- package/docs/reference/workflow-definitions.md +286 -0
- package/docs/start/first-workflow.md +287 -0
- package/docs/start/install.md +146 -0
- package/docs/start/quickstart-claude-code.md +405 -0
- package/docs/start/quickstart-pi.md +213 -0
- package/docs/templates/README.md +78 -73
- package/docs/templates/adr.md +13 -13
- package/docs/templates/architecture.md +55 -71
- package/docs/templates/bug-fix.md +13 -16
- package/docs/templates/feature.md +14 -19
- package/docs/templates/handoff.md +44 -46
- package/docs/templates/postmortem.md +30 -43
- package/docs/templates/research.md +15 -20
- package/docs/templates/review.md +49 -50
- package/docs/templates/runbook.md +38 -30
- package/docs/templates/test-plan.md +16 -23
- package/docs/templates/test-report.md +14 -17
- package/examples/README.md +9 -5
- package/examples/provenance-workflow.json +1 -1
- package/examples/webhook-workflows/jira-development.json +59 -0
- package/examples/webhook-workflows/jira-issue-updated.json +12 -0
- package/examples/workflow-signal.ts +4 -5
- package/package.json +1 -1
- package/packages/core/tui/README.md +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/README.md +31 -32
- package/plugins/kxm/dist/claude-hook.js +11 -1
- package/plugins/kxm/dist/cli.js +164 -79
- package/plugins/kxm/dist/client.js +3 -1
- package/plugins/kxm/dist/core.js +11 -1
- package/plugins/kxm/dist/extension.js +45 -13
- package/plugins/kxm/dist/mcp-server.js +20 -4
- package/plugins/kxm/dist/runtime-supervisor.js +1 -3
- package/plugins/kxm/dist/runtime.js +18 -4
- package/plugins/kxm/dist/server.js +115 -20
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/kxm/references/protocol.md +3 -1
- package/plugins/kxm/skills/kxm-browser-auth/SKILL.md +1 -1
- package/plugins/kxm/skills/kxm-browser-diagnostics/SKILL.md +5 -5
- package/plugins/kxm/skills/kxm-browser-explore/SKILL.md +2 -2
- package/plugins/kxm/skills/kxm-browser-session/SKILL.md +10 -13
- package/plugins/kxm/skills/kxm-browser-takeover/SKILL.md +1 -1
- package/plugins/kxm/skills/kxm-browser-verify/SKILL.md +1 -1
- package/plugins/kxm/skills/kxm-context-memory/SKILL.md +13 -4
- package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +3 -1
- package/plugins/kxm/skills/kxm-mind-setup/SKILL.md +2 -1
- package/plugins/kxm/skills/kxm-project-setup/SKILL.md +31 -54
- package/plugins/kxm/skills/kxm-projects/SKILL.md +1 -1
- package/plugins/kxm/skills/kxm-protocol/SKILL.md +1 -1
- package/plugins/kxm/skills/kxm-routing-improve/SKILL.md +15 -7
- package/plugins/kxm/skills/kxm-runs/SKILL.md +11 -5
- package/plugins/kxm/skills/kxm-session/SKILL.md +1 -1
- package/plugins/kxm/skills/kxm-tasks/SKILL.md +9 -7
- package/plugins/kxm/skills/kxm-workflow/SKILL.md +10 -2
- package/plugins/kxm/src/cli/system.ts +1 -1
- package/plugins/kxm/src/cli/workflows.ts +12 -7
- package/plugins/kxm/src/cli.ts +22 -8
- package/plugins/kxm/src/client.ts +4 -0
- package/plugins/kxm/src/commands.ts +23 -1
- package/plugins/kxm/src/extension.ts +20 -14
- package/plugins/kxm/src/github-watch.ts +8 -5
- 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 +1 -1
- package/plugins/kxm/src/mcp-server.ts +9 -2
- package/plugins/kxm/src/modes.ts +1 -1
- package/plugins/kxm/src/runtime-store.ts +23 -0
- package/plugins/kxm/src/workflow.ts +70 -1
- package/schemas/README.md +1 -1
- package/scripts/smoke-multi-pi.mjs +5 -1
- package/docs/agent-communication-envelopes-and-gates.md +0 -553
- package/docs/agent-skills.md +0 -198
- package/docs/architecture.md +0 -245
- package/docs/assignment-runner.md +0 -264
- package/docs/browser-automation.md +0 -139
- package/docs/configuration.md +0 -437
- package/docs/continuous-improvement.md +0 -226
- package/docs/getting-started.md +0 -277
- package/docs/harness-routing.md +0 -616
- package/docs/kb/qa-authentik-authentication.md +0 -97
- package/docs/kb/qa-extension-install-and-hub-bootstrap.md +0 -85
- package/docs/kb/qa-hub-on-a-public-host.md +0 -48
- package/docs/kb/qa-sqlite-vs-duckdb.md +0 -35
- package/docs/kb/qa-what-the-hub-stores.md +0 -64
- package/docs/kxm-handbook.md +0 -1181
- package/docs/operations.md +0 -510
- package/docs/operator-pi-packages.md +0 -67
- package/docs/provenance-gates.md +0 -295
- package/docs/skills.md +0 -47
- package/docs/test-matrix.md +0 -132
- package/docs/troubleshooting.md +0 -322
- package/docs/webhook-workflows.md +0 -240
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
# Run webhook workflows
|
|
2
|
+
|
|
3
|
+
A webhook workflow turns a signed Jira, GitHub, or generic webhook into a durable, staged run on the KXM [hub](../glossary.md#hub). A long-lived [coordinator](../glossary.md#coordinator) agent works through the stages, proves each one with keyed evidence, and pauses for CI without holding a model turn open. This guide sets up the included Jira example end to end and explains definitions, checkpoints, waits, signals, and retrospectives.
|
|
4
|
+
|
|
5
|
+
> [!IMPORTANT]
|
|
6
|
+
> Hub webhook workflows are separate from Runtime runs. `kxm run` executes `kxm.workflow.v1` YAML files in `.kxm/workflows/` on the local Runtime; see [Run your first workflow](../start/first-workflow.md). A webhook workflow is a JSON definition that the hub loads from `KXM_WEBHOOK_WORKFLOWS_FILE`. Pointing that variable at a YAML workflow stops the hub from starting.
|
|
7
|
+
|
|
8
|
+
| | Hub webhook workflow | Runtime run |
|
|
9
|
+
|---|---|---|
|
|
10
|
+
| Defined in | A JSON array of definitions with ordered `stages` | `.kxm/workflows/<id>.yaml` with `steps` |
|
|
11
|
+
| Started by | A signed `POST /v1/webhooks/<id>`, or `kxm workflow start` | `kxm run <workflow>` |
|
|
12
|
+
| Executed by | A coordinator agent connected to the hub | The Runtime supervisor on your machine |
|
|
13
|
+
| Inspected with | `kxm workflow list`, `kxm workflow get`, `kxm_workflow_get` | `kxm runs status`, `kxm runs list` |
|
|
14
|
+
|
|
15
|
+
[Architecture](../concepts/architecture.md) explains how the two planes relate.
|
|
16
|
+
|
|
17
|
+
## Before you begin
|
|
18
|
+
|
|
19
|
+
- The `kxm` CLI, and Pi with the KXM package for the coordinator: see [Install KXM](../start/install.md).
|
|
20
|
+
- An admin token for the hub and a project token for the `product` project. See [Trust model](../concepts/trust-model.md) for which credential goes where.
|
|
21
|
+
- Two separate random secrets of at least 16 characters: one that starts workflows and one that signs callbacks.
|
|
22
|
+
- For a real Jira connection, the hub behind a TLS proxy that Jira can reach, with ingress restricted to Jira. See [Deploy KXM](../operations/deploy.md). A loopback hub is enough for the local test below.
|
|
23
|
+
- For the CI stage, a GitHub token that can read checks.
|
|
24
|
+
|
|
25
|
+
## How a webhook workflow runs
|
|
26
|
+
|
|
27
|
+
The sequence below shows one run of the Jira example, from the signed delivery to the final checkpoint.
|
|
28
|
+
|
|
29
|
+
```mermaid
|
|
30
|
+
sequenceDiagram
|
|
31
|
+
participant Jira
|
|
32
|
+
participant Hub as KXM hub
|
|
33
|
+
participant Coord as Coordinator (Pi worker)
|
|
34
|
+
participant Watch as kxm gate github watch
|
|
35
|
+
participant GitHub
|
|
36
|
+
Jira->>Hub: POST /v1/webhooks/jira-development (HMAC, delivery ID)
|
|
37
|
+
Hub->>Hub: store the run and the coordinator prompt
|
|
38
|
+
Hub->>Coord: workflow prompt
|
|
39
|
+
Coord->>Hub: kxm_workflow_checkpoint for reproduce, plan, implement
|
|
40
|
+
Coord->>Hub: kxm_workflow_wait on stage checks
|
|
41
|
+
Coord-->>Hub: settle the turn while the run waits
|
|
42
|
+
Watch->>GitHub: poll the required checks
|
|
43
|
+
Watch->>Hub: signed signal with status and evidence
|
|
44
|
+
Hub->>Hub: checkpoint the waiting stage
|
|
45
|
+
Hub->>Coord: resume prompt
|
|
46
|
+
Coord->>Hub: kxm_workflow_checkpoint for report
|
|
47
|
+
Hub-->>Coord: completed = true
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The hub verifies the delivery's signature, refuses replays, deduplicates retries by delivery ID, and records the run before it answers. It keeps a SHA-256 hash of the payload and the rendered prompt, not the raw body, so keep prompt templates narrow.
|
|
51
|
+
|
|
52
|
+
## Copy the example definition
|
|
53
|
+
|
|
54
|
+
KXM ships a small, validated Jira definition at [`examples/webhook-workflows/jira-development.json`](../../examples/webhook-workflows/jira-development.json) and a matching test payload, `jira-issue-updated.json`. Copy both into your repository outside `.kxm`:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
mkdir -p ops/kxm
|
|
58
|
+
# From a KXM source checkout, or from the npm package:
|
|
59
|
+
EXAMPLES="$(npm root -g)/@kontextmind/kxm/examples/webhook-workflows"
|
|
60
|
+
cp "$EXAMPLES/jira-development.json" ops/kxm/webhook-workflows.json
|
|
61
|
+
cp "$EXAMPLES/jira-issue-updated.json" ops/kxm/jira-issue-updated.json
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
> [!WARNING]
|
|
65
|
+
> Never put a definition in `.kxm/config/workflows/`. KXM treats JSON there as legacy state and refuses to load the whole project (`legacy_state_unsupported`).
|
|
66
|
+
|
|
67
|
+
The definition has five stages: `reproduce`, `plan`, `implement`, `checks`, and `report`. This excerpt shows its top level and the stage that waits for CI.
|
|
68
|
+
|
|
69
|
+
`ops/kxm/webhook-workflows.json` (excerpt):
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
[
|
|
73
|
+
{
|
|
74
|
+
"id": "jira-development",
|
|
75
|
+
"source": "jira",
|
|
76
|
+
"project": "product",
|
|
77
|
+
"target": "coordinator",
|
|
78
|
+
"secretEnv": "JIRA_WEBHOOK_SECRET",
|
|
79
|
+
"signalSecretEnv": "WORKFLOW_SIGNAL_SECRET",
|
|
80
|
+
"event": "jira:issue_updated",
|
|
81
|
+
"filter": { "path": "issue.fields.status.name", "equals": "In Progress" },
|
|
82
|
+
"delivery": "followUp",
|
|
83
|
+
"promptTemplate": "Jira issue {{issue.key}} moved to In Progress: {{issue.fields.summary}}. ...",
|
|
84
|
+
"stages": [
|
|
85
|
+
{
|
|
86
|
+
"id": "checks",
|
|
87
|
+
"label": "Pull request checks",
|
|
88
|
+
"instructions": "Push the branch and open or update the pull request. Then call kxm_workflow_wait ...",
|
|
89
|
+
"requiredEvidence": ["github.check:ci"],
|
|
90
|
+
"maxAttempts": 3,
|
|
91
|
+
"area": "gates"
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Write your own definition
|
|
99
|
+
|
|
100
|
+
A file holds a JSON array of definitions. The hub checks every limit below at start.
|
|
101
|
+
|
|
102
|
+
| Field | Meaning |
|
|
103
|
+
|---|---|
|
|
104
|
+
| `id` | Up to 64 characters; the last segment of the webhook URL. |
|
|
105
|
+
| `source` | `jira`, `github`, or `generic` (the default). A label on the run, and it decides which signatures the hub accepts: see [Signatures and delivery IDs](#signatures-and-delivery-ids). |
|
|
106
|
+
| `project`, `target` | Hub project, and the coordinator's agent name or ID. |
|
|
107
|
+
| `secretEnv` | Variable holding the start secret. `secret` takes a literal instead; prefer the variable. |
|
|
108
|
+
| `signalSecretEnv` | Variable holding the callback secret. Without it, callbacks use the start secret. |
|
|
109
|
+
| `event` | Optional. Must equal the `X-GitHub-Event` header, or the payload's `webhookEvent` or `event` field. |
|
|
110
|
+
| `filter` | Optional. `path` is a dotted payload path whose string value must equal `equals`. |
|
|
111
|
+
| `delivery` | `followUp` (the default) or `steer` for the coordinator prompt. |
|
|
112
|
+
| `ttlMs` | Coordinator prompt lifetime. Defaults to the hub message TTL (24 hours). |
|
|
113
|
+
| `promptTemplate` | Up to 20,000 characters. `{{dotted.path}}` inserts payload values; a missing value becomes empty. |
|
|
114
|
+
| `stages` | One to 32 ordered stages. |
|
|
115
|
+
|
|
116
|
+
Each stage has an `id`, a `label`, `instructions` (up to 4,000 characters), `requiredEvidence` (up to 32 keys), `maxAttempts` (1 to 20, default 3), and an optional `area` that files its automatic journal entries under `harness`, `gates`, `implementation`, `workflow`, `documentation`, `security`, or `other`. A stage may also declare `evidencePolicies`, which require verified replies from named peers; see [Peer provenance and quorum gates](provenance-gates.md). Typed transitions (`on`, `maxTransitions`), `autoResumeLimit`, and the `reproOracle` and `planHash` locks are described in [Workflow definition reference](../reference/workflow-definitions.md).
|
|
117
|
+
|
|
118
|
+
The hub appends the stages, their evidence keys, and the coordinator procedure to the rendered prompt, so the template only needs the task.
|
|
119
|
+
|
|
120
|
+
## Validate the definition
|
|
121
|
+
|
|
122
|
+
Set both secrets, then validate the file. Validation parses it exactly as the hub will and never prints a secret.
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
export JIRA_WEBHOOK_SECRET="replace-with-a-high-entropy-secret"
|
|
126
|
+
export WORKFLOW_SIGNAL_SECRET="replace-with-a-separate-callback-secret"
|
|
127
|
+
kxm gate validate --file ops/kxm/webhook-workflows.json
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Expected output:
|
|
131
|
+
|
|
132
|
+
```text
|
|
133
|
+
validated 1 workflow(s) from file
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Start the hub with the definition
|
|
137
|
+
|
|
138
|
+
Start the hub in the same environment. It loads the definitions once, at start; restart it after every change.
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
export KXM_AUTH_TOKEN="replace-with-the-admin-token"
|
|
142
|
+
export KXM_PROJECT_TOKENS='{"product":"replace-with-the-project-token"}'
|
|
143
|
+
export KXM_WEBHOOK_WORKFLOWS_FILE=ops/kxm/webhook-workflows.json
|
|
144
|
+
kxm hub start
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
<details><summary>PowerShell</summary>
|
|
148
|
+
|
|
149
|
+
```powershell
|
|
150
|
+
$env:JIRA_WEBHOOK_SECRET = "replace-with-a-high-entropy-secret"
|
|
151
|
+
$env:WORKFLOW_SIGNAL_SECRET = "replace-with-a-separate-callback-secret"
|
|
152
|
+
$env:KXM_AUTH_TOKEN = "replace-with-the-admin-token"
|
|
153
|
+
$env:KXM_PROJECT_TOKENS = '{"product":"replace-with-the-project-token"}'
|
|
154
|
+
$env:KXM_WEBHOOK_WORKFLOWS_FILE = "ops/kxm/webhook-workflows.json"
|
|
155
|
+
kxm hub start
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
</details>
|
|
159
|
+
|
|
160
|
+
> [!WARNING]
|
|
161
|
+
> `KXM_PROJECT_TOKENS` replaces the hub's saved project-token map; it does not merge. This example starts a hub that knows only this project. On a hub that already serves other projects, build the full map with the merge command in [Set up a new project](../start/quickstart-claude-code.md#3-start-the-hub).
|
|
162
|
+
|
|
163
|
+
`KXM_WEBHOOK_WORKFLOWS` takes the JSON array inline instead. Setting both variables stops the hub from starting.
|
|
164
|
+
|
|
165
|
+
## Start the coordinator
|
|
166
|
+
|
|
167
|
+
The coordinator must have registered with the hub at least once before a webhook targets it. A delivery for a coordinator that never registered is refused with `409`, which makes Jira retry. A registered but offline coordinator is fine: the prompt waits for it in the queue.
|
|
168
|
+
|
|
169
|
+
In another terminal, start a [supervised Pi worker](pi-workers.md) as the coordinator, with the project token and the hub tools its stages need:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
export KXM_SERVER_URL=http://127.0.0.1:7331
|
|
173
|
+
export KXM_AUTH_TOKEN="replace-with-the-project-token"
|
|
174
|
+
export KXM_WORKDIR=~/work/product
|
|
175
|
+
kxm agent worker --name coordinator --project product --model <pi-model> \
|
|
176
|
+
--session-isolation workflow
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
`--session-isolation workflow` gives each run its own Pi session. Any connected harness can coordinate instead, such as Claude Code with the agent name `coordinator`.
|
|
180
|
+
|
|
181
|
+
## Send a test delivery
|
|
182
|
+
|
|
183
|
+
`kxm workflow start` signs a payload under the [KXM sender contract](#kxm-sender-contract) and posts it to the hub at `KXM_SERVER_URL`, as a provider would. It signs with `KXM_WORKFLOW_SECRET`, or with the definition's own start secret when `KXM_WEBHOOK_WORKFLOWS_FILE` is set in the same shell.
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
export KXM_SERVER_URL=http://127.0.0.1:7331
|
|
187
|
+
export KXM_WORKFLOW_SECRET="replace-with-a-high-entropy-secret"
|
|
188
|
+
kxm workflow start jira-development \
|
|
189
|
+
--payload @ops/kxm/jira-issue-updated.json --delivery-id local-test-0001
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Expected output:
|
|
193
|
+
|
|
194
|
+
```text
|
|
195
|
+
started workflow run_7c187d2a0fde408ea408f0d8c53201c8
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Repeating the command with the same delivery ID and payload returns the same run ID; the same delivery ID with a different payload is refused with `409`. Inspect runs from the hub's workspace on the hub host; these commands read its local SQLite store:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
kxm workflow list
|
|
202
|
+
kxm workflow get run_7c187d2a0fde408ea408f0d8c53201c8 --json
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Connect Jira
|
|
206
|
+
|
|
207
|
+
In Jira, create a webhook for the `jira:issue_updated` event that posts to `https://<kxm-host>/v1/webhooks/jira-development`, and set the same start secret.
|
|
208
|
+
|
|
209
|
+
### Signatures and delivery IDs
|
|
210
|
+
|
|
211
|
+
The hub accepts two ways to sign a start:
|
|
212
|
+
|
|
213
|
+
| Headers | Sent by | Accepted by |
|
|
214
|
+
|---|---|---|
|
|
215
|
+
| `x-kxm-signature`, `x-kxm-timestamp`, `x-kxm-delivery-id` | `kxm` and your own senders | Every definition. See [KXM sender contract](#kxm-sender-contract). |
|
|
216
|
+
| `X-Hub-Signature-256` or `X-Hub-Signature`, with `X-Atlassian-Webhook-Identifier` | Jira | A `jira` definition only. |
|
|
217
|
+
| `X-Hub-Signature-256` with `X-GitHub-Delivery` | GitHub | A `github` definition only. |
|
|
218
|
+
|
|
219
|
+
Jira and GitHub sign only the body, as `sha256=<hex>`; other algorithms are refused. Because the delivery ID is not signed, the body is the delivery's replay identity: a signed body starts at most one run, under one delivery ID.
|
|
220
|
+
|
|
221
|
+
| Response | Meaning |
|
|
222
|
+
|---|---|
|
|
223
|
+
| `202` | Run created and coordinator prompt queued. |
|
|
224
|
+
| `200` with `"duplicate": true` | The delivery ID and body were seen before. Only `runId` and `status` come back, never the run. |
|
|
225
|
+
| `204` | The event or filter did not match. Nothing was stored. |
|
|
226
|
+
| `401` | Missing, unsupported, or wrong signature, or a KXM signature outside its 300-second window. |
|
|
227
|
+
| `404` | No definition with that ID. |
|
|
228
|
+
| `409` `workflow_target_unavailable` | The coordinator has never registered. |
|
|
229
|
+
| `409` `webhook_delivery_conflict` | The delivery ID was already used with a different body. |
|
|
230
|
+
| `409` `webhook_payload_replayed` | A Jira or GitHub body already started a run under another delivery ID. |
|
|
231
|
+
|
|
232
|
+
Webhook authentication authorizes only workflow creation. The `report` stage updates Jira through the coordinator's own authorized Jira tool; never put Jira credentials in a definition or prompt.
|
|
233
|
+
|
|
234
|
+
## KXM sender contract
|
|
235
|
+
|
|
236
|
+
A body-only signature cannot tell a retry from a replay, so KXM's own senders sign more: `kxm workflow start`, `kxm gate signal`, `kxm gate github watch`, and [`examples/workflow-signal.ts`](../../examples/workflow-signal.ts). A start of a `generic` definition and every signal must use this contract; a body-only signature there is refused with `401 webhook_signature_missing`.
|
|
237
|
+
|
|
238
|
+
| Header | Value |
|
|
239
|
+
|---|---|
|
|
240
|
+
| `x-kxm-delivery-id` | Stable retry identifier, at most 128 characters |
|
|
241
|
+
| `x-kxm-timestamp` | Unix seconds at send time |
|
|
242
|
+
| `x-kxm-signature` | `sha256=` and the hex HMAC-SHA256 of the signed material, under the start or callback secret |
|
|
243
|
+
|
|
244
|
+
The signed material is seven newline-terminated fields followed by the exact body bytes. No field may contain a line break. `workflowWebhookHeaders` in `plugins/kxm/src/workflow.ts` builds all three headers.
|
|
245
|
+
|
|
246
|
+
```text
|
|
247
|
+
kxm-webhook-v1
|
|
248
|
+
start | signal
|
|
249
|
+
<x-kxm-timestamp>
|
|
250
|
+
<x-kxm-delivery-id>
|
|
251
|
+
<definition ID>
|
|
252
|
+
<run ID; empty for a start>
|
|
253
|
+
<signal key; empty for a start>
|
|
254
|
+
<body>
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
- A signature over any other timestamp, delivery ID, definition, run, signal key, or body is refused with `401 webhook_signature_invalid`, so a captured request cannot be replayed under a new delivery ID or against another run.
|
|
258
|
+
- An authentic signature whose timestamp is more than 300 seconds from the hub clock is refused with `401 webhook_timestamp_expired`. Sign at send time: a retry re-signs with a fresh timestamp and keeps the same delivery ID and body, which the hub answers as a duplicate.
|
|
259
|
+
- The signed kind keeps a start signature from ever verifying as a signal, even when both use the start secret.
|
|
260
|
+
|
|
261
|
+
## Follow the coordinator procedure
|
|
262
|
+
|
|
263
|
+
For every stage, the coordinator:
|
|
264
|
+
|
|
265
|
+
1. Calls `kxm_workflow_get` and works only on `currentStage`.
|
|
266
|
+
2. Records plans, decisions, contradictions, errors, and lessons with `kxm_workflow_record`, passing the `stageId`.
|
|
267
|
+
3. Gathers evidence for every `requiredEvidence` key.
|
|
268
|
+
4. For a stage with a peer policy, sends requests with `workflowContext` ([Peer provenance and quorum gates](provenance-gates.md)).
|
|
269
|
+
5. Calls `kxm_workflow_checkpoint`, or `kxm_workflow_wait` when an external system must finish the stage.
|
|
270
|
+
6. After a `warning` or `failed` result, corrects the problem and tries again until the stage passes or `maxAttempts` runs out.
|
|
271
|
+
7. Replies to the prompt only after a checkpoint reports `completed: true`, or right after entering a wait.
|
|
272
|
+
|
|
273
|
+
Replying while the run is `running` and stages remain fails the run and records a workflow error. Replying after a successful wait is expected: it releases the model turn, and the signed callback creates a fresh prompt later. If the prompt's TTL passes first, the run fails.
|
|
274
|
+
|
|
275
|
+
## Checkpoint a stage
|
|
276
|
+
|
|
277
|
+
A passing checkpoint needs a non-empty value for every required evidence key. Keys are matched after trimming, collapsing whitespace, and ignoring case; an extra key never stands in for a missing one.
|
|
278
|
+
|
|
279
|
+
Tool call (`kxm_workflow_checkpoint`):
|
|
280
|
+
|
|
281
|
+
```json
|
|
282
|
+
{
|
|
283
|
+
"runId": "run_7c187d2a0fde408ea408f0d8c53201c8",
|
|
284
|
+
"stageId": "reproduce",
|
|
285
|
+
"status": "passed",
|
|
286
|
+
"summary": "Reproduced with a failing test.",
|
|
287
|
+
"evidence": { "reproduction": "test/checkout.test.ts fails: npm test -- checkout" }
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
The CLI twin is `kxm workflow checkpoint`, run under the coordinator's agent name.
|
|
292
|
+
|
|
293
|
+
Only the assigned coordinator can read, journal, checkpoint, or wait a run, and checkpoints and waits apply only to the active stage. A `warning` or `failed` checkpoint uses up an attempt and records an error; its evidence stays in the journal but does not count toward a later pass. Reaching `maxAttempts` fails the run. The hub enforces stage order and evidence keys; the agents stay responsible for the truth of what they submit.
|
|
294
|
+
|
|
295
|
+
## Wait for CI and other external work
|
|
296
|
+
|
|
297
|
+
A coordinator should not hold a model turn open while CI runs. On the active stage it calls `kxm_workflow_wait` with a stable signal key, a summary of the expected result, any evidence it already has, and an optional timeout from 1 second to 30 days (24 hours by default). The run and stage become `waiting`, and the coordinator settles its turn. If the deadline passes, the run fails and the coordinator receives a notice.
|
|
298
|
+
|
|
299
|
+
Tool call (`kxm_workflow_wait`):
|
|
300
|
+
|
|
301
|
+
```json
|
|
302
|
+
{
|
|
303
|
+
"runId": "run_7c187d2a0fde408ea408f0d8c53201c8",
|
|
304
|
+
"stageId": "checks",
|
|
305
|
+
"signalKey": "github-pr-42-checks",
|
|
306
|
+
"summary": "Waiting for the required GitHub checks on pull request 42",
|
|
307
|
+
"timeoutMs": 3600000
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Store the run ID and signal key where the external system can find them, such as pull-request metadata; they are not secrets.
|
|
312
|
+
|
|
313
|
+
The external system reports back with a signed signal to `POST /v1/webhooks/<definition-id>/runs/<run-id>/signals/<signal-key>`. The body is `{"status": "passed" | "warning" | "failed", "summary": "...", "evidence": {...}}`, signed with the callback secret under the [KXM sender contract](#kxm-sender-contract), bound to the route's run ID and signal key, with a stable delivery ID.
|
|
314
|
+
|
|
315
|
+
- `passed` applies the evidence rule to the saved and new evidence together, then advances or completes the run.
|
|
316
|
+
- `warning` or `failed` uses up an attempt, records an error, and sends the coordinator a correction prompt while attempts remain.
|
|
317
|
+
- The same delivery ID and body returns the original receipt; the same delivery ID with a different signal or body is refused with `409`.
|
|
318
|
+
- Optional `workflow.run`, `workflow.stage`, and `workflow.signal` evidence values must match the route, or the hub refuses the signal with `409`.
|
|
319
|
+
|
|
320
|
+
The run, receipt, journal entry, and resume prompt commit in one transaction. The response carries only status flags, never the run or its evidence.
|
|
321
|
+
|
|
322
|
+
## Send a signal from the CLI
|
|
323
|
+
|
|
324
|
+
`kxm gate signal` signs and posts a signal. It needs `KXM_WORKFLOW_ID` and the callback secret, either through the active definition file or `KXM_WORKFLOW_SIGNAL_SECRET`. Evidence is `key=value` pairs.
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
export KXM_WORKFLOW_ID=jira-development
|
|
328
|
+
export KXM_WORKFLOW_SIGNAL_SECRET="replace-with-a-separate-callback-secret"
|
|
329
|
+
kxm gate signal run_7c187d2a0fde408ea408f0d8c53201c8 github-pr-42-checks passed \
|
|
330
|
+
"All required checks passed" "github.check:ci=https://github.example/org/repo/actions/runs/123" \
|
|
331
|
+
--delivery-id github-check-run-123-attempt-1
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Expected output:
|
|
335
|
+
|
|
336
|
+
```text
|
|
337
|
+
posted signed signal
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
> [!NOTE]
|
|
341
|
+
> Hub and Runtime run IDs share the `run_<32-hex>` shape. Inside a KXM project, `kxm gate signal` and `kxm workflow wait` send a run to the Runtime supervisor only when the project's Runtime store holds that run; a hub run goes to the hub. Check with `--dry-run`: the hub path prints `would post signed signal`, the Runtime path `would post signal to KXM run`.
|
|
342
|
+
|
|
343
|
+
For your own adapters, [`examples/workflow-signal.ts`](../../examples/workflow-signal.ts) shows the same signed request in about 60 lines.
|
|
344
|
+
|
|
345
|
+
## Watch GitHub checks
|
|
346
|
+
|
|
347
|
+
`kxm gate github watch` polls a pull request's check runs and posts the signal for you. It reads the token from `GITHUB_TOKEN` or `GH_TOKEN`, and the workflow ID and callback secret as `kxm gate signal` does.
|
|
348
|
+
|
|
349
|
+
```bash
|
|
350
|
+
export GITHUB_TOKEN="replace-with-a-checks-read-token"
|
|
351
|
+
kxm gate github watch --run-id run_7c187d2a0fde408ea408f0d8c53201c8 --stage-id checks \
|
|
352
|
+
--signal-key github-pr-42-checks --repo org/repo --pr 42 --required ci
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
- It reports each required check as evidence `github.check:<name>`, so `--required ci` satisfies the `github.check:ci` requirement. Without `--required`, every check counts.
|
|
356
|
+
- `failure`, `cancelled`, `timed_out`, `action_required`, `stale`, and `startup_failure` fail the stage. Only `success` passes; a `neutral` or `skipped` check keeps the watcher waiting.
|
|
357
|
+
- It polls every 15 seconds for up to 30 minutes (`--interval-ms`, `--timeout-ms`). On timeout it posts a signed `failed` signal with summary `github_watch_timeout` and exits `4`. It never invents a pass.
|
|
358
|
+
- Each invocation uses a new delivery ID that includes the head commit, and its own retries reuse it. After a failure, start a new wait and a new watcher. Pass `--delivery-id` only when an outside supervisor must retry the same callback. Without a token it exits with `github_auth_unavailable`.
|
|
359
|
+
|
|
360
|
+
## Keep a journal and export retrospectives
|
|
361
|
+
|
|
362
|
+
The coordinator records knowledge with `kxm_workflow_record` in ten categories: `plan`, `decision`, `contradiction`, `error`, `lesson`, `observation`, `hypothesis`, `experiment`, `state-change`, and `skill-candidate`. Lessons and skill candidates require evidence. Passing `stageId` binds the entry to that stage and its current attempt. The hub adds its own error entries for failed checkpoints and signals, timeouts, and early settlement.
|
|
363
|
+
|
|
364
|
+
When a run completes or fails, the hub writes a proposed retrospective to `.kxm/assets/retrospectives/<run-id>.json` and `.md`. Regenerate it with `kxm workflow export <run-id>`, and summarize learning across runs with `kxm_improvement_report`. [Continuous improvement](continuous-improvement.md) describes the review loop.
|
|
365
|
+
|
|
366
|
+
> [!CAUTION]
|
|
367
|
+
> The hub deletes a finished run and its journal 7 days after it ends. Export anything you want to keep before then. Signal deduplication for that run ends at the same time.
|
|
368
|
+
|
|
369
|
+
## Degrade a peer quorum
|
|
370
|
+
|
|
371
|
+
If a stage's peer policy declares a lower `degradation.minProducers`, an operator holding the admin token can approve that lower minimum for the current attempt only with `kxm gate degrade`. Coordinators, peers, and callback secrets cannot. See [Peer provenance and quorum gates](provenance-gates.md#degrade-only-through-an-explicit-admin-decision).
|
|
372
|
+
|
|
373
|
+
## Security notes
|
|
374
|
+
|
|
375
|
+
- The start secret authorizes creating runs; the callback secret authorizes only checkpointing a waiting run with a matching signal key. Keep them separate with `signalSecretEnv`.
|
|
376
|
+
- A KXM signature covers the timestamp, delivery ID, definition, run, and signal key, and expires after 300 seconds, so a captured request cannot be replayed under a new delivery ID or against another run. Jira and GitHub sign only the body: a captured provider delivery re-sent under its own delivery ID only returns the duplicate, and a body starts at most one run. Terminate TLS and restrict ingress anyway.
|
|
377
|
+
- Repository rules, human approvals, and harness permissions stay in charge of pushing, merging, and changing Jira.
|
|
378
|
+
|
|
379
|
+
## Troubleshooting
|
|
380
|
+
|
|
381
|
+
| Symptom | Cause | Fix |
|
|
382
|
+
|---|---|---|
|
|
383
|
+
| The hub does not start: `Unexpected token` or `must be a JSON array` | `KXM_WEBHOOK_WORKFLOWS_FILE` points at YAML or a single object. | Point it at a JSON array such as the example. |
|
|
384
|
+
| `workflow.secret must be a string` | The variable named by `secretEnv` or `signalSecretEnv` is not set. | Export it in the hub's environment. |
|
|
385
|
+
| Jira retries and the hub returns `409` | The coordinator never registered. | Start the coordinator once, then redeliver. |
|
|
386
|
+
| `kxm workflow start` prints `started workflow accepted` and no run appears | The event or filter did not match (`204`). | Check the payload's event and filter path. |
|
|
387
|
+
| `stage <id> is missing required evidence: <key>` | A required key is missing or empty. | Supply every key from `kxm_workflow_get`. |
|
|
388
|
+
| `stage <id> is not currently active` or `workflow is waiting` | Wrong stage, or the stage is paused for a signal. | Use `currentStage`; send the signal instead of a checkpoint. |
|
|
389
|
+
| The run fails right after the coordinator replies | It settled before the last checkpoint. | Checkpoint every stage, or wait, before replying. |
|
|
390
|
+
| `kxm gate signal` prints `signed signal failed` | Wrong signal key, run not waiting, a delivery-ID conflict, or a clock more than 300 seconds off. | Compare the run's `waiting.signalKey`; use a new delivery ID for a new result; check the sender's clock. |
|
|
391
|
+
| A custom sender gets `401 webhook_signature_missing` | It sends only `X-Hub-Signature-256` to a `generic` definition or a signal. | Sign with the [KXM sender contract](#kxm-sender-contract). |
|
|
392
|
+
|
|
393
|
+
## Next steps
|
|
394
|
+
|
|
395
|
+
- Make the coordinator long-lived and unattended: [Run supervised Pi workers](pi-workers.md)
|
|
396
|
+
- Require verified replies from named reviewers before a stage passes: [Peer provenance and quorum gates](provenance-gates.md)
|
|
397
|
+
- Turn journals into improvements: [Continuous improvement](continuous-improvement.md)
|
|
398
|
+
- Every definition field: [Workflow definition reference](../reference/workflow-definitions.md); every route: [Hub HTTP API reference](../reference/http-api.md)
|
|
399
|
+
- The Runtime alternative: [Run your first workflow](../start/first-workflow.md)
|
|
@@ -7,25 +7,51 @@ project: "kxm"
|
|
|
7
7
|
status: "accepted"
|
|
8
8
|
owner: "@operator"
|
|
9
9
|
created: "2026-09-14"
|
|
10
|
-
updated: "2026-09-
|
|
10
|
+
updated: "2026-09-23"
|
|
11
11
|
authority: "instruction"
|
|
12
12
|
confidence: "verified"
|
|
13
|
-
summary: "
|
|
13
|
+
summary: "How the Steel client resolves its API key and how browser work keeps secrets out of model context."
|
|
14
14
|
tags: ["browser", "credentials", "pass-cli", "security"]
|
|
15
|
-
related: ["docs/browser-automation.md", "docs/agent-skills.md"]
|
|
15
|
+
related: ["docs/guides/browser-automation.md", "docs/guides/agent-skills.md"]
|
|
16
16
|
---
|
|
17
17
|
|
|
18
18
|
# How are credentials retrieved without exposing them to the model?
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
Browser automation keeps passwords, MFA secrets and API tokens out of model
|
|
21
|
+
prompts and reasoning traces. The model sees references to credentials, never
|
|
22
|
+
their values.
|
|
23
|
+
|
|
24
|
+
## How the Steel client finds its API key
|
|
25
|
+
|
|
26
|
+
`resolveSteelConfig()` resolves each setting in this order, and never writes a
|
|
27
|
+
secret to disk or to a log:
|
|
28
|
+
|
|
29
|
+
| Setting | Resolved from |
|
|
30
|
+
|---|---|
|
|
31
|
+
| API URL | An explicit override, then `STEEL_API_URL`, then a built-in default |
|
|
32
|
+
| API key | An explicit override, then `STEEL_API_KEY`, then a `pass-cli` lookup |
|
|
33
|
+
| Session viewer URL | An explicit override, then `STEEL_UI_URL`, then `<api-url>/ui` |
|
|
34
|
+
|
|
35
|
+
The built-in default URL and the `pass-cli` lookup point at the maintainers'
|
|
36
|
+
own Steel deployment and vault. Set `STEEL_API_URL` and `STEEL_API_KEY` for
|
|
37
|
+
yours, and set `USE_PASS_CLI=false` to turn the lookup off.
|
|
21
38
|
|
|
22
39
|
## Mechanisms
|
|
23
40
|
|
|
24
|
-
1. **
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
41
|
+
1. **One secret store.** Keep secrets in a secret manager, for example Proton
|
|
42
|
+
Pass through `pass-cli`, and load them into the environment of the process
|
|
43
|
+
that needs them:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Runs the tests with secrets injected for this process only.
|
|
47
|
+
pass-cli run -- npm test
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
2. **References, not values.** A prompt receives a credential reference such
|
|
51
|
+
as `vault: "<vault>", item: "<item>"`, never the secret itself.
|
|
52
|
+
3. **Log sanitization.** The KXM browser client redacts `apiKey=` query values,
|
|
53
|
+
`steel_…` keys, and any field named like a key, secret, token, auth or
|
|
54
|
+
password before it logs or returns output.
|
|
55
|
+
4. **Human handoff for high-privilege sign-in.** For production accounts or
|
|
56
|
+
MFA, the agent does not touch the credential at all. It follows the
|
|
57
|
+
`kxm-browser-takeover` skill and lets you sign in directly.
|
|
@@ -7,29 +7,30 @@ project: "kxm"
|
|
|
7
7
|
status: "accepted"
|
|
8
8
|
owner: "@operator"
|
|
9
9
|
created: "2026-09-14"
|
|
10
|
-
updated: "2026-09-
|
|
10
|
+
updated: "2026-09-23"
|
|
11
11
|
authority: "instruction"
|
|
12
12
|
confidence: "verified"
|
|
13
|
-
summary: "
|
|
13
|
+
summary: "Capture one DOM element, attach annotations, and send structured change feedback to an agent."
|
|
14
14
|
tags: ["browser", "annotation", "screenshot", "feedback", "ui"]
|
|
15
|
-
related: ["docs/browser-automation.md", "docs/prompts/browser-annotate-feedback.md"]
|
|
15
|
+
related: ["docs/guides/browser-automation.md", "docs/prompts/browser-annotate-feedback.md"]
|
|
16
16
|
---
|
|
17
17
|
|
|
18
18
|
# How do I capture a UI section and annotate changes for an agent?
|
|
19
19
|
|
|
20
|
-
When
|
|
20
|
+
When you review a web interface in a Steel session, you can isolate one
|
|
21
|
+
component, annotate it, and send a structured change request back to an agent.
|
|
21
22
|
|
|
22
23
|
## Workflow
|
|
23
24
|
|
|
24
|
-
1. **Capture the
|
|
25
|
-
|
|
25
|
+
1. **Capture the component.** Use a Playwright element screenshot to crop only
|
|
26
|
+
the affected container:
|
|
26
27
|
|
|
27
28
|
```typescript
|
|
28
|
-
await page.locator(
|
|
29
|
+
await page.locator(".billing-card").screenshot({ path: ".kxm/artifacts/browser/billing-card.png" });
|
|
29
30
|
```
|
|
30
31
|
|
|
31
|
-
2. **
|
|
32
|
-
|
|
32
|
+
2. **Write the annotation feedback.** Record the target selector, the observed
|
|
33
|
+
issues and the required fixes:
|
|
33
34
|
|
|
34
35
|
```typescript
|
|
35
36
|
import { createAnnotationFeedback, formatAnnotationFeedbackPrompt } from "@kontextmind/kxm/runtime";
|
|
@@ -41,9 +42,9 @@ When reviewing a web interface in a Steel session, you can isolate a specific co
|
|
|
41
42
|
overallSummary: "Billing tier layout breaks on mobile viewport",
|
|
42
43
|
annotations: [
|
|
43
44
|
{
|
|
44
|
-
label: "Tier
|
|
45
|
+
label: "Tier name overflow",
|
|
45
46
|
selector: ".tier-title",
|
|
46
|
-
note: "Truncate or wrap long tier titles with ellipsis",
|
|
47
|
+
note: "Truncate or wrap long tier titles with an ellipsis",
|
|
47
48
|
severity: "fix"
|
|
48
49
|
}
|
|
49
50
|
],
|
|
@@ -56,5 +57,6 @@ When reviewing a web interface in a Steel session, you can isolate a specific co
|
|
|
56
57
|
const prompt = formatAnnotationFeedbackPrompt(feedback);
|
|
57
58
|
```
|
|
58
59
|
|
|
59
|
-
3. **Send to the
|
|
60
|
-
|
|
60
|
+
3. **Send it to the agent.** Put the rendered prompt into the agent session or
|
|
61
|
+
the workflow run. The agent reads the screenshot, finds the source, applies
|
|
62
|
+
the changes, and verifies the result with Playwright.
|
|
@@ -7,21 +7,23 @@ project: "kxm"
|
|
|
7
7
|
status: "accepted"
|
|
8
8
|
owner: "@operator"
|
|
9
9
|
created: "2026-09-14"
|
|
10
|
-
updated: "2026-09-
|
|
10
|
+
updated: "2026-09-23"
|
|
11
11
|
authority: "instruction"
|
|
12
12
|
confidence: "verified"
|
|
13
|
-
summary: "
|
|
13
|
+
summary: "Attach Playwright to an active remote Steel browser session with chromium.connectOverCDP()."
|
|
14
14
|
tags: ["browser", "playwright", "cdp", "steel", "testing"]
|
|
15
|
-
related: ["docs/browser-automation.md", "docs/kb/why-automation-opened-different-browser.md"]
|
|
15
|
+
related: ["docs/guides/browser-automation.md", "docs/kb/why-automation-opened-different-browser.md"]
|
|
16
16
|
---
|
|
17
17
|
|
|
18
18
|
# How do I connect Playwright to the existing Steel session?
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
Run Playwright against a remote Steel session instead of a local browser by
|
|
21
|
+
connecting over the Chrome DevTools Protocol (CDP).
|
|
21
22
|
|
|
22
|
-
## 1.
|
|
23
|
+
## 1. Build the CDP endpoint
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
Build the WebSocket CDP URL from the active session ID. The configuration comes
|
|
26
|
+
from `STEEL_API_URL` and `STEEL_API_KEY`:
|
|
25
27
|
|
|
26
28
|
```typescript
|
|
27
29
|
import { formatCDPEndpoint, resolveSteelConfig } from "@kontextmind/kxm/runtime";
|
|
@@ -30,8 +32,11 @@ const config = resolveSteelConfig();
|
|
|
30
32
|
const cdpUrl = formatCDPEndpoint({ id: sessionId, websocketUrl: "" }, config);
|
|
31
33
|
```
|
|
32
34
|
|
|
33
|
-
The
|
|
34
|
-
|
|
35
|
+
The result has this shape. It carries the API key, so never log it:
|
|
36
|
+
|
|
37
|
+
```text
|
|
38
|
+
wss://<steel-host>/v1/devtools?sessionId=<session-id>&apiKey=<steel-api-key>
|
|
39
|
+
```
|
|
35
40
|
|
|
36
41
|
## 2. Connect in Playwright
|
|
37
42
|
|
|
@@ -40,15 +45,15 @@ import { test, expect, chromium } from "@playwright/test";
|
|
|
40
45
|
|
|
41
46
|
test("execute test on remote steel session", async () => {
|
|
42
47
|
const browser = await chromium.connectOverCDP(process.env.STEEL_CDP_URL!);
|
|
43
|
-
|
|
44
|
-
// Use existing context or create
|
|
48
|
+
|
|
49
|
+
// Use the existing context and page, or create them.
|
|
45
50
|
const context = browser.contexts()[0] || await browser.newContext();
|
|
46
51
|
const page = context.pages()[0] || await context.newPage();
|
|
47
52
|
|
|
48
53
|
await page.goto("https://app.example.com");
|
|
49
54
|
await expect(page.getByRole("heading", { level: 1 })).toBeVisible();
|
|
50
55
|
|
|
51
|
-
//
|
|
56
|
+
// Closing drops the CDP socket; it does not end the remote session.
|
|
52
57
|
await browser.close();
|
|
53
58
|
});
|
|
54
59
|
```
|