@exaudeus/workrail 3.40.0 → 3.42.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 +48 -11
- 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-DGj8EsFR.css +1 -0
- package/dist/console-ui/assets/index-DwfWMKvv.js +28 -0
- package/dist/console-ui/index.html +2 -2
- package/dist/context-assembly/deps.d.ts +8 -0
- package/dist/context-assembly/deps.js +2 -0
- package/dist/context-assembly/index.d.ts +6 -0
- package/dist/context-assembly/index.js +50 -0
- package/dist/context-assembly/infra.d.ts +3 -0
- package/dist/context-assembly/infra.js +154 -0
- package/dist/context-assembly/types.d.ts +30 -0
- package/dist/context-assembly/types.js +2 -0
- package/dist/coordinators/pr-review.d.ts +20 -1
- package/dist/coordinators/pr-review.js +189 -4
- 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 +406 -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/domain/execution/state.d.ts +6 -6
- 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 +138 -122
- package/dist/mcp/handlers/session.d.ts +1 -0
- package/dist/mcp/handlers/session.js +61 -13
- package/dist/mcp/handlers/v2-workflow.d.ts +2 -2
- package/dist/mcp/output-schemas.d.ts +234 -234
- package/dist/mcp/server.js +1 -18
- package/dist/mcp/tools.d.ts +2 -2
- 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/mcp/v2/tools.d.ts +24 -24
- 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/durable-core/schemas/artifacts/assessment.d.ts +2 -2
- package/dist/v2/durable-core/schemas/artifacts/coordinator-signal.d.ts +2 -2
- package/dist/v2/durable-core/schemas/artifacts/loop-control.d.ts +6 -6
- package/dist/v2/durable-core/schemas/artifacts/review-verdict.d.ts +6 -6
- package/dist/v2/durable-core/schemas/compiled-workflow/index.d.ts +56 -56
- package/dist/v2/durable-core/schemas/execution-snapshot/blocked-snapshot.d.ts +83 -83
- package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.d.ts +1024 -1024
- package/dist/v2/durable-core/schemas/export-bundle/index.d.ts +2336 -2336
- package/dist/v2/durable-core/schemas/session/dag-topology.d.ts +6 -6
- package/dist/v2/durable-core/schemas/session/events.d.ts +339 -339
- package/dist/v2/durable-core/schemas/session/gaps.d.ts +30 -30
- package/dist/v2/durable-core/schemas/session/manifest.d.ts +6 -6
- package/dist/v2/durable-core/schemas/session/outputs.d.ts +8 -8
- package/dist/v2/durable-core/schemas/session/validation-event.d.ts +3 -3
- 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/context-assembly-design-candidates.md +199 -0
- package/docs/design/context-assembly-implementation-plan.md +211 -0
- package/docs/design/context-assembly-review-findings.md +112 -0
- 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 +356 -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,119 @@
|
|
|
1
|
+
# Shaping Workflow: External Research Synthesis
|
|
2
|
+
# Date: Apr 18, 2026
|
|
3
|
+
# Source: Deep research prompt answered by frontier model
|
|
4
|
+
|
|
5
|
+
## TL;DR
|
|
6
|
+
|
|
7
|
+
An 11-step prompt chain with two mandatory human gates, a self-refine loop with evaluator-optimizer split, sectioned solution divergence, and a hybrid JSON+markdown artifact. The single highest-leverage design decision: **generation and critique run on structurally different prompts (ideally different model families)** -- anchoring and self-preference bias are not mitigated by CoT or self-reflection alone (Lou & Sun 2025; Panickssery et al. 2024).
|
|
8
|
+
|
|
9
|
+
## The 11-Step Skeleton
|
|
10
|
+
|
|
11
|
+
| # | Step | Pattern | Output | Tokens |
|
|
12
|
+
|---|---|---|---|---|
|
|
13
|
+
| 1 | `ingest_and_extract` | Chain | Frame candidates, forces, open questions | 2–5k |
|
|
14
|
+
| 2 | `frame_gate` | Interrupt | Confirmed problem + appetite | small | **MANDATORY HUMAN GATE** |
|
|
15
|
+
| 3 | `diverge_solution_shapes` | Parallel ×4 | 4 candidate rough shapes | med ×4 |
|
|
16
|
+
| 4 | `converge_pick` | Separate judge | Chosen shape + rationale | small-med |
|
|
17
|
+
| 5 | `breadboard_and_elements` | Chain + 1 refine | Breadboard + fat-marker elements | 8–15k |
|
|
18
|
+
| 6 | `rabbit_holes_nogos` | Adversarial | Risks, mitigations, no-gos, assumptions | 3–6k |
|
|
19
|
+
| 7 | `context_pack_build` | Tool-augmented | File globs, utilities, conventions, related PRs | med-large |
|
|
20
|
+
| 8 | `example_map_and_gherkin` | Chain | Rules, examples, Gherkin scenarios | 3–6k |
|
|
21
|
+
| 9 | `draft_pitch` | Self-refine ×2, critic=separate prompt | Full pitch (markdown + JSON) | 8–15k ×critique |
|
|
22
|
+
| 10 | `approval_gate` | Interrupt | Approved pitch | small | **MANDATORY HUMAN GATE** |
|
|
23
|
+
| 11 | `finalize_and_handoff` | Deterministic + schema validate | Canonical artifact + pitch.md | <1k |
|
|
24
|
+
|
|
25
|
+
Total budget: 50–200k tokens depending on divergence fan-out.
|
|
26
|
+
|
|
27
|
+
## Key Empirical Findings
|
|
28
|
+
|
|
29
|
+
### What actually mitigates LLM failure modes in shaping (ranked):
|
|
30
|
+
1. **Generator ≠ Evaluator with authorship obfuscation** -- use different model families for generation vs critique. Beats anchoring, self-preference, and mode collapse simultaneously. CoT and self-reflection alone do NOT work (Lou & Sun 2025).
|
|
31
|
+
2. **Verbalized Sampling + N-alternatives-before-selection** -- prompt for a distribution, not a single answer. 1.6–2.1× diversity gain (Zhang et al. arXiv 2510.01171).
|
|
32
|
+
3. **Schema-constrained structured output** -- kills verbosity compensation, forces right abstraction level by construction.
|
|
33
|
+
4. **ClarifyGPT-style consistency check** -- generate two independent interpretations; divergence triggers clarification.
|
|
34
|
+
5. **Self-Refine with specific rubric**, bounded at 2–3 iterations (~20% absolute gain, Madaan et al. arXiv 2303.17651).
|
|
35
|
+
6. **Red-team pass** with explicit "what's hallucinated / what's missing" prompts against a separate instance.
|
|
36
|
+
|
|
37
|
+
### The right level of abstraction (encodable heuristic)
|
|
38
|
+
**Interfaces and Invariants, Not Function Bodies.**
|
|
39
|
+
|
|
40
|
+
Classify every sentence in the pitch as:
|
|
41
|
+
- **(a) Interface** -- user-visible surfaces, data objects, integration points, touched modules
|
|
42
|
+
- **(b) Invariant** -- declarative constraints (idempotency, auth model, consistency requirements, latency budgets)
|
|
43
|
+
- **(c) Exclusion** -- explicitly excluded functionality
|
|
44
|
+
- **(d) Implementation detail** -- over-specification, demote or cut
|
|
45
|
+
- **(e) Vague** -- under-specification, replace with concrete interface/invariant or ask clarifying question
|
|
46
|
+
|
|
47
|
+
A well-shaped pitch contains only (a), (b), (c).
|
|
48
|
+
|
|
49
|
+
### Shaping for AI implementers vs humans (the key asymmetry)
|
|
50
|
+
LLM implementers need:
|
|
51
|
+
- **MORE explicit** than any human spec on: interfaces, invariants, conventions, no-gos, exact API versions, file boundaries (LLMs fabricate APIs, lack tacit codebase knowledge, lack scope-shame)
|
|
52
|
+
- **LESS explicit** than junior-human spec on: standard implementation patterns (CRUD, routing, idiomatic error handling -- LLMs know these better)
|
|
53
|
+
|
|
54
|
+
The dominant failure mode to design against: **confident architectural divergence** -- agent produces working, tested, reviewable PR that reinvents an existing utility or lands logic in the wrong layer. Looks plausible in review. Neither tests nor LLM sensors reliably catch it. Only a better spec prevents it.
|
|
55
|
+
|
|
56
|
+
### Context Pack (Step 7) is the highest-leverage AI-specific addition
|
|
57
|
+
But: **LLM-generated Context Packs are measurably inferior to human-curated ones** (ETH Zurich AGENTS.md study -- LLM-generated context reduced task success in 5 of 8 settings). Treat Step 7 output as a draft requiring spot-check.
|
|
58
|
+
|
|
59
|
+
## The Artifact Schema
|
|
60
|
+
|
|
61
|
+
```jsonc
|
|
62
|
+
{
|
|
63
|
+
"shaping_run_id": "uuid",
|
|
64
|
+
"frame": {
|
|
65
|
+
"problem_story_md": "...",
|
|
66
|
+
"appetite": {
|
|
67
|
+
"calendar_weeks": 6,
|
|
68
|
+
"token_budget_est": 120000,
|
|
69
|
+
"agent_turns_est": 60,
|
|
70
|
+
"files_touched_est": 8,
|
|
71
|
+
"sizing_bucket": "small|medium|large"
|
|
72
|
+
},
|
|
73
|
+
"forces": { "push": [...], "pull": [...], "anxiety": [...], "habit": [...] }
|
|
74
|
+
},
|
|
75
|
+
"solution": {
|
|
76
|
+
"breadboard_md": "...",
|
|
77
|
+
"elements": [{ "name": "...", "description_md": "...", "classification": "interface|invariant|exclusion" }],
|
|
78
|
+
"alternatives_considered": [{ "sketch": "...", "rejected_because": "..." }]
|
|
79
|
+
},
|
|
80
|
+
"context_pack": {
|
|
81
|
+
"touch_globs": ["src/billing/**"],
|
|
82
|
+
"do_not_touch_globs": ["src/auth/**", "migrations/**"],
|
|
83
|
+
"reuse_utilities": [{ "path": "...", "symbol": "...", "signature": "...", "reason_to_reuse": "..." }],
|
|
84
|
+
"conventions_md": "...",
|
|
85
|
+
"related_prior_art": [{ "path_or_pr": "...", "relevance": "..." }]
|
|
86
|
+
},
|
|
87
|
+
"acceptance_criteria": {
|
|
88
|
+
"gherkin": "Feature: ...\n Scenario: ...",
|
|
89
|
+
"verification_commands": ["pnpm test src/billing", "tsc --noEmit"],
|
|
90
|
+
"example_map": { "rules": [...], "examples": [...], "open_questions": [...] }
|
|
91
|
+
},
|
|
92
|
+
"rabbit_holes": [{ "risk": "...", "severity": "low|med|critical", "mitigation": "...", "patch_applied": true }],
|
|
93
|
+
"no_gos": ["..."],
|
|
94
|
+
"assumptions_log": [{ "step": "...", "assumption": "...", "confidence": 0.7, "rationale": "..." }],
|
|
95
|
+
"decomposition": {
|
|
96
|
+
"walking_skeleton": { "description": "thin end-to-end slice", "files": [...] },
|
|
97
|
+
"atomic_subtasks": [{ "id": "s1", "title": "...", "depends_on": [], "est_context_window": "single", "acceptance_scenario_refs": ["scenario-1"] }]
|
|
98
|
+
},
|
|
99
|
+
"pitch_md": "# Pitch: ...\n\n## Problem\n...",
|
|
100
|
+
"build_readiness_score": { "rubric_pass_count": 5, "critical_blockers": 0 }
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## What NOT to Build
|
|
105
|
+
- Do NOT make this a dynamic autonomous agent -- shaping has a known skeleton (workflow, not agent)
|
|
106
|
+
- Do NOT use tree-of-thoughts -- no cheap partial-goal verification signal in shaping
|
|
107
|
+
- Do NOT build multi-agent role-plays -- single-voice judge with sectioning strictly dominates
|
|
108
|
+
- Do NOT skip the frame gate on "small" tasks -- wrong frame on a small task still wastes the run
|
|
109
|
+
|
|
110
|
+
## Failure Modes and Mitigations
|
|
111
|
+
|
|
112
|
+
| Failure mode | Mitigation |
|
|
113
|
+
|---|---|
|
|
114
|
+
| Mode collapse on diverge step | Verbalized Sampling framing, explicit framing diversity, auto-retry at higher temperature if >70% overlap |
|
|
115
|
+
| Self-preference on judge | Obfuscate authorship by rewriting all candidates into uniform voice; ideally different model family |
|
|
116
|
+
| Verbosity compensation on pitch | Hard max-length on JSON fields; critic checks for vague modifiers without concrete nouns |
|
|
117
|
+
| Hallucinated Context Pack entries | Tool-augment Step 7 with repo grep/AST scan; schema-validate all paths before Step 10 |
|
|
118
|
+
| Over-decomposition | Minimum subtask size = single context window; maximum 8 subtasks per pitch; if more, appetite was wrong |
|
|
119
|
+
| Silent architectural divergence | Include consistency-check sub-task: implementer lists every new file/symbol and justifies why it's not a duplicate |
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Implementation Plan: Late-Bound Goals
|
|
2
|
+
|
|
3
|
+
**Feature**: Default `goalTemplate: "{{$.goal}}"` when no `goal` and no `goalTemplate` configured.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Problem Statement
|
|
8
|
+
|
|
9
|
+
Triggers require a static `goal` in `triggers.yml`. This makes dynamic-goal use cases (PR review, incident response) require either a static placeholder goal or a custom `goalTemplate`. The feature enables: `curl -X POST /webhook/my-trigger -d '{"goal": "review PR #42"}'` without any goal pre-configuration.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Acceptance Criteria
|
|
14
|
+
|
|
15
|
+
1. A trigger with neither `goal` nor `goalTemplate` in `triggers.yml` loads successfully (no `missing_field` error).
|
|
16
|
+
2. When the webhook payload contains a `goal` field, the session is started with that value as the goal.
|
|
17
|
+
3. When the webhook payload has no `goal` field, the session is started with `'Autonomous task'` as the goal, AND a warning is logged to daemon stderr.
|
|
18
|
+
4. Existing triggers with a static `goal` field behave identically to before.
|
|
19
|
+
5. TypeScript compiles without errors (no new `any` or `!` assertions on the new code path).
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Non-Goals
|
|
24
|
+
|
|
25
|
+
- No change to `TriggerDefinition` type (`goal: string` stays required).
|
|
26
|
+
- No change to `trigger-router.ts` interpolation logic.
|
|
27
|
+
- No change to `console-routes.ts` dispatch endpoint.
|
|
28
|
+
- No `goalSource` discriminant on `TriggerDefinition` (future enhancement).
|
|
29
|
+
- No configurable fallback string other than `'Autonomous task'`.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Philosophy-Driven Constraints
|
|
34
|
+
|
|
35
|
+
- Validate at boundaries: injection must happen in `trigger-store.ts`, not in `trigger-router.ts`.
|
|
36
|
+
- Make illegal states unrepresentable: `TriggerDefinition.goal` must always be a valid `string`.
|
|
37
|
+
- YAGNI: minimal change -- named constant + ~8 lines + tests.
|
|
38
|
+
- Document why: WHY comment required above the injection block.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Invariants
|
|
43
|
+
|
|
44
|
+
1. `TriggerDefinition.goal: string` -- never `undefined` or empty string after parse.
|
|
45
|
+
2. `trigger.goal` is only used as a display/fallback value, never as a routing key.
|
|
46
|
+
3. The interpolated goal (from payload or fallback) flows to the session, not the sentinel.
|
|
47
|
+
4. Injection only fires when both `raw.goal` and `raw.goalTemplate` are absent.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Selected Approach
|
|
52
|
+
|
|
53
|
+
**Candidate A: Parse-time sentinel injection in `validateAndResolveTrigger()`**
|
|
54
|
+
|
|
55
|
+
1. Remove `'goal'` from `requiredStringFields`.
|
|
56
|
+
2. After the required-fields loop, add a block:
|
|
57
|
+
- If `raw.goal` absent AND `raw.goalTemplate` absent: set `resolvedGoal = LATE_BOUND_GOAL_SENTINEL` and `resolvedGoalTemplate = '{{$.goal}}'`. Log an INFO message.
|
|
58
|
+
- If `raw.goal` absent AND `raw.goalTemplate` present: set `resolvedGoal = LATE_BOUND_GOAL_SENTINEL`.
|
|
59
|
+
- Otherwise: `resolvedGoal = raw.goal!.trim()`.
|
|
60
|
+
3. Replace `goal: raw.goal!.trim()` on line 974 with `goal: resolvedGoal`.
|
|
61
|
+
4. If `resolvedGoalTemplate` was injected, pass it via the `goalTemplate` spread at line 980.
|
|
62
|
+
|
|
63
|
+
**Runner-up**: Candidate B (optional type) -- rejected because it cascades null checks to 3+ files.
|
|
64
|
+
|
|
65
|
+
**Rationale**: Exact match to the `concurrencyMode` default pattern at line 756. Zero downstream impact.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Vertical Slices
|
|
70
|
+
|
|
71
|
+
### Slice 1: Core implementation (~8 lines in trigger-store.ts)
|
|
72
|
+
|
|
73
|
+
**File**: `src/trigger/trigger-store.ts`
|
|
74
|
+
|
|
75
|
+
**Changes**:
|
|
76
|
+
- Add `const LATE_BOUND_GOAL_SENTINEL = 'Autonomous task';` near the top of the validation section.
|
|
77
|
+
- Remove `'goal'` from `requiredStringFields` array.
|
|
78
|
+
- Add injection block with WHY comment + INFO log.
|
|
79
|
+
- Declare `let resolvedGoal: string;` and `let resolvedGoalTemplate: string | undefined;` before the injection block.
|
|
80
|
+
- Replace `goal: raw.goal!.trim()` with `goal: resolvedGoal` in the TriggerDefinition literal.
|
|
81
|
+
- Pass `resolvedGoalTemplate` to the `goalTemplate` spread.
|
|
82
|
+
|
|
83
|
+
**Done when**: TypeScript compiles. `loadTriggerConfig` returns a valid `TriggerDefinition` for a YAML with no `goal` and no `goalTemplate`.
|
|
84
|
+
|
|
85
|
+
### Slice 2: Tests for trigger-store.ts
|
|
86
|
+
|
|
87
|
+
**File**: `tests/unit/trigger-store.test.ts`
|
|
88
|
+
|
|
89
|
+
**New test cases**:
|
|
90
|
+
1. Trigger with no `goal` and no `goalTemplate` loads successfully, has `goal = 'Autonomous task'` and `goalTemplate = '{{$.goal}}'`.
|
|
91
|
+
2. Trigger with no `goal` but an explicit `goalTemplate` loads successfully, has `goal = 'Autonomous task'` and the specified `goalTemplate`.
|
|
92
|
+
3. Trigger with a static `goal` behaves identically to before (regression).
|
|
93
|
+
|
|
94
|
+
**Done when**: All 3 tests pass.
|
|
95
|
+
|
|
96
|
+
### Slice 3: Test for trigger-router.ts integration
|
|
97
|
+
|
|
98
|
+
**File**: `tests/unit/trigger-router.test.ts`
|
|
99
|
+
|
|
100
|
+
**New test case**:
|
|
101
|
+
1. Route a webhook event with payload `{ goal: 'review PR #42' }` to a late-bound trigger. Verify `runWorkflow` is called with `goal = 'review PR #42'`.
|
|
102
|
+
2. Route a webhook event with no `goal` field to a late-bound trigger. Verify `runWorkflow` is called with `goal = 'Autonomous task'`.
|
|
103
|
+
|
|
104
|
+
**Done when**: Both tests pass.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Test Design
|
|
109
|
+
|
|
110
|
+
All tests use the existing `loadTriggerConfig` (pure function, no I/O) and `TriggerRouter` patterns from the test files. No mocks needed for Slice 2. Slice 3 uses `makeFakeRunWorkflow` pattern already in the test file.
|
|
111
|
+
|
|
112
|
+
Run: `npx vitest run tests/unit/trigger-store.test.ts tests/unit/trigger-router.test.ts`
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Risk Register
|
|
117
|
+
|
|
118
|
+
| Risk | Likelihood | Impact | Mitigation |
|
|
119
|
+
|---|---|---|---|
|
|
120
|
+
| Non-null assertion on line 974 missed | Low | Compile error | TypeScript will catch |
|
|
121
|
+
| Slice 3 test fails due to goal not threading through | Low | Test failure | Trace through route() logic |
|
|
122
|
+
| Existing trigger-store tests broken by goal no longer being required | Low | Test failure | Regression test in Slice 2 covers this |
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## PR Packaging
|
|
127
|
+
|
|
128
|
+
Single PR: `feat/late-bound-goals`. All 3 slices in one commit. ~20-30 lines total (source + tests).
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Philosophy Alignment
|
|
133
|
+
|
|
134
|
+
- validate-at-boundaries -> satisfied (injection in trigger-store.ts)
|
|
135
|
+
- make-illegal-states-unrepresentable -> satisfied (goal: string always holds)
|
|
136
|
+
- YAGNI -> satisfied (~8 source lines)
|
|
137
|
+
- document-why -> satisfied (WHY comment in injection block)
|
|
138
|
+
- prefer-explicit-domain-types -> tension (sentinel is stringly-typed; acceptable -- future `goalSource` discriminant tracked)
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Plan Confidence
|
|
143
|
+
|
|
144
|
+
- unresolvedUnknownCount: 0
|
|
145
|
+
- planConfidenceBand: High
|
|
146
|
+
- estimatedPRCount: 1
|
|
147
|
+
- followUpTickets: "Add goalSource discriminant to TriggerDefinition for console UI enhancement"
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Design Review Findings: Late-Bound Goals
|
|
2
|
+
|
|
3
|
+
**Feature**: Default `goalTemplate: "{{$.goal}}"` when no static `goal` and no `goalTemplate` is configured.
|
|
4
|
+
**Design**: Candidate A -- parse-time sentinel injection in `validateAndResolveTrigger()`.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Tradeoff Review
|
|
9
|
+
|
|
10
|
+
| Tradeoff | Conditions for failure | Status |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| Synthetic sentinel `'Autonomous task'` in `goal` field | If `trigger.goal` were used for routing/dedup (not display only). Verified: only used in `interpolateGoalTemplate` fallback and logs. | Acceptable |
|
|
13
|
+
| No `console.warn` on load for late-bound triggers | Operators may not discover this feature. Mitigated by adding a `console.log` at daemon startup when injection fires. | Acceptable with INFO log |
|
|
14
|
+
| Sentinel shows in console trigger list | Cosmetic only. Sessions get the interpolated goal from payload, not the sentinel. | Acceptable |
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Failure Mode Review
|
|
19
|
+
|
|
20
|
+
| Failure Mode | Handled? | Missing Mitigation | Risk |
|
|
21
|
+
|---|---|---|---|
|
|
22
|
+
| Console UI shows 'Autonomous task' | Yes -- cosmetic, not functional | Future: show `goalTemplate` in trigger list | Low |
|
|
23
|
+
| Payload has no `goal` field | Yes -- `interpolateGoalTemplate` already warns (line 127) | None needed | Low |
|
|
24
|
+
| Non-null assertion `raw.goal!.trim()` panics | Handled by replacing with `resolvedGoal: string` | TypeScript compile-time check | Low |
|
|
25
|
+
| `goalTemplate`-only trigger with no `goal` | Handled -- inject sentinel for this case too | None needed | Low |
|
|
26
|
+
|
|
27
|
+
No high-risk failure modes.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Runner-Up / Simpler Alternative Review
|
|
32
|
+
|
|
33
|
+
- **Candidate B** (optional type): nothing worth borrowing. Cascades null checks to 3+ files.
|
|
34
|
+
- **Simpler variant** (only inject when both absent, skip goalTemplate-only case): does NOT satisfy acceptance criteria -- existing triggers using only `goalTemplate` would still fail with `missing_field`.
|
|
35
|
+
- **Hybrid improvement**: extract `'Autonomous task'` to a named constant `LATE_BOUND_GOAL_SENTINEL` for searchability. Low cost, worth including.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Philosophy Alignment
|
|
40
|
+
|
|
41
|
+
**Satisfied**: validate-at-boundaries, make-illegal-states-unrepresentable, immutability, YAGNI, determinism.
|
|
42
|
+
|
|
43
|
+
**Under tension**: prefer-explicit-domain-types -- a `goalSource` discriminant would be more type-honest. Acceptable: documented as a future enhancement, not blocking this fix.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Findings
|
|
48
|
+
|
|
49
|
+
### Yellow: Named constant for sentinel
|
|
50
|
+
|
|
51
|
+
**Severity**: Yellow (code quality, not correctness)
|
|
52
|
+
**Finding**: `'Autonomous task'` hardcoded inline is harder to search and understand than a named constant.
|
|
53
|
+
**Fix**: `const LATE_BOUND_GOAL_SENTINEL = 'Autonomous task';` at module scope.
|
|
54
|
+
|
|
55
|
+
### Yellow: Missing INFO log at injection time
|
|
56
|
+
|
|
57
|
+
**Severity**: Yellow (operator experience)
|
|
58
|
+
**Finding**: Operators who configure a trigger without `goal`/`goalTemplate` get silent late-bound behavior. A `console.log` at injection time helps discoverability.
|
|
59
|
+
**Fix**: `console.log('[TriggerStore] Trigger "%s" has no static goal or goalTemplate -- defaulting to goalTemplate: "{{$.goal}}" (goal from payload)', rawId);`
|
|
60
|
+
|
|
61
|
+
### Yellow: Missing WHY comment in code
|
|
62
|
+
|
|
63
|
+
**Severity**: Yellow (maintainability)
|
|
64
|
+
**Finding**: The injection block needs a comment explaining the late-bound goal contract and the sentinel value.
|
|
65
|
+
|
|
66
|
+
No Red or Orange findings.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Recommended Revisions
|
|
71
|
+
|
|
72
|
+
1. Extract sentinel to named constant: `const LATE_BOUND_GOAL_SENTINEL = 'Autonomous task';`
|
|
73
|
+
2. Add `console.log` when injecting the default.
|
|
74
|
+
3. Add WHY comment above the injection block.
|
|
75
|
+
4. Update `docs/discovery/late-bound-goals.md` with these minor additions.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Residual Concerns
|
|
80
|
+
|
|
81
|
+
1. **Console UI display**: Late-bound triggers show 'Autonomous task' in the trigger list until a UI enhancement adds `goalTemplate` as a separate display field. Documented as a known limitation, not a bug.
|
|
82
|
+
2. **Future discriminant**: A `goalSource: 'static' | 'payload'` field on `TriggerDefinition` would be the cleaner long-term design. Filed in backlog. This change does not block it.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Design Candidates: Late-Bound Goals
|
|
2
|
+
|
|
3
|
+
**Feature**: Default `goalTemplate: "{{$.goal}}"` when no static `goal` and no `goalTemplate` is configured, enabling dynamic goals from the webhook payload.
|
|
4
|
+
|
|
5
|
+
**Date**: 2026-04-18
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Problem Understanding
|
|
10
|
+
|
|
11
|
+
### Core Tensions
|
|
12
|
+
|
|
13
|
+
1. **Type safety vs relaxed validation**: `TriggerDefinition.goal: string` is a required field by type. Relaxing the YAML validation (allowing `goal` to be absent) must not violate the type. Resolved by injecting a sentinel.
|
|
14
|
+
|
|
15
|
+
2. **Parse-time vs dispatch-time defaulting**: Injecting at parse time (trigger-store.ts) keeps the router clean. Injecting at dispatch time (trigger-router.ts) would require making `goal` optional in the type or adding defensive null checks everywhere `trigger.goal` is used.
|
|
16
|
+
|
|
17
|
+
3. **Sentinel vs empty string**: The fallback goal for "payload had no goal field" must be human-readable in logs and the console UI. `'Autonomous task'` is the minimum useful value.
|
|
18
|
+
|
|
19
|
+
4. **Backward compat**: Existing triggers in `triggers.yml` have a static `goal`. The change must be zero-impact for them.
|
|
20
|
+
|
|
21
|
+
### Likely Seam
|
|
22
|
+
|
|
23
|
+
`validateAndResolveTrigger()` in `src/trigger/trigger-store.ts` -- specifically between the `requiredStringFields` check (line 553) and the `goalTemplate` assembly (line 654). The new logic fits naturally there: after required non-goal fields are checked, handle the `goal`/`goalTemplate` pair together.
|
|
24
|
+
|
|
25
|
+
### What Makes It Hard
|
|
26
|
+
|
|
27
|
+
Technically simple, but the non-null assertion `goal: raw.goal!.trim()` on line 974 would panic if `goal` is removed from `requiredStringFields` without also ensuring injection. A junior developer might instead make `goal` optional in `TriggerDefinition`, cascading null checks across all consumers.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Philosophy Constraints
|
|
32
|
+
|
|
33
|
+
- **Validate at boundaries, trust inside** (CLAUDE.md): inject the default at parse time in trigger-store.ts, not at dispatch time in trigger-router.ts.
|
|
34
|
+
- **Make illegal states unrepresentable** (CLAUDE.md): `TriggerDefinition.goal` must always be a valid `string`. Sentinel injection preserves this.
|
|
35
|
+
- **YAGNI with discipline** (CLAUDE.md): `'Autonomous task'` is the minimum viable fallback. Do not add configurable fallback strings.
|
|
36
|
+
- **Repo pattern** (trigger-store.ts line 756): `concurrencyMode` defaults to `'serial'` at parse time -- exactly the pattern to follow.
|
|
37
|
+
|
|
38
|
+
No philosophy conflicts detected.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Impact Surface
|
|
43
|
+
|
|
44
|
+
- `src/trigger/trigger-store.ts` line 553: `goal` removed from `requiredStringFields`
|
|
45
|
+
- `src/trigger/trigger-store.ts` line 974: `goal: raw.goal!.trim()` must become `goal: resolvedGoal`
|
|
46
|
+
- `src/trigger/trigger-router.ts`: no change -- `interpolateGoalTemplate` already handles `{{$.goal}}` and falls back to `staticGoal`
|
|
47
|
+
- `src/trigger/types.ts`: no change -- `goal: string` stays required
|
|
48
|
+
- `src/v2/usecases/console-routes.ts` line 677: returns `t.goal` in trigger list -- will show `'Autonomous task'` for late-bound triggers (acceptable UX)
|
|
49
|
+
- `tests/unit/trigger-store.test.ts`: add 2-3 new test cases
|
|
50
|
+
- `tests/unit/trigger-router.test.ts`: add 1 test for `{{$.goal}}` dispatch
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Candidates
|
|
55
|
+
|
|
56
|
+
### Candidate A: Parse-time sentinel injection (recommended)
|
|
57
|
+
|
|
58
|
+
**Summary**: Remove `goal` from `requiredStringFields`. After the required checks, add a block: if `raw.goal` absent AND `raw.goalTemplate` absent, set `resolvedGoal = 'Autonomous task'` and inject `goalTemplate = '{{$.goal}}'`; if `raw.goal` absent AND `raw.goalTemplate` present, set `resolvedGoal = 'Autonomous task'`; otherwise use `raw.goal.trim()`. Replace `raw.goal!.trim()` on line 974 with `resolvedGoal`.
|
|
59
|
+
|
|
60
|
+
- **Tensions resolved**: type safety (sentinel ensures `string`), backward compat, parse-time defaulting
|
|
61
|
+
- **Tension accepted**: sentinel is synthetic -- `'Autonomous task'` shows in console for unconfigured triggers
|
|
62
|
+
- **Boundary**: `validateAndResolveTrigger()` in trigger-store.ts -- the correct validation boundary
|
|
63
|
+
- **Why this boundary**: all other defaults (`concurrencyMode`, `referenceUrls`, `agentConfig`) are applied here, not in the router
|
|
64
|
+
- **Failure mode**: operators reading the console trigger list see `'Autonomous task'` as the goal for late-bound triggers; acceptable since the real goal comes from the payload
|
|
65
|
+
- **Repo pattern**: follows `concurrencyMode` default exactly
|
|
66
|
+
- **Gains**: ~8 lines changed, zero downstream changes, fully backward-compatible
|
|
67
|
+
- **Losses**: nothing material
|
|
68
|
+
- **Scope**: best-fit
|
|
69
|
+
- **Philosophy**: honors validate-at-boundaries, make-illegal-states-unrepresentable, YAGNI
|
|
70
|
+
|
|
71
|
+
### Candidate B: Make `goal` optional in TriggerDefinition
|
|
72
|
+
|
|
73
|
+
**Summary**: Change `readonly goal: string` to `readonly goal?: string` in `TriggerDefinition`. Remove `goal` from `requiredStringFields`. Update trigger-router.ts and console-routes.ts to handle `goal | undefined`.
|
|
74
|
+
|
|
75
|
+
- **Tensions resolved**: type honesty -- `goal` really is absent for late-bound triggers
|
|
76
|
+
- **Tensions accepted**: type change cascades -- every consumer of `trigger.goal` must handle `undefined`
|
|
77
|
+
- **Boundary**: types.ts (type contract) + all consumers
|
|
78
|
+
- **Failure mode**: cascading null checks; `trigger.goal ?? ''` as the router fallback is worse UX than `'Autonomous task'`
|
|
79
|
+
- **Repo pattern**: departs from existing pattern where all `TriggerDefinition` fields are either required or explicitly optional
|
|
80
|
+
- **Gains**: type honesty
|
|
81
|
+
- **Losses**: type safety guarantee; cascading changes across router, console-routes, tests
|
|
82
|
+
- **Scope**: too broad -- type change touches 3+ files unnecessarily
|
|
83
|
+
- **Philosophy**: conflicts with make-illegal-states-unrepresentable, YAGNI
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Comparison and Recommendation
|
|
88
|
+
|
|
89
|
+
| Dimension | Candidate A | Candidate B |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| Type safety | Full -- `goal: string` always holds | Weakened -- `goal?: string` propagates |
|
|
92
|
+
| Files changed | 1 source + tests | 3+ source files + tests |
|
|
93
|
+
| Backward compat | 100% | 100% but requires null guards |
|
|
94
|
+
| Repo pattern fit | Exact (concurrencyMode) | Departs from established pattern |
|
|
95
|
+
| Reversibility | Trivial | Requires re-tightening type across consumers |
|
|
96
|
+
|
|
97
|
+
**Recommendation: Candidate A.** Resolves all tensions at the correct boundary with the minimum change surface. Exact match to the established `concurrencyMode` default pattern. Zero downstream impact.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Self-Critique
|
|
102
|
+
|
|
103
|
+
**Strongest counter-argument**: Candidate B is more type-honest. The sentinel `'Autonomous task'` is a lie -- the goal is really absent at config time. Operators reading the console trigger list may be confused.
|
|
104
|
+
|
|
105
|
+
**Why it still loses**: The console trigger list already shows `goalTemplate` as a separate field (if set). For late-bound triggers, the UI can be enhanced later to show `goalTemplate` instead of `goal` -- but that is a separate concern. The type lie is contained to display; routing behavior is correct.
|
|
106
|
+
|
|
107
|
+
**Narrower option**: Only inject `goalTemplate` without a sentinel `goal`, using a conditional on the object literal. But `TriggerDefinition.goal: string` is required, so this path leads back to Candidate B (type change required).
|
|
108
|
+
|
|
109
|
+
**Broader option**: Add `goalSource: 'static' | 'template' | 'payload'` discriminant to `TriggerDefinition` for explicit tracking. Useful for a future console UI enhancement, but out of scope for this 10-line fix.
|
|
110
|
+
|
|
111
|
+
**Invalidating assumption**: If `trigger.goal` is used anywhere to make routing decisions (not just as a display value or fallback), the sentinel could cause incorrect behavior. Verified: `trigger.goal` is only used in `interpolateGoalTemplate` as the `staticGoal` fallback and in log messages. Safe.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Open Questions for the Main Agent
|
|
116
|
+
|
|
117
|
+
1. Should the sentinel string be `'Autonomous task'` or something else (e.g. `'No goal configured'`, `'(goal from payload)'`)? The backlog does not specify. `'Autonomous task'` matches the product language used elsewhere.
|
|
118
|
+
2. Should a `console.warn` be emitted when both `goal` and `goalTemplate` are absent, so operators know the trigger is using late-bound goals? This would help debugging misconfigured triggers.
|