@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
@@ -8,8 +8,8 @@ finished Claude Code run in your own terminal.
8
8
  The plugin lives in this repository under `plugins/claude-code/`, and the
9
9
  repository is its marketplace (`.claude-plugin/marketplace.json`). It talks only
10
10
  to the wardby MCP server your Claude Code session already has, through
11
- `list_runs`, `list_agents`, `get_run` and `get_run_transcript`, so it shows
12
- exactly what those tools show you and needs no token of its own.
11
+ `list_runs`, `list_agents`, `get_run`, `get_run_steps` and `get_run_transcript`,
12
+ so it shows exactly what those tools show you and needs no token of its own.
13
13
 
14
14
  ## Install
15
15
 
@@ -85,15 +85,17 @@ The pane reads only, with the caller's own access:
85
85
  | Run list | `list_runs` | `agents:read` | All runs of agents you own, and the runs you started on agents shared with you |
86
86
  | Agent names, budgets | `list_agents` | `agents:read` | Agents you can see (the pane asks for `summary: true`) |
87
87
  | Run detail | `get_run` | `agents:read` | Runs you started, or of agents you own |
88
+ | Recent steps, Steps | `get_run_steps` | `agents:read` | Same as `get_run` |
88
89
  | Continue locally | `get_run_transcript` | `agents:read` | Same as `get_run` |
89
90
 
90
91
  Under Claude Code's auto mode, a plugin's MCP calls go through the same safety
91
92
  check as any other, and calls the pane makes on its own timer are refused
92
93
  unless allowed. Allow these in `/permissions` (or let `wardby claude-plugin
93
94
  install` add them): `mcp__wardby__list_runs`, `mcp__wardby__list_agents`,
94
- `mcp__wardby__get_run` and `mcp__wardby__get_run_transcript`. The pane never
95
- calls `stop_run` or any other tool that changes something. When a call is
96
- refused, the pane says which rules to add.
95
+ `mcp__wardby__get_run`, `mcp__wardby__get_run_transcript` and
96
+ `mcp__wardby__get_run_steps`. The pane never calls `stop_run` or any other tool
97
+ that changes something. When a call is refused, the pane says which rules to
98
+ add.
97
99
 
98
100
  Plugins are not sandboxed: they run as you. **Continue locally** runs
99
101
  `npx @wardby/cli@latest resume - --print` in the folder you give it, passing the
@@ -105,16 +107,58 @@ or logged. The import writes only under `~/.wardby/` and the new worktree.
105
107
  - **Ledger** (desktop): runs grouped by pull request (or Jira issue), each group
106
108
  one row with its state and total cost, folded once every run in it finished.
107
109
  A pull request with one run is one line. Runs that touch no pull request are
108
- under **Other**. Each line names its step (opened, review, fix 1, …), with
109
- sub-agent runs under their lead.
110
+ under **Other**. The newest run comes first: groups (Other too) are ordered by
111
+ their newest run, and so are the runs in a group. Each line names its step (opened, review, fix 1, …), with
112
+ sub-agent runs under their lead, and shows the run's status as a coloured
113
+ pill, its spend as "$x of $y", what started it, its task and how long it
114
+ ran, over a thin budget bar.
110
115
  - **Strips** (desktop): one card per pull request, its steps drawn as a row of
111
116
  dots; click a step's name to see it.
112
117
  - **Filters**: show all, active, failed or finished runs, or type to match an
113
118
  agent, status, trigger or task.
114
- - **Run detail**: what started the run and what it produced, its request and
115
- final answer (as markdown; **Show full text** for all of it), services,
116
- errors, the run tree, and **Continue locally** for finished Claude Code runs
117
- with `sessionResume` on (see [Resume Claude Code runs locally](coding-agent-setup.md#resume-a-run-locally)).
119
+ - **Run detail**: a header with the run's status, short id, repository, model,
120
+ duration and budget bar, then a card each for what started the run (with its
121
+ request), what it produced, the run tree, services (ready, starting or
122
+ failed), steps, the final answer and **Continue locally** for finished Claude
123
+ Code runs with `sessionResume` on (see [Resume Claude Code runs locally](coding-agent-setup.md#resume-a-run-locally)).
124
+ Each kind of card has its own colour, the same on every run: a thin bar
125
+ along its top on the desktop, its rounded border in the terminal.
126
+ The request and final answer show about three lines, as markdown, then how
127
+ many more; **Show full text** shows all of it. The pull request link and the
128
+ other actions sit at the bottom.
129
+ - **Copy**: on the desktop, each card (and an error, and an open step's detail)
130
+ has a **Copy** button that copies its full text as the server sent it. In the
131
+ terminal, **Copy section** (`y`) copies the open step's detail; **Copy run
132
+ id** is on both.
133
+ - **Recent steps** (a running run's detail): its last 8 steps, refreshed with
134
+ the run. A tool step is one row: its result at the left (✓ when it passed,
135
+ the exit code of a failed command, or ! for an error), then the tool and its
136
+ command or file; on the desktop a long command is cut with "…". What the
137
+ model said wraps to about two lines.
138
+ - **Steps** (a finished run's detail): every recorded step, 200 at a time;
139
+ **More steps** loads the next 200.
140
+ - **Step detail**: From 0.9.0, a step with detail, or a longer message, has a
141
+ ▸ at its right that opens it (▾ closes it); in the terminal, `j`/`k` also
142
+ move between such steps and `e` shows or hides one. One step is open at a
143
+ time. An open command shows "$ command", its exit status and its output;
144
+ Codex's framing lines (chunk id, wall time, token count) are left out, and
145
+ **Copy** still copies the detail exactly as stored. Detail appears only for
146
+ the agent's owner and the run's starter (and the stdio operator), on a
147
+ server at the detail level. An open detail shows its last 40 lines, with a
148
+ count of the lines above them.
149
+ - **Last step**: in the list, a running or failed run shows its newest step
150
+ under the agent's name.
151
+
152
+ Steps come from the server's run steps (see
153
+ [Run steps](debugging-agent-runs.md#run-steps)): they need a server that has
154
+ `get_run_steps` and records steps (Wardby 0.9.0 or later). Against an older
155
+ server the pane shows no steps and checks again every few refreshes. When the
156
+ server has steps off (`WARDBY_RUN_STEPS=off`) the detail says "Steps are off on
157
+ this server." A run with no recorded steps shows none. If you installed the pane
158
+ before run steps, run `wardby claude-plugin install` again (or allow
159
+ `mcp__wardby__get_run_steps` in `/permissions`): under auto mode the pane
160
+ otherwise shows which rule to add in place of the steps. `wardby claude-plugin
161
+ status` and `wardby doctor` name the missing rule.
118
162
 
119
163
  The pane refreshes every 10 seconds while a run is active and every 60 seconds
120
164
  otherwise (the plugin's **pollSeconds** and **idleSeconds** options), asks only
@@ -125,8 +169,8 @@ polls only while it is open.
125
169
 
126
170
  Claude Code's classic terminal view draws a plugin pane as a band above the
127
171
  prompt and doesn't pass mouse clicks to plugins: use the keyboard (ctrl+x tab
128
- focuses the pane, then `1`–`9` open a run, `r` refreshes, `b` goes back, `x` or
129
- Escape closes). In the fullscreen view the pane is a side bar beside the chat
172
+ focuses the pane, then `1`–`9` open a run, `r` refreshes, `b` goes back, `m`
173
+ shows full text, `y` copies an open step's detail, `x` or Escape closes). In the fullscreen view the pane is a side bar beside the chat
130
174
  and takes clicks. Turn it on with `"tui": "fullscreen"` in your Claude Code
131
175
  settings (or `--fullscreen` above, or `CLAUDE_CODE_NO_FLICKER=1`), then restart
132
176
  Claude Code. The desktop app needs none of this.
@@ -987,6 +987,23 @@ run's repository and refs, as base64url JSON). A refused download returns
987
987
  JSON `{ "error": "<code>" }`; `wardby resume` explains each code (see the
988
988
  `errors/run-transcript-download` help article).
989
989
 
990
+ ## Run steps
991
+
992
+ Claude Code and Codex runs record their steps (each command with its status, and
993
+ what the model said, one short line each) when the server's run-steps listener is
994
+ set up: `WARDBY_RUN_STEPS_LISTEN` on `wardby serve` and `WARDBY_RUN_STEPS_URL` on
995
+ the coding proxy. Without them, coding runs record no steps. Read the steps with
996
+ `get_run_steps`. The Docker quickstart registers the stdio `wardby mcp` server,
997
+ which never runs the listener, so setting `WARDBY_RUN_STEPS_URL` on its proxy
998
+ alone does nothing: coding runs' steps need a `wardby serve` with
999
+ `WARDBY_RUN_STEPS_LISTEN`. The proxy takes `WARDBY_RUN_STEPS_URL` from the
1000
+ environment (for example `http://host.docker.internal:8792`); see
1001
+ [Run steps](debugging-agent-runs.md#run-steps) and, for binding the listener
1002
+ safely, [Run steps in Security deployment](security-deployment.md#run-steps).
1003
+ For a coding agent whose owner sets `runSteps` to `detail`, also set
1004
+ `WARDBY_RUN_STEPS=detail` on the proxy, or no tool output is sent; see
1005
+ [Run steps](debugging-agent-runs.md#run-steps).
1006
+
990
1007
  ## Stop the setup
991
1008
 
992
1009
  ```sh
@@ -304,6 +304,22 @@ port-level policy — that is harmless, because the listener accepts the
304
304
  connection, sends nothing and closes it. It is not a leak; it is a fact about
305
305
  reachability that the Kubernetes launcher turns into evidence.
306
306
 
307
+ ### Run steps
308
+
309
+ The worker is not involved: after relaying each model reply unchanged, the proxy
310
+ reads it (and the newest tool results of the request) and makes one short line
311
+ per tool call or model message. It posts the lines to the control plane's
312
+ run-steps listener (`WARDBY_RUN_STEPS_URL`), authenticated by the run capability;
313
+ the control plane removes the agent's secret values before storing them. Reading
314
+ a reply for steps never delays or fails the relay: a frame the proxy can't read
315
+ gives no step, and a full queue or an unreachable server drops steps.
316
+
317
+ The worker holds the same capability, so the network keeps it off the listener:
318
+ on Kubernetes, ingress to port 8792 only from proxy pods; on Docker, the run's
319
+ isolated network. The worker's own egress is unchanged: it reaches only the
320
+ proxy. See [Run steps](security-deployment.md#run-steps) and
321
+ [Run steps](debugging-agent-runs.md#run-steps).
322
+
307
323
  ## Debug trace
308
324
 
309
325
  When a fixed code is not enough to tell why a coding agent's runs fail, an
@@ -38,6 +38,7 @@ access to.
38
38
  | `get_agent`, `list_repositories`, `list_subagents` | How the agent is set up: prompt, model, budget, repo links and triggers, sub-agents |
39
39
  | `search_help`, `get_help_article` | Help for an error code or failure category |
40
40
  | `cost_report` | What runs cost, when spend is the question |
41
+ | `get_run_steps` (from 0.9.0) | The run's steps, one line per tool call or model message, live while it runs; see [Run steps](#run-steps) |
41
42
  | `get_run_transcript` | The run's conversation, for Claude Code runs with `sessionResume` on |
42
43
  | `stop_run` | Stops a run that's stuck |
43
44
  | The PR, issue or Jira ticket | The diff, CI, the review, and the comment the run posted |
@@ -139,6 +140,117 @@ PR <PR URL> from Wardby didn't do what I expected: <what you expected>.
139
140
  Where did it go wrong? Draft a follow-up comment, but don't post it.
140
141
  ```
141
142
 
143
+ ## Run steps
144
+
145
+ While a run runs, Wardby records its steps: one line for each tool call (the
146
+ tool, and its command, file or first argument, cut to about 100 characters) with
147
+ its status once the result is back (`ok`, `error`, or `exit N`), and one line for
148
+ what the model said (cut to about 120 characters). At the default level, tool
149
+ output and full text are never recorded; see Levels below. Steps are recorded
150
+ for native runs (in the control plane and on the native sandbox) and for Claude Code and Codex runs. Every agent's
151
+ shell command shows as `Bash` (a native `run_command`, Claude Code's command tool,
152
+ Codex's `exec_command`, `shell`, `local_shell` and `shell_command`), another tool
153
+ under its own name (for example `apply_patch`), and a remote MCP tool as
154
+ `mcp:<tool>`. A Claude Code or Codex run's final answer is one `said` step: the
155
+ start of the summary it returned, or `Finished` with the outcome (for example
156
+ `Finished: changes ready`) when it returned none.
157
+
158
+ For Claude Code and Codex, the coding proxy reads the steps from the model's
159
+ replies after relaying them and posts them to the server's run-steps listener
160
+ (`POST /internal/run-steps`). A slow or unreachable listener loses steps, never
161
+ slows the run. Without the two settings in the table below, coding runs record no
162
+ steps; native runs are unaffected. Steps for a Claude Code or Codex run need the
163
+ listener, which only `wardby serve` runs.
164
+
165
+ **Levels.**
166
+
167
+ | Level | Tool input | Tool result | Model text |
168
+ | --------- | -------------------- | --------------------------------------- | -------------------- |
169
+ | `off` | - | - | - |
170
+ | `summary` | about 100 characters | status only (`ok`, `error`, `exit N`) | about 120 characters |
171
+ | `detail` | up to about 4 KB | status and the last ~2 KB of its output | up to about 8 KB |
172
+
173
+ At `summary`, the default, tool output is never recorded. At `detail` each step
174
+ also keeps a **detail** text, at most 16 KB. When the output is cut at the
175
+ start, the partial first line is dropped if it ends within 256 characters;
176
+ otherwise only the first 256 characters are dropped, so a long single-line
177
+ output, such as a JSON result, keeps about its last 1.8 KB. At every level,
178
+ terminal escape sequences (colour codes, links, clipboard writes, title changes)
179
+ and every other control character except line breaks and tabs are removed from
180
+ step text before it is stored.
181
+
182
+ A run's level is the lower of two settings, fixed when the run is dispatched (a
183
+ change reaches later runs only; lowering the ceiling also stops detail for runs
184
+ already in flight):
185
+
186
+ - the instance's ceiling, `WARDBY_RUN_STEPS` (default `summary`);
187
+ - the agent's `runSteps` (`off`, `summary` or `detail`), set by the agent's
188
+ owner with `create_agent` or `update_agent`. Unset counts as `summary`. Set
189
+ `null` to clear it.
190
+
191
+ So `detail` needs both the operator and the agent's owner. If `runSteps` asks
192
+ for more than the instance allows, the value is stored, `update_agent` and
193
+ `get_agent` say so in `warnings`, and runs record at the ceiling.
194
+
195
+ > **Warning: `detail` stores tool output.** Before a step is stored, every
196
+ > secret value the run was given is removed and anything shaped like a token or
197
+ > key is masked. A secret that exists only inside a repository file, or another
198
+ > source the run read, and that matches no known shape, is not known to Wardby
199
+ > and can remain in the detail text. That is why detail is off unless both the
200
+ > operator and the agent's owner ask for it. Turn it on only for agents whose
201
+ > tool output you are prepared to keep for the retention period.
202
+
203
+ **Reading them.** `get_run_steps` (`runId`, optional `after` and `limit`) lists
204
+ them oldest first, 50 at a time and at most 200; pass the last `seq` you have as
205
+ `after` to get only newer ones. `get_run` and `list_runs` without `agentId`
206
+ include `lastStep`, the newest step as one line. Access is the same as
207
+ `get_run`'s. At `detail`, `get_run_steps` also returns each step's `detail` to the agent's
208
+ owner and to the person who started the run (and to the stdio operator); anyone
209
+ else, including an admin who does not own the agent, gets the lines only.
210
+ `lastStep`, the admin viewer API and its `step` events never include detail.
211
+ The `wardby-runs` Claude Code pane shows them (see
212
+ [Claude Code plugin](claude-code-plugin.md#using-the-pane)); the admin viewer API
213
+ sends a `step` event for each one and lists a run's newest 200 in its detail
214
+ (see [Viewer API](viewer-api.md)), and the Wardby Viewer app shows them in a
215
+ **Steps** section.
216
+
217
+ **Limits.** A run records at most 2,000 steps; `dropped` in `get_run_steps`
218
+ counts the rest.
219
+
220
+ **Secrets.** Every step is redacted before it is stored: every secret value of
221
+ the run's agent (attached secrets, readable or brokered, and its MCP servers'
222
+ secrets), the run's own capability and its registry token are replaced with
223
+ `[REDACTED]`, then anything shaped like a token or key.
224
+ If the agent's secrets can't be read at that moment, the step is not stored.
225
+
226
+ **Retention.** Steps are deleted `RUN_STEP_RETENTION_DAYS` after they are
227
+ recorded. The scheduler leader deletes expired steps at most every ten minutes. `get_run_steps` reports
228
+ `expired` when a run finished more than `RUN_STEP_RETENTION_DAYS` ago and has no
229
+ steps left.
230
+
231
+ | Variable | Read by | Default | Meaning |
232
+ | ------------------------- | --------------------------------------------------------------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
233
+ | `WARDBY_RUN_STEPS` | `wardby serve`, `wardby scheduler`, `wardby run`, `wardby mcp`, `wardby native-gateway`, the coding proxy | `summary` | `off` records nothing; `summary` records the lines above; `detail` allows agents whose `runSteps` is `detail` to record detail. The ceiling for every run. Any other value stops start-up. |
234
+ | `RUN_STEP_RETENTION_DAYS` | `wardby serve`, `wardby scheduler`, `wardby run`, `wardby mcp`, `wardby native-gateway` | `30` | Days to keep a step, a whole number from 1 to 3650. A change applies to steps recorded after it. Any other value stops start-up. |
235
+ | `WARDBY_RUN_STEPS_LISTEN` | `wardby serve` | unset | `host:port` or `:port` of the run-steps listener the coding proxy posts coding runs' steps to. Unset: no listener. An invalid value stops start-up. |
236
+ | `WARDBY_RUN_STEPS_URL` | the coding proxy | unset | The listener's origin, for example `http://<server host>:8792`: `http` or `https`, no credentials, path, query or fragment. Unset: coding runs record no steps. An invalid value stops start-up. |
237
+
238
+ The coding proxy also reads `WARDBY_RUN_STEPS`: with `off`, or with
239
+ `WARDBY_RUN_STEPS_URL` unset, no step text leaves the proxy. For coding runs at
240
+ `detail`, set `WARDBY_RUN_STEPS=detail` on the proxy too: below `detail`, the
241
+ proxy never sends any tool output to the server. Set `WARDBY_RUN_STEPS=detail`
242
+ on every process that writes steps (`wardby serve`, the scheduler, the native
243
+ gateway and `wardby run`). The listener is a
244
+ second network path into the server; see
245
+ [Run steps in Security deployment](security-deployment.md#run-steps) for how to
246
+ restrict it.
247
+
248
+ **Upgrading.** The release adds the `RunStep` table, the `Run.stepsDropped`
249
+ column, `Agent.runSteps`, `Run.runStepsLevel` and
250
+ `CodingProxySession.runStepsLevel`; apply the migrations with
251
+ `prisma migrate deploy` as with any upgrade. Runs started before the upgrade
252
+ record at most `summary`.
253
+
142
254
  ## Run transcripts
143
255
 
144
256
  A transcript is the run's conversation: the messages, the tool calls and their
@@ -218,6 +218,13 @@ App replying to say why a mention started nothing; pass it to `up.sh` on every
218
218
  run, the same way. See
219
219
  [What a linked review agent does](code-review-agents.md#what-a-linked-review-agent-does).
220
220
 
221
+ `WARDBY_RUN_STEPS` (`off`, `summary` or `detail`) and `RUN_STEP_RETENTION_DAYS`
222
+ (1 to 3650) are optional, not secrets. Pass them to `up.sh` on every run, the
223
+ same way; unset keeps the server defaults (`summary`, 30 days). `up.sh` adds the
224
+ ceiling to the control plane, the coding proxy and the native gateway, and the
225
+ retention to the control plane and the native gateway. See
226
+ [Run steps](debugging-agent-runs.md#run-steps).
227
+
221
228
  Generate `SECRET_APP_KEY` with:
222
229
 
223
230
  ```sh
@@ -455,6 +462,14 @@ Desktop includes it). The images build concurrently, and the stages that only
455
462
  compile JavaScript run on your machine's own architecture, so building the
456
463
  `linux/amd64` images from an arm64 machine emulates only what actually ships.
457
464
 
465
+ `up.sh` adds the cluster to your kubeconfig and makes it the current context, as
466
+ `gcloud container clusters get-credentials` does. From then on it works from its
467
+ own one-cluster copy of that context, so switching contexts in another terminal
468
+ while a deploy runs doesn't send its later steps to a different cluster. The
469
+ copy is readable only by you and is removed when `up.sh` exits. Don't run two
470
+ deploys of the same cluster at once: the second stops at its first Helm step
471
+ with `another operation (install/upgrade/rollback) is in progress`.
472
+
458
473
  A control-plane restart is designed not to drop requests, at the cost of a
459
474
  slower rollout. A new pod must stay Ready for three minutes before the old one
460
475
  is retired, because the load balancer can take well over a minute to start
@@ -656,8 +671,9 @@ control plane 1.
656
671
  **Sleeping.** Every minute the scheduler leader scales the coding proxy
657
672
  (`wardby-coding-proxy`) to 0 when no coding run is pending or running and none
658
673
  finished within the idle window. It scales the native gateway
659
- (`wardby-native-gateway`) to 0 when no sandbox native run is pending, running or
660
- recently finished and the gateway has no active session. Either target sleeps
674
+ (`wardby-native-gateway`) to 0 when no sandbox native run and no coding run with
675
+ MCP tools is pending, running or recently finished, and the gateway has no
676
+ active session. Either target sleeps
661
677
  only once at least one idle window has passed since it was last woken (recorded
662
678
  in its `wardby.io/power-woke-at` annotation) and since the control plane
663
679
  started, whichever is later. The condition is checked again immediately before
@@ -665,10 +681,14 @@ scaling. The control plane
665
681
  logs `power: proxy slept (idle 30m)`.
666
682
 
667
683
  **Waking.** Starting a coding run scales the proxy to 1 and waits for a ready
668
- endpoint. Starting a sandbox run does the same for the gateway. The wait is at
684
+ endpoint. Starting a sandbox run does the same for the gateway, and so does starting a
685
+ coding run with MCP tools, whose MCP calls go through the gateway (0.9.0 and later): it wakes the
686
+ proxy and gateway at once and fails before launch with
687
+ [`coding_failure_mcp_gateway_unavailable`](../help/errors/coding-mcp-gateway-unavailable.md)
688
+ if the gateway does not wake. The wait is at
669
689
  least 5 minutes, because a cold Autopilot node can add 1 to 2 minutes of
670
- start-up; a `KUBERNETES_READY_TIMEOUT_MS` (proxy) or
671
- `NATIVE_SANDBOX_READY_TIMEOUT_MS` (gateway) above 5 minutes, set on the
690
+ start-up; a `KUBERNETES_READY_TIMEOUT_MS` (the proxy, and the gateway wake of a coding run) or
691
+ `NATIVE_SANDBOX_READY_TIMEOUT_MS` (the gateway wake of a sandbox run) above 5 minutes, set on the
672
692
  control-plane Deployment, raises it to that value. The first coding run after a
673
693
  control-plane restart also runs the cluster preflight canary, because start-up
674
694
  preflight is deferred while the proxy sleeps (`kubernetes_preflight_deferred`).
@@ -690,8 +710,9 @@ the Viewer shows any such leftover as an old replica rather than a failure.
690
710
  **Sleeping now.** To skip the idle window (for example right after a test), an
691
711
  admin calls the MCP tool `power_sleep`, optionally with `target` set to `proxy`
692
712
  or `gateway` (default both). It scales the target to 0 at once, but never stops
693
- a run: a target with a pending or running run, or for the gateway an active
694
- gateway session, is left up and its run ids are returned under `blocked`.
713
+ a run: a target with a pending or running run (for the gateway, a sandbox run
714
+ or a coding run with MCP tools), or for the gateway an active gateway session,
715
+ is left up and its run ids are returned under `blocked`.
695
716
  Recently finished runs do not block it. The next run wakes the target as usual.
696
717
  `power_status` shows each target's replicas, readiness, last wake time and
697
718
  blocking run ids. Both tools need the `power:manage` scope, which only the
@@ -113,6 +113,15 @@ deadline, and only its hash is stored.
113
113
  (the kind and GKE manifests set it). Without it `/mcp` answers `503` with
114
114
  [`mcp_unavailable`](../help/errors/mcp-unavailable.md) and coding agents get no MCP tools. See
115
115
  [Remote MCP servers](security-deployment.md#remote-mcp-servers) for the variables.
116
+ - **Low-power mode (0.9.0 and later).** With `WARDBY_LOW_POWER=1`, a coding run with MCP tools wakes the native
117
+ gateway as well as the coding proxy before its worker launches (both at once), and the gateway
118
+ stays awake while any such run is pending or running, or finished within the idle window. Those
119
+ runs appear in the gateway's `blockers` in `power_status`, and `power_sleep` refuses to sleep the
120
+ gateway while they are pending or running. If the gateway doesn't wake in time the run fails before
121
+ launch with
122
+ [`coding_failure_mcp_gateway_unavailable`](../help/errors/coding-mcp-gateway-unavailable.md),
123
+ rather than start without its MCP tools. Runs without MCP tools don't wake the gateway. See
124
+ [Low-power mode](getting-started-gke.md#low-power-mode).
116
125
  - **Worker images.** MCP for coding agents needs this release's Claude worker image and a new Codex
117
126
  driver image. An older image ignores the token and the agent gets no MCP tools; the run does not
118
127
  fail. Update the images together with the server.
@@ -145,6 +145,7 @@ integration settings, and **no Docker access**.
145
145
  | LLM provider keys | The same provider settings as the server (for example `OPENAI_API_KEY`). |
146
146
  | `SECRET_APP_KEY` | Same value as the server, so tools can read their bound secrets or send brokered ones ([details](tool-secrets.md)). |
147
147
  | Integration settings | The same repository-host and issue-tracker settings as the server. |
148
+ | `WARDBY_RUN_STEPS` | Same value as the server, if you set it: the ceiling for run steps. The gateway records at each run's own level, so set `detail` here too if agents use it ([details](debugging-agent-runs.md#run-steps)). |
148
149
  | `WARDBY_TOOL_MAX_TIMEOUT_MS` | Same value as the server, if you set it: the longest time limit a tool may use ([details](security-deployment.md#tool-time-limits)). |
149
150
  | `WARDBY_MCP_MAX_CALLS_PER_RUN` | Same value as the server, if you set it: the most remote MCP tool calls one coding run may make (default `100`; [details](security-deployment.md#remote-mcp-servers)). |
150
151
 
@@ -550,6 +551,11 @@ or the server running upkeep is not up.
550
551
  Gateway-side variables (`NATIVE_GATEWAY_LISTEN`, `NATIVE_GATEWAY_DENY_PORT`) are
551
552
  set on the gateway, not the server.
552
553
 
554
+ The gateway also records sandbox runs' steps (see
555
+ [Run steps](debugging-agent-runs.md#run-steps)), so when you change
556
+ `WARDBY_RUN_STEPS` (including `detail`) or `RUN_STEP_RETENTION_DAYS`, set them on
557
+ the gateway as well as the server.
558
+
553
559
  ## Limits and deadline
554
560
 
555
561
  - Each worker is limited by `NATIVE_SANDBOX_CPUS`, `NATIVE_SANDBOX_MEMORY_MB`,
@@ -640,6 +640,57 @@ ceiling only as far as your tools need.
640
640
  server: an older image refuses the run of an agent that has a tool with
641
641
  `timeoutMs`. Tools without `timeoutMs` run on either image.
642
642
 
643
+ ### Run steps
644
+
645
+ Runs record their steps (tool calls with a status, and what the model said, each
646
+ cut to one short line) for `get_run_steps`; see
647
+ [Run steps](debugging-agent-runs.md#run-steps). Only the control plane and the
648
+ native gateway write them, and only after removing every secret value of the
649
+ run's agent and every token-shaped value; a step whose agent secrets can't be
650
+ read is not stored. At `summary` tool output is never recorded. At `detail` (the instance and the
651
+ agent's owner must both choose it) the end of each tool's output is stored,
652
+ redacted as above; a secret that only exists in a file the run read, and that
653
+ matches no known shape, can remain. Detail is returned only to the agent's owner,
654
+ the run's starter and the stdio operator, never to the admin viewer. Set `WARDBY_RUN_STEPS=off` to
655
+ record nothing.
656
+
657
+ **Claude Code and Codex runs.** The coding proxy holds no secret values, so it
658
+ sends step text to the control plane's run-steps listener
659
+ (`WARDBY_RUN_STEPS_LISTEN` on `wardby serve`, `POST /internal/run-steps`)
660
+ unmasked. Neither side logs, stores or echoes that text or a request body; the
661
+ control plane redacts it by exact value (the same path as native runs) before
662
+ writing. The proxy sends nothing unless `WARDBY_RUN_STEPS_URL` is set and
663
+ `WARDBY_RUN_STEPS` is not `off`. It sends tool output (its last 4 KB) only for a
664
+ run at `detail`, and only when its own `WARDBY_RUN_STEPS` is `detail`; the same
665
+ listener conditions apply.
666
+
667
+ The listener is authenticated by the run's capability. The run's worker holds
668
+ that capability too (it is the worker's model API key), so it limits a caller to
669
+ its own run; it cannot tell the proxy from the worker. The network boundary keeps
670
+ workers off the port:
671
+
672
+ - **Kubernetes:** a NetworkPolicy admits ingress to port 8792 only from the
673
+ proxy's pods, and the listener has its own ClusterIP Service, off any load
674
+ balancer. Admit no worker pod to that port.
675
+ - **Docker backend:** the run's isolated network keeps run containers from
676
+ reaching the listener. A proxy container reaches a server on the host at
677
+ `host.docker.internal` (Docker Desktop). On Linux, add
678
+ `extra_hosts: ["host.docker.internal:host-gateway"]` to the proxy's service. A
679
+ `127.0.0.1` bind is unreachable from containers on Linux, and a `0.0.0.0` bind
680
+ exposes the listener to your LAN: bind to the Docker bridge address, or
681
+ firewall the port.
682
+
683
+ The proxy-to-listener hop is plain `http` on the cluster or host network by default; use `https`, or keep the listener on a trusted network, wherever the path leaves one.
684
+
685
+ Limits: a run's MCP token is masked only by its token shape. A write that times
686
+ out can still land, so a step's status can arrive before or after its step. A
687
+ cancelled run can post its last steps until its deadline.
688
+
689
+ | Variable | Read by | Default | Meaning |
690
+ | ------------------------- | ---------------- | ------- | -------------------------------------------------------------------------------------------------------------------------- |
691
+ | `WARDBY_RUN_STEPS_LISTEN` | `wardby serve` | unset | `host:port` or `:port` of the run-steps listener. Unset: none. `wardby serve` is the only process that runs it. |
692
+ | `WARDBY_RUN_STEPS_URL` | the coding proxy | unset | The listener's `http(s)` origin, without credentials or path. Unset: coding runs record no steps. Invalid: start-up stops. |
693
+
643
694
  ### Remote MCP servers
644
695
 
645
696
  Owners can register a remote MCP server (Streamable HTTP, HTTPS only) and attach
@@ -103,9 +103,14 @@ check is still pending).
103
103
 
104
104
  ### `GET /admin/api/runs/<id>`
105
105
 
106
- One run in full: the graph fields plus the run's final text and error. For
107
- coding runs, services report the **names** of their environment variables
108
- only, never values. An unknown id returns `404`.
106
+ One run in full: the graph fields plus the run's final text and error, and its
107
+ newest steps (Wardby 0.9.0 and later). `steps` lists at most the newest 200, oldest first, each with
108
+ `seq`, `at`, `kind` (`tool` or `said`), `tool`, `summary` and `status`;
109
+ `stepsOmitted` counts earlier steps not listed and `stepsDropped` counts steps
110
+ past the per-run limit that were never recorded. A step's summary is redacted
111
+ when it is recorded; tool output is never included. For coding runs, services
112
+ report the **names** of their environment variables only, never values. An
113
+ unknown id returns `404`.
109
114
 
110
115
  ### `GET /admin/api/infra`
111
116
 
@@ -131,21 +136,25 @@ run claims them.
131
136
 
132
137
  A Server-Sent Events stream (`text/event-stream`) of live changes. Frames:
133
138
 
134
- | Frame | Meaning |
135
- | ------------------------------------------------ | ---------------------------------------------------------------------------------------------- |
136
- | `retry: 3000` | Suggested client reconnect delay in milliseconds. |
137
- | `event: hello` | Sent first. Data is `{"connected": <bool>}`: whether the server's live-event connection is up. |
138
- | `event: run`, `event: service`, `event: outcome` | A run, coding-run service, or outcome changed. Each carries an `id:` line. |
139
- | `event: status` | The server's live-event connection changed. Data is `{"connected": <bool>}`. |
140
- | `event: resync` | The live-event connection is up (again). Events may have been missed: refetch the graph. |
141
- | `: ping` | Comment sent every 15 seconds to keep the connection open. |
139
+ | Frame | Meaning |
140
+ | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
141
+ | `retry: 3000` | Suggested client reconnect delay in milliseconds. |
142
+ | `event: hello` | Sent first. Data is `{"connected": <bool>}`: whether the server's live-event connection is up. |
143
+ | `event: run`, `event: service`, `event: outcome`, `event: step` | A run, coding-run service, outcome or run step changed. Each carries an `id:` line. |
144
+ | `event: status` | The server's live-event connection changed. Data is `{"connected": <bool>}`. |
145
+ | `event: resync` | The live-event connection is up (again). Events may have been missed: refetch the graph. |
146
+ | `: ping` | Comment sent every 15 seconds to keep the connection open. |
142
147
 
143
- Event payloads are small and never include final text; fetch
148
+ Event payloads are small and never include final text or tool output; fetch
144
149
  `/admin/api/runs/<id>` for detail. A `run` event carries the run's status,
145
150
  turn and token counts, cost and finish time. A `service` event carries the
146
151
  service name, its state and the attempt count. An `outcome` event carries only
147
152
  the run id and the outcome source (`pull_request`, `host_status`,
148
153
  `issue_status` or `host_check`), so refetch the run to see what changed.
154
+ From Wardby 0.9.0, a `step` event carries the run id and the step's `seq`, `stepKind` (`tool` or
155
+ `said`), `tool`, `summary`, `status` and `at`. It is sent when a step is
156
+ recorded and again when its status arrives, so apply it by `seq`. Steps are
157
+ not in the graph: fetch the run to list them.
149
158
 
150
159
  **There is no replay.** Open the event stream first, then load
151
160
  `/admin/api/graph` and apply events on top of it. Refetch `/admin/api/graph` on
@@ -2,7 +2,7 @@
2
2
  openapi: 3.1.0
3
3
  info:
4
4
  title: wardby admin viewer API
5
- version: 0.8.0
5
+ version: 0.9.0
6
6
  description: "Read-only admin API behind the wardby viewer: graph, run detail, infrastructure info and a live event stream. Response objects may gain new fields in later releases; clients should ignore unknown fields. See docs/viewer-api.md for the prose contract."
7
7
  servers:
8
8
  - url: https://{host}
@@ -122,7 +122,7 @@ paths:
122
122
  operationId: getRun
123
123
  summary: Run detail
124
124
  description: |-
125
- Full detail for one run, including final text.
125
+ Full detail for one run, including final text and its newest steps.
126
126
 
127
127
  Requires a bearer token with the `admin:view` scope, held by a caller with the admin role.
128
128
  security:
@@ -297,6 +297,7 @@ paths:
297
297
  - `retry: 3000`: suggested client reconnect delay in milliseconds.
298
298
  - `event: hello`: sent first. Data is `{"connected": <bool>}`, whether the server's live-event connection is up.
299
299
  - `event: run`, `event: service`, `event: outcome`: a run, coding-run service, or outcome changed. Each carries an `id:` line. Payloads match the `viewer-event` schema (see `x-event-schema`).
300
+ - `event: step`: a run recorded a step, or a step's status arrived. Carries an `id:` line. Data: the step's `seq`, `stepKind` (`tool` or `said`), `tool`, `summary` (redacted when it was recorded), `status` and `at`, never tool output.
300
301
  - `event: status`: the server's live-event connection changed. Data is `{"connected": <bool>}`.
301
302
  - `event: resync`: the live-event connection is up (again). Events may have been missed: refetch the graph.
302
303
  - `: ping`: comment sent every 15 seconds to keep the connection open.
@@ -1107,6 +1108,42 @@ components:
1107
1108
  type: array
1108
1109
  items:
1109
1110
  type: string
1111
+ steps:
1112
+ type: array
1113
+ items:
1114
+ type: object
1115
+ properties:
1116
+ seq:
1117
+ type: integer
1118
+ at:
1119
+ type: string
1120
+ kind:
1121
+ type: string
1122
+ enum:
1123
+ - tool
1124
+ - said
1125
+ tool:
1126
+ type:
1127
+ - string
1128
+ - "null"
1129
+ summary:
1130
+ type: string
1131
+ status:
1132
+ type:
1133
+ - string
1134
+ - "null"
1135
+ required:
1136
+ - seq
1137
+ - at
1138
+ - kind
1139
+ - tool
1140
+ - summary
1141
+ - status
1142
+ additionalProperties: false
1143
+ stepsOmitted:
1144
+ type: integer
1145
+ stepsDropped:
1146
+ type: integer
1110
1147
  coding:
1111
1148
  anyOf:
1112
1149
  - type: object
@@ -1184,6 +1221,9 @@ components:
1184
1221
  - error
1185
1222
  - finalText
1186
1223
  - childRunIds
1224
+ - steps
1225
+ - stepsOmitted
1226
+ - stepsDropped
1187
1227
  - coding
1188
1228
  additionalProperties: false
1189
1229
  viewer-event:
@@ -1282,6 +1322,42 @@ components:
1282
1322
  - runId
1283
1323
  - source
1284
1324
  additionalProperties: false
1325
+ - type: object
1326
+ properties:
1327
+ kind:
1328
+ type: string
1329
+ const: step
1330
+ runId:
1331
+ type: string
1332
+ seq:
1333
+ type: integer
1334
+ stepKind:
1335
+ type: string
1336
+ enum:
1337
+ - tool
1338
+ - said
1339
+ tool:
1340
+ type:
1341
+ - string
1342
+ - "null"
1343
+ summary:
1344
+ type: string
1345
+ status:
1346
+ type:
1347
+ - string
1348
+ - "null"
1349
+ at:
1350
+ type: string
1351
+ required:
1352
+ - kind
1353
+ - runId
1354
+ - seq
1355
+ - stepKind
1356
+ - tool
1357
+ - summary
1358
+ - status
1359
+ - at
1360
+ additionalProperties: false
1285
1361
  infra-info:
1286
1362
  type: object
1287
1363
  properties: