@wardby/cli 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/deploy/local/docker-compose.quickstart-coding.yml +5 -0
  2. package/dist/claude-plugin/command.d.ts +4 -1
  3. package/dist/claude-plugin/command.js +7 -4
  4. package/dist/cli.js +24 -0
  5. package/dist/coding/transcript/recorder.js +2 -8
  6. package/dist/core/dispatch.js +3 -0
  7. package/dist/core/engine-native.d.ts +2 -0
  8. package/dist/core/engine-native.js +13 -0
  9. package/dist/core/error-code.js +1 -0
  10. package/dist/core/listen-address.d.ts +10 -0
  11. package/dist/core/listen-address.js +14 -0
  12. package/dist/core/power-idle.d.ts +8 -2
  13. package/dist/core/power-idle.js +17 -3
  14. package/dist/core/power.d.ts +5 -0
  15. package/dist/core/power.js +5 -0
  16. package/dist/core/retention-days.d.ts +5 -0
  17. package/dist/core/retention-days.js +14 -0
  18. package/dist/core/run-steps/config.d.ts +25 -0
  19. package/dist/core/run-steps/config.js +40 -0
  20. package/dist/core/run-steps/control-chars.d.ts +16 -0
  21. package/dist/core/run-steps/control-chars.js +26 -0
  22. package/dist/core/run-steps/detail.d.ts +32 -0
  23. package/dist/core/run-steps/detail.js +240 -0
  24. package/dist/core/run-steps/extract.d.ts +52 -0
  25. package/dist/core/run-steps/extract.js +467 -0
  26. package/dist/core/run-steps/ingress.d.ts +51 -0
  27. package/dist/core/run-steps/ingress.js +160 -0
  28. package/dist/core/run-steps/last-step.d.ts +15 -0
  29. package/dist/core/run-steps/last-step.js +35 -0
  30. package/dist/core/run-steps/native.d.ts +48 -0
  31. package/dist/core/run-steps/native.js +164 -0
  32. package/dist/core/run-steps/seen.d.ts +16 -0
  33. package/dist/core/run-steps/seen.js +35 -0
  34. package/dist/core/run-steps/summarize.d.ts +57 -0
  35. package/dist/core/run-steps/summarize.js +304 -0
  36. package/dist/core/run-steps/sweep.d.ts +21 -0
  37. package/dist/core/run-steps/sweep.js +41 -0
  38. package/dist/core/run-steps/types.d.ts +89 -0
  39. package/dist/core/run-steps/types.js +39 -0
  40. package/dist/core/run-steps/wire.d.ts +35 -0
  41. package/dist/core/run-steps/wire.js +47 -0
  42. package/dist/core/run-steps/writer.d.ts +49 -0
  43. package/dist/core/run-steps/writer.js +279 -0
  44. package/dist/core/runner.d.ts +4 -0
  45. package/dist/core/runner.js +15 -1
  46. package/dist/generated/prisma/browser.d.ts +8 -0
  47. package/dist/generated/prisma/client.d.ts +8 -0
  48. package/dist/generated/prisma/commonInputTypes.d.ts +30 -0
  49. package/dist/generated/prisma/enums.d.ts +6 -0
  50. package/dist/generated/prisma/enums.js +5 -0
  51. package/dist/generated/prisma/internal/class.d.ts +11 -0
  52. package/dist/generated/prisma/internal/class.js +4 -4
  53. package/dist/generated/prisma/internal/prismaNamespace.d.ts +103 -1
  54. package/dist/generated/prisma/internal/prismaNamespace.js +19 -1
  55. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +19 -0
  56. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +19 -1
  57. package/dist/generated/prisma/models/Agent.d.ts +101 -1
  58. package/dist/generated/prisma/models/CodingProxySession.d.ts +41 -1
  59. package/dist/generated/prisma/models/Run.d.ts +503 -1
  60. package/dist/generated/prisma/models/RunStep.d.ts +1453 -0
  61. package/dist/generated/prisma/models/RunStep.js +1 -0
  62. package/dist/generated/prisma/models.d.ts +1 -0
  63. package/dist/help-index.json +102 -14
  64. package/dist/mcp/auth/access.d.ts +1 -0
  65. package/dist/mcp/context.d.ts +3 -0
  66. package/dist/mcp/index.js +10 -0
  67. package/dist/mcp/tools/agents.js +37 -7
  68. package/dist/mcp/tools/power.js +2 -2
  69. package/dist/mcp/tools/run-access.d.ts +7 -0
  70. package/dist/mcp/tools/run-access.js +14 -0
  71. package/dist/mcp/tools/run-steps.d.ts +16 -0
  72. package/dist/mcp/tools/run-steps.js +85 -0
  73. package/dist/mcp/tools/runs.js +10 -2
  74. package/dist/native-worker/gateway-server.d.ts +4 -1
  75. package/dist/native-worker/gateway-server.js +10 -2
  76. package/dist/native-worker/gateway.d.ts +3 -0
  77. package/dist/native-worker/gateway.js +8 -0
  78. package/dist/native-worker/http-server.d.ts +2 -4
  79. package/dist/native-worker/http-server.js +2 -8
  80. package/dist/providers/coding-proxy/memory-ledger.js +1 -0
  81. package/dist/providers/coding-proxy/prisma-ledger.js +9 -3
  82. package/dist/providers/coding-proxy/proxy.d.ts +26 -0
  83. package/dist/providers/coding-proxy/proxy.js +133 -42
  84. package/dist/providers/coding-proxy/run-step-forwarder.d.ts +42 -0
  85. package/dist/providers/coding-proxy/run-step-forwarder.js +206 -0
  86. package/dist/providers/coding-proxy/runtime.d.ts +7 -0
  87. package/dist/providers/coding-proxy/runtime.js +55 -3
  88. package/dist/providers/coding-proxy/types.d.ts +5 -0
  89. package/dist/providers/engine/types.d.ts +26 -0
  90. package/dist/providers/executor/container.d.ts +14 -0
  91. package/dist/providers/executor/container.js +8 -1
  92. package/dist/providers/jobs/kubernetes.d.ts +8 -0
  93. package/dist/providers/jobs/kubernetes.js +22 -4
  94. package/dist/quickstart-images.json +1 -1
  95. package/dist/serve.js +26 -18
  96. package/dist/viewer/api-schema.d.ts +66 -0
  97. package/dist/viewer/api-schema.js +31 -0
  98. package/dist/viewer/openapi.js +2 -1
  99. package/dist/viewer/run-detail.d.ts +2 -0
  100. package/dist/viewer/run-detail.js +26 -1
  101. package/docs/claude-code-plugin.md +57 -13
  102. package/docs/coding-agent-setup.md +17 -0
  103. package/docs/coding-worker-isolation.md +16 -0
  104. package/docs/debugging-agent-runs.md +112 -0
  105. package/docs/getting-started-gke.md +28 -7
  106. package/docs/mcp-servers.md +9 -0
  107. package/docs/native-sandbox.md +6 -0
  108. package/docs/security-deployment.md +51 -0
  109. package/docs/viewer-api.md +21 -12
  110. package/docs/viewer-api.openapi.yml +78 -2
  111. package/help/admin-viewer.md +1 -0
  112. package/help/claude-code-run-pane.md +27 -4
  113. package/help/deploy-gke.md +5 -0
  114. package/help/errors/coding-mcp-gateway-unavailable.md +46 -0
  115. package/help/low-power-mode.md +19 -8
  116. package/help/mcp.md +2 -0
  117. package/help/resume-runs-locally.md +4 -0
  118. package/help/run-steps.md +140 -0
  119. package/package.json +1 -1
  120. package/prisma/migrations/20261110000000_run_steps/migration.sql +26 -0
  121. package/prisma/migrations/20261111000000_run_steps_viewer_notify/migration.sql +59 -0
  122. package/prisma/migrations/20261112000000_run_steps_detail/migration.sql +9 -0
  123. package/prisma/schema.prisma +52 -0
@@ -27,6 +27,7 @@ Endpoints, all `GET`:
27
27
 
28
28
  - `/admin/api/graph?since=1h&limit=500`: a snapshot of runs in a time window.
29
29
  - `/admin/api/runs/<id>`: one run in full, including its final text and error.
30
+ From Wardby 0.9.0 it also lists the run's newest 200 steps (see `run-steps`).
30
31
  - `/admin/api/infra`: how this deployment runs coding jobs (launcher, Kubernetes namespace, platform, run labels).
31
32
  - `/admin/api/events`: a Server-Sent Events stream of live changes.
32
33
 
@@ -3,8 +3,8 @@ id: claude-code-run-pane
3
3
  title: See your runs in Claude Code (the wardby-runs plugin)
4
4
  summary: Install the wardby-runs Claude Code plugin to see your runs beside the chat, grouped by pull request, with run details and Continue locally.
5
5
  audience: operator
6
- tags: [claude code, plugin, wardby-runs, pane, claude-plugin, runs, desktop, auto mode, permissions]
7
- appliesTo: ">=0.8.0"
6
+ tags: [run steps, get_run_steps, claude code, plugin, wardby-runs, pane, claude-plugin, runs, auto mode, permissions]
7
+ appliesTo: >=0.8.0
8
8
  ---
9
9
 
10
10
  # See your runs in Claude Code
@@ -25,8 +25,10 @@ server named `wardby` yet. Or install it yourself with
25
25
  `/wardby-runs`.
26
26
 
27
27
  If the pane says auto mode refused a call, allow `mcp__wardby__list_runs`,
28
- `mcp__wardby__list_agents`, `mcp__wardby__get_run` and
29
- `mcp__wardby__get_run_transcript` in `/permissions`.
28
+ `mcp__wardby__list_agents`, `mcp__wardby__get_run`,
29
+ `mcp__wardby__get_run_transcript` and `mcp__wardby__get_run_steps` in
30
+ `/permissions`. Run `wardby claude-plugin install` again to add a rule a newer
31
+ pane needs.
30
32
 
31
33
  In the terminal, the pane takes clicks and sits beside the chat only in Claude
32
34
  Code's fullscreen view (`"tui": "fullscreen"`); otherwise use ctrl+x tab and the
@@ -37,4 +39,25 @@ keys it shows. The desktop app needs no setting.
37
39
  uninstall` removes the plugin and the rules install added. To continue a run
38
40
  from the pane, see `resume-runs-locally`.
39
41
 
42
+ A running run's detail shows its **Recent steps** (the last 8 tool calls and
43
+ messages: a ✓ or the exit code of a failed command, then the tool and its
44
+ command; what the model said wraps to about two lines); a finished run's shows all its
45
+ **Steps**, 200 at a time. In the list, a running or failed run shows its newest
46
+ step under its name. Against a server without `get_run_steps` the pane shows no
47
+ steps and checks again every few refreshes; with run steps off it says "Steps
48
+ are off on this server." See `run-steps`. Steps need Wardby 0.9.0 or later
49
+ on the server. From 0.9.0, a step with detail, or a longer message, has a ▸ at
50
+ its right that opens it; in the terminal, `j`/`k` also move between such steps
51
+ and `e` shows or hides one. An open command shows "$ command", its exit status
52
+ and its output, without Codex's framing lines. Detail appears only for the
53
+ agent's owner and the run's starter (and the stdio operator), on a server at
54
+ the detail level. An open detail shows its last 40 lines, with a count of the
55
+ lines above them.
56
+
57
+ Each section of a run's detail is a card, coloured by its kind (a bar on the
58
+ desktop, the border in the terminal). On the desktop each card, an error and an
59
+ open step's detail have a **Copy** button that copies the full text as the
60
+ server sent it, an open step's detail included in full; in the terminal, **Copy section** (`y`) copies the open
61
+ step's detail.
62
+
40
63
  The full guide is [Claude Code plugin](../docs/claude-code-plugin.md).
@@ -49,6 +49,11 @@ grants fail, `up.sh` stops before the rollout with a non-zero exit;
49
49
  without changing anything. See
50
50
  [Upgrading an existing deployment](../docs/getting-started-gke.md#upgrading-an-existing-deployment).
51
51
 
52
+ `up.sh` works from its own one-cluster copy of the cluster's
53
+ kubeconfig context, so switching contexts in another terminal mid-deploy can't
54
+ send a step to another cluster. Run one deploy of a cluster at a time: a second
55
+ stops with `another operation (install/upgrade/rollback) is in progress`.
56
+
52
57
  The Kubernetes launcher runs Codex and Claude Code coding workers; a Claude
53
58
  Code pod adds a tool-runner sidecar with its own image. Configure an identity
54
59
  provider and GitHub App before allowing people to use the public endpoint.
@@ -0,0 +1,46 @@
1
+ ---
2
+ id: errors/coding-mcp-gateway-unavailable
3
+ title: A coding run failed before launch because the native gateway did not wake
4
+ summary: In low-power mode, the run failed with coding_failure_mcp_gateway_unavailable because the native gateway that serves its MCP tools did not wake in time.
5
+ audience: operator
6
+ tags: [error, mcp, coding-agents, low-power, coding_failure_mcp_gateway_unavailable, coding_mcp_gateway_unavailable]
7
+ appliesTo: ">=0.8.0"
8
+ ---
9
+
10
+ # A coding run failed before launch because the native gateway did not wake
11
+
12
+ ## What happened
13
+
14
+ The run's error starts with `coding_failure_mcp_gateway_unavailable`, followed
15
+ by a diagnostic id. It happens only in [low-power mode](../low-power-mode.md).
16
+
17
+ A coding run with MCP tools sends every MCP call through the coding proxy to
18
+ the native gateway (`wardby-native-gateway`). In low-power mode the gateway
19
+ may be scaled to zero, so before such a run launches, wardby wakes the gateway
20
+ together with the proxy and waits for a ready endpoint. The gateway did not
21
+ become ready in time, so wardby failed the run rather than start it without
22
+ its MCP tools. Nothing was launched, so the run made no changes.
23
+
24
+ The run's error contains only the code and the diagnostic id. The control
25
+ plane log has the cause at `warn`, under the same diagnostic id, with the
26
+ internal code `coding_mcp_gateway_unavailable` and the underlying wake error,
27
+ usually [`low_power_wake_timeout`](low-power-wake-timeout.md) or
28
+ [`low_power_scale_forbidden`](low-power-scale-forbidden.md).
29
+
30
+ ## What to do
31
+
32
+ 1. Search the control plane log for the diagnostic id and read the wake error.
33
+ 2. For `low_power_wake_timeout`, check why the gateway pod did not become
34
+ ready: `kubectl -n <namespace> describe deployment wardby-native-gateway`
35
+ and the events of its pod. A cold Autopilot node can take a few minutes.
36
+ The wait is at least 5 minutes; for coding runs a
37
+ `KUBERNETES_READY_TIMEOUT_MS` above that, set on the control-plane
38
+ Deployment, raises it.
39
+ 3. For `low_power_scale_forbidden`, re-run `up.sh` with `WARDBY_LOW_POWER=1` to
40
+ apply the `wardby-power` Role.
41
+ 4. Trigger the agent again once the gateway is ready.
42
+
43
+ Runs without MCP tools do not wake the gateway and never fail this way.
44
+
45
+ See [Remote MCP servers for coding agents](../../docs/mcp-servers.md#coding-agents)
46
+ and [Low-power mode](../low-power-mode.md).
@@ -3,7 +3,7 @@ id: low-power-mode
3
3
  title: Low power mode on GKE
4
4
  summary: Low power mode: scale the coding proxy and native gateway to zero when idle to cut the cost of a lightly used GKE deployment, and what changes when they wake.
5
5
  audience: operator
6
- tags: [gke, cost, low-power, scale-to-zero, kubernetes, autoscaling, idle, power_sleep, power_status, sleep now, power:manage]
6
+ tags: [gke, cost, low-power, scale-to-zero, kubernetes, autoscaling, idle, power_sleep, power_status, sleep now, power:manage, mcp, coding-agents]
7
7
  appliesTo: ">=0.6.0"
8
8
  ---
9
9
 
@@ -38,14 +38,20 @@ only when `WARDBY_LOW_POWER=1`.
38
38
  - **Sleep.** Every minute the scheduler leader scales the coding proxy
39
39
  (`wardby-coding-proxy`) or native gateway (`wardby-native-gateway`) to 0 when
40
40
  nothing is pending or running for it and nothing finished within the idle
41
- window. The gateway also needs no active session. Either target sleeps only
41
+ window. For the gateway that means sandbox runs and coding runs with MCP
42
+ tools, and it also needs no active session. Either target sleeps only
42
43
  once at least one idle window has passed since it was last woken (annotation
43
44
  `wardby.io/power-woke-at`) and since the control plane started, whichever is
44
45
  later. The check repeats just before scaling. Log: `power: proxy slept (idle 30m)`.
45
46
  - **Wake.** A coding run scales the proxy to 1 and waits for a ready endpoint;
46
- a sandbox run does the same for the gateway. The wait is at least 5 minutes.
47
- A `KUBERNETES_READY_TIMEOUT_MS` (proxy) or `NATIVE_SANDBOX_READY_TIMEOUT_MS`
48
- (gateway) above that, set on the control-plane Deployment, raises it. Expect
47
+ a sandbox run does the same for the gateway. A coding run with MCP tools
48
+ wakes both at once, because its MCP calls go through the gateway; if the
49
+ gateway does not wake it fails before launch with
50
+ [`coding_failure_mcp_gateway_unavailable`](errors/coding-mcp-gateway-unavailable.md).
51
+ The wait is at least 5 minutes.
52
+ A `KUBERNETES_READY_TIMEOUT_MS` (the proxy, and the gateway wake of a coding
53
+ run) or `NATIVE_SANDBOX_READY_TIMEOUT_MS` (the gateway wake of a sandbox run)
54
+ above that, set on the control-plane Deployment, raises it. Expect
49
55
  about 1 to 2 minutes extra on a cold node. Log: `power: proxy woke`. A
50
56
  sandbox run woken this way may then wait for a gVisor node for its worker
51
57
  pod, so its first model call can come several minutes after it started. From
@@ -71,15 +77,17 @@ Pass `target: "proxy"` or `target: "gateway"` to sleep one; omit it for both.
71
77
 
72
78
  - It scales the target to 0 at once, ignoring the idle window and the woke-at
73
79
  grace.
74
- - It never stops a run. A target with a pending or running run, or for the
75
- gateway an active gateway session, stays up, and its run ids are returned:
80
+ - It never stops a run. A target with a pending or running run (for the
81
+ gateway, a sandbox run or a coding run with MCP tools), or for the gateway an
82
+ active gateway session, stays up, and its run ids are returned:
76
83
  `{ "slept": ["proxy"], "blocked": [{ "target": "gateway", "runIds": ["..."] }] }`.
77
84
  Recently finished runs do not block it.
78
85
  - The next run wakes the target as usual.
79
86
  - Each call is logged with `event: "power.sleep"` and the caller's principal.
80
87
 
81
88
  `power_status` returns, for each target, `replicas`, `ready`, `wokeAt` and
82
- `blockers` (the run ids keeping it busy).
89
+ `blockers` (the run ids keeping it busy; for the gateway these include
90
+ pending or running coding runs with MCP tools).
83
91
 
84
92
  Both tools need the `power:manage` scope, which only the `admin` role grants
85
93
  (see [Identity and access](identity-and-access.md)). With the mode off,
@@ -111,6 +119,9 @@ full table.
111
119
 
112
120
  - [`low_power_wake_timeout`](errors/low-power-wake-timeout.md): the target did
113
121
  not become ready in time, or its Deployment does not exist.
122
+ - [`coding_failure_mcp_gateway_unavailable`](errors/coding-mcp-gateway-unavailable.md):
123
+ a coding run with MCP tools failed before launch because the gateway did not
124
+ wake.
114
125
  - `native_sandbox_launch_abandoned` (0.7.0 and later): a sandbox run was
115
126
  stopped while the gateway woke, so no worker pod was made.
116
127
  - [`low_power_disabled`](errors/low-power-disabled.md): `power_sleep` was
package/help/mcp.md CHANGED
@@ -33,3 +33,5 @@ From Wardby 0.8.0, Claude Code can show your runs in a pane beside the chat
33
33
  through the `wardby-runs` plugin: run `wardby claude-plugin install`, then
34
34
  `/wardby-runs`. Search help for "claude code plugin" (article
35
35
  `claude-code-run-pane`, Wardby 0.8.0 and later).
36
+
37
+ To follow what a run is doing, call `get_run_steps` (article `run-steps`).
@@ -49,6 +49,10 @@ blob store nothing is stored, and `update_agent` warns you.
49
49
  (default 10 MiB, at most 64 MiB). Each run records a status (`stored`,
50
50
  `too_large`, `not_captured` or `failed`) with a reason code.
51
51
 
52
+ For a short line-per-step record of any run, without a blob store and without
53
+ continuing it, see `run-steps`; from 0.9.0 its `detail` level is a lighter way to
54
+ see tool output without a transcript.
55
+
52
56
  ## Who can read it
53
57
 
54
58
  Transcripts are stored from the first run with `sessionResume` on. Anyone
@@ -0,0 +1,140 @@
1
+ ---
2
+ id: run-steps
3
+ title: See what a run is doing (run steps)
4
+ summary: Wardby records each run's steps, one line per tool call or model message, so you can follow a running run and read what a finished one did with get_run_steps. Run steps detail adds tool output.
5
+ audience: operator
6
+ tags: [run steps, claude code run steps, get_run_steps, what is my run doing, WARDBY_RUN_STEPS, viewer step event]
7
+ appliesTo: >=0.9.0
8
+ ---
9
+
10
+ # See what a run is doing
11
+
12
+ Wardby records the steps of a run while it runs:
13
+
14
+ - one line for each **tool call**: the tool, and its command, file or first
15
+ argument, cut to about 100 characters, with its status once the result is
16
+ back (`ok`, `error`, or `exit N` for a command that exited with code N);
17
+ - one line for what the **model said**, cut to about 120 characters.
18
+
19
+ ## Levels
20
+
21
+ | Level | Tool input | Tool result | Model text |
22
+ | --------- | -------------------- | --------------------------------------- | -------------------- |
23
+ | `off` | - | - | - |
24
+ | `summary` | about 100 characters | status only (`ok`, `error`, `exit N`) | about 120 characters |
25
+ | `detail` | up to about 4 KB | status and the last ~2 KB of its output | up to about 8 KB |
26
+
27
+ At `summary`, the default, tool output is never recorded. At `detail` each step
28
+ also keeps a **detail** text, at most 16 KB. When the output is cut at the
29
+ start, the partial first line is dropped if it ends within 256 characters;
30
+ otherwise only the first 256 characters are dropped, so a long single-line
31
+ output, such as a JSON result, keeps about its last 1.8 KB. At every level,
32
+ terminal escape sequences (colour codes, links, clipboard writes, title changes)
33
+ and every other control character except line breaks and tabs are removed from
34
+ step text before it is stored.
35
+
36
+ A run's level is the lower of two settings, fixed when the run is dispatched (a
37
+ change reaches later runs only; lowering the ceiling also stops detail for runs
38
+ already in flight):
39
+
40
+ - the instance's ceiling, `WARDBY_RUN_STEPS` (default `summary`);
41
+ - the agent's `runSteps` (`off`, `summary` or `detail`), set by the agent's
42
+ owner with `create_agent` or `update_agent`. Unset counts as `summary`. Set
43
+ `null` to clear it.
44
+
45
+ So `detail` needs both the operator and the agent's owner. If `runSteps` asks
46
+ for more than the instance allows, the value is stored, `update_agent` and
47
+ `get_agent` say so in `warnings`, and runs record at the ceiling.
48
+
49
+ > **Warning: `detail` stores tool output.** Before a step is stored, every
50
+ > secret value the run was given is removed and anything shaped like a token or
51
+ > key is masked. A secret that exists only inside a repository file, or another
52
+ > source the run read, and that matches no known shape, is not known to Wardby
53
+ > and can remain in the detail text. That is why detail is off unless both the
54
+ > operator and the agent's owner ask for it. Turn it on only for agents whose
55
+ > tool output you are prepared to keep for the retention period.
56
+
57
+ Steps are recorded for native runs (in the control plane and on the native
58
+ sandbox) and for Claude Code and Codex runs. Every agent's shell command shows
59
+ as `Bash`: a native agent's `run_command`, Claude Code's command tool, and
60
+ Codex's `exec_command`, `shell`, `local_shell` and `shell_command`. Another tool
61
+ shows under its own name (for example `apply_patch`), and a remote MCP tool as
62
+ `mcp:<tool>`.
63
+
64
+ A Claude Code or Codex run's final answer is one `said` step: the start of the
65
+ summary it returned (the pull request description), or `Finished` with the
66
+ outcome (for example `Finished: changes ready`) when it returned none.
67
+
68
+ For Claude Code and Codex runs, the coding proxy reads the steps from the
69
+ model's replies after relaying them and posts them to the server, which needs a
70
+ listener for them: `WARDBY_RUN_STEPS_LISTEN` on `wardby serve` and
71
+ `WARDBY_RUN_STEPS_URL` on the proxy. The reference GKE manifests set both; the
72
+ kind overlay sets only `WARDBY_RUN_STEPS_URL`, so set
73
+ `WARDBY_RUN_STEPS_LISTEN` on the host's `wardby serve`. Without either, coding
74
+ runs record no steps; native runs are not affected. A slow or unreachable listener loses steps; it never slows the run.
75
+
76
+ ## Read the steps
77
+
78
+ - `get_run_steps` with `runId` lists a run's steps, oldest first, 50 at a time
79
+ (`limit`, at most 200). `next` is what to pass as `after` for the next page.
80
+ To follow a running run, call it again every few seconds with `after` set to
81
+ the last `seq` you have.
82
+ - `get_run` and `list_runs` (without `agentId`) include `lastStep`, the run's
83
+ newest step as one line, for example `Bash: npm test (exit 1)`.
84
+ - The `wardby-runs` Claude Code pane shows a running run's last steps and a
85
+ finished run's steps; see `claude-code-run-pane`.
86
+ - The admin viewer API sends a `step` event on `/admin/api/events` for each
87
+ step and status, and `GET /admin/api/runs/<id>` lists the run's newest 200
88
+ steps (see `admin-viewer`). The Wardby Viewer app shows them in a **Steps**
89
+ section of a run's detail.
90
+
91
+ At `detail`, `get_run_steps` also returns each step's `detail` to the agent's
92
+ owner and to the person who started the run (and to the stdio operator); anyone
93
+ else, including an admin who does not own the agent, gets the lines only. `lastStep`, the admin viewer API
94
+ and its `step` events never include detail.
95
+
96
+ You can read a run's steps when you can read the run with `get_run`.
97
+
98
+ A run records at most 2,000 steps. Later steps are not recorded; `dropped`
99
+ counts them.
100
+
101
+ ## Secrets
102
+
103
+ Before a step is stored, Wardby removes every secret value the run's agent has
104
+ (its attached secrets, readable or brokered, and its MCP servers' secrets), the
105
+ run's own capability and registry token, and
106
+ anything shaped like a token or key. A command that echoes a secret shows
107
+ `[REDACTED]` in its place.
108
+
109
+ ## Retention
110
+
111
+ Steps, and their detail, are deleted `RUN_STEP_RETENTION_DAYS` after they are
112
+ recorded (default 30). A change applies to steps recorded after it. `get_run_steps` reports `expired` when a run finished more than
113
+ `RUN_STEP_RETENTION_DAYS` ago and has no steps left.
114
+
115
+ ## Settings
116
+
117
+ | Variable | Default | What it does |
118
+ | ------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
119
+ | `WARDBY_RUN_STEPS` | `summary` | `off` records no steps; `summary` the lines above; `detail` allows agents whose `runSteps` is `detail` to record detail. The ceiling for every run. Any other value stops start-up. |
120
+ | `RUN_STEP_RETENTION_DAYS` | `30` | Days to keep a step, a whole number from 1 to 3650. Any other value stops start-up. |
121
+ | `WARDBY_RUN_STEPS_LISTEN` | unset | On `wardby serve` only: `host:port` (or `:port`) of the listener the coding proxy posts coding runs' steps to. Unset: no listener. |
122
+ | `WARDBY_RUN_STEPS_URL` | unset | On the coding proxy: the listener's origin, for example `http://<server host>:8792`, with no credentials or path. Unset: coding runs record no steps. |
123
+
124
+ Set `WARDBY_RUN_STEPS` (including `detail`) and `RUN_STEP_RETENTION_DAYS` on
125
+ every process that writes steps: `wardby serve`,
126
+ `wardby scheduler`, `wardby run`, `wardby mcp` and `wardby native-gateway`. The
127
+ coding proxy reads `WARDBY_RUN_STEPS` too: with `off`, or with
128
+ `WARDBY_RUN_STEPS_URL` unset, no step text leaves the proxy. For a coding
129
+ run's steps use `wardby serve` (the only process that runs the listener), not
130
+ the stdio `wardby mcp`.
131
+
132
+ For coding runs at `detail`, set `WARDBY_RUN_STEPS=detail` on the coding proxy
133
+ too: below `detail`, the proxy never sends any tool output to the server.
134
+
135
+ The listener is a second network path into the server, from the coding proxy
136
+ only; keep it off any load balancer. See
137
+ [Run steps](../docs/security-deployment.md#run-steps) for how to restrict it.
138
+
139
+ The full guide is
140
+ [Debugging agent runs](../docs/debugging-agent-runs.md#run-steps).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wardby/cli",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Self-hosted control plane for budget-guarded AI agents.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/wardby/wardby#readme",
@@ -0,0 +1,26 @@
1
+ -- Run steps: one line per tool call or model message, and the count of steps past the per-run cap.
2
+ ALTER TABLE "Run" ADD COLUMN "stepsDropped" INTEGER NOT NULL DEFAULT 0;
3
+
4
+ CREATE TABLE "RunStep" (
5
+ "id" TEXT NOT NULL,
6
+ "runId" TEXT NOT NULL,
7
+ "seq" INTEGER NOT NULL,
8
+ "at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
9
+ "kind" TEXT NOT NULL,
10
+ "tool" TEXT,
11
+ "callId" TEXT,
12
+ "summary" TEXT NOT NULL,
13
+ "status" TEXT,
14
+ "detail" TEXT,
15
+ "expiresAt" TIMESTAMP(3) NOT NULL,
16
+ CONSTRAINT "RunStep_pkey" PRIMARY KEY ("id")
17
+ );
18
+
19
+ CREATE UNIQUE INDEX "RunStep_runId_seq_key" ON "RunStep"("runId", "seq");
20
+
21
+ CREATE INDEX "RunStep_runId_callId_idx" ON "RunStep"("runId", "callId");
22
+
23
+ CREATE INDEX "RunStep_expiresAt_idx" ON "RunStep"("expiresAt");
24
+
25
+ ALTER TABLE "RunStep" ADD CONSTRAINT "RunStep_runId_fkey"
26
+ FOREIGN KEY ("runId") REFERENCES "Run"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,59 @@
1
+ -- Live run steps for the admin viewer (docs/viewer-api.md): a `step` event when a
2
+ -- step is recorded and when its status arrives. Only stored columns are sent:
3
+ -- the summary is already redacted when the row is written; the call id and the
4
+ -- detail text are never sent. Every other branch is unchanged.
5
+ CREATE OR REPLACE FUNCTION wardby_viewer_notify() RETURNS trigger LANGUAGE plpgsql AS $$
6
+ DECLARE
7
+ payload jsonb;
8
+ BEGIN
9
+ IF TG_TABLE_NAME = 'Run' THEN
10
+ IF TG_OP = 'UPDATE'
11
+ AND NEW."status" IS NOT DISTINCT FROM OLD."status"
12
+ AND NEW."turns" IS NOT DISTINCT FROM OLD."turns"
13
+ AND NEW."costUsd" IS NOT DISTINCT FROM OLD."costUsd"
14
+ AND NEW."tokensIn" IS NOT DISTINCT FROM OLD."tokensIn"
15
+ AND NEW."tokensOut" IS NOT DISTINCT FROM OLD."tokensOut"
16
+ AND NEW."finishedAt" IS NOT DISTINCT FROM OLD."finishedAt"
17
+ AND NEW."parentRunId" IS NOT DISTINCT FROM OLD."parentRunId" THEN
18
+ RETURN NULL; -- heartbeat-only and other invisible updates
19
+ END IF;
20
+ payload := jsonb_build_object(
21
+ 'kind', 'run', 'runId', NEW."id", 'parentRunId', NEW."parentRunId", 'agentId', NEW."agentId",
22
+ 'status', NEW."status", 'turns', NEW."turns", 'tokensIn', NEW."tokensIn", 'tokensOut', NEW."tokensOut",
23
+ 'costUsd', NEW."costUsd"::float8,
24
+ 'finishedAt', to_char(NEW."finishedAt", 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'));
25
+ ELSIF TG_TABLE_NAME = 'CodingRunServiceStatus' THEN
26
+ payload := jsonb_build_object(
27
+ 'kind', 'service', 'runId', NEW."runId", 'name', NEW."name", 'state', NEW."state", 'attempts', NEW."attempts");
28
+ ELSIF TG_TABLE_NAME = 'IssuePullRequest' THEN
29
+ payload := jsonb_build_object('kind', 'outcome', 'runId', NEW."openedByRunId", 'source', 'pull_request');
30
+ ELSIF TG_TABLE_NAME = 'RunHostStatus' THEN
31
+ payload := jsonb_build_object('kind', 'outcome', 'runId', NEW."runId", 'source', 'host_status');
32
+ ELSIF TG_TABLE_NAME = 'RunIssueStatus' THEN
33
+ payload := jsonb_build_object('kind', 'outcome', 'runId', NEW."runId", 'source', 'issue_status');
34
+ ELSIF TG_TABLE_NAME = 'RunHostCheck' THEN
35
+ payload := jsonb_build_object('kind', 'outcome', 'runId', NEW."runId", 'source', 'host_check');
36
+ ELSIF TG_TABLE_NAME = 'RunStep' THEN
37
+ IF TG_OP = 'UPDATE' AND NEW."status" IS NOT DISTINCT FROM OLD."status" THEN
38
+ RETURN NULL; -- only a step's status is shown as it changes
39
+ END IF;
40
+ payload := jsonb_build_object(
41
+ 'kind', 'step', 'runId', NEW."runId", 'seq', NEW."seq", 'stepKind', NEW."kind", 'tool', NEW."tool",
42
+ 'summary', NEW."summary", 'status', NEW."status",
43
+ 'at', to_char(NEW."at", 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'));
44
+ ELSE
45
+ RETURN NULL;
46
+ END IF;
47
+ -- A viewer event must never fail the write that caused it (e.g. a payload
48
+ -- over NOTIFY's 8000-byte limit).
49
+ BEGIN
50
+ PERFORM pg_notify('wardby_viewer', payload::text);
51
+ EXCEPTION WHEN OTHERS THEN
52
+ NULL;
53
+ END;
54
+ RETURN NULL;
55
+ END;
56
+ $$;
57
+
58
+ CREATE TRIGGER "RunStep_viewer_notify" AFTER INSERT OR UPDATE ON "RunStep"
59
+ FOR EACH ROW EXECUTE FUNCTION wardby_viewer_notify();
@@ -0,0 +1,9 @@
1
+ -- Run steps detail level: the owner's per-agent setting, the level fixed on each run at
2
+ -- dispatch, and the proxy session's copy of it. Additive, nullable: existing rows mean "unset".
3
+ CREATE TYPE "RunStepsLevel" AS ENUM ('off', 'summary', 'detail');
4
+
5
+ ALTER TABLE "Agent" ADD COLUMN "runSteps" "RunStepsLevel";
6
+
7
+ ALTER TABLE "Run" ADD COLUMN "runStepsLevel" "RunStepsLevel";
8
+
9
+ ALTER TABLE "CodingProxySession" ADD COLUMN "runStepsLevel" "RunStepsLevel";
@@ -87,6 +87,9 @@ model Agent {
87
87
  /// single-use worker container/pod. Each Run snapshots this at creation
88
88
  /// (Run.nativeExecutionMode), so a change reaches only later runs.
89
89
  nativeExecutionMode NativeExecutionMode @default(control_plane)
90
+ /// The owner's run steps level for this agent's runs; null = unset (summary). A run records
91
+ /// min(WARDBY_RUN_STEPS, this), fixed at dispatch (Run.runStepsLevel). Owner-only.
92
+ runSteps RunStepsLevel?
90
93
 
91
94
  /// Un-phased: sub-agent composition edges (see AgentSubAgent below). An
92
95
  /// agent can be a parent (has children it can delegate to) and/or a
@@ -144,6 +147,15 @@ enum AgentKind {
144
147
  coding
145
148
  }
146
149
 
150
+ /// Run steps level (docs/debugging-agent-runs.md#run-steps): off records nothing, summary one
151
+ /// redacted line per step, detail also a redacted detail text (tool input, the tail of tool
152
+ /// output, model text).
153
+ enum RunStepsLevel {
154
+ off
155
+ summary
156
+ detail
157
+ }
158
+
147
159
  /// Where a native agent's runs execute (Agent.nativeExecutionMode).
148
160
  enum NativeExecutionMode {
149
161
  control_plane @map("control-plane")
@@ -400,6 +412,12 @@ model Run {
400
412
  /// run through the server's executor and clears this.
401
413
  stopRequestedAt DateTime?
402
414
  stopRequestReason String?
415
+ /// Run steps (RunStep) not recorded because the run already had MAX_RUN_STEPS (2,000).
416
+ stepsDropped Int @default(0)
417
+ /// The run steps level this run records, fixed at dispatch: min(WARDBY_RUN_STEPS, Agent.runSteps
418
+ /// or summary). Null on runs from before the field (they record at most summary).
419
+ runStepsLevel RunStepsLevel?
420
+ steps RunStep[]
403
421
 
404
422
  /// Un-phased: sub-agent dispatch (see AgentSubAgent). Null for a
405
423
  /// top-level run; set when this run was created by a parent run's
@@ -652,6 +670,37 @@ model RunTranscript {
652
670
  @@index([expiresAt])
653
671
  }
654
672
 
673
+ /// One line of what a run did: a tool call (with its status once the result is back) or what the
674
+ /// model said. Derived by trusted code from model traffic and written only by the control plane's
675
+ /// RunStepWriter, after exact-value redaction against the run's secret values
676
+ /// (docs/debugging-agent-runs.md#run-steps). Never tool output at the summary level.
677
+ model RunStep {
678
+ id String @id @default(cuid())
679
+ runId String
680
+ run Run @relation(fields: [runId], references: [id], onDelete: Cascade)
681
+ /// Order within the run, from 1.
682
+ seq Int
683
+ at DateTime @default(now())
684
+ /// "tool" | "said"
685
+ kind String
686
+ /// The tool's display name (Bash for every agent's shell tool, mcp:<name>, Edit, ...); null for "said".
687
+ tool String?
688
+ /// The provider's call id (to attach the result), or a said:<key> id; never returned by MCP.
689
+ callId String?
690
+ /// Redacted, at most 300 characters.
691
+ summary String
692
+ /// ok | error | exit N, set when the result arrives.
693
+ status String?
694
+ /// The detail level's text: redacted tool input (~4 KB), then the last ~2 KB of its output once
695
+ /// the result is back, or the model's text (~8 KB). At most 16,384 characters. Null below detail.
696
+ detail String?
697
+ expiresAt DateTime
698
+
699
+ @@unique([runId, seq])
700
+ @@index([runId, callId])
701
+ @@index([expiresAt])
702
+ }
703
+
655
704
  /// Coding-run services (docs/coding-services.md): the catalog a repository's
656
705
  /// .wardby/services.yaml names entries from. Admin-managed over MCP
657
706
  /// (list/get_service: agents:read; create/update/delete_service:
@@ -909,6 +958,9 @@ model CodingProxySession {
909
958
  mcpTools Json?
910
959
  /// SHA-256 (base64url) of the run's MCP token; null when the run has no MCP tools.
911
960
  mcpTokenHash String? @unique
961
+ /// The run's Run.runStepsLevel, copied at session creation: the proxy sends a tool result's
962
+ /// output tail to the run-steps listener only at detail. Null on older sessions (= summary).
963
+ runStepsLevel RunStepsLevel?
912
964
  /// MCP tools/call count for this run (capped by WARDBY_MCP_MAX_CALLS_PER_RUN).
913
965
  mcpCallCount Int @default(0)
914
966