@archmax-ai/harness 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/dist/agent.d.ts +4 -2
  2. package/dist/agent.js +4 -2
  3. package/dist/agent.js.map +1 -1
  4. package/dist/assembly/compose.js +16 -3
  5. package/dist/assembly/compose.js.map +1 -1
  6. package/dist/assembly/index.d.ts +10 -0
  7. package/dist/assembly/index.js +35 -10
  8. package/dist/assembly/index.js.map +1 -1
  9. package/dist/authoring-skill/archmax-harness/references/backend-integration.md +9 -6
  10. package/dist/authoring-skill/archmax-harness/references/cases.md +3 -2
  11. package/dist/authoring-skill/archmax-harness/references/hook-and-test-scripts.md +47 -3
  12. package/dist/authoring-skill/archmax-harness/references/hooks-and-scripts.md +2 -1
  13. package/dist/authoring-skill/archmax-harness/references/wiring.md +1 -1
  14. package/dist/authoring-skill/archmax-harness/references/workflow-schema.md +29 -0
  15. package/dist/authoring-skill/archmax-harness/references/workflow-yaml.md +13 -3
  16. package/dist/cli/turn.js +1 -2
  17. package/dist/cli/turn.js.map +1 -1
  18. package/dist/core/clock.d.ts +26 -0
  19. package/dist/core/clock.js +64 -0
  20. package/dist/core/clock.js.map +1 -0
  21. package/dist/core/deepagents.d.ts +9 -2
  22. package/dist/core/deepagents.js +15 -2
  23. package/dist/core/deepagents.js.map +1 -1
  24. package/dist/core/message-readers.d.ts +21 -0
  25. package/dist/core/message-readers.js +36 -0
  26. package/dist/core/message-readers.js.map +1 -1
  27. package/dist/core/messages.d.ts +8 -8
  28. package/dist/core/messages.js +11 -13
  29. package/dist/core/messages.js.map +1 -1
  30. package/dist/kernel/kernel.js +3 -2
  31. package/dist/kernel/kernel.js.map +1 -1
  32. package/dist/lifecycle/runner.d.ts +14 -0
  33. package/dist/lifecycle/runner.js +50 -4
  34. package/dist/lifecycle/runner.js.map +1 -1
  35. package/dist/lifecycle/tool-calls.d.ts +9 -0
  36. package/dist/lifecycle/tool-calls.js +57 -0
  37. package/dist/lifecycle/tool-calls.js.map +1 -0
  38. package/dist/lifecycle/transcript.d.ts +10 -6
  39. package/dist/lifecycle/transcript.js +15 -10
  40. package/dist/lifecycle/transcript.js.map +1 -1
  41. package/dist/machine/allow.d.ts +17 -2
  42. package/dist/machine/allow.js +16 -6
  43. package/dist/machine/allow.js.map +1 -1
  44. package/dist/machine/lint-spec.js +15 -0
  45. package/dist/machine/lint-spec.js.map +1 -1
  46. package/dist/machine/machine.d.ts +5 -0
  47. package/dist/machine/machine.js +16 -4
  48. package/dist/machine/machine.js.map +1 -1
  49. package/dist/machine/spec-schema.d.ts +9 -6
  50. package/dist/machine/spec-schema.js +48 -0
  51. package/dist/machine/spec-schema.js.map +1 -1
  52. package/dist/machine/tool-names.d.ts +6 -0
  53. package/dist/machine/tool-names.js +6 -0
  54. package/dist/machine/tool-names.js.map +1 -1
  55. package/dist/machine/tool-paths.d.ts +6 -0
  56. package/dist/machine/tool-paths.js +10 -0
  57. package/dist/machine/tool-paths.js.map +1 -1
  58. package/dist/machine/types.d.ts +9 -1
  59. package/dist/machine/types.js +2 -0
  60. package/dist/machine/types.js.map +1 -1
  61. package/dist/machine/variables.d.ts +2 -0
  62. package/dist/machine/variables.js.map +1 -1
  63. package/dist/public/sandbox.d.ts +35 -3
  64. package/dist/public/sandbox.js +1 -1
  65. package/dist/public/sandbox.js.map +1 -1
  66. package/dist/public/spec.d.ts +2 -2
  67. package/dist/public/spec.js +1 -1
  68. package/dist/public/spec.js.map +1 -1
  69. package/dist/sandbox/executor.js +10 -0
  70. package/dist/sandbox/executor.js.map +1 -1
  71. package/dist/sessions/operations.js +4 -5
  72. package/dist/sessions/operations.js.map +1 -1
  73. package/dist/sessions/resume.d.ts +10 -5
  74. package/dist/sessions/resume.js +11 -8
  75. package/dist/sessions/resume.js.map +1 -1
  76. package/dist/sessions/scope.d.ts +10 -0
  77. package/dist/sessions/scope.js +17 -0
  78. package/dist/sessions/scope.js.map +1 -1
  79. package/dist/testing/case-document.d.ts +2 -0
  80. package/dist/testing/case-document.js.map +1 -1
  81. package/dist/testing/case-schema.d.ts +1 -1
  82. package/dist/testing/case-schema.js +17 -1
  83. package/dist/testing/case-schema.js.map +1 -1
  84. package/dist/testing/driver.d.ts +4 -0
  85. package/dist/testing/driver.js +19 -3
  86. package/dist/testing/driver.js.map +1 -1
  87. package/dist/testing/grade.js +3 -3
  88. package/dist/testing/grade.js.map +1 -1
  89. package/dist/testing/interpreter.js +1 -0
  90. package/dist/testing/interpreter.js.map +1 -1
  91. package/dist/workflow/checkpointer.d.ts +7 -0
  92. package/dist/workflow/checkpointer.js +28 -0
  93. package/dist/workflow/checkpointer.js.map +1 -1
  94. package/dist/workflow/control-tools.js +26 -2
  95. package/dist/workflow/control-tools.js.map +1 -1
  96. package/dist/workflow/governance.d.ts +3 -1
  97. package/dist/workflow/governance.js +27 -6
  98. package/dist/workflow/governance.js.map +1 -1
  99. package/dist/workflow/middleware.js +22 -4
  100. package/dist/workflow/middleware.js.map +1 -1
  101. package/dist/workflow/on-error.js +2 -0
  102. package/dist/workflow/on-error.js.map +1 -1
  103. package/dist/workflow/parks.js +2 -0
  104. package/dist/workflow/parks.js.map +1 -1
  105. package/dist/workflow/state.d.ts +41 -0
  106. package/dist/workflow/state.js +33 -4
  107. package/dist/workflow/state.js.map +1 -1
  108. package/dist/workflow/sub-workflow.js +7 -3
  109. package/dist/workflow/sub-workflow.js.map +1 -1
  110. package/dist/workflow/tool-service.d.ts +1 -1
  111. package/dist/workflow/tool-service.js +1 -1
  112. package/dist/workflow/tool-service.js.map +1 -1
  113. package/dist/workflow/turn-boundary.js +8 -0
  114. package/dist/workflow/turn-boundary.js.map +1 -1
  115. package/dist/workflow/turn-runner.js +36 -7
  116. package/dist/workflow/turn-runner.js.map +1 -1
  117. package/dist/workflow/visits.d.ts +21 -0
  118. package/dist/workflow/visits.js +45 -0
  119. package/dist/workflow/visits.js.map +1 -0
  120. package/package.json +1 -1
  121. package/skills/archmax-harness/references/backend-integration.md +9 -6
  122. package/skills/archmax-harness/references/cases.md +3 -2
  123. package/skills/archmax-harness/references/hook-and-test-scripts.md +47 -3
  124. package/skills/archmax-harness/references/hooks-and-scripts.md +2 -1
  125. package/skills/archmax-harness/references/wiring.md +1 -1
  126. package/skills/archmax-harness/references/workflow-schema.md +29 -0
  127. package/skills/archmax-harness/references/workflow-yaml.md +13 -3
@@ -1 +1 @@
1
- {"version":3,"file":"turn-runner.js","sourceRoot":"","sources":["../../src/workflow/turn-runner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"turn-runner.js","sourceRoot":"","sources":["../../src/workflow/turn-runner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { type VisitStart } from "./state.js";
2
+ /** One visit as found in a transcript: its number (from 1), its state and its first message's index. */
3
+ export interface VisitSpan {
4
+ number: number;
5
+ state: string;
6
+ start: number;
7
+ }
8
+ /**
9
+ * The session's visits over `messages`, in order. A session from before the
10
+ * visit log has no entry for what precedes its first logged visit: that span
11
+ * is one visit, at the session's entry state.
12
+ */
13
+ export declare function visitSpans(messages: readonly unknown[], state: unknown): VisitSpan[];
14
+ /** The visit holding the message at `index`. */
15
+ export declare function visitAt(spans: readonly VisitSpan[], index: number): VisitSpan;
16
+ /** The messages of the visit the session is in. */
17
+ export declare function currentVisitMessages(messages: readonly unknown[], state: unknown): unknown[];
18
+ /** The log entry for entering `target` with the transcript standing at `messages`. */
19
+ export declare function visitStart(target: string, messages: unknown): VisitStart[];
20
+ /** A checkpointed state's transcript. */
21
+ export declare function messagesIn(state: unknown): unknown[];
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Visits: the span of a session from its entry into a state until it next
3
+ * enters one. What a hook sees of the conversation is the current visit, and
4
+ * every tool call is attributed to the visit of the message that made it.
5
+ */
6
+ import { anchorIndex, turnAnchorOf } from "../core/messages.js";
7
+ import { readWorkflowState } from "./state.js";
8
+ /**
9
+ * The session's visits over `messages`, in order. A session from before the
10
+ * visit log has no entry for what precedes its first logged visit: that span
11
+ * is one visit, at the session's entry state.
12
+ */
13
+ export function visitSpans(messages, state) {
14
+ const fields = readWorkflowState(state);
15
+ const spans = (fields.visits ?? []).map((visit) => ({ state: visit.state, start: anchorIndex(messages, visit.from) }));
16
+ if (spans.length === 0 || spans[0].start > 0) {
17
+ spans.unshift({ state: fields.entryState ?? fields.workflowState ?? "", start: 0 });
18
+ }
19
+ return spans.map((span, i) => ({ number: i + 1, ...span }));
20
+ }
21
+ /** The visit holding the message at `index`. */
22
+ export function visitAt(spans, index) {
23
+ let found = spans[0];
24
+ for (const span of spans) {
25
+ if (span.start > index)
26
+ break;
27
+ found = span;
28
+ }
29
+ return found;
30
+ }
31
+ /** The messages of the visit the session is in. */
32
+ export function currentVisitMessages(messages, state) {
33
+ const spans = visitSpans(messages, state);
34
+ return messages.slice(spans[spans.length - 1].start);
35
+ }
36
+ /** The log entry for entering `target` with the transcript standing at `messages`. */
37
+ export function visitStart(target, messages) {
38
+ return [{ state: target, from: turnAnchorOf(messages) }];
39
+ }
40
+ /** A checkpointed state's transcript. */
41
+ export function messagesIn(state) {
42
+ const messages = state?.messages;
43
+ return Array.isArray(messages) ? messages : [];
44
+ }
45
+ //# sourceMappingURL=visits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visits.js","sourceRoot":"","sources":["../../src/workflow/visits.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAmB,MAAM,YAAY,CAAC;AAShE;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,QAA4B,EAAE,KAAc;IACrE,MAAM,MAAM,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,KAAK,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,WAAW,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IACvH,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAE,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC;QAC9C,KAAK,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,UAAU,IAAI,MAAM,CAAC,aAAa,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IACtF,CAAC;IACD,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC;AAC9D,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,OAAO,CAAC,KAA2B,EAAE,KAAa;IAChE,IAAI,KAAK,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;IACtB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,KAAK,GAAG,KAAK;YAAE,MAAM;QAC9B,KAAK,GAAG,IAAI,CAAC;IACf,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,oBAAoB,CAAC,QAA4B,EAAE,KAAc;IAC/E,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC1C,OAAO,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC,KAAK,CAAC,CAAC;AACxD,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,UAAU,CAAC,MAAc,EAAE,QAAiB;IAC1D,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;AAC3D,CAAC;AAED,yCAAyC;AACzC,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,MAAM,QAAQ,GAAI,KAA4C,EAAE,QAAQ,CAAC;IACzE,OAAO,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;AACjD,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archmax-ai/harness",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "description": "Agents can't reliably follow a process. archmax harness makes them: a governed layer over LangChain Deep Agents that enforces a declarative workflow state machine on every step.",
6
6
  "keywords": [
@@ -101,6 +101,7 @@ All optional (`CreateAgentParams`, `src/assembly/index.ts`):
101
101
  | `systemPrompt` | `string` | Appended after workspace `AGENTS.md`. |
102
102
  | `tools` | `StructuredTool[]` | Host tools, granted per state by `tools.allow`. Build them with `toolsFromMap` — see [host tools](#host-tools). A name starting `archmax_`, or naming a runtime file operation (`copy_file`, `move_file`, `remove_file`), throws `ReservedToolNameError`. |
103
103
  | `essentialTools` | `string[]` | Host tools treated as always on: disclosed and permitted in every state, whatever the workflow's `tools.allow_always` declares; a state's own entry still narrows one. |
104
+ | `clock` | `string` | Pins the "now" every model call states, for cases and evaluations: an ISO 8601 date (`2026-03-20`, midnight UTC) or a date-time with `Z`/an offset; zoneless or impossible values fail assembly, `""` means the real clock. Rendered in UTC, rounded to ten minutes, no local time. Recorded timestamps, `resumeAt` and the sandbox's `Date` stay real. Not for production: a pinned session resumed days later still thinks it is the pinned day. A case's `clock:` outranks it. |
104
105
  | `images` | `boolean \| ImageReadOptions` | Show a model with vision the images it reads: the model's `read_file` of a PNG/JPEG/GIF/WebP answers with one line and the image is added to each later request as a `user` message after that batch of tool results, read per request — no bytes in history, checkpoints, events or scripts. `{ maxBytes?, keep? }` (default 10 MB, every image). Off by default; every other binary stays refused. |
105
106
  | `toolPaths` | `Record<string, ToolPaths>` | Each host tool's path arguments and how a call uses each (`read`, `list`, `search`, `write`, `remove`, `execute`): `{ get_markdown: { path: "read" } }`. Every path rule then governs them like the built-ins'. Wins over a descriptor's `paths`; declaring a built-in's paths throws `ToolPathsError`. |
106
107
  | `checkpointer` | `BaseCheckpointSaver` | Custom-adapter escape hatch; takes precedence over the session store for checkpoint persistence. Defaults to a durable `BackendCheckpointSaver` writing through the session store. Use `MemorySaver` for ephemeral runs. |
@@ -153,7 +154,7 @@ try {
153
154
  return lastAssistantText(result.messages);
154
155
  }
155
156
  } finally {
156
- agent.dispose(sessionId); // cleans up sandbox/PTC state for the session
157
+ agent.dispose(sessionId); // frees the session's sandbox/PTC state and its checkpoints in memory; the next turn reloads them from the store
157
158
  }
158
159
  ```
159
160
 
@@ -220,11 +221,13 @@ await agent.invoke(
220
221
 
221
222
  Gotchas:
222
223
 
223
- - **A hook reads the transcript from `messages`, newest last.** A hook that keys
224
- off "the current request" should take the *last* `user` entry, not the first —
225
- the first is the message that opened the session. (A grading rubric still
226
- receives the compact `userRequest`/`history` payload its rubric is written
227
- against; there `userRequest` is the *first* user message.)
224
+ - **A hook reads its visit from `messages`, newest last** — the conversation since
225
+ the session entered the state it is in, which can span turns. A hook that keys
226
+ off "the current request" should take the *last* `user` entry; an earlier
227
+ state's messages are not in it, so what a hook needs from there is stored (a
228
+ variable, a file) or read with `toolCalls`. (A grading rubric receives the
229
+ compact `userRequest`/`history` payload its rubric is written against: `history`
230
+ is the same visit, `userRequest` the person's *latest* message.)
228
231
  - **A definition edit is adopted, not refused.** A session takes its next turn
229
232
  under the `workflow.yaml` in force now; the recorded `specHash` says which
230
233
  definition each turn ran under but gates nothing. A turn positioned in a state
@@ -19,6 +19,7 @@ it). Host-interpreted; no sandbox runs a case. **Unknown keys are errors.**
19
19
  | `variables` | `{ name: value }` seeded and **locked** every turn — the firing's payload; never seed what the agent must establish (assert it with `variables:` instead) |
20
20
  | `workspace` | `{ <run path>: content }` — strings verbatim, other YAML → JSON, `{ from: <tests-relative path> }` copies a file; only run-zone paths (`trigger.json`, `scratchpad/…`) |
21
21
  | `mocks` | `[{ tool, whenInput?, result }]` — intercepts agent and script calls alike (partial-input match) |
22
+ | `clock` | ISO 8601 date (`2026-03-20`) or date-time with `Z`/offset — the "now" every model call of the case states; pin it whenever fixtures or prompts carry dates. Prompt only: scripts, the sandbox and recorded timestamps stay real |
22
23
  | `steps` | one flat list; actions and assertions are peers; an assertion evaluates against the nearest action above it |
23
24
 
24
25
  Actions: `send: "<message>"` (a new turn; against a parked run, a reply-only
@@ -31,10 +32,10 @@ reported `not-executed`): `succeeded: true`ˢ (fails on a raise too) · `parked:
31
32
  `reachedState: <slug>`ˢ (a committed non-trigger trail step) ·
32
33
  `trail: { to?, kind?, reason?, count }`ˢ (`kind`: trigger/agent/human/on_error) ·
33
34
  `noTraversal: true`ˢ · `triggerArrival: <id>`ˢ ·
34
- `reply: { includes?, excludes? }` (token or list; `"/regex/flags"` strings are regexes; matched against the whole turn's assistant text) ·
35
+ `reply: { includes?, excludes? }` (token or list; `"/regex/flags"` strings are regexes; matched against every assistant message of the session so far, earlier turns included) ·
35
36
  `calledTool` / `notCalledTool` / `blockedTool: { name, input? }` (partial input; `blockedTool` = governance refused it — the assertion that tests a guard) ·
36
37
  `ranWorkflow: { workflow, status?, count? }` · `usedNoTools: true` (whole conversation) ·
37
- `variables: { expect, path?, locked? }` · `grade: { closedQA, atLeast }` (LLM grades the reply plus the tool record; `atLeast` is a hard bar; needs `tests.judge`).
38
+ `variables: { expect, path?, locked? }` · `grade: { closedQA, atLeast }` (LLM grades what the action's turn said plus the session's tool record so far; `atLeast` is a hard bar; needs `tests.judge`).
38
39
 
39
40
  Put the structural assertion right after the action that should satisfy it,
40
41
  before content assertions that only make sense in that state:
@@ -161,10 +161,41 @@ export default async function hook({ state, phase, trigger, variables, messages,
161
161
  | `state`, `phase` | The state whose hook is running; `"before"` or `"after"`. |
162
162
  | `trigger` | The trigger **id string** the run started from. It carries no payload — the run's input is in `variables`. |
163
163
  | `variables` | The run's variables as a plain `name → value` map, including the built-in `trigger`. Structured values are present whole (`variables.order.items[0].sku`). **Read-only**: assigning changes nothing, and the variable tools are absent from `tools.*` — a hook that could write variables would be editing the governance inputs of the state it judges. |
164
- | `messages` | The recent transcript (last 24 messages), newest last, as `{ role, text, toolCalls? }`. `role` is `user`, `assistant`, `tool` (with `tool: <name>`), `system`, or **`runtime`** — a note the runtime wrote (an arrival, a decision, an error route, a completion check; `note` names the kind). A hook that reacts to narration matches `role === "runtime"`, never a `[bracket]` prefix in a user message. |
164
+ | `messages` | The transcript of the **current visit**, newest last, as `{ role, text, toolCalls? }`: everything since the session entered the state it is in. On an advance that is the state being left, for its `after` hook and the target's `before` hook alike; a visit spans turns, and entering a state again starts a new one. `role` is `user`, `assistant`, `tool`, `system`, or **`runtime`** — a note the runtime wrote (an arrival, a decision, an error route, a completion check; `note` names the kind). A `tool` entry carries `tool` and the call's `id` with an empty `text`: its result comes from `toolCalls`. A hook that reacts to narration matches `role === "runtime"`, never a `[bracket]` prefix in a user message. |
165
+ | `toolCalls(filter?)` | **Async.** The session's tool calls with their results, across every visit, oldest first: `{ id, name, args, result, status, state, visit }` (`result` parsed when it is JSON; `status` `completed`, `failed`, `rejected` by governance, or `pending`). `filter` narrows by `name`, `state`, `visit` (a number or `"current"`), `status` and `last`; an unknown key throws. The control tools are not in it, nor are calls a script or hook made through `tools.*`. Only the records it returns are copied into the sandbox. |
165
166
  | `from`, `to`, `reason` | Present on `after` hooks: the transition the agent is attempting (`reason` is the agent's `archmax_advance` reason). |
166
167
  | `tools` | The privileged tool-call bridge (async), camelCased: `await tools.readFile({ file_path: "skills/order-data/assets/orders.json" })` → file text, or the binary notice for a binary file (see below). See [Governance](#governance-of-tools-calls). |
167
168
 
169
+ **What a hook checks.** The conversation is the model's memory, not evidence: a
170
+ hook checks its own visit and the session's records — `variables`, files, and
171
+ the tool results `toolCalls` keeps as the tools returned them. Check a result
172
+ itself, never a copy the model wrote into a variable, and never call a tool
173
+ again just to see its result (for a host whose tools reach an external system,
174
+ that is a second action):
175
+
176
+ ```js
177
+ /**
178
+ * Cancel gate — an order may be cancelled only while it is pending.
179
+ *
180
+ * Finds the latest get_order call for the order being cancelled and vetoes
181
+ * unless its result says pending.
182
+ */
183
+ export default async function hook({ toolCalls }) {
184
+ const [cancel] = await toolCalls({ name: "cancel_order", visit: "current", last: 1 });
185
+ if (!cancel) return ok();
186
+ const lookups = await toolCalls({ name: "get_order", status: "completed" });
187
+ const order = lookups.findLast((c) => c.args.id === cancel.args.id);
188
+ if (!order) return veto(`Look order ${cancel.args.id} up with get_order before cancelling it.`);
189
+ if (order.result.status !== "pending") return veto(`Order ${cancel.args.id} is ${order.result.status}, not pending.`);
190
+ return ok();
191
+ }
192
+ ```
193
+
194
+ What a later state or turn needs from the conversation (the person's words, a
195
+ decision) is stored in a variable or a file by the state that has it. A `before`
196
+ hook re-run by `archmax_reset` starts a new visit and no longer sees the
197
+ session's opening request.
198
+
168
199
  **The verdict** — the three helpers are globals:
169
200
 
170
201
  | Return | Effect |
@@ -502,6 +533,9 @@ get the exact same flow when they return `correct(reason)` under a
502
533
  into a verdict **fails closed** (veto with a snippet of the raw output). A rubric
503
534
  receives a compact JSON payload (`userRequest`, `history`, `state`, `phase`) —
504
535
  its `instructions` are written against that, not against a hook's `messages`.
536
+ `history` is the visit the hook gates, tool results in full; `userRequest` is the
537
+ person's latest message (what the reply answers), which an earlier state usually
538
+ received.
505
539
 
506
540
  ---
507
541
 
@@ -608,6 +642,7 @@ Unknown keys are validation **errors**. All keys except `title` and
608
642
  | `trigger` | `{ id }` | The trigger every driven turn of the case runs under. Carries **only an id**; `args` is rejected with a pointer to `variables:`. Validated against the workflow machine before any step — an unknown id fails the case (even with no steps), listing the workflow's declared triggers. Omitted ⇒ the manual trigger. See [Start conditions](#start-conditions-trigger-variables-and-workspace). |
609
643
  | `workspace` | `{ <path>: <content> }` | Files seeded into the case's run workspace **before any step**. Strings are written verbatim; non-string YAML values are JSON-serialized; a single-key `{ from: <tests/-relative path> }` mapping copies a file byte-for-byte. Only agent-visible run-zone paths are seedable (root-level files like `trigger.json`, or `scratchpad/…`); authored (`skills/…`), runtime-internal, and escaping paths are rejected before anything is written. |
610
644
  | `mocks` | `[{ tool, whenInput?, result }]` | Declarative tool mocks: when the driven agent calls `tool` with input partially matching `whenInput`, return `result` instead of executing. Intercepts **agent-initiated and PTC** (script `tools.*`) calls alike. See [the mocking section](#settle-the-mocking-depth-before-writing-a-case). |
645
+ | `clock` | `"2026-03-20"` / `"…T14:30:00Z"` | The date and time every model call of the case states as "now": an ISO 8601 date (midnight UTC) or a date-time with `Z` or an offset; a zoneless or impossible value is a schema error. Carried on every action, a handoff and a child session included, and outranks the host's `clock` option. See [Start conditions](#start-conditions-trigger-variables-and-workspace). |
611
646
  | `steps` | flat list of step entries | The case script: actions and assertions as peers (below). |
612
647
 
613
648
  There is **no multi-session surface** in test spec version 1 — no `sessions:`,
@@ -666,7 +701,7 @@ failure in one of them **halts the case** — see
666
701
  | `raised: true` / `raised: <code>` / `raised: { code?, reason? }` | **Structural.** The agent ended the turn with `archmax_raise({ code, reason })`. A string pins the code exactly; the mapping pins the code and matches the reason — exactly, or as a regex when written `"/pattern/flags"`. A miss names the code that was raised, or says the run did not raise. |
667
702
  | `variables: { expect, path?, locked? }` | Every named variable is set to the expected value — deep equality, so a structured expectation compares by value. `path` addresses into a value per name (`items.-1.sku` is the last element); `locked` asserts the lock state. |
668
703
  | `reachedState: <slug>` | **Structural.** A **committed** (non-trigger) audit-trail step entered the state — agent `archmax_advance`, human decision, `archmax_reset`, or `on_error` route. The trigger-arrival step never counts (it is recorded before the entry state's `before` gate runs, so a vetoed-entry run never "reaches" its start state). |
669
- | `reply: { includes?, excludes? }` | `includes`: substrings or `/pattern/flags` regex strings that must **all** match; `excludes`: none may match (leak checks — a refusal that quotes the data it refuses still leaks it). Each accepts a **single token or a list** (`includes: "ORD-1003"` works). Matched against the turn's **whole assistant transcript**, not just the final message (a parked run often closes with a short "moving on" message while the substantive answer came earlier). |
704
+ | `reply: { includes?, excludes? }` | `includes`: substrings or `/pattern/flags` regex strings that must **all** match; `excludes`: none may match (leak checks — a refusal that quotes the data it refuses still leaks it). Each accepts a **single token or a list** (`includes: "ORD-1003"` works). Matched against **every assistant message of the session so far** (earlier turns included), not just the final message (a parked run often closes with a short "moving on" message while the substantive answer came earlier). |
670
705
  | `calledTool: { name, input? }` | The turn recorded a call to tool `name`; `input` is a **partial** match — mappings match on the declared keys (nested mappings partially), arrays by deep equality, and `/pattern/flags` string values match as **regexes** (recommended for file paths, so the model's exact spelling doesn't matter). |
671
706
  | `notCalledTool: { name, input? }` | No matching call occurred. |
672
707
  | `blockedTool: { name, input? }` | A matching call was made **and refused by governance** — the assertion for testing a guard. Distinct from `notCalledTool` (nothing was tried, which proves nothing about the guard) and from a tool that ran and errored (only a policy rejection counts). Use it to test a state's `tools.allow` narrowing or its `skills.allow` set. |
@@ -674,7 +709,7 @@ failure in one of them **halts the case** — see
674
709
  | `trail: { to?, kind?, reason?, count }` | **Structural.** The number of audit-trail steps matching **all** given fields equals `count` (declare at least one of `to`/`kind`/`reason`). `kind`: `trigger` \| `agent` \| `human` \| `on_error` \| `branch` \| `join`. The trail accumulates across turns — each turn starts with a `trigger` step. |
675
710
  | `noTraversal: true` | **Structural.** The trail holds nothing but trigger arrivals — a veto at entry committed no transitions. |
676
711
  | `triggerArrival: <trigger-id>` | **Structural.** Some trail step has `kind: trigger` and that trigger id as its reason — the run really started from the declared trigger. |
677
- | `grade: { closedQA, atLeast }` | LLM-grade the turn's **run evidence** against the `closedQA` criterion — its final reply *plus* a bounded chronological record of the assistant messages and tool calls behind it (each call with input, output, and status; mocked calls appear like real ones). The two are weighed differently: a criterion about what the user was **told** is met only by the reply, one about what the agent **did** may be met by the record — so `closedQA: looked the order up before answering` is a legitimate criterion, not only phrasing checks. **`atLeast` decides the outcome** — score ≥ `atLeast` passes, below it **fails the case**; the judge model's own opinion never overrides your declared bar, so the record can't disagree with the verdict. The judge is instructed to keep its reason to 1–3 sentences, so it fits on the verdict line. Requires `tests.judge` in `workflow.yaml`. |
712
+ | `grade: { closedQA, atLeast }` | LLM-grade the turn's **run evidence** against the `closedQA` criterion — what the action's turn said (every text of that turn, empty when it said nothing) *plus* a bounded chronological record of the session's assistant messages and tool calls so far (each call with input, output, and status; mocked calls appear like real ones). The two are weighed differently: a criterion about what the user was **told** is met only by the reply, one about what the agent **did** may be met by the record — so `closedQA: looked the order up before answering` is a legitimate criterion, not only phrasing checks. **`atLeast` decides the outcome** — score ≥ `atLeast` passes, below it **fails the case**; the judge model's own opinion never overrides your declared bar, so the record can't disagree with the verdict. The judge is instructed to keep its reason to 1–3 sentences, so it fits on the verdict line. Requires `tests.judge` in `workflow.yaml`. |
678
713
 
679
714
  **Regex form**: a string shaped `/pattern/flags` is compiled as a regular
680
715
  expression; anything else matches as a plain substring (in `reply` tokens) or
@@ -783,6 +818,15 @@ Two operational notes:
783
818
  _earlier_ run whose declaration was since removed can linger. The default
784
819
  (per-run unique sessions) starts clean.
785
820
 
821
+ **Pin the date** with a case-level `clock:` whenever a fixture or a prompt
822
+ carries a date (an order placed on `2026-03-15`, "refundable for 30 days"):
823
+ without it the model reads the real "now" and the verdict drifts with the
824
+ calendar. Pin on a ten-minute mark (the line is rounded down to ten minutes,
825
+ UTC only). Only the prompt is pinned — hook scripts, the sandbox's `Date`, a
826
+ park's `resumeAt` and every recorded timestamp stay real, so a script that needs
827
+ the case's "today" reads it from a seeded variable, and a wait resumed in the
828
+ case is resumed with `deliver`, not by its `until`.
829
+
786
830
  ### Recovering a case's inputs from previous runs
787
831
 
788
832
  A trigger carries only an id, so everything a firing passes arrives as
@@ -38,7 +38,8 @@ export default async function hook({ state, phase, trigger, variables, messages,
38
38
  | `state`, `phase` | state slug; `"before"` or `"after"` |
39
39
  | `trigger` | trigger id string (payload is in `variables`) |
40
40
  | `variables` | read-only `name → value` map incl. `trigger`; structured values whole |
41
- | `messages` | last 24 messages, newest last, `{ role, text, toolCalls? }`; roles `user`/`assistant`/`tool`/`system`/`runtime` (a runtime note; match `role === "runtime"`, never a `[bracket]`) |
41
+ | `messages` | the **current visit**, newest last: everything since the session entered the state it is in (on an advance, the state being left; it can span turns). `{ role, text, toolCalls? }`; roles `user`/`assistant`/`tool`/`system`/`runtime` (a runtime note; match `role === "runtime"`, never a `[bracket]`). A `tool` entry has `tool`, `id` and an empty `text`: read results with `toolCalls` |
42
+ | `toolCalls(filter?)` | async: the session's tool calls with results, every visit, oldest first, `{ id, name, args, result, status, state, visit }`; filter by `name`, `state`, `visit` (number or `"current"`), `status`, `last`. No control tools. Only what it returns enters the sandbox |
42
43
  | `from`, `to`, `reason` | `after` only: the attempted transition and the agent's reason |
43
44
  | `tools` | async camelCased bridge: `tools.readFile`, `tools.writeFile`, `tools.archmaxWorkflowEnrichOrder(...)`; no control tools; a hook's lists every tool on the bridge, not only the state's |
44
45
 
@@ -13,7 +13,7 @@ const agent = await createAgent({
13
13
  workspace: { rootDir, /* mounts?, sessionStore? */ },
14
14
  onEvent: (e) => { usage.handler(e); publish(e); }, // omit for the console subscriber
15
15
  // model | modelFactory, tools, systemPrompt, middleware, backend, checkpointer,
16
- // trigger, variables, promptCache, pricing, hookExecutors …
16
+ // trigger, variables, promptCache, pricing, clock, hookExecutors …
17
17
  });
18
18
  const sessionId = `run-${runId}`;
19
19
  const result = await agent.invoke(
@@ -115,6 +115,17 @@ runtime?:
115
115
  engine?: string # "archmax-harness"
116
116
  version?: string | number # target contract — use "2"
117
117
 
118
+ budget?:
119
+ maxModelCalls?: number # default 50 — model calls ONE TURN may make across EVERY
120
+ # state (a turn = one message, decision, reply or delivery;
121
+ # each counts from zero). A state's maxTurns restarts on
122
+ # every entry, so only this ends an agent cycling between
123
+ # states. Exhaustion routes once through the current
124
+ # state's on_error, else the turn ends rejected; the agent
125
+ # is warned once a fifth is left. The step limit follows
126
+ # it. RAISE it for turns that legitimately run long (a long
127
+ # state chain, a self-driven review cycle).
128
+
118
129
  settings?:
119
130
  model?: string # MODEL ID ONLY — the id every state of this workflow runs
120
131
  # on, over the endpoint/credentials the assembly already
@@ -265,6 +276,18 @@ states:
265
276
  # reference GUARANTEED rather than dependent on agent
266
277
  # behavior. Name here anything a downstream guard
267
278
  # reads.
279
+ locks?: [string] # RUN VARIABLES this state OWNS: only it may write them,
280
+ # on any visit, before or after they are set; a write
281
+ # from another state is refused naming the owner. To
282
+ # change one (the person meant another order), the
283
+ # session goes BACK to the owner, whose rules apply
284
+ # again, and later guards re-read it — so give the
285
+ # owner an edge back from the states that use it. Any
286
+ # variable, guarded or not. One owner per variable;
287
+ # never `trigger`/`title`; not on a human state.
288
+ # `lock: true` is refused for an owned variable, and a
289
+ # host-supplied value with the same name stays final.
290
+ # Pair with `requires` to make the owner set it.
268
291
  title?: string # HUMAN-READABLE label. Presentational only: carried
269
292
  # in a human state's pending-decision record and shown
270
293
  # in host surfaces. REACHES NO MODEL CALL. Never a
@@ -1297,6 +1320,12 @@ states:
1297
1320
  escalation: { instructions: "Explain the failure and hand off." } # terminal
1298
1321
  ```
1299
1322
 
1323
+ The workflow's root `budget: { maxModelCalls }` (default 50) bounds a whole
1324
+ turn across states, beside each state's own `maxTurns`; its exhaustion routes the
1325
+ same way. A turn that throws (model failure, cancellation, the step limit) ends
1326
+ the session `failed` with exit code `turn-error`, `turn-cancelled` or
1327
+ `recursion-limit`; the next message starts a fresh turn.
1328
+
1300
1329
  Two failure classes, routed differently:
1301
1330
 
1302
1331
  - **Recoverable rejections keep the agent in place**: an invalid edge in
@@ -23,6 +23,7 @@ tests: # case suite config (no tests.config.js exists)
23
23
  maxConcurrency: 1 # only 1 accepted
24
24
  caseTimeoutMs: 120000 # per-case wall clock, distinct from settings.timeoutMs
25
25
  judge: {} # required for grade:; {} = env default model, or { model, modelOptions }
26
+ budget: { maxModelCalls: 50 } # model calls one turn may make across ALL states (default 50); raise for long turns
26
27
  metadata: { canvas: {} } # host data, never read; accepted at root, state, rubric — leave as found
27
28
  extensions: { hooks: [] } # host-registered hook kinds; rarely authored
28
29
 
@@ -87,6 +88,7 @@ states:
87
88
  allow: [refund-policy] # adds to allow_always (bundle readable + scripts runnable, no path entries)
88
89
  forbid: [order-data] # subtracts, even from allow_always ([] never denies)
89
90
  requires: [case_id] # variables that must be set before this state may be left
91
+ locks: [case_id] # variables only this state may write (change = come back here)
90
92
  model: claude-opus-5 # MODEL ID ONLY — this state's turns; beats settings.model
91
93
  budget: { maxTurns: 12, timeoutMs: 60000, maxParks: 5 } # bounds the state's segment
92
94
  on_error: closed # where a terminal failure of this segment routes
@@ -132,7 +134,10 @@ states:
132
134
  - **Variables.** One flat checkpointed `name → value` store, never declared.
133
135
  Host seeds (`--variables`, `createAgent({ variables })`, a delivery) are
134
136
  **locked**; the agent uses `archmax_get_variables`/`archmax_set_variables`
135
- (`lock: true` to freeze). Reserved: `trigger` (current trigger id, always
137
+ (`lock: true` to freeze, until `archmax_reset` releases the agent's locks).
138
+ A value that must be decided in one place yet stay correctable belongs to a
139
+ state: `locks: [order_id]` lets only that state write it, so a change routes
140
+ back there. Reserved: `trigger` (current trigger id, always
136
141
  locked) and `title` (the run's one-line task label, agent-owned; a
137
142
  sub-workflow child is not asked for one). A
138
143
  `${{name}}` / `${{a.b.-1.c}}` reference works in `tools.allow` globs
@@ -187,7 +192,8 @@ states:
187
192
  bind them; `forbid_always`, safety rules and the workflow's mount grants
188
193
  (any state's) do. Human states may not declare hooks.
189
194
  - **Budgets and `on_error`.** `budget.maxTurns`/`timeoutMs`/`maxParks` bound
190
- a state's segment. A terminal failure (hook execution error, exhausted
195
+ a state's segment; the root `budget.maxModelCalls` (default 50) bounds a whole
196
+ turn across states, so a cycle between states ends there. A terminal failure (hook execution error, exhausted
191
197
  corrections, exhausted budget, exhausted parks) routes to `on_error`
192
198
  instead of failing the run; recoverable rejections (invalid edge, veto,
193
199
  in-budget correct) keep the agent in place. Declare `on_error` on states
@@ -277,7 +283,11 @@ Closed by default; **deny beats allow, and no narrower level widens a denial.**
277
283
  state entry naming a granted tool **narrows** it there, and one naming a
278
284
  default tool the list omits grants it there. `task` is ungrantable (error).
279
285
  - **Entries**: bare name; `{ tool, args: { <arg>: ["glob", …] } }` (matches
280
- if any glob matches; `${{var}}` allowed); `{ tool, paths: [...] }`
286
+ if any glob matches; `${{var}}` allowed). An argument the tool declares as a
287
+ path (else `file_path`) is canonicalized first; any other argument (a URL, an
288
+ address, an email) is matched **as written**, and a denial also catches its
289
+ path form. `""` matches only an empty value. `*` never crosses `/` (`**` does).
290
+ `{ tool, paths: [...] }`
281
291
  (guards every path argument the tool declares — all must match to grant,
282
292
  any matching denies). `"*"` as the tool is legal only in a
283
293
  forbid list. Globs match dot-segments (`secrets/**` covers `secrets/.env`).