@exaudeus/workrail 1.1.0 → 1.3.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 (102) hide show
  1. package/dist/application/services/validation-engine.d.ts +15 -1
  2. package/dist/application/services/validation-engine.js +81 -51
  3. package/dist/application/services/workflow-compiler.d.ts +3 -0
  4. package/dist/application/services/workflow-compiler.js +26 -0
  5. package/dist/application/services/workflow-interpreter.d.ts +4 -1
  6. package/dist/application/services/workflow-interpreter.js +85 -24
  7. package/dist/application/services/workflow-service.js +19 -2
  8. package/dist/manifest.json +299 -83
  9. package/dist/mcp/handlers/shared/with-timeout.d.ts +1 -0
  10. package/dist/mcp/handlers/shared/with-timeout.js +9 -0
  11. package/dist/mcp/handlers/v2-advance-core.d.ts +45 -0
  12. package/dist/mcp/handlers/v2-advance-core.js +433 -0
  13. package/dist/mcp/handlers/v2-context-budget.d.ts +17 -0
  14. package/dist/mcp/handlers/v2-context-budget.js +169 -0
  15. package/dist/mcp/handlers/v2-error-mapping.d.ts +34 -0
  16. package/dist/mcp/handlers/v2-error-mapping.js +125 -0
  17. package/dist/mcp/handlers/v2-execution-helpers.js +4 -1
  18. package/dist/mcp/handlers/v2-execution.d.ts +19 -0
  19. package/dist/mcp/handlers/v2-execution.js +278 -765
  20. package/dist/mcp/handlers/v2-state-conversion.d.ts +40 -0
  21. package/dist/mcp/handlers/v2-state-conversion.js +132 -0
  22. package/dist/mcp/handlers/v2-token-ops.d.ts +33 -0
  23. package/dist/mcp/handlers/v2-token-ops.js +62 -0
  24. package/dist/mcp/handlers/v2-workflow.js +3 -8
  25. package/dist/mcp/handlers/workflow.js +4 -11
  26. package/dist/mcp/output-schemas.d.ts +272 -20
  27. package/dist/mcp/output-schemas.js +20 -1
  28. package/dist/mcp/server.js +2 -0
  29. package/dist/mcp/tool-descriptions.js +67 -51
  30. package/dist/mcp/types.d.ts +2 -0
  31. package/dist/mcp/v2/tools.d.ts +23 -2
  32. package/dist/mcp/v2/tools.js +35 -4
  33. package/dist/types/workflow-definition.d.ts +19 -0
  34. package/dist/v2/durable-core/constants.d.ts +2 -0
  35. package/dist/v2/durable-core/constants.js +3 -1
  36. package/dist/v2/durable-core/domain/ack-advance-append-plan.d.ts +1 -0
  37. package/dist/v2/durable-core/domain/ack-advance-append-plan.js +11 -1
  38. package/dist/v2/durable-core/domain/artifact-contract-validator.d.ts +31 -0
  39. package/dist/v2/durable-core/domain/artifact-contract-validator.js +98 -0
  40. package/dist/v2/durable-core/domain/blocked-node-builder.d.ts +20 -0
  41. package/dist/v2/durable-core/domain/blocked-node-builder.js +94 -0
  42. package/dist/v2/durable-core/domain/bundle-builder.d.ts +27 -0
  43. package/dist/v2/durable-core/domain/bundle-builder.js +93 -0
  44. package/dist/v2/durable-core/domain/bundle-validator.d.ts +4 -0
  45. package/dist/v2/durable-core/domain/bundle-validator.js +129 -0
  46. package/dist/v2/durable-core/domain/decision-trace-builder.d.ts +33 -0
  47. package/dist/v2/durable-core/domain/decision-trace-builder.js +92 -0
  48. package/dist/v2/durable-core/domain/function-definition-expander.js +1 -1
  49. package/dist/v2/durable-core/domain/loop-control-evaluator.d.ts +13 -0
  50. package/dist/v2/durable-core/domain/loop-control-evaluator.js +24 -0
  51. package/dist/v2/durable-core/domain/observation-builder.d.ts +16 -0
  52. package/dist/v2/durable-core/domain/observation-builder.js +42 -0
  53. package/dist/v2/durable-core/domain/outputs.js +2 -2
  54. package/dist/v2/durable-core/domain/prompt-renderer.js +37 -4
  55. package/dist/v2/durable-core/domain/reason-model.d.ts +3 -1
  56. package/dist/v2/durable-core/domain/reason-model.js +16 -3
  57. package/dist/v2/durable-core/domain/recommendation-warnings.d.ts +20 -0
  58. package/dist/v2/durable-core/domain/recommendation-warnings.js +35 -0
  59. package/dist/v2/durable-core/domain/risk-policy-guardrails.d.ts +15 -0
  60. package/dist/v2/durable-core/domain/risk-policy-guardrails.js +78 -0
  61. package/dist/v2/durable-core/domain/validation-criteria-validator.d.ts +8 -0
  62. package/dist/v2/durable-core/domain/validation-criteria-validator.js +30 -0
  63. package/dist/v2/durable-core/domain/validation-event-builder.d.ts +26 -0
  64. package/dist/v2/durable-core/domain/validation-event-builder.js +100 -0
  65. package/dist/v2/durable-core/domain/validation-loader.d.ts +11 -0
  66. package/dist/v2/durable-core/domain/validation-loader.js +21 -0
  67. package/dist/v2/durable-core/projections/snapshot-state.js +1 -1
  68. package/dist/v2/durable-core/schemas/artifacts/index.d.ts +4 -0
  69. package/dist/v2/durable-core/schemas/artifacts/index.js +18 -0
  70. package/dist/v2/durable-core/schemas/artifacts/loop-control.d.ts +66 -0
  71. package/dist/v2/durable-core/schemas/artifacts/loop-control.js +47 -0
  72. package/dist/v2/durable-core/schemas/execution-snapshot/blocked-snapshot.d.ts +598 -0
  73. package/dist/v2/durable-core/schemas/execution-snapshot/blocked-snapshot.js +89 -0
  74. package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.d.ts +3801 -57
  75. package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.js +12 -2
  76. package/dist/v2/durable-core/schemas/execution-snapshot/index.d.ts +2 -0
  77. package/dist/v2/durable-core/schemas/execution-snapshot/index.js +3 -1
  78. package/dist/v2/durable-core/schemas/export-bundle/index.d.ts +4682 -758
  79. package/dist/v2/durable-core/schemas/session/events.d.ts +158 -45
  80. package/dist/v2/durable-core/schemas/session/events.js +8 -1
  81. package/dist/v2/durable-core/schemas/session/validation-event.d.ts +68 -0
  82. package/dist/v2/durable-core/schemas/session/validation-event.js +52 -0
  83. package/dist/v2/durable-core/tokens/payloads.d.ts +16 -16
  84. package/dist/v2/infra/local/workspace-anchor/index.d.ts +9 -0
  85. package/dist/v2/infra/local/workspace-anchor/index.js +44 -0
  86. package/dist/v2/ports/workspace-anchor.port.d.ts +18 -0
  87. package/dist/v2/ports/workspace-anchor.port.js +2 -0
  88. package/dist/v2/projections/artifacts.d.ts +22 -0
  89. package/dist/v2/projections/artifacts.js +53 -0
  90. package/dist/v2/projections/projection-timing.d.ts +13 -0
  91. package/dist/v2/projections/projection-timing.js +23 -0
  92. package/dist/v2/projections/run-dag.d.ts +1 -1
  93. package/dist/v2/projections/run-status-signals.js +3 -8
  94. package/dist/v2/usecases/export-session.d.ts +47 -0
  95. package/dist/v2/usecases/export-session.js +92 -0
  96. package/dist/v2/usecases/import-session.d.ts +34 -0
  97. package/dist/v2/usecases/import-session.js +57 -0
  98. package/package.json +1 -1
  99. package/spec/workflow.schema.json +60 -0
  100. package/spec/workflow.schema.v0.0.1.json +38 -0
  101. package/workflows/coding-task-workflow-agentic.json +11 -18
  102. package/workflows/test-artifact-loop-control.json +59 -0
@@ -64,48 +64,54 @@ Remember: inspecting is read-only. Call start_workflow when ready to begin.`,
64
64
  The workflow represents the user's plan for this task. Each step will tell you exactly what to do. Your job is to execute each step's instructions and report back.
65
65
 
66
66
  What you'll receive:
67
- - stateToken: Your session handle (save this - you'll need it for every continue_workflow call)
68
- - ackToken: Your completion acknowledgement token (include when advancing to next step)
69
- - pending: The first step's instructions (in pending.prompt)
70
- - nextIntent: What to do next (usually "perform_pending_then_continue")
67
+ - pending.prompt: The first step's instructions — read this and do what it says
68
+ - nextCall: A pre-built template for your next continue_workflow call (copy its params when done)
69
+ - nextIntent: How to approach this step (usually "perform_pending_then_continue")
71
70
  - preferences: Execution mode settings (autonomy level, risk policy)
72
71
 
73
72
  What to do:
74
- 1. Execute the pending step exactly as its prompt describes
75
- 2. Call continue_workflow with stateToken + ackToken + your output
76
- 3. Don't predict what comes next - the workflow will tell you
73
+ 1. Read pending.prompt and execute the step exactly as described
74
+ 2. When done, call continue_workflow using the params from nextCall (it has intent, stateToken, ackToken pre-filled)
75
+ 3. Optionally add output.notesMarkdown summarizing your work
76
+ 4. Don't predict what comes next - the workflow will tell you
77
77
 
78
78
  Context auto-loads: If you provide context at start, WorkRail remembers it. On future continue_workflow calls, only pass context if you have NEW information to add.`,
79
79
  continue_workflow: `Get the next step in the workflow (WorkRail v2, feature-flagged).
80
80
 
81
- This is an interactive conversation with state. Each call either:
82
- - Advances to the next step (when you include ackToken)
83
- - Recovers the current step (when you omit ackToken)
81
+ QUICK START How to call back after completing a step:
82
+ Copy the params from the "nextCall" field of the previous response. It has intent, stateToken, and ackToken pre-filled. Just add your output if desired.
84
83
 
85
- How to use:
86
- - WITH ackToken: "I completed the pending step, here's my output. What's next?"
87
- WorkRail advances to next step and returns it
88
-
89
- - WITHOUT ackToken: "Remind me what the current step is"
90
- WorkRail returns the same pending step (no advancement, useful after rewinds)
84
+ Two modes — set intent explicitly:
85
+
86
+ ADVANCE (intent: "advance"):
87
+ - "I completed the current step; give me the next one"
88
+ - Requires: intent + stateToken + ackToken (all provided in nextCall.params)
89
+ - Optional: output (your work summary), context (if facts changed)
90
+ - Result: WorkRail advances to next step and returns it
91
+
92
+ REHYDRATE (intent: "rehydrate"):
93
+ - "Remind me what the current step is" (after rewind or lost context)
94
+ - Requires: intent + stateToken
95
+ - Do NOT include ackToken or output
96
+ - Result: Same pending step returned; no advancement; side-effect-free
91
97
 
92
- IMPORTANT - Don't predict next steps:
93
- After completing a step, don't ask "should we move to implementation?" or propose next steps.
94
- Just call continue_workflow - the workflow decides what's actually next (might be validation, review, or something you didn't expect).
98
+ Reading the response:
99
+ - pending.prompt: The step's instructions what to do
100
+ - nextCall: Pre-built template for your next call how to proceed when done (null if workflow complete)
101
+ - nextIntent: How to approach this step — execute it, confirm first, or continue working on it
95
102
 
96
- nextIntent tells you what to do:
97
- - "perform_pending_then_continue": Execute this step immediately, then call continue_workflow again
103
+ nextIntent values:
104
+ - "perform_pending_then_continue": Execute this step, then use nextCall to advance
98
105
  - "await_user_confirmation": Auto-confirm and proceed (you don't need to wait for user in agent mode)
99
- - "rehydrate_only": You just recovered state; continue working on the current step
100
- - "complete": Workflow finished; no more steps
106
+ - "rehydrate_only": You just recovered state; continue working on the current step, then use nextCall
107
+ - "complete": Workflow finished; nextCall is null
101
108
 
102
109
  Parameters:
103
- - stateToken (required): Your session handle from start_workflow or previous continue_workflow
104
- - ackToken (optional): Include to advance; omit to rehydrate
105
- - context (optional): NEW facts only (e.g., user provided a branch name). Omit if unchanged - WorkRail auto-loads previous context
106
- - output.notesMarkdown (optional): Fresh summary of THIS step only - never accumulated
107
- Example WRONG: "Phase 0: planning. Phase 1: implementation."
108
- Example RIGHT: "Implemented user authentication with OAuth2; added 3 endpoints."
110
+ - intent (required): "advance" or "rehydrate"
111
+ - stateToken (required): From nextCall.params or previous response
112
+ - ackToken (required for advance): From nextCall.params or previous response
113
+ - context (optional): NEW facts only. Omit if unchanged - WorkRail auto-loads previous context
114
+ - output.notesMarkdown (optional, advance only): Fresh summary of THIS step only - never accumulated
109
115
 
110
116
  The workflow is the user's structured instructions. Follow each step exactly as described.`,
111
117
  },
@@ -183,17 +189,17 @@ This is read-only. Call start_workflow when ready to commit to following the wor
183
189
  The workflow is the USER'S VOICE expressing their plan for this task. Each step is a DIRECT INSTRUCTION from the user (or workflow author representing user intent). You MUST execute each step exactly as specified.
184
190
 
185
191
  What you receive:
186
- - stateToken: Your session identifier - round-trip this UNCHANGED in every continue_workflow call
187
- - ackToken: Your step completion token - include when advancing to next step
188
- - pending.prompt: The user's first instruction for you
189
- - nextIntent: Prescriptive directive for what to do next
192
+ - pending.prompt: The user's first instruction execute this EXACTLY as described
193
+ - nextCall: Pre-built template for your next continue_workflow call copy its params when done
194
+ - nextIntent: How to approach this step (execute, confirm, etc.)
190
195
  - preferences: User's execution mode preferences (autonomy, risk tolerance)
191
196
 
192
197
  REQUIRED BEHAVIOR:
193
198
  1. Execute the pending step EXACTLY as the prompt describes
194
- 2. Call continue_workflow after completing each step (don't predict or skip ahead)
195
- 3. Round-trip tokens UNCHANGED (don't decode, inspect, or modify them)
196
- 4. Follow the workflow to completion - don't improvise alternative approaches
199
+ 2. When done, call continue_workflow using the params from nextCall (intent, stateToken, ackToken are pre-filled)
200
+ 3. Optionally add output.notesMarkdown summarizing your work
201
+ 4. Round-trip tokens UNCHANGED (don't decode, inspect, or modify them)
202
+ 5. Follow the workflow to completion - don't improvise alternative approaches
197
203
 
198
204
  Context handling:
199
205
  - Pass context at start to establish baseline facts
@@ -203,38 +209,48 @@ Context handling:
203
209
 
204
210
  The workflow represents the USER'S PLAN. The pending step returned is a DIRECT INSTRUCTION you MUST follow.
205
211
 
206
- Two modes of operation:
212
+ HOW TO CALL — Copy nextCall.params from the previous response. It has intent, stateToken, and ackToken pre-filled. Add output if desired.
207
213
 
208
- ADVANCEMENT (with ackToken):
214
+ Two modes — set intent explicitly:
215
+
216
+ ADVANCE (intent: "advance"):
209
217
  - Purpose: "I completed the current step; give me the next instruction"
210
- - Include: stateToken + ackToken + output
218
+ - Requires: intent + stateToken + ackToken (all in nextCall.params from previous response)
219
+ - Optional: output (your work summary), context (if facts changed)
211
220
  - Result: WorkRail advances to next step
212
221
  - Idempotent: Safe to retry with same tokens if unsure
213
222
 
214
- REHYDRATION (without ackToken):
223
+ REHYDRATE (intent: "rehydrate"):
215
224
  - Purpose: "Remind me what the current step is" (after rewind/lost context)
216
- - Include: stateToken only (no ackToken, no output)
225
+ - Requires: intent + stateToken only
226
+ - Do NOT include ackToken or output
217
227
  - Result: Same pending step returned; no advancement
218
228
  - Side-effect-free: No durable writes; pure state recovery
219
229
 
220
230
  REQUIRED BEHAVIOR:
221
231
  1. Execute the step EXACTLY as specified in pending.prompt
222
- 2. Do NOT predict what comes next - call continue_workflow and the workflow will tell you
223
- 3. Do NOT skip steps, combine steps, or improvise your own approach
224
- 4. Do NOT ask "should we move to implementation?" - just call continue_workflow
232
+ 2. When done, call continue_workflow using nextCall.params do NOT construct params manually
233
+ 3. Do NOT predict what comes next - call continue_workflow and the workflow will tell you
234
+ 4. Do NOT skip steps, combine steps, or improvise your own approach
235
+
236
+ Reading the response:
237
+ - pending.prompt: The step's instructions — WHAT to do
238
+ - nextCall: Pre-built call template — HOW to proceed when done (null if complete)
239
+ - nextIntent: Step disposition — HOW to approach this step
225
240
 
226
241
  nextIntent is PRESCRIPTIVE (not advisory):
227
- - "perform_pending_then_continue": Execute this step, then call continue_workflow immediately
242
+ - "perform_pending_then_continue": Execute this step, then use nextCall to advance
228
243
  - "await_user_confirmation": Auto-confirm and proceed (workflow represents user's intent)
229
- - "rehydrate_only": State recovery occurred; continue working on current step
230
- - "complete": Workflow finished; no further calls needed
244
+ - "rehydrate_only": State recovery occurred; continue working on current step, then use nextCall
245
+ - "complete": Workflow finished; nextCall is null
231
246
 
232
247
  Parameters:
233
- - stateToken (required): Round-trip unchanged from previous response
234
- - ackToken (optional): Include to ADVANCE, omit to REHYDRATE
248
+ - intent (required): "advance" or "rehydrate"
249
+ - stateToken (required): From nextCall.params or previous response
250
+ - ackToken (required for advance): From nextCall.params or previous response
235
251
  - context (optional): NEW facts only (auto-merges with previous). Omit if unchanged
236
- - output.notesMarkdown (optional): Summary of THIS step only (fresh, never accumulated)
252
+ - output.notesMarkdown (optional, advance only): Summary of THIS step only (fresh, never accumulated)
237
253
 
238
- The workflow is the user's structured will. Follow it exactly - it may validate, loop, or branch in ways you don't predict.`,
254
+ The workflow is the user's structured will. Follow it exactly it may validate, loop, or branch in ways you don't predict.`,
239
255
  },
240
256
  };
@@ -12,6 +12,7 @@ import type { CryptoPortV2 } from '../v2/durable-core/canonical/hashing.js';
12
12
  import type { IdFactoryV2 } from '../v2/infra/local/id-factory/index.js';
13
13
  import type { JsonValue } from './output-schemas.js';
14
14
  import type { TokenCodecPorts } from '../v2/durable-core/tokens/token-codec-ports.js';
15
+ import type { WorkspaceAnchorPortV2 } from '../v2/ports/workspace-anchor.port.js';
15
16
  export interface SessionHealthDetails {
16
17
  readonly health: SessionHealthV2;
17
18
  }
@@ -51,6 +52,7 @@ export interface V2Dependencies {
51
52
  readonly crypto: CryptoPortV2;
52
53
  readonly idFactory: IdFactoryV2;
53
54
  readonly tokenCodecPorts: TokenCodecPorts;
55
+ readonly workspaceAnchor?: WorkspaceAnchorPortV2;
54
56
  }
55
57
  export interface ToolContext {
56
58
  readonly workflowService: WorkflowService;
@@ -24,7 +24,8 @@ export declare const V2StartWorkflowInput: z.ZodObject<{
24
24
  context?: Record<string, unknown> | undefined;
25
25
  }>;
26
26
  export type V2StartWorkflowInput = z.infer<typeof V2StartWorkflowInput>;
27
- export declare const V2ContinueWorkflowInput: z.ZodObject<{
27
+ export declare const V2ContinueWorkflowInput: z.ZodEffects<z.ZodObject<{
28
+ intent: z.ZodEnum<["advance", "rehydrate"]>;
28
29
  stateToken: z.ZodString;
29
30
  ackToken: z.ZodOptional<z.ZodString>;
30
31
  context: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -38,7 +39,26 @@ export declare const V2ContinueWorkflowInput: z.ZodObject<{
38
39
  notesMarkdown?: string | undefined;
39
40
  artifacts?: unknown[] | undefined;
40
41
  }>>;
41
- }, "strip", z.ZodTypeAny, {
42
+ }, "strict", z.ZodTypeAny, {
43
+ intent: "advance" | "rehydrate";
44
+ stateToken: string;
45
+ context?: Record<string, unknown> | undefined;
46
+ output?: {
47
+ notesMarkdown?: string | undefined;
48
+ artifacts?: unknown[] | undefined;
49
+ } | undefined;
50
+ ackToken?: string | undefined;
51
+ }, {
52
+ intent: "advance" | "rehydrate";
53
+ stateToken: string;
54
+ context?: Record<string, unknown> | undefined;
55
+ output?: {
56
+ notesMarkdown?: string | undefined;
57
+ artifacts?: unknown[] | undefined;
58
+ } | undefined;
59
+ ackToken?: string | undefined;
60
+ }>, {
61
+ intent: "advance" | "rehydrate";
42
62
  stateToken: string;
43
63
  context?: Record<string, unknown> | undefined;
44
64
  output?: {
@@ -47,6 +67,7 @@ export declare const V2ContinueWorkflowInput: z.ZodObject<{
47
67
  } | undefined;
48
68
  ackToken?: string | undefined;
49
69
  }, {
70
+ intent: "advance" | "rehydrate";
50
71
  stateToken: string;
51
72
  context?: Record<string, unknown> | undefined;
52
73
  output?: {
@@ -12,16 +12,47 @@ exports.V2StartWorkflowInput = zod_1.z.object({
12
12
  context: zod_1.z.record(zod_1.z.unknown()).optional().describe('External facts influencing execution (ticketId, branch, constraints). Pass once at start to establish baseline. WorkRail auto-loads context on subsequent continue_workflow calls. Only pass context again if facts have CHANGED (e.g., user provided new information). Do NOT re-pass unchanged values.'),
13
13
  });
14
14
  exports.V2ContinueWorkflowInput = zod_1.z.object({
15
- stateToken: zod_1.z.string().min(1).describe('Your session handle from start_workflow or previous continue_workflow. Pass this in EVERY continue_workflow call to identify your session. Round-trip exactly as received - never decode, inspect, or modify it. This is different from ackToken (which is your completion receipt).'),
16
- ackToken: zod_1.z.string().min(1).optional().describe('Your step completion receipt. Include this to ADVANCE to the next step. OMIT this entirely to REHYDRATE (recover current step after rewind/lost context). Pattern: stateToken identifies WHERE you are; ackToken says "I finished here, next step please."'),
15
+ intent: zod_1.z.enum(['advance', 'rehydrate']).describe('What you want to do. ' +
16
+ '"advance": I completed the current step requires ackToken. ' +
17
+ '"rehydrate": Remind me what the current step is (state recovery after rewind/lost context) — do NOT include ackToken or output.'),
18
+ stateToken: zod_1.z.string().min(1).describe('Your session handle from start_workflow or previous continue_workflow. Pass this in EVERY continue_workflow call to identify your session. Round-trip exactly as received — never decode, inspect, or modify it.'),
19
+ ackToken: zod_1.z.string().min(1).optional().describe('Your step completion receipt. Required when intent is "advance". Must be omitted when intent is "rehydrate".'),
17
20
  context: zod_1.z.record(zod_1.z.unknown()).optional().describe('External facts (only if CHANGED since last call). Omit this entirely if no facts changed. WorkRail auto-merges with previous context. Example: if context={branch:"main"} at start, do NOT re-pass it unless branch changed. Pass only NEW or OVERRIDDEN values.'),
18
21
  output: zod_1.z
19
22
  .object({
20
- notesMarkdown: zod_1.z.string().min(1).optional().describe('Summary of work completed in THIS step only - fresh and specific to this step. Do NOT append previous step notes. WorkRail concatenates notes across steps automatically. WRONG: "Phase 0: planning. Phase 1: implemented." RIGHT: "Implemented OAuth2 with 3 endpoints; added token validation middleware." Aim for ≤10 lines.'),
23
+ notesMarkdown: zod_1.z.string().min(1).optional().describe('Summary of work completed in THIS step only fresh and specific to this step. Do NOT append previous step notes. WorkRail concatenates notes across steps automatically. WRONG: "Phase 0: planning. Phase 1: implemented." RIGHT: "Implemented OAuth2 with 3 endpoints; added token validation middleware." Aim for ≤10 lines.'),
21
24
  artifacts: zod_1.z.array(zod_1.z.unknown()).optional().describe('Optional structured artifacts (schema is workflow/contract-defined)'),
22
25
  })
23
26
  .optional()
24
- .describe('Optional durable output to attach to the current node'),
27
+ .describe('Durable output to attach to the current node. Only valid when intent is "advance".'),
28
+ }).strict().superRefine((data, ctx) => {
29
+ if (data.intent === 'advance' && !data.ackToken) {
30
+ ctx.addIssue({
31
+ code: zod_1.z.ZodIssueCode.custom,
32
+ path: ['ackToken'],
33
+ message: 'intent is "advance" but ackToken is missing. ' +
34
+ 'To advance to the next step, include the ackToken from the previous start_workflow or continue_workflow response. ' +
35
+ 'If you don\'t have an ackToken, set intent to "rehydrate" to recover the current step.',
36
+ });
37
+ }
38
+ if (data.intent === 'rehydrate' && data.ackToken) {
39
+ ctx.addIssue({
40
+ code: zod_1.z.ZodIssueCode.custom,
41
+ path: ['ackToken'],
42
+ message: 'intent is "rehydrate" but ackToken was provided. ' +
43
+ 'Rehydration recovers the current step without advancing — it does not accept ackToken. ' +
44
+ 'To advance, set intent to "advance". To rehydrate, remove ackToken.',
45
+ });
46
+ }
47
+ if (data.intent === 'rehydrate' && data.output) {
48
+ ctx.addIssue({
49
+ code: zod_1.z.ZodIssueCode.custom,
50
+ path: ['output'],
51
+ message: 'intent is "rehydrate" but output was provided. ' +
52
+ 'Rehydration is read-only state recovery — it does not accept output. ' +
53
+ 'To submit output and advance, set intent to "advance" and include ackToken.',
54
+ });
55
+ }
25
56
  });
26
57
  exports.V2_TOOL_TITLES = {
27
58
  list_workflows: 'List Workflows (v2)',
@@ -1,4 +1,8 @@
1
1
  import { ValidationCriteria } from './validation';
2
+ export interface OutputContract {
3
+ readonly contractRef: string;
4
+ readonly required?: boolean;
5
+ }
2
6
  export interface WorkflowStepDefinition {
3
7
  readonly id: string;
4
8
  readonly title: string;
@@ -9,6 +13,7 @@ export interface WorkflowStepDefinition {
9
13
  readonly requireConfirmation?: boolean;
10
14
  readonly runCondition?: Readonly<Record<string, unknown>>;
11
15
  readonly validationCriteria?: ValidationCriteria;
16
+ readonly outputContract?: OutputContract;
12
17
  readonly functionDefinitions?: readonly FunctionDefinition[];
13
18
  readonly functionCalls?: readonly FunctionCall[];
14
19
  readonly functionReferences?: readonly string[];
@@ -18,9 +23,18 @@ export interface LoopStepDefinition extends WorkflowStepDefinition {
18
23
  readonly loop: LoopConfigDefinition;
19
24
  readonly body: string | readonly WorkflowStepDefinition[];
20
25
  }
26
+ export type LoopConditionSource = {
27
+ readonly kind: 'artifact_contract';
28
+ readonly contractRef: string;
29
+ readonly loopId: string;
30
+ } | {
31
+ readonly kind: 'context_variable';
32
+ readonly condition: Readonly<Record<string, unknown>>;
33
+ };
21
34
  export interface LoopConfigDefinition {
22
35
  readonly type: 'while' | 'until' | 'for' | 'forEach';
23
36
  readonly condition?: Readonly<Record<string, unknown>>;
37
+ readonly conditionSource?: LoopConditionSource;
24
38
  readonly items?: string;
25
39
  readonly count?: number | string;
26
40
  readonly maxIterations: number;
@@ -46,6 +60,10 @@ export interface FunctionCall {
46
60
  readonly name: string;
47
61
  readonly args: Readonly<Record<string, unknown>>;
48
62
  }
63
+ export interface WorkflowRecommendedPreferences {
64
+ readonly recommendedAutonomy?: 'guided' | 'full_auto_stop_on_user_deps' | 'full_auto_never_stop';
65
+ readonly recommendedRiskPolicy?: 'conservative' | 'balanced' | 'aggressive';
66
+ }
49
67
  export interface WorkflowDefinition {
50
68
  readonly id: string;
51
69
  readonly name: string;
@@ -56,6 +74,7 @@ export interface WorkflowDefinition {
56
74
  readonly clarificationPrompts?: readonly string[];
57
75
  readonly metaGuidance?: readonly string[];
58
76
  readonly functionDefinitions?: readonly FunctionDefinition[];
77
+ readonly recommendedPreferences?: WorkflowRecommendedPreferences;
59
78
  }
60
79
  export declare function isLoopStepDefinition(step: WorkflowStepDefinition | LoopStepDefinition): step is LoopStepDefinition;
61
80
  export declare function isWorkflowStepDefinition(step: WorkflowStepDefinition | LoopStepDefinition): step is WorkflowStepDefinition;
@@ -5,6 +5,8 @@ export declare const MAX_DECISION_TRACE_ENTRIES = 25;
5
5
  export declare const MAX_DECISION_TRACE_ENTRY_SUMMARY_BYTES = 512;
6
6
  export declare const MAX_DECISION_TRACE_TOTAL_BYTES = 8192;
7
7
  export declare const MAX_OUTPUT_NOTES_MARKDOWN_BYTES = 4096;
8
+ export declare const MAX_VALIDATION_ISSUES_BYTES = 4096;
9
+ export declare const MAX_VALIDATION_SUGGESTIONS_BYTES = 4096;
8
10
  export declare const MAX_CONTEXT_BYTES: number;
9
11
  export declare const MAX_CONTEXT_DEPTH = 64;
10
12
  export declare const MAX_OBSERVATION_SHORT_STRING_LENGTH = 80;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DELIMITER_SAFE_ID_PATTERN = exports.SHA256_DIGEST_PATTERN = exports.RECOVERY_BUDGET_BYTES = exports.TRUNCATION_MARKER = exports.DEFAULT_RETRY_AFTER_MS = exports.SESSION_LOCK_RETRY_AFTER_MS = exports.MAX_OBSERVATION_SHORT_STRING_LENGTH = exports.MAX_CONTEXT_DEPTH = exports.MAX_CONTEXT_BYTES = exports.MAX_OUTPUT_NOTES_MARKDOWN_BYTES = exports.MAX_DECISION_TRACE_TOTAL_BYTES = exports.MAX_DECISION_TRACE_ENTRY_SUMMARY_BYTES = exports.MAX_DECISION_TRACE_ENTRIES = exports.MAX_BLOCKER_SUGGESTED_FIX_BYTES = exports.MAX_BLOCKER_MESSAGE_BYTES = exports.MAX_BLOCKERS = void 0;
3
+ exports.DELIMITER_SAFE_ID_PATTERN = exports.SHA256_DIGEST_PATTERN = exports.RECOVERY_BUDGET_BYTES = exports.TRUNCATION_MARKER = exports.DEFAULT_RETRY_AFTER_MS = exports.SESSION_LOCK_RETRY_AFTER_MS = exports.MAX_OBSERVATION_SHORT_STRING_LENGTH = exports.MAX_CONTEXT_DEPTH = exports.MAX_CONTEXT_BYTES = exports.MAX_VALIDATION_SUGGESTIONS_BYTES = exports.MAX_VALIDATION_ISSUES_BYTES = exports.MAX_OUTPUT_NOTES_MARKDOWN_BYTES = exports.MAX_DECISION_TRACE_TOTAL_BYTES = exports.MAX_DECISION_TRACE_ENTRY_SUMMARY_BYTES = exports.MAX_DECISION_TRACE_ENTRIES = exports.MAX_BLOCKER_SUGGESTED_FIX_BYTES = exports.MAX_BLOCKER_MESSAGE_BYTES = exports.MAX_BLOCKERS = void 0;
4
4
  exports.MAX_BLOCKERS = 10;
5
5
  exports.MAX_BLOCKER_MESSAGE_BYTES = 512;
6
6
  exports.MAX_BLOCKER_SUGGESTED_FIX_BYTES = 1024;
@@ -8,6 +8,8 @@ exports.MAX_DECISION_TRACE_ENTRIES = 25;
8
8
  exports.MAX_DECISION_TRACE_ENTRY_SUMMARY_BYTES = 512;
9
9
  exports.MAX_DECISION_TRACE_TOTAL_BYTES = 8192;
10
10
  exports.MAX_OUTPUT_NOTES_MARKDOWN_BYTES = 4096;
11
+ exports.MAX_VALIDATION_ISSUES_BYTES = 4096;
12
+ exports.MAX_VALIDATION_SUGGESTIONS_BYTES = 4096;
11
13
  exports.MAX_CONTEXT_BYTES = 256 * 1024;
12
14
  exports.MAX_CONTEXT_DEPTH = 64;
13
15
  exports.MAX_OBSERVATION_SHORT_STRING_LENGTH = 80;
@@ -16,6 +16,7 @@ export declare function buildAckAdvanceAppendPlanV1(args: {
16
16
  readonly outcome?: AdvanceOutcomeV1;
17
17
  readonly extraEventsToAppend?: readonly EventToAppendV1[];
18
18
  readonly toNodeId?: string;
19
+ readonly toNodeKind?: 'step' | 'blocked_attempt';
19
20
  readonly snapshotRef?: SnapshotRef;
20
21
  readonly causeKind?: 'intentional_fork' | 'non_tip_advance';
21
22
  readonly minted: {
@@ -46,6 +46,12 @@ function buildAckAdvanceAppendPlanV1(args) {
46
46
  }
47
47
  const nextIndexAfterExtra = nextEventIndex + 1 + extra.length;
48
48
  if (outcome.kind === 'blocked') {
49
+ if (typeof console !== 'undefined' && console.warn) {
50
+ console.warn('[DEPRECATED] Creating advance_recorded with blocked outcome. ' +
51
+ 'Use blocked_attempt nodes instead (see ADR 008). ' +
52
+ 'This path will be removed in 2 releases. ' +
53
+ 'Set USE_BLOCKED_NODES=true to use the new model.');
54
+ }
49
55
  if (outputsToAppend && outputsToAppend.length > 0) {
50
56
  return (0, neverthrow_1.err)({
51
57
  code: 'INVARIANT_VIOLATION',
@@ -72,8 +78,12 @@ function buildAckAdvanceAppendPlanV1(args) {
72
78
  const toNodeId = args.toNodeId;
73
79
  const snapshotRef = args.snapshotRef;
74
80
  const causeKind = args.causeKind;
81
+ const toNodeKind = args.toNodeKind ?? 'step';
75
82
  const nodeCreatedEventIndex = nextIndexAfterExtra;
76
83
  const edgeCreatedEventIndex = nextIndexAfterExtra + 1;
84
+ if (toNodeKind !== 'step' && toNodeKind !== 'blocked_attempt') {
85
+ return (0, neverthrow_1.err)({ code: 'INVARIANT_VIOLATION', message: 'toNodeKind must be step|blocked_attempt' });
86
+ }
77
87
  const advancedEvents = [
78
88
  {
79
89
  v: 1,
@@ -84,7 +94,7 @@ function buildAckAdvanceAppendPlanV1(args) {
84
94
  dedupeKey: `node_created:${sessionId}:${runId}:${toNodeId}`,
85
95
  scope: { runId, nodeId: toNodeId },
86
96
  data: {
87
- nodeKind: 'step',
97
+ nodeKind: toNodeKind,
88
98
  parentNodeId: fromNodeId,
89
99
  workflowHash,
90
100
  snapshotRef,
@@ -0,0 +1,31 @@
1
+ import type { Result } from 'neverthrow';
2
+ import type { OutputContract } from '../../../types/workflow-definition.js';
3
+ export type ArtifactContractValidationError = {
4
+ readonly code: 'MISSING_REQUIRED_ARTIFACT';
5
+ readonly contractRef: string;
6
+ readonly message: string;
7
+ } | {
8
+ readonly code: 'INVALID_ARTIFACT_SCHEMA';
9
+ readonly contractRef: string;
10
+ readonly message: string;
11
+ readonly issues: readonly string[];
12
+ } | {
13
+ readonly code: 'UNKNOWN_CONTRACT_REF';
14
+ readonly contractRef: string;
15
+ readonly message: string;
16
+ };
17
+ export type ArtifactContractValidationResult = {
18
+ readonly valid: true;
19
+ readonly artifact: unknown;
20
+ } | {
21
+ readonly valid: false;
22
+ readonly error: ArtifactContractValidationError;
23
+ };
24
+ export declare function validateArtifactContract(artifacts: readonly unknown[], contract: OutputContract): ArtifactContractValidationResult;
25
+ export declare function requiresArtifactValidation(outputContract: OutputContract | undefined): boolean;
26
+ export declare function formatArtifactValidationError(error: ArtifactContractValidationError): {
27
+ readonly code: string;
28
+ readonly message: string;
29
+ readonly suggestedFix?: string;
30
+ };
31
+ export declare function extractValidatedArtifact(artifacts: readonly unknown[], contract: OutputContract): Result<unknown, ArtifactContractValidationError>;
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateArtifactContract = validateArtifactContract;
4
+ exports.requiresArtifactValidation = requiresArtifactValidation;
5
+ exports.formatArtifactValidationError = formatArtifactValidationError;
6
+ exports.extractValidatedArtifact = extractValidatedArtifact;
7
+ const neverthrow_1 = require("neverthrow");
8
+ const index_js_1 = require("../schemas/artifacts/index.js");
9
+ function validateArtifactContract(artifacts, contract) {
10
+ const { contractRef, required = true } = contract;
11
+ if (!(0, index_js_1.isValidContractRef)(contractRef)) {
12
+ return {
13
+ valid: false,
14
+ error: {
15
+ code: 'UNKNOWN_CONTRACT_REF',
16
+ contractRef,
17
+ message: `Unknown artifact contract reference: ${contractRef}`,
18
+ },
19
+ };
20
+ }
21
+ switch (contractRef) {
22
+ case index_js_1.LOOP_CONTROL_CONTRACT_REF:
23
+ return validateLoopControlContract(artifacts, contractRef, required);
24
+ default:
25
+ return {
26
+ valid: false,
27
+ error: {
28
+ code: 'UNKNOWN_CONTRACT_REF',
29
+ contractRef,
30
+ message: `No validator implemented for contract: ${contractRef}`,
31
+ },
32
+ };
33
+ }
34
+ }
35
+ function validateLoopControlContract(artifacts, contractRef, required) {
36
+ const loopControlArtifacts = artifacts.filter(index_js_1.isLoopControlArtifact);
37
+ if (loopControlArtifacts.length === 0) {
38
+ if (required) {
39
+ return {
40
+ valid: false,
41
+ error: {
42
+ code: 'MISSING_REQUIRED_ARTIFACT',
43
+ contractRef,
44
+ message: `Required artifact missing: ${contractRef}. Agent must provide an artifact with kind='wr.loop_control'.`,
45
+ },
46
+ };
47
+ }
48
+ return { valid: true, artifact: null };
49
+ }
50
+ const artifact = loopControlArtifacts[0];
51
+ const parseResult = index_js_1.LoopControlArtifactV1Schema.safeParse(artifact);
52
+ if (!parseResult.success) {
53
+ const issues = parseResult.error.issues.map(issue => `${issue.path.join('.')}: ${issue.message}`);
54
+ return {
55
+ valid: false,
56
+ error: {
57
+ code: 'INVALID_ARTIFACT_SCHEMA',
58
+ contractRef,
59
+ message: `Artifact schema validation failed for ${contractRef}`,
60
+ issues,
61
+ },
62
+ };
63
+ }
64
+ return { valid: true, artifact: parseResult.data };
65
+ }
66
+ function requiresArtifactValidation(outputContract) {
67
+ if (!outputContract)
68
+ return false;
69
+ return outputContract.required !== false;
70
+ }
71
+ function formatArtifactValidationError(error) {
72
+ switch (error.code) {
73
+ case 'MISSING_REQUIRED_ARTIFACT':
74
+ return {
75
+ code: 'MISSING_REQUIRED_OUTPUT',
76
+ message: error.message,
77
+ suggestedFix: `Provide an artifact with kind matching the contract: ${error.contractRef}`,
78
+ };
79
+ case 'INVALID_ARTIFACT_SCHEMA':
80
+ return {
81
+ code: 'INVALID_REQUIRED_OUTPUT',
82
+ message: `${error.message}: ${error.issues.join('; ')}`,
83
+ suggestedFix: `Fix the artifact schema errors and retry`,
84
+ };
85
+ case 'UNKNOWN_CONTRACT_REF':
86
+ return {
87
+ code: 'INVARIANT_VIOLATION',
88
+ message: error.message,
89
+ };
90
+ }
91
+ }
92
+ function extractValidatedArtifact(artifacts, contract) {
93
+ const result = validateArtifactContract(artifacts, contract);
94
+ if (result.valid) {
95
+ return (0, neverthrow_1.ok)(result.artifact);
96
+ }
97
+ return (0, neverthrow_1.err)(result.error);
98
+ }
@@ -0,0 +1,20 @@
1
+ import { type Result } from 'neverthrow';
2
+ import type { Sha256PortV2 } from '../../ports/sha256.port.js';
3
+ import type { AttemptId } from '../ids/index.js';
4
+ import type { ExecutionSnapshotFileV1 } from '../schemas/execution-snapshot/index.js';
5
+ import type { BlockerReportV1, ReasonV1 } from './reason-model.js';
6
+ export type BlockedNodeBuildError = {
7
+ readonly code: 'BLOCKED_NODE_INVARIANT_VIOLATION';
8
+ readonly message: string;
9
+ } | {
10
+ readonly code: 'BLOCKED_NODE_UNSUPPORTED_STATE';
11
+ readonly message: string;
12
+ };
13
+ export declare function buildBlockedNodeSnapshot(args: {
14
+ readonly priorSnapshot: ExecutionSnapshotFileV1;
15
+ readonly primaryReason: ReasonV1;
16
+ readonly attemptId: AttemptId;
17
+ readonly validationRef: string;
18
+ readonly blockers: BlockerReportV1;
19
+ readonly sha256: Sha256PortV2;
20
+ }): Result<ExecutionSnapshotFileV1, BlockedNodeBuildError>;