@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.
- package/README.md +58 -14
- package/dist/agent.d.ts +4 -6
- package/dist/agent.js +4 -6
- package/dist/agent.js.map +1 -1
- package/dist/assembly/compose.d.ts +5 -24
- package/dist/assembly/compose.js +28 -42
- package/dist/assembly/compose.js.map +1 -1
- package/dist/assembly/delegation-registry.js +3 -14
- package/dist/assembly/delegation-registry.js.map +1 -1
- package/dist/assembly/index.d.ts +8 -15
- package/dist/assembly/index.js +21 -28
- package/dist/assembly/index.js.map +1 -1
- package/dist/assembly/plain.js +1 -2
- package/dist/assembly/plain.js.map +1 -1
- package/dist/authoring-skill/archmax-harness/README.md +5 -5
- package/dist/authoring-skill/archmax-harness/SKILL.md +72 -30
- package/dist/authoring-skill/archmax-harness/references/backend-integration.md +33 -24
- package/dist/authoring-skill/archmax-harness/references/cases.md +41 -8
- package/dist/authoring-skill/archmax-harness/references/{hook-and-test-scripts.md → code-state-and-test-scripts.md} +599 -339
- package/dist/authoring-skill/archmax-harness/references/code-states-and-scripts.md +115 -0
- package/dist/authoring-skill/archmax-harness/references/diagnostics.md +40 -7
- package/dist/authoring-skill/archmax-harness/references/wiring.md +19 -10
- package/dist/authoring-skill/archmax-harness/references/workflow-schema.md +404 -293
- package/dist/authoring-skill/archmax-harness/references/workflow-yaml.md +151 -62
- package/dist/cli/state-flow.d.ts +1 -1
- package/dist/cli/state-flow.js +18 -28
- package/dist/cli/state-flow.js.map +1 -1
- package/dist/cli/turn.d.ts +1 -1
- package/dist/cli/turn.js +1 -1
- package/dist/core/code-description.d.ts +3 -3
- package/dist/core/code-description.js +3 -3
- package/dist/core/deepagents.d.ts +5 -12
- package/dist/core/deepagents.js +4 -17
- package/dist/core/deepagents.js.map +1 -1
- package/dist/core/events.d.ts +23 -42
- package/dist/core/events.js +21 -24
- package/dist/core/events.js.map +1 -1
- package/dist/core/message-readers.d.ts +4 -5
- package/dist/core/message-readers.js +3 -8
- package/dist/core/message-readers.js.map +1 -1
- package/dist/core/messages.d.ts +0 -22
- package/dist/core/messages.js +1 -32
- package/dist/core/messages.js.map +1 -1
- package/dist/core/mounts.js +3 -3
- package/dist/core/platform-prompt.generated.js +1 -1
- package/dist/core/platform-prompt.generated.js.map +1 -1
- package/dist/core/session-zone.d.ts +1 -1
- package/dist/core/session-zone.js +1 -1
- package/dist/core/workspace-context.d.ts +1 -1
- package/dist/core/workspace.d.ts +1 -1
- package/dist/core/workspace.js +1 -1
- package/dist/core/zones.d.ts +4 -4
- package/dist/core/zones.js +5 -5
- package/dist/core/zones.js.map +1 -1
- package/dist/env.d.ts +9 -9
- package/dist/index.d.ts +3 -7
- package/dist/index.js +2 -4
- package/dist/index.js.map +1 -1
- package/dist/kernel/kernel.d.ts +31 -61
- package/dist/kernel/kernel.js +54 -112
- package/dist/kernel/kernel.js.map +1 -1
- package/dist/machine/lint-spec.js +53 -12
- package/dist/machine/lint-spec.js.map +1 -1
- package/dist/machine/machine.d.ts +36 -18
- package/dist/machine/machine.js +62 -51
- package/dist/machine/machine.js.map +1 -1
- package/dist/machine/spec-schema.d.ts +32 -180
- package/dist/machine/spec-schema.js +52 -113
- package/dist/machine/spec-schema.js.map +1 -1
- package/dist/machine/tool-names.d.ts +34 -16
- package/dist/machine/tool-names.js +41 -16
- package/dist/machine/tool-names.js.map +1 -1
- package/dist/machine/types.d.ts +14 -46
- package/dist/machine/types.js +0 -2
- package/dist/machine/types.js.map +1 -1
- package/dist/machine/validate-spec.d.ts +1 -1
- package/dist/public/sandbox.d.ts +59 -95
- package/dist/public/sandbox.js +22 -23
- package/dist/public/sandbox.js.map +1 -1
- package/dist/public/spec.d.ts +13 -20
- package/dist/public/spec.js +12 -20
- package/dist/public/spec.js.map +1 -1
- package/dist/runtime/contract.d.ts +11 -12
- package/dist/runtime/contract.js +16 -16
- package/dist/runtime/contract.js.map +1 -1
- package/dist/sandbox/assets/parts/code-state.js +17 -0
- package/dist/sandbox/executor.d.ts +25 -7
- package/dist/sandbox/executor.js +56 -57
- package/dist/sandbox/executor.js.map +1 -1
- package/dist/sandbox/imports.d.ts +10 -10
- package/dist/sandbox/imports.js +11 -11
- package/dist/sandbox/imports.js.map +1 -1
- package/dist/sandbox/prelude.d.ts +5 -6
- package/dist/sandbox/prelude.js +38 -35
- package/dist/sandbox/prelude.js.map +1 -1
- package/dist/sandbox/ptc-gateway.d.ts +19 -18
- package/dist/sandbox/ptc-gateway.js +26 -22
- package/dist/sandbox/ptc-gateway.js.map +1 -1
- package/dist/sessions/resume.d.ts +2 -2
- package/dist/sessions/resume.js +1 -1
- package/dist/sessions/resume.js.map +1 -1
- package/dist/sessions/scope.d.ts +0 -10
- package/dist/sessions/scope.js +0 -17
- package/dist/sessions/scope.js.map +1 -1
- package/dist/sessions/summary.d.ts +1 -1
- package/dist/testing/assertions.js +9 -4
- package/dist/testing/assertions.js.map +1 -1
- package/dist/testing/driver.js +3 -0
- package/dist/testing/driver.js.map +1 -1
- package/dist/testing/interpreter.d.ts +2 -2
- package/dist/testing/runner.d.ts +2 -2
- package/dist/validate/validate.d.ts +1 -1
- package/dist/validate/validate.js +39 -70
- package/dist/validate/validate.js.map +1 -1
- package/dist/workflow/automated-controls.d.ts +63 -0
- package/dist/workflow/automated-controls.js +158 -0
- package/dist/workflow/automated-controls.js.map +1 -0
- package/dist/workflow/automated.d.ts +112 -0
- package/dist/workflow/automated.js +163 -0
- package/dist/workflow/automated.js.map +1 -0
- package/dist/workflow/code-state.d.ts +46 -0
- package/dist/workflow/code-state.js +26 -0
- package/dist/workflow/code-state.js.map +1 -0
- package/dist/workflow/control-tools.d.ts +54 -23
- package/dist/workflow/control-tools.js +99 -79
- package/dist/workflow/control-tools.js.map +1 -1
- package/dist/workflow/file-values.d.ts +4 -4
- package/dist/workflow/file-values.js +1 -1
- package/dist/workflow/file-values.js.map +1 -1
- package/dist/workflow/governance.d.ts +11 -22
- package/dist/workflow/governance.js +47 -104
- package/dist/workflow/governance.js.map +1 -1
- package/dist/workflow/grader-state.d.ts +55 -0
- package/dist/workflow/grader-state.js +182 -0
- package/dist/workflow/grader-state.js.map +1 -0
- package/dist/workflow/history.d.ts +11 -0
- package/dist/{lifecycle/transcript.js → workflow/history.js} +24 -8
- package/dist/workflow/history.js.map +1 -0
- package/dist/workflow/middleware.d.ts +14 -17
- package/dist/workflow/middleware.js +109 -95
- package/dist/workflow/middleware.js.map +1 -1
- package/dist/workflow/move-events.d.ts +21 -0
- package/dist/workflow/move-events.js +20 -0
- package/dist/workflow/move-events.js.map +1 -0
- package/dist/workflow/on-error.js +0 -2
- package/dist/workflow/on-error.js.map +1 -1
- package/dist/workflow/parks.d.ts +9 -2
- package/dist/workflow/parks.js +16 -10
- package/dist/workflow/parks.js.map +1 -1
- package/dist/workflow/paths.d.ts +9 -9
- package/dist/workflow/paths.js +19 -19
- package/dist/workflow/paths.js.map +1 -1
- package/dist/workflow/render-prompt.d.ts +7 -5
- package/dist/workflow/render-prompt.js +22 -37
- package/dist/workflow/render-prompt.js.map +1 -1
- package/dist/workflow/signature-checks.d.ts +2 -2
- package/dist/workflow/snapshot.js +5 -29
- package/dist/workflow/snapshot.js.map +1 -1
- package/dist/workflow/state.d.ts +14 -42
- package/dist/workflow/state.js +20 -33
- package/dist/workflow/state.js.map +1 -1
- package/dist/workflow/sub-workflow.d.ts +3 -3
- package/dist/workflow/sub-workflow.js +3 -7
- package/dist/workflow/sub-workflow.js.map +1 -1
- package/dist/workflow/tool-service.d.ts +1 -9
- package/dist/workflow/tool-service.js +21 -24
- package/dist/workflow/tool-service.js.map +1 -1
- package/dist/workflow/turn-boundary.d.ts +2 -2
- package/dist/workflow/turn-boundary.js +10 -15
- package/dist/workflow/turn-boundary.js.map +1 -1
- package/dist/workflow/turn-runner.js +5 -6
- package/dist/workflow/turn-runner.js.map +1 -1
- package/package.json +2 -2
- package/skills/archmax-harness/README.md +5 -5
- package/skills/archmax-harness/SKILL.md +72 -30
- package/skills/archmax-harness/references/backend-integration.md +33 -24
- package/skills/archmax-harness/references/cases.md +41 -8
- package/skills/archmax-harness/references/{hook-and-test-scripts.md → code-state-and-test-scripts.md} +599 -339
- package/skills/archmax-harness/references/code-states-and-scripts.md +115 -0
- package/skills/archmax-harness/references/diagnostics.md +40 -7
- package/skills/archmax-harness/references/wiring.md +19 -10
- package/skills/archmax-harness/references/workflow-schema.md +404 -293
- package/skills/archmax-harness/references/workflow-yaml.md +151 -62
- package/dist/authoring-skill/archmax-harness/references/hooks-and-scripts.md +0 -89
- package/dist/lifecycle/hook-shape.d.ts +0 -44
- package/dist/lifecycle/hook-shape.js +0 -66
- package/dist/lifecycle/hook-shape.js.map +0 -1
- package/dist/lifecycle/runner.d.ts +0 -197
- package/dist/lifecycle/runner.js +0 -419
- package/dist/lifecycle/runner.js.map +0 -1
- package/dist/lifecycle/tool-calls.d.ts +0 -9
- package/dist/lifecycle/tool-calls.js +0 -57
- package/dist/lifecycle/tool-calls.js.map +0 -1
- package/dist/lifecycle/transcript.d.ts +0 -31
- package/dist/lifecycle/transcript.js.map +0 -1
- package/dist/rubrics/rubrics.d.ts +0 -135
- package/dist/rubrics/rubrics.js +0 -145
- package/dist/rubrics/rubrics.js.map +0 -1
- package/dist/sandbox/assets/parts/core.js +0 -22
- package/dist/sandbox/assets/parts/lifecycle-hook.js +0 -47
- package/dist/sandbox/bridge.d.ts +0 -16
- package/dist/sandbox/bridge.js +0 -95
- package/dist/sandbox/bridge.js.map +0 -1
- package/dist/workflow/visits.d.ts +0 -21
- package/dist/workflow/visits.js +0 -45
- package/dist/workflow/visits.js.map +0 -1
- package/skills/archmax-harness/references/hooks-and-scripts.md +0 -89
|
@@ -93,9 +93,10 @@ All optional (`CreateAgentParams`, `src/assembly/index.ts`):
|
|
|
93
93
|
| `rootDir` | `string` | Workspace root for the default filesystem backend. Defaults to cwd. |
|
|
94
94
|
| `workflow` | `string` | Named workflow under `workflows/<name>/`. |
|
|
95
95
|
| `trigger` | `TriggerInput` | `{ id, args? }`; defaults to `{ id: "manual" }`. Selects the start state. |
|
|
96
|
-
| `model` | `BaseChatModel` | Defaults to the env-configured OpenAI-compatible model. Inject your own to bypass env. |
|
|
96
|
+
| `model` | `BaseChatModel` | Defaults to the env-configured OpenAI-compatible model. Inject your own to bypass env. An explicit instance outranks every model id a spec declares, grader states' included. |
|
|
97
|
+
| `modelFactory` | `(role, env, requested?) => BaseChatModel` | Supplies the chat model per role: `agent` (the session's turns), `judge` (the case judge for `grade:`) and `grader` (a grader state's subagent). `requested` is the id the state or `settings.model` declares, for the `agent` and `grader` roles; a grader state's model is `modelFactory("grader", env, id)`, resolved once per composition. `env` is a thunk, so a factory with its own credentials never loads `.env`. |
|
|
97
98
|
| `backend` | `BackendProtocolV2` | Serves the authored workspace. Defaults to a `FilesystemBackend` over `rootDir`. |
|
|
98
|
-
| `authoring` | `BackendProtocolV2` | Serves the **authoring plane** — `workflows/**` (machine specs
|
|
99
|
+
| `authoring` | `BackendProtocolV2` | Serves the **authoring plane** — `workflows/**` (machine specs, `WORKFLOW.md`, code-state `scripts/`, `tests/`) — to the runtime only. Never a route in the agent's workspace, so no tool call or PTC call can reach it. Defaults to `backend` when you supply one, else a `FilesystemBackend` over the resolved root. Handing it to a **writable** mount throws `AuthoringBackendExposedError`; a read-only mount may share it. |
|
|
99
100
|
| `mounts` | `Record<string, MountSpec>` | Authored mounts as a `CompositeBackend` route table: key → backend, or `{ backend, readOnly, governed, searchable }`. Trailing slash = directory mount; no slash = exact-path file mount (`"/AGENTS.md"`). Read-only unless declared otherwise. `governed: true` hands the spec's `mounts` block the decision of **which states** may reach it (closed by default, like a skill bundle) and whether each may write there; a mount not so declared is visible in every state exactly as today, so a table that marks nothing behaves unchanged. `readOnly` is a **ceiling** the spec cannot lift: a governed writable mount is written only by a workflow whose own grants ask `access: read_write` (a bare grant reads it, so serving one route writable for one workflow in a delegation tree opens it to no other), and a grant may narrow a writable mount to reads in a state, never open a read-only one. A key may be several segments deep (`"/catalogs/eu/"`); its **first** segment is what is reserved, so `/workflows/x/` fails as `/workflows/` does. Omitted on the default backend: `defaultMounts(rootDir)` (the conventional table — `/skills/`, `/.platform/`, `/AGENTS.md`, none governed — extend with `{ ...defaultMounts(root), "/templates/": … }`). Omitted with a custom `backend`: nothing authored is served; expose subtrees with `mountSubtree(backend, "skills")`. A key colliding with a run area — or naming the authoring-plane prefix (`/workflows/`) — throws `MountCollisionError`. `searchable: false` declares a mount the runtime never searches on its own initiative: a root-wide `grep`/`glob` fans out over the other mounts only, while a search the agent addresses at the mount (or a path inside it) still reaches its backend and returns the backend's own answer — matches or `{ error }` — verbatim. Use it for a backend that must refuse searches (a remote folder served live). Listing and reading are unchanged, the prompt marks it "browse only", names arrive as `MountPrefixes.unsearchable`, and it is ignored on a file mount. |
|
|
100
101
|
| `sessionStore` | `SessionStore` | Physical storage for run state (checkpoints, artifacts, the per-session `scratchpad/`). Zero-config default: `createFilesystemSessionStore({ dir: "<rootDir>/sessions" })`. Required with a custom `backend` (else `SessionStoreRequiredError`). Build with `createBackendSessionStore({ backend, prefix? })` for S3-style storage or `createMemorySessionStore()` for ephemeral runs. |
|
|
101
102
|
| `systemPrompt` | `string` | Appended after workspace `AGENTS.md`. |
|
|
@@ -145,7 +146,7 @@ const sessionId = `run-${runId}`;
|
|
|
145
146
|
try {
|
|
146
147
|
const result = await agent.invoke(
|
|
147
148
|
{ messages: [{ role: "user", content: prompt }] },
|
|
148
|
-
{ configurable: { thread_id: sessionId }
|
|
149
|
+
{ configurable: { thread_id: sessionId } },
|
|
149
150
|
);
|
|
150
151
|
// result: { messages, status?, workflowState?, pendingDecision? }
|
|
151
152
|
if (result.status === WORKFLOW_STATUSES.awaitingDecision) {
|
|
@@ -158,6 +159,12 @@ try {
|
|
|
158
159
|
}
|
|
159
160
|
```
|
|
160
161
|
|
|
162
|
+
A turn runs under LangGraph's `recursionLimit` — 400 unless the config passes its
|
|
163
|
+
own, which wins. It is the one bound on a whole turn (every model call, tool batch
|
|
164
|
+
and automated state is a step; a state's `budget.maxTurns`/`maxParks` bound that
|
|
165
|
+
state); a turn that hits it ends the session `failed` with exit code
|
|
166
|
+
`recursion-limit`.
|
|
167
|
+
|
|
161
168
|
To cancel a turn, pass an `AbortSignal` as `signal` in the same config (or in
|
|
162
169
|
`agent.workflow.send(sessionId, input, { signal })`) and abort it. The turn's
|
|
163
170
|
scripts stop with it, in the session and its child sessions: a pending `tools.*`
|
|
@@ -187,12 +194,12 @@ checkpoint:
|
|
|
187
194
|
// turn 1
|
|
188
195
|
await agent.invoke(
|
|
189
196
|
{ messages: [{ role: "user", content: "Look up order 1234" }] },
|
|
190
|
-
{ configurable: { thread_id: "chat-42" }
|
|
197
|
+
{ configurable: { thread_id: "chat-42" } },
|
|
191
198
|
);
|
|
192
199
|
// turn 2 — SAME thread_id; only the new message
|
|
193
200
|
await agent.invoke(
|
|
194
201
|
{ messages: [{ role: "user", content: "And when did it ship?" }] },
|
|
195
|
-
{ configurable: { thread_id: "chat-42" }
|
|
202
|
+
{ configurable: { thread_id: "chat-42" } },
|
|
196
203
|
);
|
|
197
204
|
```
|
|
198
205
|
|
|
@@ -215,19 +222,20 @@ await agent.invoke(
|
|
|
215
222
|
{ role: "user", content: "what was my first message?" }, // the new turn
|
|
216
223
|
],
|
|
217
224
|
},
|
|
218
|
-
{ configurable: { thread_id: "chat-42" }
|
|
225
|
+
{ configurable: { thread_id: "chat-42" } },
|
|
219
226
|
);
|
|
220
227
|
```
|
|
221
228
|
|
|
222
229
|
Gotchas:
|
|
223
230
|
|
|
224
|
-
- **
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
+
- **An automated state reads the whole session.** A code state and a grader
|
|
232
|
+
both call `archmax_get_history` for `{ messages, trail }` (a script:
|
|
233
|
+
`JSON.parse(await tools.archmax_get_history())`). Its `messages` are the whole
|
|
234
|
+
transcript, oldest first, which can span turns: a script that keys off "the
|
|
235
|
+
current request" should take the *last* `user` entry, not the first — the
|
|
236
|
+
first is the message that opened the session. A tool entry carries its tool,
|
|
237
|
+
the call `id` and the result as its `text`. Neither's own calls or messages
|
|
238
|
+
enter the session's transcript.
|
|
231
239
|
- **A definition edit is adopted, not refused.** A session takes its next turn
|
|
232
240
|
under the `workflow.yaml` in force now; the recorded `specHash` says which
|
|
233
241
|
definition each turn ran under but gates nothing. A turn positioned in a state
|
|
@@ -236,7 +244,7 @@ Gotchas:
|
|
|
236
244
|
in seeded history, or the provider may reject the turn.
|
|
237
245
|
- **Not every message in a session's transcript is a person's.** The runtime
|
|
238
246
|
writes **runtime notes** of its own — `[decision]`, `[event]`, `[error]`,
|
|
239
|
-
`[
|
|
247
|
+
`[grader]`, `[code]`, `[sub-workflow: …]` — carried as a synthetic `archmax_note` tool call
|
|
240
248
|
and its result, so a model cannot read them as a person speaking. Ask
|
|
241
249
|
`isRuntimeNote(message)` / `runtimeNoteKind(message)` rather than inferring
|
|
242
250
|
authorship from role or a `[bracket]` prefix: a turn's lower bound is a human
|
|
@@ -249,7 +257,7 @@ Gotchas:
|
|
|
249
257
|
import { isRuntimeNote, runtimeNoteKind } from "@archmax-ai/harness";
|
|
250
258
|
|
|
251
259
|
const opensTurn = (m: unknown) => isRuntimeNote(m) || m.role === "user";
|
|
252
|
-
const label = (m: unknown) => runtimeNoteKind(m); // "decision" | "event" | … | null
|
|
260
|
+
const label = (m: unknown) => runtimeNoteKind(m); // "decision" | "event" | "grader" | "code" | … | null
|
|
253
261
|
```
|
|
254
262
|
|
|
255
263
|
## Human-in-the-loop
|
|
@@ -264,6 +272,7 @@ if (result.status === WORKFLOW_STATUSES.awaitingDecision && result.pendingDecisi
|
|
|
264
272
|
// When they pick:
|
|
265
273
|
const outcome = await agent.workflow.decide(sessionId, { target: chosenTo, comment });
|
|
266
274
|
// DecideOutcome: { status?, workflowState?, reparked, state?, reply, rejected?, exit?, messages }
|
|
275
|
+
// `reply` is every assistant text of the turn, in order ("" when it said nothing).
|
|
267
276
|
// `rejected` says why when status is "rejected"; Outcome from send() carries it too.
|
|
268
277
|
// `exit` is { success: false, code, reason } when the agent ended the run with
|
|
269
278
|
// archmax_raise (status "failed"), { success: true } when it completed.
|
|
@@ -425,17 +434,17 @@ Pass `onEvent`; every `WorkflowLifecycleEvent` carries a `level`
|
|
|
425
434
|
| --- | --- |
|
|
426
435
|
| `state-enter` / `state-leave` | state entered / left (`{ state, next? }`) |
|
|
427
436
|
| `advance` | transition committed (`{ from, to, reason?, callId? }` — `callId` names the `archmax_advance` call that drove it; absent on the turn's opening arrival) |
|
|
428
|
-
| `
|
|
437
|
+
| `code-start` / `code-output` / `code-result` | a code state's run: `{ state, script }`, one `{ state, line }` per `console.log` line, then `{ state, status: "ok" \| "error", durationMs, reason? }` |
|
|
438
|
+
| `grader-start` / `grader-result` | a grader state's run: `{ state }`, then `{ state, status, durationMs, reason? }`. `status` is `ok` when the run settled on a decision or a completion; a move then emits `advance`/`state-leave`/`state-enter` (a code state's tool calls carry `origin: "script"`, a grader's none) |
|
|
429
439
|
| `parked` / `decided` | human state parked / resolved (`{ state, sessionId, awaiting, to? }` — a human park carries no `callId`, since no call asked for it) |
|
|
430
440
|
| `parked` (`awaiting: "input"`) / `delivered` | the agent parked the run (`{ state, sessionId, reason, resumeAt?, callId }` — the `archmax_wait` call) / an event resumed it (`{ state, trigger, to }`, where `to` is the same state) |
|
|
431
441
|
| `raised` | the agent ended the session as a failure (`{ state, sessionId, code, reason, callId }` — the `archmax_raise` call); a `state-leave` follows, and the outcome is `failed` |
|
|
432
442
|
| `agent-text` | complete assistant message in a state (`{ state, text, messageId }` — always the id the message is stored under; absent only on a `partial: true` event from a failed turn) |
|
|
433
443
|
| `agent-text-delta` | streaming text chunk (`{ state, text, messageId? }`) — flows for every run, even when the graph is driven with `invoke`; a sub-workflow child's chunks carry the child's `sessionId` and `subWorkflowDispatchId`, never the caller's state |
|
|
434
444
|
| `tool-called` / `tool-result` / `tool-blocked` | tool passed governance (`callId`, `args`) / settled (`callId`, `status`, `durationMs`, `output` preview) / denied. **Every** governed call, `archmax_advance` included — so a UI drawing a row per call draws one per transition; filter it if you already draw transitions from `advance`/`state-leave`. A tool that **throws** settles `status: "error"` with the error's message as `output` while the turn **continues**: the model reads the same message as the call's answer, and nothing is thrown out of `send` — read tool failures here |
|
|
435
|
-
| `
|
|
436
|
-
| `model-usage` | per model call provider token usage: `inputTokens` (the total, of which the cache counts are a breakdown), `outputTokens`, `cacheReadTokens`, `cacheCreationTokens`, `model?` (the id the call was priced against), `costUsd?` |
|
|
445
|
+
| `model-usage` | per model call — the agent's or a grader's, under its state — provider token usage: `inputTokens` (the total, of which the cache counts are a breakdown), `outputTokens`, `cacheReadTokens`, `cacheCreationTokens`, `model?` (the id the call was priced against), `costUsd?` |
|
|
437
446
|
| `prompt-shaping` | assembly-time payload summary: resolved `profile`, prompt-cache mechanism, `withheld` built-ins |
|
|
438
|
-
| `skills-loaded`, `
|
|
447
|
+
| `skills-loaded`, `graph-topology`, `workflow-reset`, `warning` | assembly-time diagnostics |
|
|
439
448
|
|
|
440
449
|
For a browser client, forward these over SSE/WebSocket keyed by `sessionId`. To
|
|
441
450
|
render a live chat transcript: accumulate `agent-text-delta` into a bubble
|
|
@@ -508,7 +517,7 @@ gives the same figure live in process.
|
|
|
508
517
|
### Measuring anything else — fold the stream
|
|
509
518
|
|
|
510
519
|
The SDK keeps no metrics ledger. Tool calls, governance refusals, the path a run
|
|
511
|
-
took,
|
|
520
|
+
took, every send-back by a grader or code state, and how long a session
|
|
512
521
|
sat waiting for a person are all already on the typed event stream, and a backend
|
|
513
522
|
counts what it needs:
|
|
514
523
|
|
|
@@ -581,8 +590,8 @@ interface RunMetadata { sessionId: string; workflow: string;
|
|
|
581
590
|
runtimeContract: ResolvedRuntimeContract; packageVersion: string; }
|
|
582
591
|
// trail.json: { sessionId, workflow, steps: TrailStep[] } — the audit trail,
|
|
583
592
|
// re-derived from checkpointed state on every artifact write.
|
|
584
|
-
// TrailStep: { to, kind: "trigger"|"agent"|"human"|"
|
|
585
|
-
// reason?, ts }
|
|
593
|
+
// TrailStep: { to, kind: "trigger"|"agent"|"human"|"grader"|"code"|
|
|
594
|
+
// "sub-workflow"|"reset"|"on_error", reason?, workflow?, status?, ts }
|
|
586
595
|
```
|
|
587
596
|
|
|
588
597
|
An **API** reads these files (through the same session store, or via `runArtifactPaths(sessionId)`
|
|
@@ -591,7 +600,7 @@ path helpers) to render historical runs: `graph.json`/`graph.mmd` for the diagra
|
|
|
591
600
|
path (which edges committed, by whom, and why), `variables.json` for the run's
|
|
592
601
|
inputs and established facts (also what a test author reads to reproduce a real
|
|
593
602
|
firing's payload — see
|
|
594
|
-
[
|
|
603
|
+
[code-state-and-test-scripts.md](code-state-and-test-scripts.md#recovering-a-cases-inputs-from-previous-runs)).
|
|
595
604
|
Note it holds whatever the run's variables held, verbatim: a host that must not
|
|
596
605
|
persist an input should not seed it. To persist artifacts yourself,
|
|
597
606
|
stream the run and call `agent.emitRunArtifacts(sessionId, trajectory)`.
|
|
@@ -752,7 +761,7 @@ workflow-governed target.
|
|
|
752
761
|
an `archmax_workflow_<slug>` tool), the child runtime is
|
|
753
762
|
composed from the *same* resolved inputs as the parent — model and
|
|
754
763
|
`modelFactory`, `backend` and `mounts`, `sessionStore` and `checkpointer`, host
|
|
755
|
-
`tools` and `essentialTools`, `sandboxRuntime`,
|
|
764
|
+
`tools` and `essentialTools`, `sandboxRuntime`,
|
|
756
765
|
`policyRules`, `promptCache` and `pricing` — differing only in
|
|
757
766
|
the machine that governs it and the system prompt that machine renders. So a
|
|
758
767
|
custom backend serves the child's spec too, and a `modelFactory` supplies its
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Load this when writing or fixing a case. Worked examples for every shape
|
|
4
4
|
(mocks, human decisions, triggered starts, fixtures):
|
|
5
|
-
[`
|
|
5
|
+
[`code-state-and-test-scripts.md`](code-state-and-test-scripts.md) Part 2.
|
|
6
6
|
|
|
7
7
|
## Keys, actions, assertions
|
|
8
8
|
|
|
@@ -18,7 +18,7 @@ it). Host-interpreted; no sandbox runs a case. **Unknown keys are errors.**
|
|
|
18
18
|
| `trigger` | `{ id }` only — the trigger every turn runs under (default `manual`); unknown id fails the case |
|
|
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
|
-
| `mocks` | `[{ tool, whenInput?, result }]` — intercepts agent and
|
|
21
|
+
| `mocks` | `[{ tool, whenInput?, result }]` — intercepts the agent's, its scripts', code states' and graders' calls alike (partial-input match); a control tool (`archmax_*`) is never mocked |
|
|
22
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 |
|
|
23
23
|
| `steps` | one flat list; actions and assertions are peers; an assertion evaluates against the nearest action above it |
|
|
24
24
|
|
|
@@ -27,17 +27,50 @@ message that leaves it parked), `decide: { to, comment? }`,
|
|
|
27
27
|
`deliver: { trigger, variables? }`.
|
|
28
28
|
|
|
29
29
|
Assertions (**structural** ones halt the case when they miss; later steps are
|
|
30
|
-
reported `not-executed`): `succeeded: true`ˢ (
|
|
30
|
+
reported `not-executed`): `succeeded: true`ˢ (the action's outcome is `completed` — not parked, raised, failed or rejected; a failed or blocked tool call the turn recovered from does not fail it, and a miss says why) · `parked: true | decision | input | { channel?, state? }`ˢ ·
|
|
31
31
|
`raised: true | <code> | { code?, reason? }`ˢ (the agent ended the turn with `archmax_raise`; code exact, reason exact or `"/regex/flags"`) ·
|
|
32
|
-
`reachedState: <slug>`ˢ (a committed non-trigger trail step) ·
|
|
33
|
-
`trail: { to?, kind?, reason?, count }`ˢ (`kind`: trigger/agent/human/on_error) ·
|
|
34
|
-
`noTraversal: true`ˢ · `triggerArrival: <id>`ˢ ·
|
|
32
|
+
`reachedState: <slug>`ˢ (a committed non-trigger trail step, an automated state's move included) ·
|
|
33
|
+
`trail: { to?, kind?, reason?, count }`ˢ (`kind`: trigger/agent/human/grader/code/sub-workflow/reset/on_error; `count: 0` = never entered) ·
|
|
34
|
+
`noTraversal: true`ˢ (nothing moved; a code or grader move is a traversal) · `triggerArrival: <id>`ˢ ·
|
|
35
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) ·
|
|
36
|
-
`calledTool` / `notCalledTool` / `blockedTool: { name, input? }` (partial input; `blockedTool` = governance refused it — the assertion that tests a guard) ·
|
|
37
|
-
`ranWorkflow: { workflow, status?, count? }` · `usedNoTools: true` (whole conversation) ·
|
|
36
|
+
`calledTool` / `notCalledTool` / `blockedTool: { name, input? }` (partial input; `blockedTool` = governance refused it — the assertion that tests a guard; the agent's calls only) ·
|
|
37
|
+
`ranWorkflow: { workflow, status?, count? }` (whoever dispatched) · `usedNoTools: true` (whole conversation, the agent's calls only) ·
|
|
38
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`).
|
|
39
39
|
|
|
40
40
|
Put the structural assertion right after the action that should satisfy it,
|
|
41
41
|
before content assertions that only make sense in that state:
|
|
42
42
|
|
|
43
43
|
```yaml
|
|
44
|
+
steps:
|
|
45
|
+
- send: "I want a refund for ORD-1001"
|
|
46
|
+
- reachedState: refund-review # structural: if this misses, the case stops here
|
|
47
|
+
- parked: true
|
|
48
|
+
- reply:
|
|
49
|
+
includes: "approved"
|
|
50
|
+
- decide: { to: refund-closed }
|
|
51
|
+
- succeeded: true
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Grader and code states are asserted by what they committed.** Their own tool
|
|
55
|
+
calls are not in the transcript (`calledTool`, `usedNoTools` never see them) and a
|
|
56
|
+
grader's words are never the `reply`. Pin the move and its reason with `trail`,
|
|
57
|
+
a raise with `raised`, a write with `variables`:
|
|
58
|
+
|
|
59
|
+
```yaml
|
|
60
|
+
steps:
|
|
61
|
+
- send: "Hi, I'm support@acmecorp.com. Which orders are delayed?"
|
|
62
|
+
- trail: { kind: code, to: identify-case, count: 1 } # the entry screen passed it
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```yaml
|
|
66
|
+
trigger: { id: enrichment_requested } # the enrichment path
|
|
67
|
+
variables: { orders_to_enrich: [{ order_id: "ORD-1002" }, { order_id: "ORD-1003" }] }
|
|
68
|
+
steps:
|
|
69
|
+
- send: "Enrich the requested orders."
|
|
70
|
+
- trail: { kind: grader, to: enrich-order, count: 0 } # no send-back
|
|
71
|
+
- trail: { kind: grader, to: enrichment-reported, count: 1 } # the grader approved
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
A case that declines at entry asserts `reachedState: <declining state>` plus
|
|
75
|
+
`trail: { to: <first work state>, count: 0 }`; one whose entry code state raised
|
|
76
|
+
asserts `raised: <code>` and `noTraversal: true`.
|