@archmax-ai/harness 0.9.0 → 1.0.1

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 (207) hide show
  1. package/README.md +58 -14
  2. package/dist/agent.d.ts +4 -6
  3. package/dist/agent.js +4 -6
  4. package/dist/agent.js.map +1 -1
  5. package/dist/assembly/compose.d.ts +5 -24
  6. package/dist/assembly/compose.js +28 -42
  7. package/dist/assembly/compose.js.map +1 -1
  8. package/dist/assembly/delegation-registry.js +3 -14
  9. package/dist/assembly/delegation-registry.js.map +1 -1
  10. package/dist/assembly/index.d.ts +8 -15
  11. package/dist/assembly/index.js +21 -28
  12. package/dist/assembly/index.js.map +1 -1
  13. package/dist/assembly/plain.js +1 -2
  14. package/dist/assembly/plain.js.map +1 -1
  15. package/dist/authoring-skill/archmax-harness/README.md +5 -5
  16. package/dist/authoring-skill/archmax-harness/SKILL.md +72 -30
  17. package/dist/authoring-skill/archmax-harness/references/backend-integration.md +33 -24
  18. package/dist/authoring-skill/archmax-harness/references/cases.md +41 -8
  19. package/dist/authoring-skill/archmax-harness/references/{hook-and-test-scripts.md → code-state-and-test-scripts.md} +599 -339
  20. package/dist/authoring-skill/archmax-harness/references/code-states-and-scripts.md +115 -0
  21. package/dist/authoring-skill/archmax-harness/references/diagnostics.md +40 -7
  22. package/dist/authoring-skill/archmax-harness/references/wiring.md +19 -10
  23. package/dist/authoring-skill/archmax-harness/references/workflow-schema.md +404 -293
  24. package/dist/authoring-skill/archmax-harness/references/workflow-yaml.md +151 -62
  25. package/dist/cli/state-flow.d.ts +1 -1
  26. package/dist/cli/state-flow.js +18 -28
  27. package/dist/cli/state-flow.js.map +1 -1
  28. package/dist/cli/turn.d.ts +1 -1
  29. package/dist/cli/turn.js +1 -1
  30. package/dist/core/code-description.d.ts +3 -3
  31. package/dist/core/code-description.js +3 -3
  32. package/dist/core/deepagents.d.ts +5 -12
  33. package/dist/core/deepagents.js +4 -17
  34. package/dist/core/deepagents.js.map +1 -1
  35. package/dist/core/events.d.ts +23 -42
  36. package/dist/core/events.js +21 -24
  37. package/dist/core/events.js.map +1 -1
  38. package/dist/core/message-readers.d.ts +4 -5
  39. package/dist/core/message-readers.js +3 -8
  40. package/dist/core/message-readers.js.map +1 -1
  41. package/dist/core/messages.d.ts +0 -22
  42. package/dist/core/messages.js +1 -32
  43. package/dist/core/messages.js.map +1 -1
  44. package/dist/core/mounts.js +3 -3
  45. package/dist/core/platform-prompt.generated.js +1 -1
  46. package/dist/core/platform-prompt.generated.js.map +1 -1
  47. package/dist/core/session-zone.d.ts +1 -1
  48. package/dist/core/session-zone.js +1 -1
  49. package/dist/core/workspace-context.d.ts +1 -1
  50. package/dist/core/workspace.d.ts +1 -1
  51. package/dist/core/workspace.js +1 -1
  52. package/dist/core/zones.d.ts +4 -4
  53. package/dist/core/zones.js +5 -5
  54. package/dist/core/zones.js.map +1 -1
  55. package/dist/env.d.ts +9 -9
  56. package/dist/index.d.ts +3 -7
  57. package/dist/index.js +2 -4
  58. package/dist/index.js.map +1 -1
  59. package/dist/kernel/kernel.d.ts +31 -61
  60. package/dist/kernel/kernel.js +54 -112
  61. package/dist/kernel/kernel.js.map +1 -1
  62. package/dist/machine/lint-spec.js +53 -12
  63. package/dist/machine/lint-spec.js.map +1 -1
  64. package/dist/machine/machine.d.ts +36 -18
  65. package/dist/machine/machine.js +62 -51
  66. package/dist/machine/machine.js.map +1 -1
  67. package/dist/machine/spec-schema.d.ts +32 -180
  68. package/dist/machine/spec-schema.js +52 -113
  69. package/dist/machine/spec-schema.js.map +1 -1
  70. package/dist/machine/tool-names.d.ts +34 -16
  71. package/dist/machine/tool-names.js +41 -16
  72. package/dist/machine/tool-names.js.map +1 -1
  73. package/dist/machine/types.d.ts +14 -46
  74. package/dist/machine/types.js +0 -2
  75. package/dist/machine/types.js.map +1 -1
  76. package/dist/machine/validate-spec.d.ts +1 -1
  77. package/dist/public/sandbox.d.ts +59 -95
  78. package/dist/public/sandbox.js +22 -23
  79. package/dist/public/sandbox.js.map +1 -1
  80. package/dist/public/spec.d.ts +13 -20
  81. package/dist/public/spec.js +12 -20
  82. package/dist/public/spec.js.map +1 -1
  83. package/dist/runtime/contract.d.ts +11 -12
  84. package/dist/runtime/contract.js +16 -16
  85. package/dist/runtime/contract.js.map +1 -1
  86. package/dist/sandbox/assets/parts/code-state.js +17 -0
  87. package/dist/sandbox/executor.d.ts +25 -7
  88. package/dist/sandbox/executor.js +56 -57
  89. package/dist/sandbox/executor.js.map +1 -1
  90. package/dist/sandbox/imports.d.ts +10 -10
  91. package/dist/sandbox/imports.js +11 -11
  92. package/dist/sandbox/imports.js.map +1 -1
  93. package/dist/sandbox/prelude.d.ts +5 -6
  94. package/dist/sandbox/prelude.js +38 -35
  95. package/dist/sandbox/prelude.js.map +1 -1
  96. package/dist/sandbox/ptc-gateway.d.ts +19 -18
  97. package/dist/sandbox/ptc-gateway.js +26 -22
  98. package/dist/sandbox/ptc-gateway.js.map +1 -1
  99. package/dist/sessions/resume.d.ts +2 -2
  100. package/dist/sessions/resume.js +1 -1
  101. package/dist/sessions/resume.js.map +1 -1
  102. package/dist/sessions/scope.d.ts +0 -10
  103. package/dist/sessions/scope.js +0 -17
  104. package/dist/sessions/scope.js.map +1 -1
  105. package/dist/sessions/summary.d.ts +1 -1
  106. package/dist/testing/assertions.js +9 -4
  107. package/dist/testing/assertions.js.map +1 -1
  108. package/dist/testing/driver.js +3 -0
  109. package/dist/testing/driver.js.map +1 -1
  110. package/dist/testing/interpreter.d.ts +2 -2
  111. package/dist/testing/runner.d.ts +2 -2
  112. package/dist/validate/validate.d.ts +1 -1
  113. package/dist/validate/validate.js +39 -70
  114. package/dist/validate/validate.js.map +1 -1
  115. package/dist/workflow/automated-controls.d.ts +63 -0
  116. package/dist/workflow/automated-controls.js +158 -0
  117. package/dist/workflow/automated-controls.js.map +1 -0
  118. package/dist/workflow/automated.d.ts +112 -0
  119. package/dist/workflow/automated.js +163 -0
  120. package/dist/workflow/automated.js.map +1 -0
  121. package/dist/workflow/code-state.d.ts +46 -0
  122. package/dist/workflow/code-state.js +26 -0
  123. package/dist/workflow/code-state.js.map +1 -0
  124. package/dist/workflow/control-tools.d.ts +54 -23
  125. package/dist/workflow/control-tools.js +99 -79
  126. package/dist/workflow/control-tools.js.map +1 -1
  127. package/dist/workflow/file-values.d.ts +4 -4
  128. package/dist/workflow/file-values.js +1 -1
  129. package/dist/workflow/file-values.js.map +1 -1
  130. package/dist/workflow/governance.d.ts +11 -22
  131. package/dist/workflow/governance.js +47 -104
  132. package/dist/workflow/governance.js.map +1 -1
  133. package/dist/workflow/grader-state.d.ts +55 -0
  134. package/dist/workflow/grader-state.js +182 -0
  135. package/dist/workflow/grader-state.js.map +1 -0
  136. package/dist/workflow/history.d.ts +11 -0
  137. package/dist/{lifecycle/transcript.js → workflow/history.js} +24 -8
  138. package/dist/workflow/history.js.map +1 -0
  139. package/dist/workflow/middleware.d.ts +14 -17
  140. package/dist/workflow/middleware.js +109 -95
  141. package/dist/workflow/middleware.js.map +1 -1
  142. package/dist/workflow/move-events.d.ts +21 -0
  143. package/dist/workflow/move-events.js +20 -0
  144. package/dist/workflow/move-events.js.map +1 -0
  145. package/dist/workflow/on-error.js +0 -2
  146. package/dist/workflow/on-error.js.map +1 -1
  147. package/dist/workflow/parks.d.ts +9 -2
  148. package/dist/workflow/parks.js +16 -10
  149. package/dist/workflow/parks.js.map +1 -1
  150. package/dist/workflow/paths.d.ts +9 -9
  151. package/dist/workflow/paths.js +19 -19
  152. package/dist/workflow/paths.js.map +1 -1
  153. package/dist/workflow/render-prompt.d.ts +7 -5
  154. package/dist/workflow/render-prompt.js +22 -37
  155. package/dist/workflow/render-prompt.js.map +1 -1
  156. package/dist/workflow/signature-checks.d.ts +2 -2
  157. package/dist/workflow/snapshot.js +5 -29
  158. package/dist/workflow/snapshot.js.map +1 -1
  159. package/dist/workflow/state.d.ts +14 -42
  160. package/dist/workflow/state.js +20 -33
  161. package/dist/workflow/state.js.map +1 -1
  162. package/dist/workflow/sub-workflow.d.ts +3 -3
  163. package/dist/workflow/sub-workflow.js +3 -7
  164. package/dist/workflow/sub-workflow.js.map +1 -1
  165. package/dist/workflow/tool-service.d.ts +1 -9
  166. package/dist/workflow/tool-service.js +21 -24
  167. package/dist/workflow/tool-service.js.map +1 -1
  168. package/dist/workflow/turn-boundary.d.ts +2 -2
  169. package/dist/workflow/turn-boundary.js +10 -15
  170. package/dist/workflow/turn-boundary.js.map +1 -1
  171. package/dist/workflow/turn-runner.js +5 -6
  172. package/dist/workflow/turn-runner.js.map +1 -1
  173. package/package.json +2 -2
  174. package/skills/archmax-harness/README.md +5 -5
  175. package/skills/archmax-harness/SKILL.md +72 -30
  176. package/skills/archmax-harness/references/backend-integration.md +33 -24
  177. package/skills/archmax-harness/references/cases.md +41 -8
  178. package/skills/archmax-harness/references/{hook-and-test-scripts.md → code-state-and-test-scripts.md} +599 -339
  179. package/skills/archmax-harness/references/code-states-and-scripts.md +115 -0
  180. package/skills/archmax-harness/references/diagnostics.md +40 -7
  181. package/skills/archmax-harness/references/wiring.md +19 -10
  182. package/skills/archmax-harness/references/workflow-schema.md +404 -293
  183. package/skills/archmax-harness/references/workflow-yaml.md +151 -62
  184. package/dist/authoring-skill/archmax-harness/references/hooks-and-scripts.md +0 -89
  185. package/dist/lifecycle/hook-shape.d.ts +0 -44
  186. package/dist/lifecycle/hook-shape.js +0 -66
  187. package/dist/lifecycle/hook-shape.js.map +0 -1
  188. package/dist/lifecycle/runner.d.ts +0 -197
  189. package/dist/lifecycle/runner.js +0 -419
  190. package/dist/lifecycle/runner.js.map +0 -1
  191. package/dist/lifecycle/tool-calls.d.ts +0 -9
  192. package/dist/lifecycle/tool-calls.js +0 -57
  193. package/dist/lifecycle/tool-calls.js.map +0 -1
  194. package/dist/lifecycle/transcript.d.ts +0 -31
  195. package/dist/lifecycle/transcript.js.map +0 -1
  196. package/dist/rubrics/rubrics.d.ts +0 -135
  197. package/dist/rubrics/rubrics.js +0 -145
  198. package/dist/rubrics/rubrics.js.map +0 -1
  199. package/dist/sandbox/assets/parts/core.js +0 -22
  200. package/dist/sandbox/assets/parts/lifecycle-hook.js +0 -47
  201. package/dist/sandbox/bridge.d.ts +0 -16
  202. package/dist/sandbox/bridge.js +0 -95
  203. package/dist/sandbox/bridge.js.map +0 -1
  204. package/dist/workflow/visits.d.ts +0 -21
  205. package/dist/workflow/visits.js +0 -45
  206. package/dist/workflow/visits.js.map +0 -1
  207. package/skills/archmax-harness/references/hooks-and-scripts.md +0 -89
@@ -0,0 +1,115 @@
1
+ # Code-state scripts and `archmax_run` scripts — contracts
2
+
3
+ Load this when writing a `workflows/<slug>/scripts/*.js` code-state script or a
4
+ `skills/<cap>/scripts/*.js` source. Worked examples, graders and the sandbox details:
5
+ [`code-state-and-test-scripts.md`](code-state-and-test-scripts.md) Part 1.
6
+
7
+ ## Code-state script contract
8
+
9
+ A `type: code` state runs its `script: scripts/<file>.js` on entry, with no model
10
+ call. The default export is called once with the state's input and **moves the
11
+ session itself**; its return value is never read. The typed import is optional
12
+ (`import { defineCodeState } from "@archmax-ai/harness/sandbox";`, placed below the
13
+ JSDoc; no other import is allowed).
14
+
15
+ ```js
16
+ /**
17
+ * Request screen — the request must name a requester the order records know.
18
+ *
19
+ * Reads the first user message from the session's history, extracts the first
20
+ * email address, reads skills/order-data/assets/orders.json (this state enables
21
+ * `order-data`) and matches the address against every record's `email`,
22
+ * case-insensitively.
23
+ *
24
+ * Advances to identify-case when it matches; otherwise to decline, with a reason
25
+ * naming what was missing. An unreadable order database throws, so the state
26
+ * fails through its on_error.
27
+ */
28
+ import { defineCodeState } from "@archmax-ai/harness/sandbox";
29
+
30
+ export default defineCodeState(async ({ tools }) => {
31
+ const { messages } = JSON.parse(await tools.archmax_get_history());
32
+ const orders = JSON.parse(await tools.read_file({ file_path: "skills/order-data/assets/orders.json" }));
33
+ const text = messages.find((m) => m.role === "user")?.text ?? "";
34
+ const email = text.match(/[\w.+-]+@[\w.-]+\.\w{2,}/)?.[0];
35
+ if (!email) return tools.archmax_advance({ to: "decline", reason: "The request names no email address." });
36
+ if (!orders.some((o) => o.email?.toLowerCase() === email.toLowerCase())) {
37
+ return tools.archmax_advance({ to: "decline", reason: `"${email}" is not in the order database.` });
38
+ }
39
+ await tools.archmax_advance({ to: "identify-case", reason: `${email} is a known requester.` });
40
+ });
41
+ ```
42
+
43
+ | Input | Meaning |
44
+ | --- | --- |
45
+ | `state` | the code state's own slug |
46
+ | `trigger?` | the turn's trigger id string (its payload is in `variables`) |
47
+ | `variables` | read-only `name → value` map incl. `trigger`; structured values whole; write with `tools.archmax_set_variables` |
48
+ | `tools` | **exactly this state's surface**, async, by own name or camelCase (`tools.read_file`, `tools.readFile`): its grants (the workflow-wide grant included, minus `write_todos`, `archmax_eval`, `archmax_run`), the host's essential tools, and the controls `archmax_advance` (absent when terminal), `archmax_raise`, `archmax_get_variables`, `archmax_set_variables`, `archmax_get_history` — the same set a grader gets. Anything else is `undefined`; a call with no argument passes `{}` |
49
+
50
+ **The session's history** is read, not passed in:
51
+ `const { messages, trail } = JSON.parse(await tools.archmax_get_history())` — the
52
+ same `SessionHistory` a grader reads, the session as it stood when the state was
53
+ entered, whole and oldest first.
54
+
55
+ | Field | Meaning |
56
+ | --- | --- |
57
+ | `messages` | the **whole** transcript, `{ role, text, toolCalls?, tool?, id?, note? }`; roles `user`/`assistant`/`tool`/`system`/`runtime` (a runtime note, `note` naming its kind; match `role === "runtime"`, never a `[bracket]`). An `assistant` entry lists the calls it made in `toolCalls` (`{ id?, name, args }`); the `tool` entry whose `id` matches carries the tool's name in `tool` and its result in `text` (`JSON.parse` it when the tool returns JSON) |
58
+ | `trail` | the whole trail, `{ to, kind, reason?, workflow?, status?, ts }`; `kind` is `trigger`/`agent`/`human`/`grader`/`code`/`sub-workflow`/`reset`/`on_error` — count rounds here |
59
+
60
+ | The script… | Effect |
61
+ | --- | --- |
62
+ | calls `tools.archmax_advance({ to, reason })` and finishes | the session moves; `reason` reaches the next state as a `[code]` runtime note and the trail as a `kind: code` step |
63
+ | calls `tools.archmax_raise({ code, reason })` | the session ends `failed` with that code and reason |
64
+ | finishes without either, in a terminal state | the session completes, held to the trigger's `returns` |
65
+ | finishes without either, in a non-terminal state | **fails**: `code state '<slug>' finished without calling archmax_advance or archmax_raise` → `on_error` |
66
+ | throws, times out (`settings.timeoutMs`), imports a foreign module, or its file is missing | fails → `on_error` (else the session ends rejected); a recorded advance is discarded, a raise still stands |
67
+
68
+ A refused call **rejects** with governance's reason, and a `try`/`catch` can
69
+ handle it: a blocked tool, an `archmax_advance` to an undeclared target, or one
70
+ made while the state's `requires` are unmet. The first accepted advance or raise is
71
+ the state's decision; a later one is refused. A write with `archmax_set_variables`
72
+ is visible to the script's later calls, so it can set what `requires` names and
73
+ then advance. Each run starts in a fresh REPL; `console.log` lines become
74
+ `code-output` events. A file with no default export runs as a plain script, reading
75
+ the same input from the global `args` and calling the global `tools`.
76
+
77
+ **Governance is the state's own.** Every `tools.*` call is decided against the code
78
+ state, exactly as the agent's calls are: grant what the script reads or calls in
79
+ *that* state's `skills.allow` / `mounts.allow` / `tools.allow`; its `forbid`,
80
+ `forbid_always` and the safety rules bind it. A code state is never offered
81
+ `write_todos`, `archmax_eval`, `archmax_run`, `archmax_wait`, `archmax_reset` or
82
+ `task`, even when the workflow's `tools.allow_always` lists one (naming one in its
83
+ own `tools.allow` is a `validate` error). Neither script kind can read `workflows/**`.
84
+
85
+ ## `archmax_run` script contract
86
+
87
+ `skills/<cap>/scripts/<x>.js`: plain script, top-level `await`, `tools.*`,
88
+ `console.log`; the **last expression** is the tool result. Input is the global
89
+ `args`: the arguments the model passed plus `args.variables` (read-only snapshot).
90
+ No control tool and no `task` is on its `tools`. Fan-out shape:
91
+
92
+ ```js
93
+ /** One sub-run per requested order, all in flight at once; returns [{ order_id, ...returns }]. */
94
+ const orders = args.variables?.orders_to_enrich ?? [];
95
+ await Promise.all(orders.map(async (o) => {
96
+ const answer = await tools.archmaxWorkflowEnrichOrder({ order_id: o.order_id });
97
+ return { order_id: o.order_id, ...JSON.parse(answer).returns };
98
+ }));
99
+ ```
100
+
101
+ The calling state must allow every tool the script calls (here
102
+ `archmax_workflow_enrich-order`). A script's `tools` holds exactly the calling
103
+ state's tools, re-scoped at every evaluation (so after an advance the same REPL
104
+ holds the next state's); a tool the state does not offer is `undefined`, and a call
105
+ the state's guards refuse rejects with governance's reason. The listing is
106
+ camelCased, but a tool's own name reaches the same function
107
+ (`tools["archmax_workflow_enrich-order"]`, `tools.read_file`).
108
+
109
+ **Return into a variable.** A script's result reaches the model as one tool
110
+ message and no further — it is not written to the variable store, and a script
111
+ cannot write there itself. Return structured data and name the variable in the
112
+ calling state's `requires:`; the model then records it with
113
+ `archmax_set_variables`, which is what makes it survive the state, guard a
114
+ `tools.allow` glob, and be quotable downstream as `${{name}}` instead of retyped.
115
+ A result worth computing is worth naming.
@@ -16,12 +16,19 @@ message alone. Migrations for removed keys in full:
16
16
  | Workflow slug / state key not kebab-case, or empty | Rename the directory or key; update every `to`, `on_error`, `reachedState` |
17
17
  | transitions to / on_error routes to undefined state | Fix the slug or add the state |
18
18
  | no start state / multiple states declare trigger | Exactly one state per trigger id declares it under `triggers:` |
19
- | hook must be a `{ script }` or `{ rubric }` entry / single-key tagged object | One kind per hook; the only sidecar is `max_iterations`; `specification:` and `subagent:` do not exist |
20
- | hook script must be inside `hooks/` / not found | `before: { script: hooks/<x>.js }`, file at `workflows/<slug>/hooks/<x>.js` |
21
- | `before` declares `max_iterations` | Remove it; before hooks are ok/veto |
22
- | Human state must declare instructions / transitions / description; self-edge; hook can never run; two transitions of one type | Add them; route out of the state; drop the hook; one target per labelled type |
19
+ | `Unrecognized key: "before"` / `"after"` (on a state) or `"extensions"` (at the root) | Hooks are removed — rewrite each as a state: a `before` check → a **code state in front** of the state (the trigger's entry, or the target of the edge into it); an `after` check → a **code state behind** it (the state's transition points at it, and it advances onward or back with the reason); a `rubric:` → a **grader state** behind it with an edge back. `max_iterations` has no successor: count rounds from the trail. Move `workflows/<slug>/hooks/*.js` to `scripts/` and rewrite them to the code-state contract. Delete `extensions` |
20
+ | `Unrecognized key: "budget"` (at the root) | The root turn budget (`budget.maxModelCalls`) is removed: delete the key. A turn is bounded by LangGraph's `recursionLimit` (400 unless the host's invoke config sets one); bound a state with its own `budget.maxTurns` |
21
+ | Code state declares no `script` | Add `script: scripts/<x>.js`, file at `workflows/<slug>/scripts/<x>.js` |
22
+ | state declares `script`, which only a code state runs | Declare `type: code`, or drop the key |
23
+ | `script` must be a workflow-relative path inside `scripts/` ending in .js or .mjs | `scripts/<x>.js` — no `hooks/`, no `skills/…` path, no `..` |
24
+ | Code state `<s>` script not found / imports from `<x>` | Create the file under the workflow's `scripts/`; import only from `@archmax-ai/harness/*` |
25
+ | Code state declares `instructions` / `model` / `budget` / `evidence` / `approvers` | Drop them: a code state makes no model call and cannot park. Say what the script does in its JSDoc and `summary`; `settings.timeoutMs` bounds it |
26
+ | Grader state must declare `instructions` / at least one transition | Write the criteria in `instructions`; give it an edge onward and, to send work back, one to the graded state |
27
+ | Grader state declares `evidence` / `approvers` / `budget.maxParks` | Drop them: a grader presents nothing to a person and cannot park |
28
+ | `<type>` state is never offered `write_todos` / `archmax_eval` / `archmax_run` / `archmax_wait` / `archmax_reset` | Drop the entry from the state's `tools.allow`: a script calls tools directly, and an automated state cannot park or reset. The same tools in the workflow's `allow_always` are simply not offered there |
29
+ | Human state must declare instructions / transitions / description; self-edge; two transitions of one type | Add them; route out of the state; one target per labelled type |
23
30
  | `'*'` is not a tool name a grant may use | Move the wildcard to `forbid`/`forbid_always` |
24
- | allows `task` — not grantable | Delete the entry; declare a `rubric:` hook instead |
31
+ | allows `task` — not grantable | Delete the entry; grade with a grader state, delegate with `archmax_workflow_<slug>` |
25
32
  | allows `archmax_run` on `skills/<x>/…` which the state does not enable | Add `x` to the state's `skills.allow` (or `allow_always`), or drop the entry |
26
33
  | `forbid_always` denies `archmax_advance` | Remove it |
27
34
  | not a skill slug (path/glob given) | Use the bundle directory name; narrow paths with a `tools.allow` entry |
@@ -34,10 +41,12 @@ message alone. Migrations for removed keys in full:
34
41
  | state has several transitions but no `instructions` (warning) | Add routing guidance so the agent classifies the request before it advances |
35
42
  | `transitions.<i>.description` must be a non-empty string here: '<target>' declares none (error) | The edge is undescribed and its target has no `instructions` to route on. Add the condition under which the edge is taken (say *when*, not *where*), or give the target instructions |
36
43
  | `transitions.<i>.description` must be a non-empty string on a human state's transition (error) | It is the decision's button label: say what choosing it means |
44
+ | `transitions.<i>.description` must be a non-empty string here: '<target>' is a grader / is a code state (error) | An edge into an automated state cannot route on its target: a grader's instructions are its criteria, never shown to the work it judges, and a code state has none. Say when the work is ready for the check (`The reply is drafted`). A code state's own edges may stay undescribed |
37
45
  | `transitions.<i>.description` must be a string (error) | Quote it, or omit it to route on the target's instructions |
38
46
  | trigger declares unknown key (warning) | Keys read: `session`, `message`, `connection`, `requires`, `returns`; others are preserved |
39
47
  | `maxConcurrency` above 1 | Set to 1 or remove |
40
48
  | `WORKFLOW.md` has frontmatter | Strip it; the machine is `workflow.yaml` |
49
+ | `Unsupported runtime contract 'archmax-harness@1'` (or `@2`). Supported: `archmax-harness@3` | Contracts 1 and 2 (the hook contract) are retired: rewrite any hooks as states (above), then set `runtime: { engine: archmax-harness, version: "3" }` or drop the block (omitted resolves to `3`) |
41
50
  | case: missing/over-long `title`/`description`; unknown key; assertion before action; `expect:` block; malformed regex; unknown trigger; `from:` missing | Fix the field named; assertions are steps; regexes are `"/p/f"` strings |
42
51
  | `agent cannot read workflows/**` (instructions/evidence/workspace path) | Move the text to a skill bundle or the path to `scratchpad/` |
43
52
 
@@ -48,6 +57,30 @@ root `skills.allow`→`skills.allow_always`; state `type: wait`→`archmax_wait`
48
57
  `type: trigger`→`triggers:` on the entered state; `type: workflow`,
49
58
  `workflow:`, `params:`, `for_each:`, `result:`→an agent state allowing
50
59
  `archmax_workflow_<slug>` + `requires`; `parallel:`/`join:`→sequenced states;
51
- transition `on:`→nothing (the resumed state picks its edge); `subagent:` hook
52
- →`rubric:`; `max_corrections`→`max_iterations`; single-file `WORKFLOW.md` with
60
+ transition `on:`→nothing (the resumed state picks its edge); state
61
+ `before:`/`after:` (with `{ script }`, `{ rubric }`, `subagent:`, custom kinds,
62
+ `max_iterations`/`max_corrections`)→code and grader states; root
63
+ `extensions:`→nothing; root `budget: { maxModelCalls }`→nothing (the
64
+ `recursionLimit` bounds a turn); single-file `WORKFLOW.md` with
53
65
  frontmatter→`workflow.yaml` + prose.
66
+
67
+ ## Reading a failed run
68
+
69
+ `archmax test` and `archmax run` report an automated state's run in the state
70
+ flow (stderr): `› code scripts/<x>.js` with each `console.log` line beneath it,
71
+ `› grader grading`, and `✖ code failed <reason>` / `✖ grader failed <reason>` when
72
+ the run failed. The raw events (`--verbose`, or `onEvent` in a host) are
73
+ `code-start { state, script }`, `code-output { state, line }`,
74
+ `code-result { state, status, durationMs, reason? }`, `grader-start { state }` and
75
+ `grader-result { state, status, durationMs, reason? }`; a failure then shows as
76
+ `state-error-routed` (its `on_error`) or a rejected session.
77
+
78
+ | Reason says | Fix |
79
+ | --- | --- |
80
+ | `code state '<s>' finished without calling archmax_advance or archmax_raise` | Every path through a non-terminal state's script must advance or raise; returning a value does nothing |
81
+ | `grader state '<s>' finished without calling …` | The grader answered in text: say in its `instructions` exactly when to take each transition and when to raise |
82
+ | `grader state '<s>' exhausted its turn budget (maxTurns: N)` / `exceeded its time budget` | Raise `budget.maxTurns`/`timeoutMs` (reading the history and deciding is two calls), or narrow what it must read |
83
+ | `archmax_advance rejected: cannot advance from '<s>' to '<t>'` / `cannot leave '<s>' yet: '<v>' is not set` | The script's `to` is not one of its state's transitions, or its `requires` are unmet — fix the target, or set the variable first |
84
+ | `archmax_advance rejected: this state has decided` | A script or grader decided twice; only the first advance or raise counts |
85
+ | a tool refused, or `tools.<x>` is `undefined`, inside a code state | Grant it on the **code state itself** (`tools.allow`, `skills.allow`, `mounts.allow`); another state's grant does not reach it |
86
+ | `GraphRecursionError` / the session `failed` with `recursion-limit` | The turn hit LangGraph's `recursionLimit` (400 unless the host sets one): states cycle within one turn — automated states advancing to each other, or an agent looping between states. Add an edge out, or count rounds from the trail and give up; where one state loops on its own, bound it with `budget.maxTurns` + `on_error` |
@@ -12,13 +12,14 @@ const agent = await createAgent({
12
12
  workflow: "order-lookup",
13
13
  workspace: { rootDir, /* mounts?, sessionStore? */ },
14
14
  onEvent: (e) => { usage.handler(e); publish(e); }, // omit for the console subscriber
15
- // model | modelFactory, tools, systemPrompt, middleware, backend, checkpointer,
16
- // trigger, variables, promptCache, pricing, clock, hookExecutors …
15
+ // model | modelFactory (roles agent, judge, grader), tools, systemPrompt,
16
+ // middleware, backend, checkpointer, trigger, variables, promptCache, pricing,
17
+ // clock …
17
18
  });
18
19
  const sessionId = `run-${runId}`;
19
20
  const result = await agent.invoke(
20
21
  { messages: [{ role: "user", content: prompt }] },
21
- { configurable: { thread_id: sessionId }, recursionLimit: 50 },
22
+ { configurable: { thread_id: sessionId } }, // recursionLimit: 400 unless you pass one
22
23
  );
23
24
  if (result.status === WORKFLOW_STATUSES.awaitingDecision) {
24
25
  // result.pendingDecision.transitions → show to a person, then:
@@ -81,13 +82,19 @@ agent.dispose(sessionId);
81
82
  `SessionSummary.exit` carries the same while the session is `failed`, and a
82
83
  later message is an ordinary new turn. `rejected` is governance ending it
83
84
  (`Outcome.rejected`, no `exit`). A host that read "not rejected" as success
84
- must check `failed` too.
85
+ must check `failed` too. A turn that throws also ends the session `failed`,
86
+ with code `turn-error`, `turn-cancelled` or `recursion-limit` — LangGraph's
87
+ `recursionLimit` (400 unless the invoke config passes one) is the one bound on
88
+ a whole turn. `Outcome.reply` is every assistant text of the turn, in order.
85
89
  - **Events**: `onEvent` receives the one typed stream — `state-enter/leave`,
86
- `advance`, `hook-verdict`, `parked/decided`, `raised` (the agent ended the
87
- session failed: `state`, `code`, `reason`, `callId`), `tool-called/result/blocked`
88
- (every governed call, `archmax_advance` included, grouped by `callId`),
89
- `agent-text-delta`/`agent-text` (streaming by default, `messageId` groups
90
- chunks), `model-usage` (provider counts per turn), `title-set`, warnings.
90
+ `advance`, `code-start/output/result` and `grader-start/result` (a code or
91
+ grader state's run; `status` `ok`/`error`, `reason` on a failure),
92
+ `parked/decided`, `raised` (the session ended failed: `state`, `code`,
93
+ `reason`, `callId`), `tool-called/result/blocked` (every governed call,
94
+ `archmax_advance` included, grouped by `callId`; a code state's carry
95
+ `origin: "script"`), `agent-text-delta`/`agent-text` (streaming by default,
96
+ `messageId` groups chunks; a grader's tokens never stream), `model-usage`
97
+ (provider counts per call, a grader's under its state), `title-set`, warnings.
91
98
  There is no run-end event; `invoke` returning is the end. Metrics are a
92
99
  fold over this stream; only cost is checkpointed (`sessions.get(id).usage`).
93
100
  - **Artifacts** per session under `sessions/<id>/artifacts/`: `graph.json`/`.mmd`,
@@ -98,7 +105,9 @@ agent.dispose(sessionId);
98
105
  target or a case with `mocks:` is refused before running.
99
106
  - **Model/caching**: `ARCHMAX_API_BASE_URL`, `ARCHMAX_API_KEY`, `ARCHMAX_MODEL`
100
107
  (+ `ARCHMAX_TEMPERATURE`, `ARCHMAX_MAX_TOKENS`, `ARCHMAX_STREAMING`) for any
101
- OpenAI-compatible endpoint, or pass `model`. Prompt prefix is cache-marked
108
+ OpenAI-compatible endpoint, or pass `model`. A `modelFactory(role, env, id)`
109
+ serves each role its own model: `agent`, `judge` (case `grade:`), `grader`
110
+ (a grader state, asked for the state's `model` id). Prompt prefix is cache-marked
102
111
  by default (`promptCache: { enabled, ttl: "5m" | "1h" }`); watch
103
112
  `cacheReadTokens`. Pricing via `pricing: { "<model-substring>": { input, output, cacheRead, cacheWrite } }`
104
113
  or `ARCHMAX_PRICE_*`; without it report tokens, never a guessed cost. A keyed