@exaudeus/workrail 3.40.0 → 3.41.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.
- package/dist/cli/commands/init.js +0 -3
- package/dist/cli-worktrain.js +8 -0
- package/dist/cli.js +0 -18
- package/dist/config/app-config.d.ts +0 -16
- package/dist/config/app-config.js +0 -14
- package/dist/config/config-file.js +0 -3
- package/dist/console-ui/assets/index-CQt4UhPB.js +28 -0
- package/dist/console-ui/assets/index-DGj8EsFR.css +1 -0
- package/dist/console-ui/index.html +2 -2
- package/dist/coordinators/pr-review.d.ts +17 -0
- package/dist/coordinators/pr-review.js +164 -0
- package/dist/daemon/daemon-events.d.ts +9 -1
- package/dist/daemon/soul-template.d.ts +2 -2
- package/dist/daemon/soul-template.js +11 -1
- package/dist/daemon/workflow-runner.d.ts +14 -1
- package/dist/daemon/workflow-runner.js +395 -25
- package/dist/di/container.js +1 -25
- package/dist/di/tokens.d.ts +0 -3
- package/dist/di/tokens.js +0 -3
- package/dist/engine/engine-factory.js +0 -1
- package/dist/infrastructure/console-defaults.d.ts +1 -0
- package/dist/infrastructure/console-defaults.js +4 -0
- package/dist/infrastructure/session/index.d.ts +0 -1
- package/dist/infrastructure/session/index.js +1 -3
- package/dist/manifest.json +87 -103
- package/dist/mcp/handlers/session.d.ts +1 -0
- package/dist/mcp/handlers/session.js +61 -13
- package/dist/mcp/server.js +1 -18
- package/dist/mcp/transports/http-entry.js +0 -2
- package/dist/mcp/transports/stdio-entry.js +1 -2
- package/dist/mcp/types.d.ts +0 -2
- package/dist/trigger/daemon-console.d.ts +2 -0
- package/dist/trigger/daemon-console.js +1 -1
- package/dist/trigger/trigger-listener.d.ts +2 -0
- package/dist/trigger/trigger-listener.js +3 -1
- package/dist/trigger/trigger-router.d.ts +4 -3
- package/dist/trigger/trigger-router.js +4 -3
- package/dist/trigger/trigger-store.js +17 -4
- package/dist/v2/usecases/console-routes.d.ts +2 -1
- package/dist/v2/usecases/console-routes.js +29 -5
- package/dist/v2/usecases/console-service.js +14 -0
- package/dist/v2/usecases/console-types.d.ts +1 -0
- package/docs/authoring.md +16 -16
- package/docs/design/coordinator-message-queue-drain-plan.md +241 -0
- package/docs/design/coordinator-message-queue-drain-review.md +120 -0
- package/docs/design/coordinator-message-queue-drain.md +289 -0
- package/docs/design/shaping-workflow-external-research.md +119 -0
- package/docs/discovery/late-bound-goals-impl-plan.md +147 -0
- package/docs/discovery/late-bound-goals-review.md +82 -0
- package/docs/discovery/late-bound-goals.md +118 -0
- package/docs/discovery/steer-endpoint-design-candidates.md +288 -0
- package/docs/discovery/steer-endpoint-design-review-findings.md +104 -0
- package/docs/discovery/steer-endpoint-implementation-plan.md +284 -0
- package/docs/ideas/backlog.md +292 -0
- package/docs/ideas/design-candidates-console-session-tree-impl.md +64 -0
- package/docs/ideas/design-candidates-session-tree-view.md +196 -0
- package/docs/ideas/design-review-findings-console-session-tree-impl.md +75 -0
- package/docs/ideas/design-review-findings-session-tree-view.md +88 -0
- package/docs/ideas/implementation_plan_session_tree_view.md +238 -0
- package/package.json +2 -1
- package/spec/authoring-spec.json +16 -16
- package/spec/shape.schema.json +178 -0
- package/spec/workflow-tags.json +232 -47
- package/workflows/coding-task-workflow-agentic.json +491 -480
- package/workflows/wr.shaping.json +182 -0
- package/dist/console-ui/assets/index-8dh0Psu-.css +0 -1
- package/dist/console-ui/assets/index-CXWCAonr.js +0 -28
- package/dist/infrastructure/session/DashboardHeartbeat.d.ts +0 -8
- package/dist/infrastructure/session/DashboardHeartbeat.js +0 -39
- package/dist/infrastructure/session/DashboardLockRelease.d.ts +0 -2
- package/dist/infrastructure/session/DashboardLockRelease.js +0 -29
- package/dist/infrastructure/session/HttpServer.d.ts +0 -60
- package/dist/infrastructure/session/HttpServer.js +0 -912
- package/workflows/coding-task-workflow-agentic.lean.v2.json +0 -648
- package/workflows/coding-task-workflow-agentic.v2.json +0 -324
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
# Implementation Plan: POST /api/v2/sessions/:sessionId/steer
|
|
2
|
+
|
|
3
|
+
**Branch:** `feat/session-steer-endpoint`
|
|
4
|
+
**Confidence:** High
|
|
5
|
+
**PR count:** 1
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Problem Statement
|
|
10
|
+
|
|
11
|
+
A coordinator script needs to inject text into an agent's next turn during a running daemon session.
|
|
12
|
+
The `steer()` mechanism in `AgentLoop` already delivers injected text. The gap is a bridge between
|
|
13
|
+
an HTTP endpoint and the closure-scoped `pendingSteerText` variable in `runWorkflow()`.
|
|
14
|
+
|
|
15
|
+
Additionally, the existing `pendingSteerText: string | null` is a single-value field that silently
|
|
16
|
+
drops coordinator steers when overwritten by `onAdvance()`. This must be fixed first (R1 finding).
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Acceptance Criteria
|
|
21
|
+
|
|
22
|
+
1. `POST /api/v2/sessions/:sessionId/steer` with body `{ "text": "..." }` returns HTTP 200
|
|
23
|
+
`{ "success": true }` when the sessionId belongs to an active daemon session.
|
|
24
|
+
2. The injected text is delivered to the agent via `agent.steer()` on the next `turn_end` event,
|
|
25
|
+
concatenated after the step text from `onAdvance()`.
|
|
26
|
+
3. Returns HTTP 404 `{ "success": false, "error": "Session not found or not a daemon session" }`
|
|
27
|
+
when the sessionId is not in the registry.
|
|
28
|
+
4. Returns HTTP 503 `{ "success": false, "error": "Steer not available..." }` in standalone console
|
|
29
|
+
mode (no steerRegistry injected).
|
|
30
|
+
5. Returns HTTP 400 for missing or non-string `text` body.
|
|
31
|
+
6. Multiple calls to the endpoint between `turn_end` events: all injected texts are delivered in the
|
|
32
|
+
same steer message, joined with `\n\n`.
|
|
33
|
+
7. After the session completes, calling the endpoint returns 404.
|
|
34
|
+
8. The existing `pendingSteerText` variable is replaced by `pendingSteerParts: string[]` and
|
|
35
|
+
`onAdvance()` behavior is unchanged from the caller's perspective (step advance still works).
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Non-Goals
|
|
40
|
+
|
|
41
|
+
- Auth token on the endpoint (v1 is localhost-only, network binding is the security layer)
|
|
42
|
+
- `waitForCoordinator` blocking gate mechanism (Phase 2B, separate task)
|
|
43
|
+
- `wr.coordinator_signal` artifact schema (Phase A, separate task)
|
|
44
|
+
- MCP-mode injection (deferred to v2)
|
|
45
|
+
- Crash recovery for in-flight steers (in-memory only, v1 known limitation)
|
|
46
|
+
- Structured request body beyond `{ text: string }` (v2 concern)
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Philosophy-Driven Constraints
|
|
51
|
+
|
|
52
|
+
- **DI for boundaries**: `SteerRegistry` must be injected into `mountConsoleRoutes()` and
|
|
53
|
+
`runWorkflow()`. No module-level singletons.
|
|
54
|
+
- **Errors as data**: HTTP responses use `{ success: bool, error?: string }` shape. No thrown
|
|
55
|
+
exceptions at the route level.
|
|
56
|
+
- **Validate at boundaries**: 400 for invalid body, 503 for disabled, 404 for not-found -- all
|
|
57
|
+
checked before touching the registry.
|
|
58
|
+
- **YAGNI**: Only what's listed in acceptance criteria. No speculative extension points.
|
|
59
|
+
- **Explicit domain types**: Named type alias `SteerRegistry` (not raw `Map<string, fn>` literal).
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Invariants
|
|
64
|
+
|
|
65
|
+
1. `pendingSteerParts` is only mutated in two places: `onAdvance()` (push step text) and the steer
|
|
66
|
+
callback registered in the `SteerRegistry` (push coordinator text). No other writer.
|
|
67
|
+
2. `pendingSteerParts` is only read and drained in the `turn_end` subscriber. Single reader.
|
|
68
|
+
3. JavaScript single-threaded event loop: no race between push and drain.
|
|
69
|
+
4. The steer callback is registered after `workrailSessionId` is decoded from the continueToken,
|
|
70
|
+
and deregistered in `runWorkflow()`'s `finally` block. No stale entries possible.
|
|
71
|
+
5. The endpoint is only active when `steerRegistry` is provided to `mountConsoleRoutes()`.
|
|
72
|
+
The standalone console does not provide it.
|
|
73
|
+
6. The `steerRegistry` param is optional on all functions. No existing callers are broken.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Selected Approach
|
|
78
|
+
|
|
79
|
+
**Hybrid (type alias + parameter injection):**
|
|
80
|
+
- Named type alias `export type SteerRegistry = Map<string, (text: string) => void>` in
|
|
81
|
+
`src/daemon/workflow-runner.ts`.
|
|
82
|
+
- `pendingSteerText: string | null` replaced by `const pendingSteerParts: string[] = []`.
|
|
83
|
+
- `onAdvance()` uses `pendingSteerParts.push(stepText)`.
|
|
84
|
+
- turn_end subscriber drains with `const parts = pendingSteerParts.splice(0)` and calls
|
|
85
|
+
`agent.steer(buildUserMessage(parts.join('\n\n')))` if `parts.length > 0`.
|
|
86
|
+
- `runWorkflow()` gains optional `steerRegistry?: SteerRegistry` param. After workrailSessionId
|
|
87
|
+
is decoded, calls `steerRegistry?.set(workrailSessionId, (text) => pendingSteerParts.push(text))`.
|
|
88
|
+
In `finally`: `steerRegistry?.delete(workrailSessionId)`.
|
|
89
|
+
- `mountConsoleRoutes()` gains optional `steerRegistry?: SteerRegistry` param after `triggerRouter`.
|
|
90
|
+
- `POST /api/v2/sessions/:sessionId/steer` endpoint added in `console-routes.ts`.
|
|
91
|
+
- `TriggerRouter` constructor gains optional `steerRegistry?: SteerRegistry`; passes to
|
|
92
|
+
`runWorkflowFn()` calls in `route()` and `dispatch()`.
|
|
93
|
+
- `RunWorkflowFn` type in `trigger-router.ts` extended with optional 6th param.
|
|
94
|
+
|
|
95
|
+
**Runner-Up:** C2 (SteerRegistry class). Loses only for having a new file for 3 trivial operations.
|
|
96
|
+
Use if the registry gains additional methods or needs isolated unit tests.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Vertical Slices
|
|
101
|
+
|
|
102
|
+
### Slice 1: Fix `pendingSteerText` -> `pendingSteerParts` (R1 prerequisite)
|
|
103
|
+
|
|
104
|
+
**Files:** `src/daemon/workflow-runner.ts` only.
|
|
105
|
+
|
|
106
|
+
**Change:**
|
|
107
|
+
- Rename `pendingSteerText: string | null` to `const pendingSteerParts: string[] = []`.
|
|
108
|
+
- Update `onAdvance()`: `pendingSteerText = stepText` -> `pendingSteerParts.push(stepText)`.
|
|
109
|
+
- Update turn_end subscriber drain:
|
|
110
|
+
- Before: `if (pendingSteerText !== null && !isComplete) { ... }`
|
|
111
|
+
- After: `if (!isComplete) { const parts = pendingSteerParts.splice(0); if (parts.length > 0) { agent.steer(buildUserMessage(parts.join('\n\n'))); } }`
|
|
112
|
+
- Note: `isComplete` guard moves outside the `splice(0)` -- drain always happens, steer only if not complete and parts non-empty.
|
|
113
|
+
|
|
114
|
+
**Acceptance:** Existing behavior unchanged. Session still receives step text on each advance.
|
|
115
|
+
No coordinator injection yet. Build+type-check passes. Existing tests pass.
|
|
116
|
+
|
|
117
|
+
**Risk:** Low. Pure refactor, no behavior change from external perspective.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
### Slice 2: SteerRegistry type alias + runWorkflow() registration
|
|
122
|
+
|
|
123
|
+
**Files:** `src/daemon/workflow-runner.ts`.
|
|
124
|
+
|
|
125
|
+
**Change:**
|
|
126
|
+
- Add: `export type SteerRegistry = Map<string, (text: string) => void>;`
|
|
127
|
+
- Add optional param to `runWorkflow()`: `steerRegistry?: SteerRegistry`
|
|
128
|
+
- After `workrailSessionId` is decoded (line ~2190): register callback:
|
|
129
|
+
```typescript
|
|
130
|
+
if (steerRegistry && workrailSessionId) {
|
|
131
|
+
steerRegistry.set(workrailSessionId, (text: string) => { pendingSteerParts.push(text); });
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
- In `finally` block: `if (steerRegistry && workrailSessionId) { steerRegistry.delete(workrailSessionId); }`
|
|
135
|
+
- Add code comment on the `set()` call documenting the registration gap.
|
|
136
|
+
|
|
137
|
+
**Acceptance:** `runWorkflow()` compiles with new optional param. Existing callers unchanged.
|
|
138
|
+
Manual test: if a steerRegistry Map is passed and a callback is registered/called during a session,
|
|
139
|
+
text is pushed to `pendingSteerParts`.
|
|
140
|
+
|
|
141
|
+
**Risk:** Low. Additive change, no behavior change when `steerRegistry` is undefined.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
### Slice 3: TriggerRouter wiring
|
|
146
|
+
|
|
147
|
+
**Files:** `src/trigger/trigger-router.ts`.
|
|
148
|
+
|
|
149
|
+
**Change:**
|
|
150
|
+
- Import `SteerRegistry` from `workflow-runner.js`.
|
|
151
|
+
- Extend `RunWorkflowFn` type with optional 6th param:
|
|
152
|
+
`steerRegistry?: SteerRegistry` after `emitter?`.
|
|
153
|
+
- Add `private readonly steerRegistry?: SteerRegistry` to `TriggerRouter`.
|
|
154
|
+
- Add `steerRegistry?: SteerRegistry` to TriggerRouter constructor params.
|
|
155
|
+
- Assign in constructor: `this.steerRegistry = steerRegistry`.
|
|
156
|
+
- Update `route()` call: `this.runWorkflowFn(workflowTrigger, this.ctx, this.apiKey, undefined, this.emitter, this.steerRegistry)`
|
|
157
|
+
- Update `dispatch()` call: same.
|
|
158
|
+
|
|
159
|
+
**Acceptance:** TriggerRouter compiles. `runWorkflow` in production TriggerRouter path passes
|
|
160
|
+
steerRegistry to the agent loop. Existing trigger tests unaffected (registry is optional).
|
|
161
|
+
|
|
162
|
+
**Risk:** Low. Additive param. All existing test calls to `TriggerRouter` pass `undefined` or
|
|
163
|
+
omit the param.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
### Slice 4: HTTP endpoint in console-routes.ts
|
|
168
|
+
|
|
169
|
+
**Files:** `src/v2/usecases/console-routes.ts`.
|
|
170
|
+
|
|
171
|
+
**Change:**
|
|
172
|
+
- Import `SteerRegistry` from `../../daemon/workflow-runner.js`.
|
|
173
|
+
- Add `steerRegistry?: SteerRegistry` to `mountConsoleRoutes()` param list (after `triggerRouter`).
|
|
174
|
+
- Add endpoint after the `POST /api/v2/auto/dispatch` block:
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
// POST /api/v2/sessions/:sessionId/steer
|
|
178
|
+
// Injects text into a running daemon session's next agent turn.
|
|
179
|
+
// Daemon-only: requires steerRegistry to be provided at server startup.
|
|
180
|
+
// Auth: localhost-only (127.0.0.1 binding). No token auth in v1.
|
|
181
|
+
// TODO(v2): Add token auth before any multi-user or remote deployment.
|
|
182
|
+
app.post('/api/v2/sessions/:sessionId/steer', express.json(), (req: Request, res: Response) => {
|
|
183
|
+
if (!steerRegistry) {
|
|
184
|
+
res.status(503).json({ success: false, error: 'Steer not available (not a daemon context).' });
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const { sessionId } = req.params;
|
|
188
|
+
const body = req.body as { text?: unknown };
|
|
189
|
+
const text = typeof body.text === 'string' ? body.text.trim() : '';
|
|
190
|
+
if (!text) {
|
|
191
|
+
res.status(400).json({ success: false, error: 'text is required and must be a non-empty string.' });
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const callback = steerRegistry.get(sessionId);
|
|
195
|
+
if (!callback) {
|
|
196
|
+
res.status(404).json({ success: false, error: 'Session not found or not a daemon session.' });
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
callback(text);
|
|
200
|
+
res.json({ success: true });
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Acceptance:** All 5 HTTP response cases work correctly (200, 400, 404, 503). Session receives
|
|
205
|
+
injected text on next turn_end. Standalone console returns 503.
|
|
206
|
+
|
|
207
|
+
**Risk:** Low. New endpoint, no changes to existing routes.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
### Slice 5: Daemon wiring in daemon-console.ts
|
|
212
|
+
|
|
213
|
+
**Files:** `src/trigger/daemon-console.ts`.
|
|
214
|
+
|
|
215
|
+
**Change:**
|
|
216
|
+
- Import `SteerRegistry` from `../daemon/workflow-runner.js`.
|
|
217
|
+
- Before constructing `TriggerRouter`: `const steerRegistry: SteerRegistry = new Map();`
|
|
218
|
+
- Pass to `TriggerRouter` constructor: `new TriggerRouter(index, ctx, apiKey, runWorkflow, execFn, ..., steerRegistry)`
|
|
219
|
+
- Pass to `mountConsoleRoutes()`: add `steerRegistry` as the last argument (after `triggerRouter`).
|
|
220
|
+
- Also update the direct `runWorkflow()` call in `console-routes.ts` `POST /auto/dispatch` path
|
|
221
|
+
(when `triggerRouter` is absent): pass `steerRegistry` as 6th arg.
|
|
222
|
+
|
|
223
|
+
**Acceptance:** End-to-end: daemon starts, `POST /auto/dispatch` creates a session, coordinator
|
|
224
|
+
calls `POST /sessions/:id/steer`, agent receives injected text on next turn.
|
|
225
|
+
|
|
226
|
+
**Risk:** Medium. This is the wiring step that connects all slices. Most likely source of missed
|
|
227
|
+
call sites.
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Test Design
|
|
232
|
+
|
|
233
|
+
### Unit tests (workflow-runner.ts)
|
|
234
|
+
- Test that `onAdvance()` pushes to `pendingSteerParts`.
|
|
235
|
+
- Test that turn_end subscriber joins and steers when `pendingSteerParts.length > 0`.
|
|
236
|
+
- Test that multiple pushes (simulate both `onAdvance` and steer callback) produce joined text.
|
|
237
|
+
- Test that `steerRegistry.set()` is called after workrailSessionId decoded.
|
|
238
|
+
- Test that `steerRegistry.delete()` is called in finally (mock registry, verify delete).
|
|
239
|
+
|
|
240
|
+
### Integration test (console-routes.ts)
|
|
241
|
+
- Mock `steerRegistry` with a Map. POST to endpoint with valid body -> 200, callback called.
|
|
242
|
+
- POST with empty body -> 400.
|
|
243
|
+
- POST with unknown sessionId -> 404.
|
|
244
|
+
- POST without steerRegistry injected -> 503.
|
|
245
|
+
|
|
246
|
+
### Regression: existing tests
|
|
247
|
+
- All existing `runWorkflow()` tests must pass unchanged (optional param, default undefined).
|
|
248
|
+
- All existing `mountConsoleRoutes()` tests must pass unchanged.
|
|
249
|
+
- All existing TriggerRouter tests must pass unchanged.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Risk Register
|
|
254
|
+
|
|
255
|
+
| Risk | Likelihood | Impact | Mitigation |
|
|
256
|
+
|---|---|---|---|
|
|
257
|
+
| Missed call site for steerRegistry in dispatch/route | Medium | High | Search for all `runWorkflowFn(` calls in trigger-router.ts before submitting |
|
|
258
|
+
| `pendingSteerParts.splice(0)` stale closure ref | Low | High | splice(0) mutates in-place; closure over array variable (not array contents) is safe |
|
|
259
|
+
| Registration gap causes 404 for early steers | Very low | Low | Document with code comment; coordinator retries on 404 |
|
|
260
|
+
| `mountConsoleRoutes` callers not updated | Low | Medium | Only 3 callers: daemon-console.ts, standalone-console.ts (no change), console-routes.ts |
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## PR Packaging Strategy
|
|
265
|
+
|
|
266
|
+
Single PR on branch `feat/session-steer-endpoint`. All 5 slices together. The R1 fix (Slice 1) is
|
|
267
|
+
small enough that it doesn't need its own PR. The endpoint is only usable when all slices are present.
|
|
268
|
+
|
|
269
|
+
PR title: `feat(console): add POST /api/v2/sessions/:sessionId/steer for coordinator injection`
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## Philosophy Alignment Per Slice
|
|
274
|
+
|
|
275
|
+
| Slice | Principle | Status |
|
|
276
|
+
|---|---|---|
|
|
277
|
+
| S1 pendingSteerParts | Immutability by default | Tension (mutable array) -- acceptable, mutation bounded |
|
|
278
|
+
| S1 pendingSteerParts | Compose with small pure functions | Satisfied -- drain is one expression |
|
|
279
|
+
| S2 SteerRegistry type | Explicit domain types | Satisfied -- named alias |
|
|
280
|
+
| S2 runWorkflow registration | DI for boundaries | Satisfied -- injected, not global |
|
|
281
|
+
| S3 TriggerRouter | Make illegal states unrepresentable | Satisfied -- optional param can't be confused with required |
|
|
282
|
+
| S4 HTTP endpoint | Validate at boundaries | Satisfied -- 400/503/404 before touching registry |
|
|
283
|
+
| S4 HTTP endpoint | Errors as data | Satisfied -- { success: bool } shape |
|
|
284
|
+
| S5 daemon wiring | YAGNI | Satisfied -- single Map, no extra abstraction |
|
package/docs/ideas/backlog.md
CHANGED
|
@@ -5891,3 +5891,295 @@ Coordinator logic:
|
|
|
5891
5891
|
- Phase 1: coordinator scripts withhold `complete_step` advancement until the condition is met. This already works today -- the coordinator just doesn't advance the session until the fix agent is done.
|
|
5892
5892
|
- Phase 2: the coordinator passes structured context when advancing: `complete_step(session, { injectedContext: fixSummary })`. The session receives it as part of the next step's prompt.
|
|
5893
5893
|
- Phase 3: declarative pipelines -- workflow JSON declares that step N waits for an external condition before proceeding. The coordinator reads this and manages the timing automatically. No hand-coded coordinator script needed for common patterns.
|
|
5894
|
+
|
|
5895
|
+
---
|
|
5896
|
+
|
|
5897
|
+
### Coordinatable workflow steps: confirmation points the coordinator can satisfy (needs discovery, Apr 18, 2026)
|
|
5898
|
+
|
|
5899
|
+
⚠️ **Needs discovery before implementation. The questions below are open, not answered.**
|
|
5900
|
+
|
|
5901
|
+
**The insight:** workflows already have `requireConfirmation: true` on certain steps -- these are natural coordination points. Right now they pause for a human. The idea is to make them also pausable-for-a-coordinator, so a coordinator (or another agent) can be the one that responds instead of a human.
|
|
5902
|
+
|
|
5903
|
+
**The vision:**
|
|
5904
|
+
A workflow reaches a `requireConfirmation` step. In MCP mode (human-driven), it behaves exactly as today -- pauses and waits. In daemon/coordinator mode, instead of blocking forever, the coordinator can:
|
|
5905
|
+
- Inject a synthesized answer based on external work it just did ("architecture review found X, proceed with approach A")
|
|
5906
|
+
- Spawn another agent to generate the answer and inject its output
|
|
5907
|
+
- Ask a discovery agent to weigh in and forward the result
|
|
5908
|
+
- Simply forward a human's message from the message queue
|
|
5909
|
+
|
|
5910
|
+
The original session never knows whether a human or a coordinator satisfied the confirmation. It just receives the next turn with context.
|
|
5911
|
+
|
|
5912
|
+
**Why this is powerful:**
|
|
5913
|
+
Today the coordinator is external to the workflow -- it orchestrates sessions from outside. This makes the workflow itself coordinatable from within, so multi-agent collaboration can be declared in the workflow spec rather than bolted on in coordinator scripts.
|
|
5914
|
+
|
|
5915
|
+
**What's unknown and needs discovery:**
|
|
5916
|
+
1. **Mechanism:** is this an enriched `requireConfirmation` (add a `coordinatable: true` flag?), a new step type (`requireCoordinatorInput`?), or something at the engine level? Tradeoffs between each.
|
|
5917
|
+
2. **What gets injected:** always a structured decision ("proceed/revise/abort + findings"), or also data injection ("here are the file contents", "here's what the API returned")? How does the step receive it -- as a new tool call result, as a steer, as part of the step prompt?
|
|
5918
|
+
3. **Coordinator discovery:** how does the coordinator know a step is waiting for it vs waiting for a human? Does it poll the session state? Does the session emit a `coordinator_gate_pending` event? (This connects to the `waitForCoordinator` spec in this backlog.)
|
|
5919
|
+
4. **Timeout/fallback:** if the coordinator never responds, what happens? Fall back to human? Error? Configurable?
|
|
5920
|
+
5. **MCP invariant:** must behave identically to today in MCP/human-driven mode. The coordinator path is additive, not a behavior change for existing users.
|
|
5921
|
+
|
|
5922
|
+
**Relationship to other specs:**
|
|
5923
|
+
- "Long-running sessions: stay open across agent handoffs" -- the session pauses at the confirmation point, coordinator acts, session resumes
|
|
5924
|
+
- "POST /api/v2/sessions/:id/steer" -- this might be the injection mechanism
|
|
5925
|
+
- `signal_coordinator` tool -- the session might signal the coordinator instead of blocking
|
|
5926
|
+
- `waitForCoordinator` step flag (already in this backlog) -- same underlying need, different framing
|
|
5927
|
+
- "Coordinator review mode: self-healing vs comment-and-wait" -- confirmation points are where that routing decision gets expressed
|
|
5928
|
+
|
|
5929
|
+
---
|
|
5930
|
+
|
|
5931
|
+
## Architecture Decision: Three-Workflow Pipeline (Apr 18, 2026)
|
|
5932
|
+
|
|
5933
|
+
### Decision
|
|
5934
|
+
|
|
5935
|
+
The canonical WorkRail workflow pipeline for new features is:
|
|
5936
|
+
|
|
5937
|
+
```
|
|
5938
|
+
wr.discovery (optional) → wr.shaping (optional) → coding-task-workflow-agentic
|
|
5939
|
+
```
|
|
5940
|
+
|
|
5941
|
+
Each workflow is independently useful. The pipeline is an optional chain, not a required sequence.
|
|
5942
|
+
|
|
5943
|
+
### Rationale
|
|
5944
|
+
|
|
5945
|
+
**wr.discovery** produces a direction -- what problem is worth solving. Output: structured discovery notes at `.workrail/discovery/`.
|
|
5946
|
+
|
|
5947
|
+
**wr.shaping** produces a bounded pitch -- what specifically to build and explicitly NOT build, at a product level. Output: `.workrail/current-pitch.md`. Faithful Shape Up methodology. Tech-agnostic. No code-level content.
|
|
5948
|
+
|
|
5949
|
+
**coding-task-workflow-agentic** produces running code -- engineering approach, sliced implementation, verification. When pitch.md exists (Phase 0.5), it skips design ideation and translates the pitch directly into an engineering approach. The pitch's no-gos and appetite are binding constraints.
|
|
5950
|
+
|
|
5951
|
+
### No TechSpec workflow needed
|
|
5952
|
+
|
|
5953
|
+
The coding workflow already does everything a TechSpec workflow would do: Phase 1b generates design candidates, Phase 1c selects and challenges the approach, Phase 3 writes the spec and implementation plan. Adding a separate TechSpec workflow would duplicate this and create a question of which is canonical. The coding workflow is the engineering planning layer.
|
|
5954
|
+
|
|
5955
|
+
**The split that matters is product vs engineering:**
|
|
5956
|
+
- Product decisions (what to build, for whom, within what time) → wr.shaping
|
|
5957
|
+
- Engineering decisions (how to build it, which interfaces, which tests) → coding workflow
|
|
5958
|
+
|
|
5959
|
+
### When to skip shaping
|
|
5960
|
+
|
|
5961
|
+
- Task is small, concrete, and clearly scoped → go straight to coding workflow
|
|
5962
|
+
- Discovery already produced a bounded, implementable direction
|
|
5963
|
+
- You have a pre-written ticket or spec that already defines what to build
|
|
5964
|
+
|
|
5965
|
+
### Faithful Shape Up constraint
|
|
5966
|
+
|
|
5967
|
+
wr.shaping is tech-agnostic. A pitch for a Kotlin Android app and a pitch for a Python API service look structurally identical. No file paths, no function signatures, no implementation details. This makes pitches usable by human engineering teams at companies using Shape Up, not just WorkRail's coding workflow.
|
|
5968
|
+
|
|
5969
|
+
### Phase 0.5 mechanics
|
|
5970
|
+
|
|
5971
|
+
When `coding-task-workflow-agentic` finds `.workrail/current-pitch.md`:
|
|
5972
|
+
1. Reads all five pitch sections (Problem, Appetite, Solution/Elements, Rabbit Holes, No-Gos)
|
|
5973
|
+
2. Sets `shapedInputDetected=true`
|
|
5974
|
+
3. Skips phases 1a-1c (hypothesis, design generation, challenge-and-select)
|
|
5975
|
+
4. Phase 1d translates pitch elements/invariants/no-gos into an engineering approach
|
|
5976
|
+
5. Plan audit (Phase 4) checks for drift against the pitch
|
|
5977
|
+
6. Appetite is a hard ceiling -- oversized engineering work becomes follow-up tickets
|
|
5978
|
+
|
|
5979
|
+
|
|
5980
|
+
---
|
|
5981
|
+
|
|
5982
|
+
## Idea: `context-gather` Step Type (Apr 19, 2026)
|
|
5983
|
+
|
|
5984
|
+
### Problem
|
|
5985
|
+
|
|
5986
|
+
Phase 0.5 in the coding workflow currently looks for a shaped pitch by checking a local path. This doesn't handle: coordinator-injected context, manually written docs (GDoc, Confluence, Notion), Glean-indexed artifacts, or URLs embedded in the task description. The search logic is duplicated if other workflows need the same document.
|
|
5987
|
+
|
|
5988
|
+
### Proposed primitive
|
|
5989
|
+
|
|
5990
|
+
A new engine-level step type `context-gather` that resolves a named context artifact from ordered sources:
|
|
5991
|
+
|
|
5992
|
+
```json
|
|
5993
|
+
{
|
|
5994
|
+
"type": "context-gather",
|
|
5995
|
+
"id": "gather-pitch",
|
|
5996
|
+
"contextType": "shaped-pitch",
|
|
5997
|
+
"outputVar": "shapedInput",
|
|
5998
|
+
"optional": true,
|
|
5999
|
+
"sources": ["coordinator-injected", "local-paths", "task-url", "glean"]
|
|
6000
|
+
}
|
|
6001
|
+
```
|
|
6002
|
+
|
|
6003
|
+
**Source resolution order (stops at first hit):**
|
|
6004
|
+
1. `coordinator-injected` -- coordinator already attached context of this type to the session (most common in autonomous mode)
|
|
6005
|
+
2. `local-paths` -- check `.workrail/current-pitch.md`, `pitch.md`, `PRD.md`, `.workrail/pitches/` (most recent)
|
|
6006
|
+
3. `task-url` -- extract any URL from the task description and fetch via WebFetch or matching MCP (GDoc, Confluence, Notion)
|
|
6007
|
+
4. `glean` -- search Glean for recent docs matching the task keywords and `contextType`; opt-in only (risk of false positives silently constraining wrong scope)
|
|
6008
|
+
|
|
6009
|
+
If `optional: true` and no source resolves: `outputVar = null`, workflow continues normally.
|
|
6010
|
+
|
|
6011
|
+
### Why engine-level, not a routine
|
|
6012
|
+
|
|
6013
|
+
- Coordinator intercept requires the engine to check "has this type already been provided?" before running any search -- a routine can't express that
|
|
6014
|
+
- `contextType` is a declared intent multiple workflows can share (`wr.shaping`, `coding-task-workflow`, `wr.discovery`) without duplicating resolver logic
|
|
6015
|
+
- New sources (Linear, Jira, Notion) get added to the engine once, immediately available to all workflows
|
|
6016
|
+
|
|
6017
|
+
### Relationship to existing work
|
|
6018
|
+
|
|
6019
|
+
- Replaces/supersedes Phase 0.5's current local-path check in `coding-task-workflow-agentic`
|
|
6020
|
+
- Coordinator PR-review flow would inject `shaped-pitch` context before spawning the coding session
|
|
6021
|
+
- Any workflow that needs "find the spec/pitch/PRD for this task" uses the same step type
|
|
6022
|
+
|
|
6023
|
+
### Open questions
|
|
6024
|
+
|
|
6025
|
+
- How does the coordinator inject context into a session? Via a session variable set before `start_workflow`, or a new `inject_context` call?
|
|
6026
|
+
- How does `task-url` distinguish a GDoc URL from a Confluence URL from a Notion URL? MCP routing by domain?
|
|
6027
|
+
- What is the `contextType` vocabulary? Start with `shaped-pitch` -- what else? (`discovery-notes`, `design-spec`, `api-contract`?)
|
|
6028
|
+
- Glean false-positive risk: wrong document fed as shaped input silently constrains wrong scope. Needs confidence threshold or explicit user confirmation when Glean is the only hit.
|
|
6029
|
+
|
|
6030
|
+
|
|
6031
|
+
---
|
|
6032
|
+
|
|
6033
|
+
## Completed (Apr 19, 2026)
|
|
6034
|
+
|
|
6035
|
+
### wr.shaping -- Faithful Shape Up shaping workflow
|
|
6036
|
+
|
|
6037
|
+
Created `workflows/wr.shaping.json`. Faithful Shape Up methodology, tech-agnostic, produces `.workrail/current-pitch.md` only. Nine steps: ingest → frame gate → diverge (6 shapes, Verbalized Sampling) → converge → breadboard + elements → rabbit holes + no-gos → draft/critique loop → approval gate → write pitch.md. Two human gates with autonomous fallback. Appetite is calendar-time only (xs/s/m/l/xl). No code-level content -- a pitch for a Kotlin app and a pitch for a Python service look structurally identical.
|
|
6038
|
+
|
|
6039
|
+
### coding-task-workflow-agentic -- Upstream context Phase 0.5
|
|
6040
|
+
|
|
6041
|
+
Added Phase 0.5 "Locate Upstream Context" to `coding-task-workflow-agentic.json`. Format-agnostic: the agent uses whatever tools are available (repo search, WebFetch, Confluence/Notion/Glean MCPs, etc.) to find any upstream document -- pitch, PRD, BRD, RFC, design doc, user story, Jira epic, etc. Sets `upstreamSpecDetected` + `solutionFixed` flags. When `solutionFixed=true`, design ideation phases (1a-1c) are skipped and Phase 1d translates upstream constraints directly into an engineering approach. Plan audit (Phase 4) checks for drift against `upstreamBoundaries` whenever an upstream document was found.
|
|
6042
|
+
|
|
6043
|
+
Also consolidated from three workflow variants to one canonical file.
|
|
6044
|
+
|
|
6045
|
+
|
|
6046
|
+
---
|
|
6047
|
+
|
|
6048
|
+
## Current state update (Apr 19, 2026)
|
|
6049
|
+
|
|
6050
|
+
**npm version: v3.40.0**
|
|
6051
|
+
|
|
6052
|
+
### What shipped since v3.36.0 (Apr 18 -- Apr 19)
|
|
6053
|
+
|
|
6054
|
+
- ✅ **`wr.shaping`** -- faithful Shape Up shaping workflow (9 steps, two human gates with autonomous fallback)
|
|
6055
|
+
- ✅ **`coding-task-workflow-agentic` Phase 0.5** -- upstream context detection; skips design phases when solution is pre-specified. Three-workflow pipeline: shaping → discovery → coding.
|
|
6056
|
+
- ✅ **Coding workflow consolidated** -- from three variants (lean, full, lean.v2) to one canonical file.
|
|
6057
|
+
- ✅ **HttpServer removed from MCP server** (#601) -- pure stdio. MCP server can no longer accidentally start an HTTP server.
|
|
6058
|
+
- ✅ **Late-bound goals** (#604) -- `goalTemplate: "{{$.goal}}"` defaults for webhook-driven sessions. Goals can come from the payload, not just the static trigger definition.
|
|
6059
|
+
- ✅ **Coordinator message queue drain** (#606) -- `pr-review` coordinator reads `~/.workrail/message-queue.jsonl` before each spawn cycle. `worktrain tell stop`, `skip-pr <n>`, `add-pr <n>` work.
|
|
6060
|
+
- ✅ **Notifications shipped** -- `NotificationService` implemented, wired into `TriggerRouter` via `trigger-listener.ts`. `WORKTRAIN_NOTIFY_MACOS=true` and `WORKTRAIN_NOTIFY_WEBHOOK=<url>` in `~/.workrail/config.json`.
|
|
6061
|
+
- ✅ **`worktrain run pr-review`** -- fully wired coordinator command. `spawnSession` → `awaitSessions` → `getAgentResult` (session-wide artifact aggregation) → `parseFindingsFromNotes` → route by severity.
|
|
6062
|
+
- ✅ **`wr.review_verdict` artifact path** -- end-to-end wired: `mr-review-workflow.agentic.v2.json` phase-6 emits it, `artifact-contract-validator.ts` validates it at `continue_workflow` time, coordinator reads it with keyword-scan fallback.
|
|
6063
|
+
- ✅ **`worktrain logs` / `worktrain health`** -- structured daemon log tailing and per-session health summary. `worktrain status <id>` deprecated in favor of `worktrain health <id>`.
|
|
6064
|
+
- ✅ **`signal_coordinator` tool** -- agent can emit structured mid-session signals (`progress`, `finding`, `data_needed`, `approval_needed`, `blocked`) without advancing the step.
|
|
6065
|
+
- ✅ **`ChildWorkflowRunResult` + `assertNever`** -- spawn_agent delivery_failed bug fixed. `delivery_failed` impossible state is compile-time excluded.
|
|
6066
|
+
- ✅ **`lastStepArtifacts` on `WorkflowRunSuccess`** -- `onComplete` callback forwards artifacts alongside notes. Coordinator can read typed artifacts from result without a separate HTTP call.
|
|
6067
|
+
- ✅ **`steerRegistry` + POST `/sessions/:id/steer`** -- coordinator injection endpoint wired in daemon console. Running sessions register a steer callback; coordinators can inject mid-session messages via HTTP.
|
|
6068
|
+
- ✅ **GitHub polling adapters** -- `github_issues_poll` and `github_prs_poll` providers fully implemented alongside existing `gitlab_poll`.
|
|
6069
|
+
- ✅ **Knowledge graph spike** -- `src/knowledge-graph/` module: DuckDB in-memory + ts-morph indexer + two validation queries. NOT yet wired to an MCP tool (ts-morph in devDependencies).
|
|
6070
|
+
- ✅ **`worktrain daemon --install`** -- launchd plist creation, load, verify. Daemon survives MCP server reconnects.
|
|
6071
|
+
- ✅ **Performance sweep** -- April 2026 sweep identified 10 highest-leverage fixes, filed as issues #248-257. Not yet merged.
|
|
6072
|
+
|
|
6073
|
+
### Accurate limitations (as of v3.40.0)
|
|
6074
|
+
|
|
6075
|
+
1. **Console session tree UI not built** -- `parentSessionId` is stored in the `session_created` event and in `WorkflowRunSuccess`. Console `RunLineageDag` shows the per-session step DAG only. Cross-session parent-child tree is data-only. PRs #607 (tree view) and #608 (steer endpoint) are OPEN.
|
|
6076
|
+
2. **Daemon tool set is minimal** -- agent has: `complete_step`, `continue_workflow` (deprecated), `Bash`, `Read`, `Write`, `report_issue`, `spawn_agent`, `signal_coordinator`. No `Glob`, `Grep`, or `Edit`. Read/Write are thin wrappers.
|
|
6077
|
+
3. **`worktrain tell` messages only drained by coordinator** -- `drainMessageQueue` is called by `runPrReviewCoordinator`, not by the daemon loop. A running autonomous session cannot receive mid-run injections from `worktrain tell`. The `steerRegistry` HTTP endpoint is the mid-session channel.
|
|
6078
|
+
4. **Knowledge graph not wired** -- module exists, ts-morph must move to dependencies before an MCP tool can be built.
|
|
6079
|
+
5. **`spawn_agent` return missing `artifacts`** -- returns `{ childSessionId, outcome, notes }` only. Typed artifacts from child session are not surfaced to the parent agent. `lastStepArtifacts` on `WorkflowRunSuccess` exists but spawn_agent doesn't return it.
|
|
6080
|
+
6. **`worktrain inbox --watch` stub** -- `--watch` flag prints "not yet implemented" and exits.
|
|
6081
|
+
7. **Artifact store not built** -- agents still dump markdown/files directly into the repo. `~/.workrail/artifacts/` directory structure not created.
|
|
6082
|
+
8. **Performance issues not fixed** -- issues #248-257 filed from April sweep. `continue_workflow` triggers 6+ event log scans, full session rebuild per `/api/v2/sessions` request, N+1 workflow fetches, no caching.
|
|
6083
|
+
9. **No auto-commit** -- agents can write code but do not commit, push, or open PRs autonomously.
|
|
6084
|
+
10. **Assessment gates not battle-tested** -- end-to-end flow with `outputContract: required: true` not validated in production use.
|
|
6085
|
+
|
|
6086
|
+
### Open PRs to merge
|
|
6087
|
+
|
|
6088
|
+
- **#607** `feat(console): add session tree view for coordinator sessions` -- cross-session parent-child hierarchy in console. Blocked on: `parentSessionId` data is in store but console routes need to surface it.
|
|
6089
|
+
- **#608** `feat(console): add POST /api/v2/sessions/:sessionId/steer for coordinator injection` -- NOTE: this endpoint is already implemented in `daemon-console.ts` via `steerRegistry`. PR #608 may be adding this to the MCP server console separately. Check before merging.
|
|
6090
|
+
- **#610** `feat(workflows): add wr.shaping` -- the shaping workflow. Ready to merge.
|
|
6091
|
+
- **#587** `fix(mcp): add assertNever exhaustiveness guard to TriggerRouter` -- likely already applied in codebase (ChildWorkflowRunResult assertNever is live). May be a duplicate or different scope. Check.
|
|
6092
|
+
|
|
6093
|
+
### Next priorities (groomed Apr 19)
|
|
6094
|
+
|
|
6095
|
+
1. **Merge #610 (wr.shaping)** -- ready. Workflow is implemented and in the branch.
|
|
6096
|
+
2. **Merge #587 (TriggerRouter assertNever)** -- quick fix, check if still relevant.
|
|
6097
|
+
3. **Review and merge #607 + #608** -- console tree view and steer endpoint. Verify #608 doesn't duplicate what's already live in daemon-console.ts.
|
|
6098
|
+
4. **Performance fixes** -- issues #248-257. Pick highest-leverage first: SessionIndex (#248) and console projection cache (#249) eliminate most of the repeated scans.
|
|
6099
|
+
5. **Daemon tool set: add Glob + Grep** -- agents routinely need to search files. `Read` + `Bash` grep is slow and lossy. Native `Glob` and `Grep` tools would make coding sessions more reliable.
|
|
6100
|
+
6. **`spawn_agent` artifacts gap** -- add `artifacts?: readonly unknown[]` to the return value. `lastStepArtifacts` is already on `WorkflowRunSuccess`; wiring it through is ~30 LOC.
|
|
6101
|
+
7. **Knowledge graph wiring** -- move `ts-morph` and `@duckdb/node-api` to dependencies, add `query_knowledge_graph` MCP tool.
|
|
6102
|
+
8. **Artifact store foundation** -- `~/.workrail/artifacts/` directory, write path in `complete_step`.
|
|
6103
|
+
|
|
6104
|
+
---
|
|
6105
|
+
|
|
6106
|
+
### wr.shaping workflow: shape messy problems into implementation-ready specs (needs authoring, Apr 18, 2026)
|
|
6107
|
+
|
|
6108
|
+
**Status:** Design complete. Ready to author as a WorkRail workflow JSON.
|
|
6109
|
+
|
|
6110
|
+
**Design docs:**
|
|
6111
|
+
- `docs/design/shaping-workflow-discovery.md` -- WorkRail-internal discovery findings
|
|
6112
|
+
- `docs/design/shaping-workflow-external-research.md` -- External research synthesis (Shape Up, LLM failure modes, artifact schema)
|
|
6113
|
+
|
|
6114
|
+
**The gap this fills:** WorkRail has `wr.discovery` (divergent) and `coding-task-workflow-agentic` (convergent). Shaping is the missing middle -- converting messy discovery output into a bounded, implementation-ready spec without mid-implementation rabbit holes.
|
|
6115
|
+
|
|
6116
|
+
**The 11-step skeleton (see design doc for full detail):**
|
|
6117
|
+
1. ingest_and_extract -- extract problem frames, forces, open questions
|
|
6118
|
+
2. **frame_gate** -- MANDATORY HUMAN GATE: confirm problem + appetite
|
|
6119
|
+
3. diverge_solution_shapes -- 4 parallel rough shapes with varied framings
|
|
6120
|
+
4. converge_pick -- SEPARATE JUDGE (different model/prompt): pick best shape
|
|
6121
|
+
5. breadboard_and_elements -- fat-marker breadboard + Interface/Invariant/Exclusion classification
|
|
6122
|
+
6. rabbit_holes_nogos -- adversarial: risks, mitigations, no-gos, assumptions
|
|
6123
|
+
7. context_pack_build -- file globs, reuse_utilities, conventions, do-not-touch boundaries
|
|
6124
|
+
8. example_map_and_gherkin -- Given/When/Then acceptance criteria + verification commands
|
|
6125
|
+
9. draft_pitch -- self-refine ×2, SEPARATE CRITIC (obfuscated authorship)
|
|
6126
|
+
10. **approval_gate** -- MANDATORY HUMAN GATE: approve, edit, or restart
|
|
6127
|
+
11. finalize_and_handoff -- schema validation, emit shape.json + pitch.md
|
|
6128
|
+
|
|
6129
|
+
**The single most important design decision:** generator and critic run on structurally different prompts (ideally different model families). CoT and self-reflection alone do NOT mitigate anchoring or self-preference bias (Lou & Sun 2025; Panickssery et al. 2024).
|
|
6130
|
+
|
|
6131
|
+
**Output artifact:** `shape.json` -- contains problem story, appetite (multi-dimensional: calendar + tokens + turns + files), breadboard, elements, context_pack (file boundaries + reuse_utilities), Gherkin acceptance criteria, rabbit holes, no-gos, decomposition with walking skeleton, assumptions_log, build_readiness_score.
|
|
6132
|
+
|
|
6133
|
+
**Key insight for AI implementers:** LLMs need MORE explicit specs than humans on interfaces/invariants/file boundaries (no tacit knowledge, no scope-shame), but LESS explicit than junior humans on standard patterns. The dominant failure mode is confident architectural divergence -- working code that reinvents an existing utility. Context Pack (Step 7) directly prevents this.
|
|
6134
|
+
|
|
6135
|
+
**Next action:** author `wr.shaping` as a WorkRail workflow JSON using workflow-for-workflows, then update `coding-task-workflow-agentic` Phase 0 to detect and consume `shape.json` when present.
|
|
6136
|
+
|
|
6137
|
+
---
|
|
6138
|
+
|
|
6139
|
+
## Coordinator architecture: separation of concerns (Apr 19, 2026)
|
|
6140
|
+
|
|
6141
|
+
**Decision: defer knowledge graph implementation until the context assembly layer is designed.**
|
|
6142
|
+
|
|
6143
|
+
### The god class problem
|
|
6144
|
+
|
|
6145
|
+
`src/coordinators/pr-review.ts` is already ~500 LOC doing: session dispatch, result aggregation, finding classification, merge routing, message queue drain, and outbox writes. Adding knowledge graph queries, context bundle assembly, upstream doc fetching, and prior session lookups would make it a god class.
|
|
6146
|
+
|
|
6147
|
+
"Coordinator" is not a class or a script -- it is a **layer** that orchestrates across multiple concerns. Those concerns need to be separated before we add more to them.
|
|
6148
|
+
|
|
6149
|
+
### The right layering
|
|
6150
|
+
|
|
6151
|
+
```
|
|
6152
|
+
Trigger layer src/trigger/ receives events, validates, enqueues
|
|
6153
|
+
Dispatch layer (TBD) decides which workflow + what goal
|
|
6154
|
+
Context assembly (TBD) gathers and packages context before spawning
|
|
6155
|
+
Orchestration layer src/coordinators/ spawns, awaits, routes, retries, escalates
|
|
6156
|
+
Delivery layer src/trigger/delivery posts results back to origin systems
|
|
6157
|
+
```
|
|
6158
|
+
|
|
6159
|
+
**Context assembly** is the missing layer. Before dispatching a coding session, something needs to:
|
|
6160
|
+
- Run `buildIndex()` and query "what imports the file being changed"
|
|
6161
|
+
- Find the upstream pitch/PRD/BRD for the task
|
|
6162
|
+
- Pull relevant prior session notes
|
|
6163
|
+
- Package everything as a structured context bundle
|
|
6164
|
+
|
|
6165
|
+
This is NOT the orchestration script's job. The orchestration script should call `assembleContext(task, workspace)` and receive a bundle -- it should not know how that bundle was gathered.
|
|
6166
|
+
|
|
6167
|
+
### Why the knowledge graph belongs in context assembly, not in the daemon
|
|
6168
|
+
|
|
6169
|
+
Two options were considered:
|
|
6170
|
+
- **Daemon tool** (`makeQueryKnowledgeGraphTool` in `workflow-runner.ts`) -- agent queries mid-session on demand
|
|
6171
|
+
- **Coordinator pre-fetch** -- coordinator runs queries before spawning, injects answers as context
|
|
6172
|
+
|
|
6173
|
+
The coordinator pre-fetch is better for known patterns (e.g. "what imports the file being changed" before a coding task). The agent doesn't need to know the graph exists -- it just gets the relevant facts as context. This also avoids adding `ts-morph` + DuckDB to the production build.
|
|
6174
|
+
|
|
6175
|
+
The daemon tool approach is only better for ad-hoc mid-session queries the agent discovers dynamically. That's a secondary use case for v1.
|
|
6176
|
+
|
|
6177
|
+
### What to build before the knowledge graph
|
|
6178
|
+
|
|
6179
|
+
1. **Design the `ContextAssembler` abstraction** -- takes task description + workspace + trigger metadata, returns a structured context bundle. The knowledge graph is one of several sources (alongside upstream docs, prior session notes, repo state).
|
|
6180
|
+
2. **Refactor `pr-review.ts`** to use a `ContextAssembler` for the bits that fit there.
|
|
6181
|
+
3. **Then** implement knowledge graph as a `ContextAssembler` plugin -- not as a coordinator script addition and not as a daemon tool.
|
|
6182
|
+
|
|
6183
|
+
### Anti-pattern to avoid
|
|
6184
|
+
|
|
6185
|
+
Adding knowledge graph calls directly into `pr-review.ts` or any other coordinator script. That immediately creates the god class we're trying to avoid and couples the orchestration layer to a specific context source.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Design Candidates: Console Session Tree Implementation (Phase 3)
|
|
2
|
+
|
|
3
|
+
*2026-04-18 -- This document covers only the remaining Slice 5 (SessionTreeView UI component)*
|
|
4
|
+
*Phase 1 and Phase 2 artifacts: see design-candidates-session-tree-view.md and design-review-findings-session-tree-view.md*
|
|
5
|
+
|
|
6
|
+
## Problem Understanding
|
|
7
|
+
|
|
8
|
+
Slices 1-4 are implemented. The remaining work is Slice 5: add a SessionTreeView rendering path to SessionList.tsx.
|
|
9
|
+
|
|
10
|
+
**Tensions:**
|
|
11
|
+
- Expand toggle vs card navigation: two click targets on the same logical row. Resolved by a flex row with separate button elements.
|
|
12
|
+
- Per-coordinator expand state vs pure component: expand state lives in useState (UI state, not business logic -- correct placement).
|
|
13
|
+
- Auto-expand for in_progress: requires checking status in state initialization.
|
|
14
|
+
|
|
15
|
+
**Likely seam:** SessionList.tsx (presenter) + session-list-use-cases.ts (pure function buildSessionTree, already built).
|
|
16
|
+
|
|
17
|
+
**What makes it hard:** The expand toggle must be keyboard-navigable separately from the card AND must not trigger card navigation on click.
|
|
18
|
+
|
|
19
|
+
## Philosophy Constraints
|
|
20
|
+
|
|
21
|
+
- Pure presenter: no business logic in the component
|
|
22
|
+
- Immutability: expand state is a ReadonlyMap or regular Map in useState
|
|
23
|
+
- Functional/declarative: map SessionTreeNode[] to JSX
|
|
24
|
+
- Compose with small functions: SessionTreeView as a named function, separate from SessionList
|
|
25
|
+
|
|
26
|
+
## Impact Surface
|
|
27
|
+
|
|
28
|
+
- SessionList.tsx: adding viewMode branch
|
|
29
|
+
- session-list-use-cases.ts: already has buildSessionTree exported
|
|
30
|
+
- session-list-reducer.ts: already has viewMode + view_mode_changed
|
|
31
|
+
|
|
32
|
+
## Candidates
|
|
33
|
+
|
|
34
|
+
### Candidate A: SessionTreeView inline in SessionList.tsx (only candidate)
|
|
35
|
+
|
|
36
|
+
**Summary:** A `SessionTreeView` function component in SessionList.tsx takes `SessionTreeNode[]`, initializes expand state as `Map<string, boolean>` (auto-expand in_progress), and renders a flex row with [expand-toggle, coordinator-card] and children in a TreeLine wrapper below when expanded.
|
|
37
|
+
|
|
38
|
+
**Tensions resolved:** Expand/navigate separation (separate button elements). Accepts: expand state resets on navigation (transient UI state is acceptable).
|
|
39
|
+
|
|
40
|
+
**Boundary:** SessionList.tsx presenter layer.
|
|
41
|
+
|
|
42
|
+
**Failure mode:** Expand toggle accidentally triggers card navigation. Fixed by: expand toggle button is outside the coordinator ConsoleCard, not nested inside it.
|
|
43
|
+
|
|
44
|
+
**Repo pattern:** Follows SessionGroup component pattern in SessionList.tsx exactly.
|
|
45
|
+
|
|
46
|
+
**Gains:** Simple, pure, testable in isolation. **Loses:** Expand state resets when navigating away (transient).
|
|
47
|
+
|
|
48
|
+
**Scope:** Best-fit.
|
|
49
|
+
|
|
50
|
+
**Philosophy:** All principles honored.
|
|
51
|
+
|
|
52
|
+
## Comparison and Recommendation
|
|
53
|
+
|
|
54
|
+
Single candidate; no comparison needed. Candidate A is the correct approach.
|
|
55
|
+
|
|
56
|
+
## Self-Critique
|
|
57
|
+
|
|
58
|
+
Strongest counter-argument: expand state should be in the reducer (durable within page session). Counter-counter: expand state is UI state, not domain state. Reducer is for interaction state that needs to persist across renders (search, filter, sort, pagination). Expand state for individual coordinator rows is more like accordion state -- local useState is correct.
|
|
59
|
+
|
|
60
|
+
Pivot condition: if user feedback shows expand state loss is disruptive, move to reducer with `expanded_coordinators: ReadonlySet<string>` field.
|
|
61
|
+
|
|
62
|
+
## Open Questions for the Main Agent
|
|
63
|
+
|
|
64
|
+
None. Implementation is fully specified in docs/ideas/design-candidates-session-tree-view.md and the Phase 2 design spec.
|