@archmax-ai/harness 0.8.0 → 1.0.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/README.md +58 -14
- package/dist/agent.d.ts +4 -4
- package/dist/agent.js +4 -4
- package/dist/assembly/compose.d.ts +5 -24
- package/dist/assembly/compose.js +40 -41
- 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 +18 -15
- package/dist/assembly/index.js +50 -32
- 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 +35 -23
- package/dist/authoring-skill/archmax-harness/references/cases.md +44 -10
- package/dist/authoring-skill/archmax-harness/references/{hook-and-test-scripts.md → code-state-and-test-scripts.md} +617 -313
- 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 +416 -276
- package/dist/authoring-skill/archmax-harness/references/workflow-yaml.md +161 -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 +2 -3
- 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/code-description.d.ts +3 -3
- package/dist/core/code-description.js +3 -3
- package/dist/core/deepagents.d.ts +5 -5
- package/dist/core/deepagents.js +4 -4
- 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 +22 -2
- package/dist/core/message-readers.js +31 -0
- package/dist/core/message-readers.js.map +1 -1
- package/dist/core/messages.d.ts +1 -23
- package/dist/core/messages.js +2 -35
- 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 +57 -114
- package/dist/kernel/kernel.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 +68 -12
- package/dist/machine/lint-spec.js.map +1 -1
- package/dist/machine/machine.d.ts +39 -16
- package/dist/machine/machine.js +72 -49
- package/dist/machine/machine.js.map +1 -1
- package/dist/machine/spec-schema.d.ts +34 -179
- package/dist/machine/spec-schema.js +93 -106
- package/dist/machine/spec-schema.js.map +1 -1
- package/dist/machine/tool-names.d.ts +34 -10
- package/dist/machine/tool-names.js +41 -10
- 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 +14 -38
- package/dist/machine/types.js.map +1 -1
- package/dist/machine/validate-spec.d.ts +1 -1
- package/dist/machine/variables.d.ts +2 -0
- package/dist/machine/variables.js.map +1 -1
- package/dist/public/sandbox.d.ts +62 -66
- package/dist/public/sandbox.js +22 -23
- package/dist/public/sandbox.js.map +1 -1
- package/dist/public/spec.d.ts +12 -19
- package/dist/public/spec.js +11 -19
- 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 -47
- 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/operations.js +4 -5
- package/dist/sessions/operations.js.map +1 -1
- package/dist/sessions/resume.d.ts +12 -7
- package/dist/sessions/resume.js +12 -9
- package/dist/sessions/resume.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/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 +22 -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.d.ts +2 -2
- package/dist/testing/interpreter.js +1 -0
- package/dist/testing/interpreter.js.map +1 -1
- 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/checkpointer.d.ts +7 -0
- package/dist/workflow/checkpointer.js +28 -0
- package/dist/workflow/checkpointer.js.map +1 -1
- 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 +121 -77
- 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 +13 -22
- package/dist/workflow/governance.js +51 -87
- 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/workflow/history.js +64 -0
- package/dist/workflow/history.js.map +1 -0
- package/dist/workflow/middleware.d.ts +14 -17
- package/dist/workflow/middleware.js +106 -74
- 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/parks.d.ts +9 -2
- package/dist/workflow/parks.js +16 -8
- 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 +19 -6
- package/dist/workflow/state.js +24 -8
- package/dist/workflow/state.js.map +1 -1
- package/dist/workflow/sub-workflow.d.ts +3 -3
- 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 -7
- package/dist/workflow/turn-boundary.js.map +1 -1
- package/dist/workflow/turn-runner.js +30 -2
- package/dist/workflow/turn-runner.js.map +1 -1
- package/package.json +1 -1
- package/skills/archmax-harness/README.md +5 -5
- package/skills/archmax-harness/SKILL.md +72 -30
- package/skills/archmax-harness/references/backend-integration.md +35 -23
- package/skills/archmax-harness/references/cases.md +44 -10
- package/skills/archmax-harness/references/{hook-and-test-scripts.md → code-state-and-test-scripts.md} +617 -313
- 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 +416 -276
- package/skills/archmax-harness/references/workflow-yaml.md +161 -62
- package/dist/authoring-skill/archmax-harness/references/hooks-and-scripts.md +0 -88
- 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 -183
- package/dist/lifecycle/runner.js +0 -373
- package/dist/lifecycle/runner.js.map +0 -1
- package/dist/lifecycle/transcript.d.ts +0 -27
- package/dist/lifecycle/transcript.js +0 -43
- 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/skills/archmax-harness/references/hooks-and-scripts.md +0 -88
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The complete, field-by-field reference for authoring a `workflow.yaml` (and its
|
|
4
4
|
optional `WORKFLOW.md` prose addendum) plus the surrounding workspace. Targets
|
|
5
|
-
**runtime contract version `"
|
|
5
|
+
**runtime contract version `"3"`**.
|
|
6
6
|
|
|
7
7
|
## Contents
|
|
8
8
|
|
|
@@ -14,9 +14,9 @@ optional `WORKFLOW.md` prose addendum) plus the surrounding workspace. Targets
|
|
|
14
14
|
- [Tool governance](#tool-governance)
|
|
15
15
|
- [Skill governance](#skill-governance)
|
|
16
16
|
- [Mount governance](#mount-governance)
|
|
17
|
-
- [
|
|
17
|
+
- [Grader and code states](#grader-and-code-states)
|
|
18
18
|
— full script contract + worked examples in
|
|
19
|
-
[`
|
|
19
|
+
[`code-state-and-test-scripts.md`](code-state-and-test-scripts.md)
|
|
20
20
|
- [Budgets & error routing](#budgets--error-routing)
|
|
21
21
|
- [Human states](#human-states)
|
|
22
22
|
- [Sub-workflows are tools](#sub-workflows-are-tools)
|
|
@@ -36,8 +36,8 @@ optional `WORKFLOW.md` prose addendum) plus the surrounding workspace. Targets
|
|
|
36
36
|
- **`workflow.yaml`** — the canonical, enforced machine spec: a **pure YAML
|
|
37
37
|
mapping** (no `---` frontmatter fences, no markdown). Parsed with YAML 1.2
|
|
38
38
|
semantics and validated against the schema. Top-level keys: `title`,
|
|
39
|
-
`instructions`, `runtime`, `settings`, `tests`, `tools`, `skills`,
|
|
40
|
-
`
|
|
39
|
+
`instructions`, `disabled`, `runtime`, `settings`, `tests`, `tools`, `skills`,
|
|
40
|
+
`mounts`, `metadata`, `states`.
|
|
41
41
|
- **`WORKFLOW.md`** (optional, beside it) — a plain-markdown prose addendum with
|
|
42
42
|
**no frontmatter**. A frontmatter block in `WORKFLOW.md` when `workflow.yaml`
|
|
43
43
|
exists is a **validation error**. The runtime renders the system prompt's
|
|
@@ -91,7 +91,7 @@ disabled?: boolean # `true` = this machine STARTS NO NEW TURNS. Bound
|
|
|
91
91
|
# (a new turn is new work) — all rejected AT THE TURN
|
|
92
92
|
# BOUNDARY with `status: rejected` and
|
|
93
93
|
# "Refusing to start: workflow '<slug>' is disabled.",
|
|
94
|
-
# before any state
|
|
94
|
+
# before any state or model call; and a caller's
|
|
95
95
|
# `archmax_workflow_<slug>` call (blocked call, refusal
|
|
96
96
|
# kind `disabled`, before a child is composed — it also
|
|
97
97
|
# beats a declared MOCK, so a case can never assert a
|
|
@@ -113,7 +113,17 @@ disabled?: boolean # `true` = this machine STARTS NO NEW TURNS. Bound
|
|
|
113
113
|
|
|
114
114
|
runtime?:
|
|
115
115
|
engine?: string # "archmax-harness"
|
|
116
|
-
version?: string | number # target contract —
|
|
116
|
+
version?: string | number # target contract — "3", the only one supported (and
|
|
117
|
+
# the default when omitted). "1"/"2" are refused as
|
|
118
|
+
# an unsupported runtime contract, naming the
|
|
119
|
+
# supported set.
|
|
120
|
+
|
|
121
|
+
# There is NO root `budget`: declaring one is an unknown-key load error
|
|
122
|
+
# (`Unrecognized key: "budget"`). A whole turn — one message, decision, reply or
|
|
123
|
+
# delivery, across every state — is bounded by LangGraph's `recursionLimit`:
|
|
124
|
+
# 400 unless the host's invoke config sets its own, which wins. A turn that hits
|
|
125
|
+
# it ends the session `failed` (exit code `recursion-limit`). A state's own
|
|
126
|
+
# `budget` bounds that state (see Budgets & error routing).
|
|
117
127
|
|
|
118
128
|
settings?:
|
|
119
129
|
model?: string # MODEL ID ONLY — the id every state of this workflow runs
|
|
@@ -122,8 +132,12 @@ settings?:
|
|
|
122
132
|
# it. NOT a block: `{ id, temperature }` is a load error —
|
|
123
133
|
# sampling and endpoint are env/host configuration.
|
|
124
134
|
# Opaque: nothing validates the id against a list.
|
|
125
|
-
#
|
|
126
|
-
|
|
135
|
+
# A grader state runs on it too unless it declares its
|
|
136
|
+
# own `model`. Does NOT change the case judge
|
|
137
|
+
# (tests.judge.model).
|
|
138
|
+
timeoutMs?: number # default 15000; per sandbox evaluation — one
|
|
139
|
+
# archmax_eval/archmax_run call, or a code state's
|
|
140
|
+
# whole script
|
|
127
141
|
memoryLimitBytes?: number
|
|
128
142
|
maxPtcCalls?: number | null
|
|
129
143
|
maxResultChars?: number
|
|
@@ -139,8 +153,9 @@ tests?: # cases suite config (declarative data;
|
|
|
139
153
|
caseTimeoutMs?: number # per-case WALL-CLOCK budget, default 120000 —
|
|
140
154
|
# deliberately named differently from
|
|
141
155
|
# settings.timeoutMs (per-script sandbox budget)
|
|
142
|
-
judge?: # {} = enable the
|
|
143
|
-
model?: string # env-configured default model
|
|
156
|
+
judge?: # {} = enable the case judge (`grade:`) with the
|
|
157
|
+
model?: string # env-configured default model — unrelated to
|
|
158
|
+
# grader states
|
|
144
159
|
modelOptions?: { temperature?: number, maxTokens?: number }
|
|
145
160
|
# applied over the env model config, via the
|
|
146
161
|
# factory's `judge` role; other keys are errors
|
|
@@ -212,20 +227,15 @@ mounts?:
|
|
|
212
227
|
# validate ERRORS on a name the table does not mount.
|
|
213
228
|
forbid_always?: string[] # MOUNT NAMES out of reach of EVERY state, governed or
|
|
214
229
|
# not, whatever any list grants. Deny beats allow.
|
|
215
|
-
# Binds this workflow's
|
|
216
|
-
# inherited by every sub-workflow, naming the workflow
|
|
230
|
+
# Binds this workflow's grader and code states too,
|
|
231
|
+
# and is inherited by every sub-workflow, naming the workflow
|
|
217
232
|
# that declared it. A NAME only — a denial is total, so
|
|
218
233
|
# there is no `access` to qualify. (A root `mounts.allow`
|
|
219
234
|
# is a load error: one mounts model.)
|
|
220
235
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
#
|
|
224
|
-
# unknown hook kinds are validation errors
|
|
225
|
-
|
|
226
|
-
editor?: Record<string, unknown> # HOST PRESENTATION METADATA — an authoring
|
|
227
|
-
# UI's own state (canvas state positions keyed by
|
|
228
|
-
# slug, edge routing). The one key the runtime
|
|
236
|
+
metadata?: Record<string, unknown> # HOST PRESENTATION METADATA — an authoring
|
|
237
|
+
# UI's own state (canvas zoom, pan). Also accepted on
|
|
238
|
+
# a state (its position). The one key the runtime
|
|
229
239
|
# never reads: not shape-checked, not prompted,
|
|
230
240
|
# not governed, and excluded from the spec hash.
|
|
231
241
|
# Do NOT author it by hand and do NOT invent a
|
|
@@ -233,7 +243,8 @@ editor?: Record<string, unknown> # HOST PRESENTATION METADATA — an authoring
|
|
|
233
243
|
# rewrite a spec that already carries one, or the
|
|
234
244
|
# builder that wrote it loses every state position.
|
|
235
245
|
# The root is otherwise STRICT: any other
|
|
236
|
-
# unrecognized key is a load error
|
|
246
|
+
# unrecognized key is a load error — `extensions:`
|
|
247
|
+
# included.
|
|
237
248
|
|
|
238
249
|
states: Record<slug, MachineState> # REQUIRED; ≥1 state, ≥1 with a trigger
|
|
239
250
|
# the KEY is the state's SLUG (see below)
|
|
@@ -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
|
+
# whenever it is active, 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
|
|
@@ -273,11 +296,26 @@ states:
|
|
|
273
296
|
# NOT a substitute for `instructions` (behavior
|
|
274
297
|
# guidance) or a transition `description` (routing).
|
|
275
298
|
|
|
276
|
-
type?: "agent" | "human"
|
|
277
|
-
# default "agent"
|
|
299
|
+
type?: "agent" | "human" | "grader" | "code"
|
|
300
|
+
# default "agent" = the session's agent works here.
|
|
301
|
+
# "human" = a person routes.
|
|
302
|
+
# "grader" = an isolated grader judges the session
|
|
303
|
+
# and decides the edge (see Grader and code states).
|
|
304
|
+
# "code" = the runtime runs `script` and the
|
|
305
|
+
# script decides the edge.
|
|
306
|
+
# grader + code = AUTOMATED states: run on entry
|
|
307
|
+
# with no call of the agent's model.
|
|
278
308
|
# (`workflow` was removed: a state CALLS another
|
|
279
309
|
# workflow by allowing `archmax_workflow_<slug>`.)
|
|
280
310
|
|
|
311
|
+
script?: string # CODE STATES ONLY, and REQUIRED there: the file the
|
|
312
|
+
# state runs, workflow-relative inside `scripts/`,
|
|
313
|
+
# ending .js or .mjs, no `..` segment —
|
|
314
|
+
# script: scripts/check-refund.js
|
|
315
|
+
# resolves to workflows/<slug>/scripts/check-refund.js
|
|
316
|
+
# on the authoring plane. A code state without it,
|
|
317
|
+
# or `script` on any other type, is a LOAD ERROR.
|
|
318
|
+
|
|
281
319
|
|
|
282
320
|
triggers?: Record<triggerId, TriggerDeclaration | null>
|
|
283
321
|
# marks a start state. The KEY is the trigger id
|
|
@@ -323,16 +361,19 @@ states:
|
|
|
323
361
|
# target's summary is never disclosed.
|
|
324
362
|
|
|
325
363
|
instructions?: string # behavior guidance.
|
|
326
|
-
# agent
|
|
364
|
+
# agent state: surfaced to the LLM while active.
|
|
327
365
|
# Write the state's behavior INLINE here; a
|
|
328
366
|
# skill bundle (skills/<slug>/) is a capability
|
|
329
367
|
# with data or scripts, enabled by slug — not a
|
|
330
368
|
# per-state instruction sheet.
|
|
331
|
-
# human state:
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
#
|
|
369
|
+
# human state: REQUIRED — the decision for the packet
|
|
370
|
+
# grader state: REQUIRED — its criteria, the
|
|
371
|
+
# grader's system prompt after a compiled-in
|
|
372
|
+
# preamble; never shown to the agent
|
|
373
|
+
# code state: a validate ERROR (nothing reads it)
|
|
374
|
+
# There is NO `before:`/`after:` on a state: hooks
|
|
375
|
+
# are removed, and a leftover key is a load error
|
|
376
|
+
# naming it (rewrite it as a grader or code state).
|
|
336
377
|
|
|
337
378
|
tools?:
|
|
338
379
|
allow?: AllowEntry[] # grants beyond the workflow-wide grant (plus the
|
|
@@ -343,8 +384,8 @@ states:
|
|
|
343
384
|
forbid?: ForbidEntry[] # what this state DENIES while active — beating the
|
|
344
385
|
# workflow-wide grant, this state's own allow,
|
|
345
386
|
# and a host's essentialTools alike. Deny beats allow.
|
|
346
|
-
# Not inherited by a sub-workflow
|
|
347
|
-
#
|
|
387
|
+
# Not inherited by a sub-workflow. Binds a grader's
|
|
388
|
+
# and a code state's calls exactly as the agent's.
|
|
348
389
|
|
|
349
390
|
skills?:
|
|
350
391
|
allow?: string[] # which CAPABILITIES this state ADDS, by SLUG — on top
|
|
@@ -366,11 +407,11 @@ states:
|
|
|
366
407
|
# listing of skills/ does not show it.
|
|
367
408
|
# A tools.allow entry still NARROWS within the enabled
|
|
368
409
|
# set: the two compose as enabled AND allowed.
|
|
369
|
-
#
|
|
370
|
-
# skills/order-data/assets/orders.json
|
|
371
|
-
#
|
|
372
|
-
# agent's use of a capability while
|
|
373
|
-
#
|
|
410
|
+
# A grader or code state is bound like the agent: a
|
|
411
|
+
# code state reading skills/order-data/assets/orders.json
|
|
412
|
+
# enables `order-data` in ITS OWN list. To gate the
|
|
413
|
+
# agent's use of a capability while a check still
|
|
414
|
+
# reads it, enable it on the check's state only.
|
|
374
415
|
|
|
375
416
|
mounts?:
|
|
376
417
|
allow?: MountGrant[] # which MOUNTS this state ADDS, by NAME — on top of the
|
|
@@ -399,27 +440,32 @@ states:
|
|
|
399
440
|
# every declaration) AND undisclosed — absent from the
|
|
400
441
|
# volatile prompt block, and filtered out of every
|
|
401
442
|
# ls/glob/grep result, the WORKSPACE ROOT LISTING
|
|
402
|
-
# included.
|
|
403
|
-
#
|
|
404
|
-
#
|
|
405
|
-
# governed mount only if allow_always or SOME state's
|
|
406
|
-
# allow names it, in any state, and writes it only
|
|
407
|
-
# where a grant of the workflow asks `read_write`.
|
|
443
|
+
# included. Binds a grader's and a code state's
|
|
444
|
+
# calls exactly as the agent's: grant the mount on
|
|
445
|
+
# the automated state that reads it.
|
|
408
446
|
|
|
409
447
|
model?: string # MODEL ID ONLY — this state's turns run on it (and the
|
|
410
448
|
# handoff message of a park it holds). Most specific of
|
|
411
449
|
# state.model > settings.model > the assembly's default
|
|
412
450
|
# (createAgent `model`/`modelFactory`, else ARCHMAX_MODEL).
|
|
413
451
|
# Same id as settings.model ⇒ validate WARNS (inert).
|
|
414
|
-
#
|
|
415
|
-
#
|
|
452
|
+
# On a GRADER state: the id the grader runs on, resolved
|
|
453
|
+
# under the `grader` model role
|
|
454
|
+
# (modelFactory("grader", env, id)). On a CODE state: a
|
|
455
|
+
# validate ERROR. A sub-workflow resolves its OWN
|
|
456
|
+
# spec's ids — nothing is inherited across it.
|
|
416
457
|
# A host passing an explicit `model` INSTANCE to
|
|
417
458
|
# createAgent outranks every declared id (one assembly
|
|
418
459
|
# warning names them); `modelFactory` is what honours one.
|
|
419
460
|
|
|
420
461
|
budget?: # per-state execution budget
|
|
421
|
-
maxTurns?: number # bounds this turn's agent loop
|
|
422
|
-
|
|
462
|
+
maxTurns?: number # bounds this turn's agent loop (on a grader: the
|
|
463
|
+
# grader's own model calls)
|
|
464
|
+
timeoutMs?: number # aborts the turn when exceeded (on a grader: its run)
|
|
465
|
+
maxParks?: number # bounds archmax_wait parks here (see Parking); an
|
|
466
|
+
# ERROR on a grader, which cannot park
|
|
467
|
+
# Any `budget` on a CODE state is a validate ERROR:
|
|
468
|
+
# settings.timeoutMs bounds its script.
|
|
423
469
|
on_error?: string # state to route to on a TERMINAL failure
|
|
424
470
|
# (see Budgets & error routing)
|
|
425
471
|
|
|
@@ -435,20 +481,24 @@ states:
|
|
|
435
481
|
#
|
|
436
482
|
# THE AGENT SEES THIS STATE'S EDGES AND NOTHING
|
|
437
483
|
# ELSE OF THE GRAPH — no other state's slug, title,
|
|
438
|
-
# summary,
|
|
439
|
-
# the states. So `description` is what it
|
|
440
|
-
# about where an edge leads. An UNDESCRIBED
|
|
441
|
-
# (absent/empty/blank) routes on the TARGET's
|
|
484
|
+
# summary, script, criteria or transitions, and no
|
|
485
|
+
# count of the states. So `description` is what it
|
|
486
|
+
# knows about where an edge leads. An UNDESCRIBED
|
|
487
|
+
# edge (absent/empty/blank) routes on the TARGET's
|
|
442
488
|
# `instructions` instead: the edge's line carries
|
|
443
489
|
# them, quoted, and nothing else of the target.
|
|
444
490
|
# LOAD ERRORS (states.<slug>.transitions.<i>.description):
|
|
445
491
|
# an undescribed edge whose target has no
|
|
446
492
|
# instructions; an undescribed transition of a
|
|
447
|
-
# HUMAN state (it is the button label);
|
|
448
|
-
#
|
|
449
|
-
#
|
|
450
|
-
#
|
|
451
|
-
#
|
|
493
|
+
# HUMAN state (it is the button label); an
|
|
494
|
+
# undescribed edge INTO a GRADER (its instructions
|
|
495
|
+
# are criteria the work it judges is never shown)
|
|
496
|
+
# or a CODE state (it has none); a non-string. A
|
|
497
|
+
# code state's OWN edges may go undescribed: its
|
|
498
|
+
# script routes, and nothing reads them. An
|
|
499
|
+
# undescribed edge INTO a human state discloses
|
|
500
|
+
# that state's reviewer-facing instructions to the
|
|
501
|
+
# agent — describe it to keep them private.
|
|
452
502
|
#
|
|
453
503
|
# Write it for a reader standing in THIS state,
|
|
454
504
|
# which knows nothing about the target:
|
|
@@ -460,10 +510,15 @@ states:
|
|
|
460
510
|
# undescribed.
|
|
461
511
|
#
|
|
462
512
|
# The rendered line marks a target that is a human
|
|
463
|
-
# decision node
|
|
464
|
-
# or what follows it);
|
|
465
|
-
|
|
466
|
-
|
|
513
|
+
# decision node, a grader, a code state or terminal
|
|
514
|
+
# (each changes the call or what follows it);
|
|
515
|
+
# nothing else of the target.
|
|
516
|
+
# A GRADER needs at least one transition (validate
|
|
517
|
+
# ERROR otherwise): with nowhere to send the session
|
|
518
|
+
# it could only approve by saying nothing.
|
|
519
|
+
|
|
520
|
+
# human states only (ignored on agent states; a validate ERROR on grader and
|
|
521
|
+
# code states):
|
|
467
522
|
evidence?: string[] # workspace-relative paths always shown in the packet —
|
|
468
523
|
# a baseline the advancing agent adds to (see Human states)
|
|
469
524
|
approvers?: string[] # informational; not enforced
|
|
@@ -576,8 +631,9 @@ states:
|
|
|
576
631
|
overwrite a value the session already holds. So do not list a return in a
|
|
577
632
|
state's `requires:` unless that state must settle it, and do not tell the
|
|
578
633
|
agent to lock one before its value is final. `note` is reserved: a `returns`
|
|
579
|
-
list may not declare it. Want the model to try again?
|
|
580
|
-
|
|
634
|
+
list may not declare it. Want the model to try again? Route the finishing
|
|
635
|
+
state through a code state that checks the returns and sends the work back
|
|
636
|
+
with what is missing. Each entry is a bare
|
|
581
637
|
run-variable name (**untyped**: any value, `null` included) or a strict object
|
|
582
638
|
`{ name, type?, description? }` naming one; the spellings mix in one list and
|
|
583
639
|
names are distinct across both. What the agent must *do* to produce a
|
|
@@ -598,14 +654,15 @@ states:
|
|
|
598
654
|
refused before any model call), **at the write** (`archmax_set_variables` of a
|
|
599
655
|
mistyped typed return of the current trigger is a correctable refusal,
|
|
600
656
|
nothing written) and **at completion** (a mistyped typed return rejects the
|
|
601
|
-
run — in practice a
|
|
657
|
+
run — in practice a code state's write). There are no enums, item or
|
|
602
658
|
property schemas, ranges or optional inputs. An unknown `type`, an unknown
|
|
603
659
|
entry key (`default:`) or an empty `description` fails load.
|
|
604
660
|
- **A `file` value must name a readable file.** The pure rule checks only the
|
|
605
661
|
path's shape (a host runs it in a browser). At the start, at the write, at
|
|
606
662
|
completion and at a delegation call the runtime also requires a **file** (not a
|
|
607
663
|
folder) at the path that the session may read **on runtime authority** — the
|
|
608
|
-
|
|
664
|
+
runtime's own `file`-value read check, the one call that runs outside a state's
|
|
665
|
+
surface: zone rules (`checkpoints/`, `artifacts/`, `_specs/`
|
|
609
666
|
never), the workflow's mount grants taken across all its states (a governed
|
|
610
667
|
mount no state is granted is out of reach), and every workflow-wide
|
|
611
668
|
`forbid_always`, inherited ones included; the state's allow list and its own
|
|
@@ -661,8 +718,8 @@ states:
|
|
|
661
718
|
connection: acme-slack
|
|
662
719
|
```
|
|
663
720
|
|
|
664
|
-
One state, one behavior, one governance block; `${{trigger}}` and a
|
|
665
|
-
`
|
|
721
|
+
One state, one behavior, one governance block; `${{trigger}}` and a code
|
|
722
|
+
state's `trigger` input still read which one actually fired.
|
|
666
723
|
- Every declared trigger **starts** a session. An event that must only ever reach
|
|
667
724
|
a live one — a ticket closing, a payment settling — is declared nowhere: the
|
|
668
725
|
host names the session it delivers into (`archmax deliver <session> --trigger
|
|
@@ -678,7 +735,8 @@ along the chosen edge, a delivery resumes the parked state.
|
|
|
678
735
|
|
|
679
736
|
**Runtime control tools** carry the `archmax_` prefix — `archmax_advance`,
|
|
680
737
|
`archmax_reset`, `archmax_wait`, `archmax_raise`, `archmax_run`,
|
|
681
|
-
`archmax_get_variables`, `archmax_set_variables
|
|
738
|
+
`archmax_get_variables`, `archmax_set_variables`, and `archmax_get_history`
|
|
739
|
+
(grader states only) — and are the names to write in `tools.allow`,
|
|
682
740
|
`tools.allow_always`, and the `forbid`/`forbid_always` lists. These are the only spellings:
|
|
683
741
|
an entry naming an unprefixed variant governs a tool that does not exist,
|
|
684
742
|
silently granting nothing. A host tool may never claim the prefix. Deep Agents' built-ins
|
|
@@ -689,7 +747,9 @@ silently granting nothing. A host tool may never claim the prefix. Deep Agents'
|
|
|
689
747
|
`edit_file`, `copy_file`, `move_file`, `remove_file`, `glob`, `grep`, `write_todos`,
|
|
690
748
|
`archmax_eval`, `archmax_run`) when the key is absent, plus the always-allowed
|
|
691
749
|
`archmax_advance` and a host's `essentialTools`, minus whatever a `forbid` list
|
|
692
|
-
denies there, and read-only-zone protection.
|
|
750
|
+
denies there, and read-only-zone protection. A grader or code state never has
|
|
751
|
+
`write_todos`, `archmax_eval`, `archmax_run`, `archmax_wait` or `archmax_reset`,
|
|
752
|
+
even when `allow_always` lists one; a grader also has `archmax_get_history`.
|
|
693
753
|
Governance is closed by default; there is no way to declare a fully open state.
|
|
694
754
|
- `tools.forbid` on a state **denies** while that state is active, beating every
|
|
695
755
|
grant that would otherwise reach it (see [Tool governance](#tool-governance)).
|
|
@@ -707,8 +767,8 @@ success.
|
|
|
707
767
|
- The call must be the **only** tool call in its message; one sent beside other
|
|
708
768
|
calls is refused, and the model may raise again on its own.
|
|
709
769
|
- The session ends **at once** with status `failed` (classified **finished**): no
|
|
710
|
-
further model call, no `on_error` route, no
|
|
711
|
-
|
|
770
|
+
further model call, no `on_error` route, no further state (an automated one
|
|
771
|
+
included), no check of the trigger's `returns`. A rejection still
|
|
712
772
|
pending from earlier in the turn is replaced. The position stays where the
|
|
713
773
|
agent raised; the trail records no step.
|
|
714
774
|
- The host reads `Outcome.kind === "failed"` and `Outcome.exit`
|
|
@@ -722,9 +782,11 @@ success.
|
|
|
722
782
|
its own `on_error`, or raise in turn.
|
|
723
783
|
- Ban it with `tools: { forbid_always: [archmax_raise] }` (or one state's
|
|
724
784
|
`tools.forbid`) where the agent must not end the run itself — say, a state that
|
|
725
|
-
must always reach a person instead.
|
|
785
|
+
must always reach a person instead. An agent's script cannot call it; a code
|
|
786
|
+
state's script (`tools.archmax_raise`) and a grader can, and their raise ends the
|
|
787
|
+
session the same way (the note and `exit` carry their code).
|
|
726
788
|
- Test it with the structural `raised` assertion; `succeeded: true` fails on a
|
|
727
|
-
raise.
|
|
789
|
+
raise (it passes only on a `completed` outcome).
|
|
728
790
|
|
|
729
791
|
## Transitions
|
|
730
792
|
|
|
@@ -732,12 +794,17 @@ success.
|
|
|
732
794
|
transitions:
|
|
733
795
|
- to: string # REQUIRED target state SLUG (must exist); never a title
|
|
734
796
|
description?: string # shown to the agent (or human) to pick the edge; absent =
|
|
735
|
-
# the agent is shown the target's instructions instead
|
|
797
|
+
# the agent is shown the target's instructions instead.
|
|
798
|
+
# REQUIRED on a human state's edges and on an edge INTO
|
|
799
|
+
# a grader or code state; a code state's own may omit it
|
|
736
800
|
type?: "approve" | "reject" | "refine" | "none" # default "none"
|
|
737
801
|
```
|
|
738
802
|
|
|
739
803
|
- On **agent** states the agent chooses one edge via
|
|
740
804
|
`archmax_advance({ to, reason })`; `type` is metadata only.
|
|
805
|
+
- On **grader** and **code** states the grader or the script chooses one the same
|
|
806
|
+
way (`archmax_advance` / `tools.archmax_advance`), and its `reason` is what the
|
|
807
|
+
next state reads.
|
|
741
808
|
- On **human** states each transition is a selectable outcome (a UI renders one
|
|
742
809
|
button per edge, labeled by `type`). `refine` conventionally loops back to a
|
|
743
810
|
prior state with the reviewer's comment as correction. Max one transition per
|
|
@@ -773,16 +840,19 @@ declare that key, the **default tools** (`ls`, `read_file`, `write_file`, `edit_
|
|
|
773
840
|
`archmax_run`). A declared list **replaces** the default tools, never adds to them:
|
|
774
841
|
`allow_always: []` grants no tool workflow-wide, and a list without `write_file` leaves
|
|
775
842
|
every state that does not name it unable to write. So a new workflow lists all twelve and
|
|
776
|
-
removes only what it must not have. Every state also has
|
|
777
|
-
any tool the host passes as `essentialTools`, whatever the workflow declares.
|
|
778
|
-
|
|
779
|
-
|
|
843
|
+
removes only what it must not have. Every state also has its type's control tools and
|
|
844
|
+
any tool the host passes as `essentialTools`, whatever the workflow declares. A grader or
|
|
845
|
+
code state is never offered `write_todos`, `archmax_eval`, `archmax_run`, `archmax_wait` or
|
|
846
|
+
`archmax_reset`, even when `allow_always` lists them, and naming one in its own `tools.allow`
|
|
847
|
+
is a `validate` error.
|
|
848
|
+
One built-in is **never disclosed** and grantable by nothing: `task`, Deep Agents'
|
|
849
|
+
subagent dispatch — grading is a grader state and delegating is a sub-workflow, so naming
|
|
780
850
|
`task` in an allow entry is a `validate` error and the kernel refuses the call.
|
|
781
851
|
The planning scratchpad `write_todos` and the sandbox
|
|
782
852
|
`archmax_eval` are default tools: granted wherever the workflow-wide grant includes
|
|
783
853
|
them (everywhere, in a workflow without `allow_always`). `write_todos`'s prompt
|
|
784
|
-
guidance appears only when some state is granted it, and
|
|
785
|
-
whether or not
|
|
854
|
+
guidance appears only when some state is granted it, and code states run in the
|
|
855
|
+
sandbox whether or not any state is granted `archmax_eval`. The interpreter is safe as a
|
|
786
856
|
default because the `tools.*` calls its code makes are decided against the active
|
|
787
857
|
state by the same kernel: it adds computation over what the state permits, not
|
|
788
858
|
access. `archmax_run` — executing an authored script **file** — is a default tool on
|
|
@@ -816,10 +886,11 @@ entries: `tools.forbid_always` at the root denies in every state,
|
|
|
816
886
|
`tools.forbid` on a state denies while it is active — subtracting from the
|
|
817
887
|
workflow-wide grant, the state's own `allow` and a host's `essentialTools` alike.
|
|
818
888
|
**Deny beats allow, and no narrower level widens a denial.** A forbid entry may
|
|
819
|
-
name the tool `*` (every tool); `*` in an allow list is a load error. A
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
889
|
+
name the tool `*` (every tool); `*` in an allow list is a load error. A grader's
|
|
890
|
+
calls and a code state's script are governed against **their own state** exactly
|
|
891
|
+
as the agent is — its `allow`, its `forbid`, `forbid_always` and every ancestor's.
|
|
892
|
+
The only call on runtime authority is the runtime's own `file`-value read check,
|
|
893
|
+
held to the workflow's grants across all its states and to every `forbid_always`.
|
|
823
894
|
|
|
824
895
|
Precedence (highest first):
|
|
825
896
|
|
|
@@ -833,20 +904,27 @@ Precedence (highest first):
|
|
|
833
904
|
`tools.forbid` denies in the active state, both regardless of any allow list
|
|
834
905
|
(and regardless of always-allowed): deny beats allow, and no narrower level
|
|
835
906
|
widens a denial.
|
|
836
|
-
2. **
|
|
907
|
+
2. **The state type's control tools** (baked in): an agent or human state has
|
|
908
|
+
`archmax_advance`, `archmax_reset`, `archmax_wait`, `archmax_raise` and the
|
|
909
|
+
variable tools; a code state `archmax_advance`, `archmax_raise` and the
|
|
910
|
+
variable tools; a grader state those plus `archmax_get_history`.
|
|
837
911
|
3. **Per-state `tools.allow`** — the listed tools/arg-shapes. An entry for a
|
|
838
912
|
tool the workflow-wide grant includes takes precedence (**narrower wins** —
|
|
839
913
|
the state's arg globs apply, not the broader grant); an entry for a default
|
|
840
914
|
tool a declared `allow_always` omits grants it in this state.
|
|
841
915
|
4. **The workflow-wide grant** — the declared `tools.allow_always`, guards
|
|
842
|
-
included, or the default tools, unconstrained, when the key is absent
|
|
916
|
+
included, or the default tools, unconstrained, when the key is absent; in a
|
|
917
|
+
grader or code state, minus `write_todos`, `archmax_eval` and `archmax_run`.
|
|
843
918
|
5. **The host's `essentialTools`** — always permitted when no state entry
|
|
844
919
|
narrows them, whatever the workflow declares.
|
|
845
920
|
6. **Default**: everything else is blocked.
|
|
846
921
|
|
|
847
922
|
**Progressive disclosure.** The model is only shown the active state's tool
|
|
848
|
-
surface: the workflow-wide grant + a host's `essentialTools` + the state's entries +
|
|
849
|
-
(hidden in terminal states), minus every tool
|
|
923
|
+
surface: the workflow-wide grant + a host's `essentialTools` + the state's entries +
|
|
924
|
+
its control tools (`archmax_advance` hidden in terminal states), minus every tool
|
|
925
|
+
denied there by name. A grader is offered, and a code state's script reaches,
|
|
926
|
+
exactly the same derivation for its own state — anything else is absent
|
|
927
|
+
(`undefined` on `tools`). Disclosure is
|
|
850
928
|
name-level (arg-constrained tools stay visible; the kernel enforces args at
|
|
851
929
|
call time) and never replaces enforcement. **Skills are disclosed the same way**:
|
|
852
930
|
each model call names only the skills the active state enables, with their
|
|
@@ -900,7 +978,7 @@ Why by slug, and not with `tools.allow` paths:
|
|
|
900
978
|
Capability governance and execution governance are different questions.
|
|
901
979
|
- **`archmax_run` can only ever be narrowed.** A non-overridable kernel rule
|
|
902
980
|
(`script.skill-only`) confines it to skill bundles, so a script the agent writes
|
|
903
|
-
into `scratchpad/` is unrunnable, a
|
|
981
|
+
into `scratchpad/` is unrunnable, a code-state script is unrunnable, and
|
|
904
982
|
`{ tool: archmax_run, paths: ["**"] }` grants nothing — `validate` reports such
|
|
905
983
|
an entry as inert.
|
|
906
984
|
|
|
@@ -916,11 +994,12 @@ that serves bundles, naming what the workflow cannot reach. Any root block silen
|
|
|
916
994
|
that last one — write `skills: { allow_always: [] }` when a workflow grants no
|
|
917
995
|
capability workflow-wide.
|
|
918
996
|
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
997
|
+
Grader and code states are **not exempt**: their calls are governed against their
|
|
998
|
+
own state, so a code state that reads a bundle enables it in its own
|
|
999
|
+
`skills.allow`. That is the shape to reach for when a capability should *enforce*
|
|
1000
|
+
something without being *usable* by the agent: the agent's state enables nothing,
|
|
1001
|
+
and a code state in front of or behind it enables the bundle and runs the check. A
|
|
1002
|
+
bundle the workflow's `skills.forbid_always` denies is refused to every state.
|
|
924
1003
|
|
|
925
1004
|
**`tools.forbid` on a state denies there**, and `tools.forbid_always` at the
|
|
926
1005
|
root denies everywhere. Both beat every grant. Migrating a workflow off the
|
|
@@ -1060,20 +1139,13 @@ so `AGENTS.md.bak` is not captured.
|
|
|
1060
1139
|
governs mounts, naming what the workflow cannot reach. Any root block silences
|
|
1061
1140
|
it — write `mounts: { allow_always: [] }` when a workflow reaches none.
|
|
1062
1141
|
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
mount only a child grants, and the caller's hooks cannot reach that;
|
|
1071
|
-
- it writes one only where a grant of the workflow asks `access: read_write`, and an
|
|
1072
|
-
`access: read` in `allow_always` narrows any mount for it (`mount.read-only`).
|
|
1073
|
-
|
|
1074
|
-
So when a hook needs a mount no state's model should see, grant it in a state the
|
|
1075
|
-
hook's workflow has anyway, or in `allow_always` with the model's states
|
|
1076
|
-
forbidding it.
|
|
1142
|
+
Grader and code states are bound by **their own state's** mount grants, exactly
|
|
1143
|
+
like the agent: a code state reads a governed mount only where `allow_always` or
|
|
1144
|
+
its own `mounts.allow` grants it, and writes one only where that grant asks
|
|
1145
|
+
`access: read_write`. So when a check needs a mount no agent state should see,
|
|
1146
|
+
grant it on the check's code state alone. The one read on runtime authority is
|
|
1147
|
+
the runtime's own `file`-value check, held to the **workflow's** grants taken
|
|
1148
|
+
across every state and to every `forbid_always`.
|
|
1077
1149
|
|
|
1078
1150
|
## Session variables in guards
|
|
1079
1151
|
|
|
@@ -1201,89 +1273,149 @@ the tool's effect instead (the file it wrote, the variable it set).
|
|
|
1201
1273
|
To write the characters `${{…}}` literally — authoring a guard through
|
|
1202
1274
|
`write_file`, or writing about the syntax — double the dollar: `$${{name}}`.
|
|
1203
1275
|
|
|
1204
|
-
This is **model-facing
|
|
1205
|
-
|
|
1206
|
-
|
|
1276
|
+
This is **model-facing**: the agent's calls and a grader's get it. An
|
|
1277
|
+
`archmax_run` script (`args.variables`) or a code state's script (`variables`)
|
|
1278
|
+
already holds the run's variables and composes strings in JavaScript, so `${{…}}`
|
|
1279
|
+
in a script's `tools.*` call is ordinary text.
|
|
1207
1280
|
|
|
1208
|
-
##
|
|
1281
|
+
## Grader and code states
|
|
1209
1282
|
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
# resolves to workflows/<slug>/hooks/check.js
|
|
1215
|
-
after: { rubric: reply-tone, max_iterations: 2 } # per hook, not per state
|
|
1216
|
-
# a script after hook may carry the same budget:
|
|
1217
|
-
# after: { script: hooks/review.js, max_iterations: 2 }
|
|
1218
|
-
|
|
1219
|
-
# or a list, run in sequence (short-circuits on veto):
|
|
1220
|
-
before:
|
|
1221
|
-
- { script: hooks/pre.js }
|
|
1222
|
-
- rubric: { instructions: judge the tone }
|
|
1223
|
-
```
|
|
1283
|
+
Two state types are run by the runtime itself, together called **automated
|
|
1284
|
+
states**. Both are ordinary states on the graph: entered by a transition, leaving
|
|
1285
|
+
by `archmax_advance` along one of their own transitions, ending the session with
|
|
1286
|
+
`archmax_raise`, governed by their own tool surface.
|
|
1224
1287
|
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
- **`max_iterations`** bounds how many times a `correct` may send the agent back
|
|
1259
|
-
before the hook becomes a hard veto: inside the declaration for a rubric, a
|
|
1260
|
-
sidecar for a script or custom hook, else 0. It is counted **per hook by
|
|
1261
|
-
position**, so two graders on one state never share a budget. An
|
|
1262
|
-
exhausted budget is a *terminal failure* — see
|
|
1263
|
-
[Budgets & error routing](#budgets--error-routing).
|
|
1264
|
-
- **`before` hooks are ok/veto only**: a `correct` verdict from a `before` hook
|
|
1265
|
-
is treated as veto, and `max_iterations` on a `before` hook is a validation
|
|
1266
|
-
error.
|
|
1267
|
-
- **Fail-closed**: a hook that *errors* vetoes the transition. A rubric
|
|
1268
|
-
whose response cannot be parsed into a verdict also fails closed (**veto**),
|
|
1269
|
-
never coerced to ok — as does a `model` id the host cannot serve.
|
|
1270
|
-
|
|
1271
|
-
The hook contract — a default-export function called with one object:
|
|
1272
|
-
|
|
1273
|
-
```js
|
|
1274
|
-
export default async function hook({
|
|
1275
|
-
state, phase /* "before"|"after" */, trigger, variables, messages,
|
|
1276
|
-
from, to, reason, // from/to/reason present on "after"
|
|
1277
|
-
tools, // privileged tool bridge, e.g. tools.readFile
|
|
1278
|
-
}) {
|
|
1279
|
-
return ok(); // or veto(reason) / correct(reason)
|
|
1280
|
-
}
|
|
1288
|
+
```yaml
|
|
1289
|
+
states:
|
|
1290
|
+
check-refund: # CODE state: runs its script, makes no model call
|
|
1291
|
+
type: code
|
|
1292
|
+
script: scripts/check-refund.js
|
|
1293
|
+
skills: { allow: [order-data] } # what the SCRIPT reads is granted HERE
|
|
1294
|
+
on_error: escalation
|
|
1295
|
+
transitions:
|
|
1296
|
+
- to: refund-review
|
|
1297
|
+
description: The recorded decision matches the refund policy.
|
|
1298
|
+
- to: refund-request
|
|
1299
|
+
description: The recorded decision contradicts the policy; send it back with the fix.
|
|
1300
|
+
|
|
1301
|
+
review-enrichment: # GRADER state: an isolated grader decides the edge
|
|
1302
|
+
type: grader
|
|
1303
|
+
budget: { maxTurns: 8 } # the grader's own model calls
|
|
1304
|
+
on_error: escalation
|
|
1305
|
+
skills: { allow: [] } # the GRADER is governed HERE too: no bundle, and
|
|
1306
|
+
# read_file (workflow-wide) reads the returned files
|
|
1307
|
+
instructions: |- # REQUIRED — its criteria
|
|
1308
|
+
Read `orders_to_enrich` and `enrichments` with archmax_get_variables, then
|
|
1309
|
+
each entry's `enrichment_file` with read_file.
|
|
1310
|
+
- One entry per requested `order_id`, none missing or extra, each `delayed`
|
|
1311
|
+
matching its file: advance to `enrichment-reported`.
|
|
1312
|
+
- Otherwise advance back to `enrich-order`, naming what to fix. Before
|
|
1313
|
+
that, count the steps in archmax_get_history's trail with kind "grader"
|
|
1314
|
+
and to "enrich-order"; if there are already two, raise
|
|
1315
|
+
`enrichment-incorrect` instead.
|
|
1316
|
+
transitions: # REQUIRED — at least one
|
|
1317
|
+
- to: enrichment-reported
|
|
1318
|
+
description: Every requested order has exactly one enrichment, and each matches its file.
|
|
1319
|
+
- to: enrich-order
|
|
1320
|
+
description: The enrichments are incomplete or do not match their files; send them back.
|
|
1281
1321
|
```
|
|
1282
1322
|
|
|
1283
|
-
|
|
1284
|
-
`
|
|
1285
|
-
|
|
1286
|
-
|
|
1323
|
+
- **When it runs.** Whenever the session's position reaches it — the agent's
|
|
1324
|
+
`archmax_advance`, another automated state's advance, a human decision, an
|
|
1325
|
+
`on_error` route, a turn that opens there, an `archmax_reset` back to it — the
|
|
1326
|
+
runtime runs it **in place of the next model call** of the session's agent. The
|
|
1327
|
+
agent's model is not called while the session stands there (and an automated
|
|
1328
|
+
state spends none of the agent's `maxTurns`). Consecutive automated states run
|
|
1329
|
+
one after another, each committed as its own checkpointed step, so **a chain of
|
|
1330
|
+
code states makes no model call at all**. A chain that never reaches an agent
|
|
1331
|
+
state, a human state or a completion is stopped by the turn's recursion limit
|
|
1332
|
+
(`GraphRecursionError`: the session ends `failed` with `recursion-limit`, not an
|
|
1333
|
+
`on_error` route).
|
|
1334
|
+
- **What it is offered** — exactly its own state's surface: its `tools.allow`, the
|
|
1335
|
+
workflow-wide grant (`allow_always`, or the default tools when it is absent), the
|
|
1336
|
+
host's essential tools, and its controls — one set for both types:
|
|
1337
|
+
`archmax_advance` (absent when terminal), `archmax_raise`,
|
|
1338
|
+
`archmax_get_variables`, `archmax_set_variables`, `archmax_get_history` (which
|
|
1339
|
+
no agent or human state is offered). Never `write_todos`, `archmax_eval`,
|
|
1340
|
+
`archmax_run`, `archmax_wait` or `archmax_reset` — not even when `allow_always`
|
|
1341
|
+
lists one (it is left out), and naming one in its own `tools.allow` is a
|
|
1342
|
+
`validate` error. An automated state **cannot park**. Every call is decided by
|
|
1343
|
+
the kernel against that state, like the agent's: its `forbid`, `forbid_always`,
|
|
1344
|
+
its skill and mount grants and every safety rule bind it, and a tool another
|
|
1345
|
+
state grants is not reachable.
|
|
1346
|
+
- **How it moves.** `archmax_advance({ to, reason })` is checked at the call (the
|
|
1347
|
+
edge exists, the state's `requires` hold against the variables at that moment;
|
|
1348
|
+
`evidence` only into a human state); a refusal can be retried. The **first
|
|
1349
|
+
accepted** advance or raise is the decision and every later one is refused. The
|
|
1350
|
+
decision takes effect when the run finishes **cleanly**.
|
|
1351
|
+
- **How it settles** — exactly one of:
|
|
1352
|
+
- **advanced** → the session moves; the trail records `{ to, kind: grader | code,
|
|
1353
|
+
reason }`; a runtime note carries the reason to the next state
|
|
1354
|
+
(`[grader] The '<state>' grader moved the run to '<to>': <reason>. …` /
|
|
1355
|
+
`[code] '<state>' moved the run to '<to>': <reason>. …`); a human target parks
|
|
1356
|
+
after its one reply-only handoff message.
|
|
1357
|
+
- **raised** → the session ends `failed`, its `exit` carrying the code and reason.
|
|
1358
|
+
- **completed** → a terminal state finished without either call; the session
|
|
1359
|
+
completes, held to the trigger's `returns`.
|
|
1360
|
+
- **failed** → it threw, timed out, exhausted its budget, or a **non-terminal**
|
|
1361
|
+
state finished without either call (`<type> state '<slug>' finished without
|
|
1362
|
+
calling archmax_advance or archmax_raise`) — routed through `on_error`, else the
|
|
1363
|
+
session ends rejected. A raise wins over an error; an error discards a recorded
|
|
1364
|
+
advance. Finishing undecided is never read as approval.
|
|
1365
|
+
- **The history.** Both types read the session with `archmax_get_history`, which
|
|
1366
|
+
answers `{ messages, trail }` (`SessionHistory`) as JSON: the session as it
|
|
1367
|
+
stood when the state was entered, built from the checkpointed session, whole
|
|
1368
|
+
and oldest first, nothing windowed. A message is `{ role, text, toolCalls?,
|
|
1369
|
+
tool?, id?, note? }`: an `assistant` entry lists its calls (`{ id?, name, args
|
|
1370
|
+
}`), the `tool` entry with the matching `id` carries the result inline in
|
|
1371
|
+
`text`, and a runtime note is `role: "runtime"` with its `note` kind.
|
|
1372
|
+
- **Code state.** `script` (required, `scripts/<file>.js|.mjs`) runs in the QuickJS
|
|
1373
|
+
sandbox in a fresh REPL per run. Its default export receives `{ state, trigger?,
|
|
1374
|
+
variables, tools }` and reads the history with
|
|
1375
|
+
`JSON.parse(await tools.archmax_get_history())`. Its return value is never
|
|
1376
|
+
read. `instructions`, `model`, `budget`, `evidence` and `approvers` on a code
|
|
1377
|
+
state are `validate` errors; `settings.timeoutMs` bounds the script. A
|
|
1378
|
+
**terminal** code state completes the session when its script returns: approve
|
|
1379
|
+
into one to end without another model call. Script contract and worked
|
|
1380
|
+
examples: [`code-state-and-test-scripts.md`](code-state-and-test-scripts.md).
|
|
1381
|
+
- **Grader state.** A subagent with its own isolated context, built per run: its
|
|
1382
|
+
system prompt is a compiled-in grader preamble followed by the state's
|
|
1383
|
+
`instructions`; its task names the state and lists its transitions; it reads the
|
|
1384
|
+
session only through `archmax_get_history`, and the work with its own state's
|
|
1385
|
+
tools. Its messages never enter the session's transcript, its tokens never
|
|
1386
|
+
stream, it writes no checkpoint; the session reads its decision as the trail
|
|
1387
|
+
step and the note. `instructions` (non-empty) and at least one transition are
|
|
1388
|
+
required; `evidence`, `approvers` and `budget.maxParks` are errors. Every edge
|
|
1389
|
+
into a grader needs a `description` (its instructions are never shown to the
|
|
1390
|
+
work it judges). `budget.maxTurns` bounds its own model calls and
|
|
1391
|
+
`budget.timeoutMs` its run. `model` (else `settings.model`) is resolved under the
|
|
1392
|
+
**`grader`** model role.
|
|
1393
|
+
- **No iteration budget.** Nothing caps how often a state is entered. A
|
|
1394
|
+
state that gives up after N rounds counts them in the trail — each earlier
|
|
1395
|
+
send-back is `{ kind: grader | code, to: <graded state> }` — and raises (or routes
|
|
1396
|
+
to an escalation) on the round after.
|
|
1397
|
+
- **Events.** `code-start { state, script }`, `code-output { state, line }`,
|
|
1398
|
+
`code-result { state, status, durationMs, reason? }`, `grader-start { state }`,
|
|
1399
|
+
`grader-result { state, status, durationMs, reason? }`; a grader's model calls are
|
|
1400
|
+
`model-usage` events under its state, added to the session's usage.
|
|
1401
|
+
|
|
1402
|
+
**Patterns.**
|
|
1403
|
+
|
|
1404
|
+
| Want | Shape |
|
|
1405
|
+
| --- | --- |
|
|
1406
|
+
| A deterministic check **in front of** a state | Make the trigger's entry (or the edge's target) a code state that advances to the work, or to a state that declines it (its agent reads the reason from the note) — or raises |
|
|
1407
|
+
| A check **behind** a state | Point the state's transition at a code state with an edge onward and an edge **back**; a send-back's `reason` is the correction the agent reads |
|
|
1408
|
+
| **Approve, then end** | Approve into a terminal code state (records the result, completes the session, no model call) |
|
|
1409
|
+
| Judgment (tone, completeness) | A grader behind the graded state, with an edge back; its `instructions` say when to raise, counting send-backs in the trail. Grade work the agent hands on (a report, a draft), not a live answer: a turn's `reply` is every text the agent said in it, a draft sent back included |
|
|
1410
|
+
|
|
1411
|
+
**Migrating hooks.** `before:`/`after:` are removed, with every hook kind
|
|
1412
|
+
(`{ script }`, `{ rubric }`, custom kinds), `max_iterations` and the root
|
|
1413
|
+
`extensions:` block; each is a load error naming the key. A `before` check becomes
|
|
1414
|
+
a code state in front of the state; an `after` check a code state behind it; a
|
|
1415
|
+
rubric a grader state behind it. Move `workflows/<slug>/hooks/*.js` to
|
|
1416
|
+
`scripts/` and rewrite each to the code-state contract (it advances or raises
|
|
1417
|
+
instead of returning `ok`/`veto`/`correct`), and grant on the code state whatever
|
|
1418
|
+
the script reads — there is no runtime authority any more.
|
|
1287
1419
|
|
|
1288
1420
|
## Budgets & error routing
|
|
1289
1421
|
|
|
@@ -1297,23 +1429,35 @@ states:
|
|
|
1297
1429
|
escalation: { instructions: "Explain the failure and hand off." } # terminal
|
|
1298
1430
|
```
|
|
1299
1431
|
|
|
1432
|
+
A state's `budget` bounds that state (a grader's own calls are bounded by its
|
|
1433
|
+
`budget.maxTurns`). There is no workflow-wide budget: a whole turn is bounded by
|
|
1434
|
+
LangGraph's `recursionLimit`, 400 unless the host's invoke config sets its own (a
|
|
1435
|
+
sub-workflow's child session runs under the same bound). A turn that throws (model
|
|
1436
|
+
failure, cancellation, the recursion limit) ends the session `failed` with exit
|
|
1437
|
+
code `turn-error`, `turn-cancelled` or `recursion-limit`; the next message starts
|
|
1438
|
+
a fresh turn.
|
|
1439
|
+
|
|
1300
1440
|
Two failure classes, routed differently:
|
|
1301
1441
|
|
|
1302
|
-
- **Recoverable rejections keep the
|
|
1303
|
-
`archmax_advance
|
|
1304
|
-
|
|
1305
|
-
throws** is answered the same way: its error message is the
|
|
1306
|
-
error-status answer, and it never routes to `on_error`.
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
`on_error
|
|
1442
|
+
- **Recoverable rejections keep the mover in place**: an invalid edge in
|
|
1443
|
+
`archmax_advance` or an unmet `requires` — the agent (or a grader, or a code
|
|
1444
|
+
state's script, which may catch it) sees the reason and retries within the same
|
|
1445
|
+
state. A **tool that throws** is answered the same way: its error message is the
|
|
1446
|
+
call's error-status answer, and it never routes to `on_error`. A grader or code
|
|
1447
|
+
state *sending the work back* is a transition, not a failure: the agent reads
|
|
1448
|
+
the reason as a note.
|
|
1449
|
+
- **Terminal failures route to `on_error`**: an exhausted `budget`
|
|
1450
|
+
(`maxTurns`/`timeoutMs`/`maxParks`), a failed sub-workflow call nothing
|
|
1451
|
+
recovered, and a grader or code state that **fails** — threw, timed out, spent
|
|
1452
|
+
its budget, or finished without advancing or raising. With `on_error: <state>`
|
|
1453
|
+
declared, the run routes to that state (the failure reason arrives as an
|
|
1454
|
+
`[error]` runtime note) instead of ending; without it, the failure ends the run.
|
|
1455
|
+
|
|
1456
|
+
**Recommendation**: declare `on_error` on every grader and code state, so a script
|
|
1457
|
+
error or an undecided grader routes somewhere instead of ending the session
|
|
1458
|
+
rejected. The bundled example demonstrates this: `refund-request` pairs
|
|
1459
|
+
`budget.maxTurns: 12` with `on_error: escalation`, and its `screen-request`,
|
|
1460
|
+
`check-refund` and `review-enrichment` route to the same terminal escalation state.
|
|
1317
1461
|
|
|
1318
1462
|
## Human states
|
|
1319
1463
|
|
|
@@ -1494,7 +1638,7 @@ enrich:
|
|
|
1494
1638
|
(`.platform/system/GRAPH_STATE.md`) keeps that by wrapping what children skip
|
|
1495
1639
|
in `<!-- top-level-only -->` … `<!-- /top-level-only -->` lines.
|
|
1496
1640
|
- **Same agent, isolated context, own session.** Same model, backend, host
|
|
1497
|
-
tools; its **own**
|
|
1641
|
+
tools; its **own** grader and code states, from its own spec; a fresh transcript; the child's *own* per-state
|
|
1498
1642
|
`tools.allow`. It is a separate session (`<parent>~<n>`, `n` the dispatch's
|
|
1499
1643
|
number in the caller, counted across its turns) with its own checkpoints and its own `scratchpad/`: a path it reads or writes
|
|
1500
1644
|
resolves inside its own session, so it can neither see nor overwrite a file of
|
|
@@ -1629,6 +1773,14 @@ This is a change: these keys used to be ignored without a diagnostic. A spec tha
|
|
|
1629
1773
|
| state `for_each:` | One call per entry — parallel tool calls from the agent, or `Promise.all` in a script |
|
|
1630
1774
|
| state `result:` | The tool result; add `requires: [<name>]` to the state so it must record what it needs |
|
|
1631
1775
|
| `archmax_advance({ params })` | The call's arguments |
|
|
1776
|
+
| state `before:` (any hook kind) | A **code state in front** of the state — the trigger's entry, or the target of the edge into it — advancing to the work or to a declining state (see [Grader and code states](#grader-and-code-states)) |
|
|
1777
|
+
| state `after: { script: hooks/<x>.js }` | A **code state behind** the state: its transition points at the code state, which advances onward or back with the fix as its `reason` |
|
|
1778
|
+
| `{ rubric: … }` hook | A **grader state** behind the graded state with an edge back; the criteria become its `instructions` |
|
|
1779
|
+
| `max_iterations` / `max_corrections` | Nothing: the grader's `instructions` (or the script) count send-backs in the trail and raise |
|
|
1780
|
+
| root `extensions:` (custom hook kinds) and `createAgent({ hookExecutors })` | Nothing: a custom check is a code state |
|
|
1781
|
+
| `workflows/<slug>/hooks/*.js` returning `ok`/`veto`/`correct` | `workflows/<slug>/scripts/*.js` that call `tools.archmax_advance` / `tools.archmax_raise` |
|
|
1782
|
+
| `runtime.version: "1"` / `"2"` | `"3"` (or omit `runtime`) — 1 and 2 are refused as unsupported |
|
|
1783
|
+
| root `budget: { maxModelCalls }` | Nothing — delete it. A turn is bounded by LangGraph's `recursionLimit` (400 unless the host's invoke config sets one); bound a state with its own `budget.maxTurns` |
|
|
1632
1784
|
|
|
1633
1785
|
**No fan-out.** There is no concurrent branching: a run traverses **one state at a
|
|
1634
1786
|
time**, moved only by `archmax_advance`, a human decision, or `on_error` routing.
|
|
@@ -1660,7 +1812,8 @@ it costs latency, not correctness.
|
|
|
1660
1812
|
```
|
|
1661
1813
|
workflows/<name>/workflow.yaml # the machine (canonical spec)
|
|
1662
1814
|
workflows/<name>/WORKFLOW.md # OPTIONAL prose addendum (no frontmatter)
|
|
1663
|
-
workflows/<name>/
|
|
1815
|
+
workflows/<name>/scripts/*.js # code-state scripts — run BY THE RUNTIME when a
|
|
1816
|
+
# `type: code` state is entered.
|
|
1664
1817
|
# Authoring plane: the agent cannot read or run
|
|
1665
1818
|
# anything under workflows/
|
|
1666
1819
|
workflows/<name>/tests/ # cases (*.test.yaml, one case per
|
|
@@ -1671,9 +1824,10 @@ skills/<capability>/SKILL.md # a capability, bundled (Agent Skills format).
|
|
|
1671
1824
|
# behavior goes inline in that state's
|
|
1672
1825
|
# `instructions` (never one SKILL.md per state)
|
|
1673
1826
|
skills/<capability>/scripts/*.js # its archmax_run sources — run BY THE AGENT
|
|
1674
|
-
# (
|
|
1675
|
-
#
|
|
1676
|
-
#
|
|
1827
|
+
# (code-state scripts are NOT here: they live in
|
|
1828
|
+
# workflows/<name>/scripts/ — same directory
|
|
1829
|
+
# name, other plane — are run by the runtime,
|
|
1830
|
+
# and the agent cannot read them)
|
|
1677
1831
|
skills/<capability>/assets/* # its inputs (read-only)
|
|
1678
1832
|
scratchpad/ # THE working area: intermediate files and the
|
|
1679
1833
|
# artifacts a state produces. The run IS the
|
|
@@ -1689,7 +1843,7 @@ This is the **complete** directory contract — there are no other magic
|
|
|
1689
1843
|
locations, so design the full file set up front instead of exploring.
|
|
1690
1844
|
Reference these paths **workspace-relative, without a leading slash**
|
|
1691
1845
|
(`skills/order-data/assets/orders.json`, `scratchpad/refund.json`) — in `instructions`, `evidence`,
|
|
1692
|
-
allow-entry globs, and paths referenced inside
|
|
1846
|
+
allow-entry globs, and paths referenced inside code-state scripts alike.
|
|
1693
1847
|
|
|
1694
1848
|
The authoring-plane prefix (`workflows/`) is **reserved**: a
|
|
1695
1849
|
host cannot mount it (`MountCollisionError` at assembly), an allow entry naming
|
|
@@ -1697,48 +1851,13 @@ it is a `validate` **error**, and a script that reads it is blocked with a
|
|
|
1697
1851
|
diagnostic naming the prefix. Never write an allow entry, an `evidence` path, or a
|
|
1698
1852
|
case `workspace:` key under it.
|
|
1699
1853
|
|
|
1700
|
-
### Grading rubrics — inline on the hook
|
|
1701
|
-
|
|
1702
|
-
A grader is the **value** of the hook that applies it, not a file beside the spec
|
|
1703
|
-
and not a named entry a hook points at:
|
|
1704
|
-
|
|
1705
|
-
```yaml
|
|
1706
|
-
states:
|
|
1707
|
-
respond:
|
|
1708
|
-
after:
|
|
1709
|
-
- rubric:
|
|
1710
|
-
instructions: |- # REQUIRED — the criteria and the verdict
|
|
1711
|
-
Judge the tone of the last assistant message. Return `ok`,
|
|
1712
|
-
`correct` or `veto`, always with a concise `reason`.
|
|
1713
|
-
max_iterations?: number # the grade-and-retry budget, counted per hook
|
|
1714
|
-
model?: string # a model id to grade on, resolved through the
|
|
1715
|
-
# assembly's modelFactory (third argument)
|
|
1716
|
-
metadata?: object # host data the runtime never reads
|
|
1717
|
-
```
|
|
1718
|
-
|
|
1719
|
-
A rubric has **no name**, and there is deliberately **no `title`, `description`
|
|
1720
|
-
or `response_format`**: nothing routes to a rubric, nothing labels one in a prompt
|
|
1721
|
-
(a display label goes in `metadata`), and a rubric only ever grades — so every
|
|
1722
|
-
dispatch requests the verdict schema unconditionally.
|
|
1723
|
-
|
|
1724
|
-
Verdict shape: `{ verdict: "ok"|"correct"|"veto", reason: string }`.
|
|
1725
|
-
|
|
1726
|
-
Two states needing the same grader declare it **twice**: a state legible on its
|
|
1727
|
-
own beats a de-duplicated document. Its identity is its position (state, phase,
|
|
1728
|
-
index), which is what the runtime dispatches it under and what its retry budget
|
|
1729
|
-
is keyed against — so two graders on one state never share a budget.
|
|
1730
|
-
|
|
1731
|
-
The agent learns nothing of it: the rendered graph says a phase carries a hook of
|
|
1732
|
-
kind `rubric`, there is no `task` tool to call one with, and no sandbox context
|
|
1733
|
-
has `task()`.
|
|
1734
|
-
|
|
1735
1854
|
## Cases
|
|
1736
1855
|
|
|
1737
1856
|
Declarative YAML case documents under `workflows/<name>/tests/*.test.yaml`
|
|
1738
1857
|
(or `.test.yml`) — **one case per file**; the case id is the tests/-relative
|
|
1739
1858
|
path minus the extension. `archmax test <workflow>` interprets them
|
|
1740
1859
|
**host-side**: no QuickJS sandbox runs a case (the sandbox serves only
|
|
1741
|
-
|
|
1860
|
+
code-state scripts and `archmax_run` inside the driven agent). These are distinct
|
|
1742
1861
|
from the runtime's own colocated unit tests. Only `*.test.yaml`/`*.test.yml`
|
|
1743
1862
|
files are discovered — a leftover `*.test.js` is ignored without a diagnostic.
|
|
1744
1863
|
|
|
@@ -1751,14 +1870,14 @@ the static feedback while a workflow is out of service.
|
|
|
1751
1870
|
(`maxConcurrency`, `caseTimeoutMs`, `judge` — see [Root fields](#root-fields)).
|
|
1752
1871
|
`tests.config.js` is **REMOVED**: its presence is a hard error in
|
|
1753
1872
|
`archmax test` and `archmax validate`. There is no `reporters` option.
|
|
1754
|
-
`judge: {}` enables the
|
|
1873
|
+
`judge: {}` enables the case judge (`grade:`) with the env-configured default model.
|
|
1755
1874
|
|
|
1756
1875
|
**The complete case schema (top-level keys, the flat step-entry list, the
|
|
1757
1876
|
assertion grammar, start conditions, mocks, `from:` fixture references) plus
|
|
1758
1877
|
worked examples — routing,
|
|
1759
1878
|
tool mocking, human-in-the-loop decide flows, triggered starts — and the
|
|
1760
1879
|
JS→YAML migration table are in
|
|
1761
|
-
[`
|
|
1880
|
+
[`code-state-and-test-scripts.md`](code-state-and-test-scripts.md).**
|
|
1762
1881
|
|
|
1763
1882
|
Every case carries two mandatory prose fields, both length-capped: a `title`
|
|
1764
1883
|
(a short label, ≤ 60 chars, printed beside the verdict) and a `description`
|
|
@@ -1773,7 +1892,7 @@ errors — fail closed.
|
|
|
1773
1892
|
|
|
1774
1893
|
```yaml
|
|
1775
1894
|
# Nothing is mocked — the case reads the real orders.json fixture, so the
|
|
1776
|
-
# denial is re-derived by the refund
|
|
1895
|
+
# denial is re-derived by the check-refund code state rather than asserted directly.
|
|
1777
1896
|
title: Refund denied for a shipped order
|
|
1778
1897
|
description: >-
|
|
1779
1898
|
A refund for shipped A-1002 parks at refund-review; the reviewer confirms the
|
|
@@ -1792,7 +1911,7 @@ steps:
|
|
|
1792
1911
|
|
|
1793
1912
|
## Code description docblocks
|
|
1794
1913
|
|
|
1795
|
-
**Every** code file in the workspace — both kinds:
|
|
1914
|
+
**Every** code file in the workspace — both kinds: code-state scripts and
|
|
1796
1915
|
`archmax_run` sources — MUST begin with a leading JSDoc block (`/** ... */`): a
|
|
1797
1916
|
title line, a blank line, then prose stating what the code does **and how**.
|
|
1798
1917
|
Cases are YAML documents, not code — they carry no JSDoc block;
|
|
@@ -1800,15 +1919,16 @@ their mandatory `title` and `description` fields are short and capped instead,
|
|
|
1800
1919
|
with any longer rationale in a YAML comment above the document. The canonical
|
|
1801
1920
|
statement of the
|
|
1802
1921
|
rule, with the required depth per file kind, is
|
|
1803
|
-
[SKILL.md →
|
|
1922
|
+
[SKILL.md → Workspace layout](../SKILL.md#workspace-layout-complete--nothing-else-exists).
|
|
1804
1923
|
|
|
1805
1924
|
- The block is the **human-facing contract**: admin UIs display only this
|
|
1806
1925
|
description, and the code below it must honor it. Author
|
|
1807
1926
|
description-first; when behavior changes, update the description, then edit
|
|
1808
1927
|
the code to match — never leave the two contradicting each other.
|
|
1809
1928
|
- Write it deep enough to review on its own: inputs read, the sequence of
|
|
1810
|
-
checks or assertions, and each outcome the code can produce
|
|
1811
|
-
|
|
1929
|
+
checks or assertions, and each outcome the code can produce (for a code-state
|
|
1930
|
+
script: each `archmax_advance` target and why, each `archmax_raise` code). A
|
|
1931
|
+
one-line label is not enough.
|
|
1812
1932
|
- It is inert at runtime — the QuickJS sandbox executes the source verbatim
|
|
1813
1933
|
(typed `@archmax-ai/harness/*` imports excepted; they are stripped).
|
|
1814
1934
|
- The prose ends at the first `@tag` line (tags are allowed but not part of
|
|
@@ -1816,7 +1936,7 @@ rule, with the required depth per file kind, is
|
|
|
1816
1936
|
`@specification` tag and no generation marker: the block is a human
|
|
1817
1937
|
description, not a machine contract.
|
|
1818
1938
|
- `archmax validate` **errors** when an cases is missing its
|
|
1819
|
-
`description` (the case schema requires it).
|
|
1939
|
+
`description` (the case schema requires it). Code-state scripts and `archmax_run`
|
|
1820
1940
|
sources are **not** checked — there the JSDoc rule is an authoring standard
|
|
1821
1941
|
you uphold yourself.
|
|
1822
1942
|
- Backends/UIs extract it with the package's exported
|
|
@@ -1857,30 +1977,49 @@ owns no second artifact area.
|
|
|
1857
1977
|
|
|
1858
1978
|
The bundled reference workspace lives at
|
|
1859
1979
|
`examples/customer-support/workflows/order-lookup/` and demonstrates the full
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
(ASCII graph + domain constraints).
|
|
1863
|
-
example. It declares a top-level `title`
|
|
1864
|
-
slugs below as the reference targets, plus
|
|
1865
|
-
holding the rules that hold across every state —
|
|
1866
|
-
live, the tenancy rule, and naming an order's exact
|
|
1867
|
-
them. Its shape:
|
|
1868
|
-
|
|
1869
|
-
- `
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
`
|
|
1980
|
+
v3 layout: `workflow.yaml` (the machine, including the `tests:` block and its
|
|
1981
|
+
grader and code states), the code-state scripts under `scripts/`, plus a
|
|
1982
|
+
frontmatter-free `WORKFLOW.md` prose addendum (ASCII graph + domain constraints).
|
|
1983
|
+
Read them in full for a real, working example. It declares a top-level `title`
|
|
1984
|
+
and a `title` on every state, with the slugs below as the reference targets, plus
|
|
1985
|
+
a top-level `instructions` block holding the rules that hold across every state —
|
|
1986
|
+
where the data and artifacts live, the tenancy rule, and naming an order's exact
|
|
1987
|
+
status — so no state repeats them. Its shape:
|
|
1988
|
+
|
|
1989
|
+
- `screen-request` (code, `triggers: { manual: , email_reply: }`,
|
|
1990
|
+
`scripts/screen-request.js`, enables `order-data`) — a check **in front**: it
|
|
1991
|
+
merges the requester and tenancy checks before any model call, and advances to
|
|
1992
|
+
`identify-case`, or to `decline` (a terminal agent state that tells the person
|
|
1993
|
+
why) with the reason.
|
|
1994
|
+
- `identify-case` routes to one of four paths via transition descriptions.
|
|
1995
|
+
- `orders-question` (terminal) answers from `skills/order-data/assets/orders.json`:
|
|
1996
|
+
a live answer ends where it is given.
|
|
1873
1997
|
- `refund-request` records a decision to `scratchpad/refund.json` (no allow entry
|
|
1874
|
-
narrows it — the working area is open to `write_file` wherever it is granted;
|
|
1875
|
-
correct file is the
|
|
1876
|
-
`
|
|
1877
|
-
|
|
1878
|
-
|
|
1998
|
+
narrows it — the working area is open to `write_file` wherever it is granted;
|
|
1999
|
+
what holds the agent to a correct file is the check) and advances to
|
|
2000
|
+
`check-refund`, a code state **behind** it (`scripts/check-refund.js`): it
|
|
2001
|
+
re-derives the policy, advances to `refund-review` when the decision matches,
|
|
2002
|
+
sends it back with the fix otherwise, and escalates after two send-backs.
|
|
2003
|
+
`refund-request` pairs `budget.maxTurns: 12` with `on_error: escalation`.
|
|
1879
2004
|
- `refund-review` (`type: human`, `evidence: [scratchpad/refund.json]`) — a person
|
|
1880
2005
|
approves (→ terminal `refund-closed`) or refines (→ back to `refund-request`).
|
|
1881
|
-
- `escalation` (terminal) is the error handler entered
|
|
1882
|
-
`refund
|
|
2006
|
+
- `escalation` (terminal) is the error handler entered via `on_error` (and from
|
|
2007
|
+
`check-refund`'s give-up edge).
|
|
1883
2008
|
- `general-question` (terminal) answers and writes `scratchpad/answer.json`.
|
|
2009
|
+
- `report-requested` (terminal, `triggers: { report_requested: }`) writes
|
|
2010
|
+
`scratchpad/report.json` for the requested `company` and summarizes it; when the
|
|
2011
|
+
order records cannot be read, the agent ends the session with `archmax_raise`
|
|
2012
|
+
instead (case `orders-unavailable`).
|
|
2013
|
+
- `enrich-order` (`triggers: { enrichment_requested: }`) — a host-fired path no
|
|
2014
|
+
person waits on — runs the `order-enrichment` fan-out script with `archmax_run`,
|
|
2015
|
+
records its result as `enrichments` (`requires: [enrichments]`), and advances to
|
|
2016
|
+
`review-enrichment`, a **grader** behind it: it reads `orders_to_enrich` and
|
|
2017
|
+
`enrichments` with `archmax_get_variables` and each returned `enrichment_file`
|
|
2018
|
+
with `read_file`. It approves into `enrichment-reported` (a terminal agent state
|
|
2019
|
+
that summarizes the enrichments), sends them back to `enrich-order` naming what
|
|
2020
|
+
to fix, or raises `enrichment-incorrect` once it has sent them back twice,
|
|
2021
|
+
counted on `archmax_get_history`'s trail. Case `enrichment-fan-out` pins the
|
|
2022
|
+
approval as a `grader` trail step.
|
|
1884
2023
|
|
|
1885
2024
|
### Source of truth in code
|
|
1886
2025
|
|
|
@@ -1888,13 +2027,14 @@ If you need to confirm a detail, these files define the schema in the runtime:
|
|
|
1888
2027
|
|
|
1889
2028
|
| Concern | File |
|
|
1890
2029
|
| --- | --- |
|
|
1891
|
-
|
|
|
2030
|
+
| The schema (every key, `script` grammar, `refineSpec`) | `src/machine/spec-schema.ts` |
|
|
2031
|
+
| Per-type lint errors and warnings | `src/machine/lint-spec.ts` |
|
|
2032
|
+
| Spec types (`MachineState`, `Transition`, `AllowEntry`, `WorkflowTestsConfig`) | `src/machine/types.ts` |
|
|
1892
2033
|
| Two-file loading (`workflow.yaml` + prose addendum) | `src/machine/load-spec.ts` |
|
|
1893
|
-
|
|
|
1894
|
-
| allow-entry matching
|
|
2034
|
+
| Type-aware surfaces (`controlTools`, `disclosedTools`, `checkAllowed`) | `src/machine/machine.ts`, `tool-names.ts` |
|
|
2035
|
+
| allow-entry matching | `src/machine/allow.ts` |
|
|
1895
2036
|
| Rendered workflow header + per-state graph block | `src/workflow/render-prompt.ts` |
|
|
1896
|
-
|
|
|
2037
|
+
| Automated states: running, outcomes, controls, code and grader runs, history | `src/workflow/automated.ts`, `automated-controls.ts`, `code-state.ts`, `grader-state.ts`, `history.ts` |
|
|
1897
2038
|
| Governance kernel (precedence) | `src/kernel/kernel.ts` |
|
|
1898
|
-
| `archmax_advance`
|
|
2039
|
+
| `archmax_advance` and the other control tools | `src/workflow/control-tools.ts` |
|
|
1899
2040
|
| Checkpointed state, `WORKFLOW_STATUSES` | `src/workflow/state.ts` |
|
|
1900
|
-
| Grading rubrics, verdict schema | `src/rubrics/rubrics.ts` |
|