@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.
- package/dist/agent.d.ts +4 -2
- package/dist/agent.js +4 -2
- package/dist/agent.js.map +1 -1
- package/dist/assembly/compose.js +16 -3
- package/dist/assembly/compose.js.map +1 -1
- package/dist/assembly/index.d.ts +10 -0
- package/dist/assembly/index.js +35 -10
- package/dist/assembly/index.js.map +1 -1
- package/dist/authoring-skill/archmax-harness/references/backend-integration.md +9 -6
- package/dist/authoring-skill/archmax-harness/references/cases.md +3 -2
- package/dist/authoring-skill/archmax-harness/references/hook-and-test-scripts.md +47 -3
- package/dist/authoring-skill/archmax-harness/references/hooks-and-scripts.md +2 -1
- package/dist/authoring-skill/archmax-harness/references/wiring.md +1 -1
- package/dist/authoring-skill/archmax-harness/references/workflow-schema.md +29 -0
- package/dist/authoring-skill/archmax-harness/references/workflow-yaml.md +13 -3
- package/dist/cli/turn.js +1 -2
- package/dist/cli/turn.js.map +1 -1
- package/dist/core/clock.d.ts +26 -0
- package/dist/core/clock.js +64 -0
- package/dist/core/clock.js.map +1 -0
- package/dist/core/deepagents.d.ts +9 -2
- package/dist/core/deepagents.js +15 -2
- package/dist/core/deepagents.js.map +1 -1
- package/dist/core/message-readers.d.ts +21 -0
- package/dist/core/message-readers.js +36 -0
- package/dist/core/message-readers.js.map +1 -1
- package/dist/core/messages.d.ts +8 -8
- package/dist/core/messages.js +11 -13
- package/dist/core/messages.js.map +1 -1
- package/dist/kernel/kernel.js +3 -2
- package/dist/kernel/kernel.js.map +1 -1
- package/dist/lifecycle/runner.d.ts +14 -0
- package/dist/lifecycle/runner.js +50 -4
- package/dist/lifecycle/runner.js.map +1 -1
- package/dist/lifecycle/tool-calls.d.ts +9 -0
- package/dist/lifecycle/tool-calls.js +57 -0
- package/dist/lifecycle/tool-calls.js.map +1 -0
- package/dist/lifecycle/transcript.d.ts +10 -6
- package/dist/lifecycle/transcript.js +15 -10
- package/dist/lifecycle/transcript.js.map +1 -1
- package/dist/machine/allow.d.ts +17 -2
- package/dist/machine/allow.js +16 -6
- package/dist/machine/allow.js.map +1 -1
- package/dist/machine/lint-spec.js +15 -0
- package/dist/machine/lint-spec.js.map +1 -1
- package/dist/machine/machine.d.ts +5 -0
- package/dist/machine/machine.js +16 -4
- package/dist/machine/machine.js.map +1 -1
- package/dist/machine/spec-schema.d.ts +9 -6
- package/dist/machine/spec-schema.js +48 -0
- package/dist/machine/spec-schema.js.map +1 -1
- package/dist/machine/tool-names.d.ts +6 -0
- package/dist/machine/tool-names.js +6 -0
- package/dist/machine/tool-names.js.map +1 -1
- package/dist/machine/tool-paths.d.ts +6 -0
- package/dist/machine/tool-paths.js +10 -0
- package/dist/machine/tool-paths.js.map +1 -1
- package/dist/machine/types.d.ts +9 -1
- package/dist/machine/types.js +2 -0
- package/dist/machine/types.js.map +1 -1
- package/dist/machine/variables.d.ts +2 -0
- package/dist/machine/variables.js.map +1 -1
- package/dist/public/sandbox.d.ts +35 -3
- package/dist/public/sandbox.js +1 -1
- package/dist/public/sandbox.js.map +1 -1
- package/dist/public/spec.d.ts +2 -2
- package/dist/public/spec.js +1 -1
- package/dist/public/spec.js.map +1 -1
- package/dist/sandbox/executor.js +10 -0
- package/dist/sandbox/executor.js.map +1 -1
- package/dist/sessions/operations.js +4 -5
- package/dist/sessions/operations.js.map +1 -1
- package/dist/sessions/resume.d.ts +10 -5
- package/dist/sessions/resume.js +11 -8
- package/dist/sessions/resume.js.map +1 -1
- package/dist/sessions/scope.d.ts +10 -0
- package/dist/sessions/scope.js +17 -0
- package/dist/sessions/scope.js.map +1 -1
- package/dist/testing/case-document.d.ts +2 -0
- package/dist/testing/case-document.js.map +1 -1
- package/dist/testing/case-schema.d.ts +1 -1
- package/dist/testing/case-schema.js +17 -1
- package/dist/testing/case-schema.js.map +1 -1
- package/dist/testing/driver.d.ts +4 -0
- package/dist/testing/driver.js +19 -3
- package/dist/testing/driver.js.map +1 -1
- package/dist/testing/grade.js +3 -3
- package/dist/testing/grade.js.map +1 -1
- package/dist/testing/interpreter.js +1 -0
- package/dist/testing/interpreter.js.map +1 -1
- package/dist/workflow/checkpointer.d.ts +7 -0
- package/dist/workflow/checkpointer.js +28 -0
- package/dist/workflow/checkpointer.js.map +1 -1
- package/dist/workflow/control-tools.js +26 -2
- package/dist/workflow/control-tools.js.map +1 -1
- package/dist/workflow/governance.d.ts +3 -1
- package/dist/workflow/governance.js +27 -6
- package/dist/workflow/governance.js.map +1 -1
- package/dist/workflow/middleware.js +22 -4
- package/dist/workflow/middleware.js.map +1 -1
- package/dist/workflow/on-error.js +2 -0
- package/dist/workflow/on-error.js.map +1 -1
- package/dist/workflow/parks.js +2 -0
- package/dist/workflow/parks.js.map +1 -1
- package/dist/workflow/state.d.ts +41 -0
- package/dist/workflow/state.js +33 -4
- package/dist/workflow/state.js.map +1 -1
- package/dist/workflow/sub-workflow.js +7 -3
- package/dist/workflow/sub-workflow.js.map +1 -1
- package/dist/workflow/tool-service.d.ts +1 -1
- package/dist/workflow/tool-service.js +1 -1
- package/dist/workflow/tool-service.js.map +1 -1
- package/dist/workflow/turn-boundary.js +8 -0
- package/dist/workflow/turn-boundary.js.map +1 -1
- package/dist/workflow/turn-runner.js +36 -7
- package/dist/workflow/turn-runner.js.map +1 -1
- package/dist/workflow/visits.d.ts +21 -0
- package/dist/workflow/visits.js +45 -0
- package/dist/workflow/visits.js.map +1 -0
- package/package.json +1 -1
- package/skills/archmax-harness/references/backend-integration.md +9 -6
- package/skills/archmax-harness/references/cases.md +3 -2
- package/skills/archmax-harness/references/hook-and-test-scripts.md +47 -3
- package/skills/archmax-harness/references/hooks-and-scripts.md +2 -1
- package/skills/archmax-harness/references/wiring.md +1 -1
- package/skills/archmax-harness/references/workflow-schema.md +29 -0
- 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":"AAOA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;
|
|
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.
|
|
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); //
|
|
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
|
|
224
|
-
|
|
225
|
-
the
|
|
226
|
-
|
|
227
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 —
|
|
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` |
|
|
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
|
|
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
|
|
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)
|
|
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`).
|