@archmax-ai/harness 0.1.0 → 0.2.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 +45 -4
- package/dist/assembly/compose.js +3 -5
- package/dist/assembly/compose.js.map +1 -1
- package/dist/assembly/delegation-registry.js +4 -2
- package/dist/assembly/delegation-registry.js.map +1 -1
- package/dist/authoring-skill/archmax-harness/README.md +24 -16
- package/dist/authoring-skill/archmax-harness/SKILL.md +7 -1
- package/dist/authoring-skill/archmax-harness/references/backend-integration.md +58 -2
- package/dist/authoring-skill/archmax-harness/references/workflow-schema.md +66 -20
- package/dist/authoring-skill/archmax-harness/references/workflow-yaml.md +14 -4
- package/dist/cli/turn.d.ts +9 -4
- package/dist/cli/turn.js +35 -8
- package/dist/cli/turn.js.map +1 -1
- package/dist/cli.js +4 -5
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +12 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -1
- package/dist/machine/machine.d.ts +9 -0
- package/dist/machine/machine.js +13 -0
- package/dist/machine/machine.js.map +1 -1
- package/dist/machine/signature.d.ts +104 -0
- package/dist/machine/signature.js +210 -0
- package/dist/machine/signature.js.map +1 -0
- package/dist/machine/spec-schema.d.ts +158 -8
- package/dist/machine/spec-schema.js +78 -6
- package/dist/machine/spec-schema.js.map +1 -1
- package/dist/machine/triggers.d.ts +6 -2
- package/dist/machine/triggers.js +7 -2
- package/dist/machine/triggers.js.map +1 -1
- package/dist/machine/types.d.ts +7 -1
- package/dist/machine/types.js.map +1 -1
- package/dist/machine/variables.d.ts +2 -0
- package/dist/machine/variables.js +4 -0
- package/dist/machine/variables.js.map +1 -1
- package/dist/public/spec.d.ts +17 -2
- package/dist/public/spec.js +15 -1
- package/dist/public/spec.js.map +1 -1
- package/dist/sessions/resume.d.ts +4 -0
- package/dist/sessions/resume.js +8 -1
- package/dist/sessions/resume.js.map +1 -1
- package/dist/workflow/control-tools.d.ts +7 -2
- package/dist/workflow/control-tools.js +27 -2
- package/dist/workflow/control-tools.js.map +1 -1
- package/dist/workflow/governance.js +28 -19
- package/dist/workflow/governance.js.map +1 -1
- package/dist/workflow/middleware.js +5 -10
- package/dist/workflow/middleware.js.map +1 -1
- package/dist/workflow/prompt-cache.d.ts +0 -2
- package/dist/workflow/prompt-cache.js +0 -7
- package/dist/workflow/prompt-cache.js.map +1 -1
- package/dist/workflow/render-prompt.js +28 -7
- package/dist/workflow/render-prompt.js.map +1 -1
- package/dist/workflow/signature-checks.d.ts +22 -0
- package/dist/workflow/signature-checks.js +52 -0
- package/dist/workflow/signature-checks.js.map +1 -0
- package/dist/workflow/sub-workflow.d.ts +23 -16
- package/dist/workflow/sub-workflow.js +78 -14
- package/dist/workflow/sub-workflow.js.map +1 -1
- package/dist/workflow/tool-service.js +1 -1
- package/dist/workflow/tool-service.js.map +1 -1
- package/dist/workflow/turn-boundary.js +4 -5
- package/dist/workflow/turn-boundary.js.map +1 -1
- package/dist/workflow/workflow-tools.d.ts +17 -9
- package/dist/workflow/workflow-tools.js +30 -17
- package/dist/workflow/workflow-tools.js.map +1 -1
- package/package.json +2 -1
- package/skills/archmax-harness/README.md +82 -0
- package/skills/archmax-harness/SKILL.md +247 -0
- package/skills/archmax-harness/references/backend-integration.md +681 -0
- package/skills/archmax-harness/references/cases.md +41 -0
- package/skills/archmax-harness/references/diagnostics.md +51 -0
- package/skills/archmax-harness/references/hook-and-test-scripts.md +1070 -0
- package/skills/archmax-harness/references/hooks-and-scripts.md +82 -0
- package/skills/archmax-harness/references/wiring.md +101 -0
- package/skills/archmax-harness/references/workflow-schema.md +1677 -0
- package/skills/archmax-harness/references/workflow-yaml.md +251 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# `workflow.yaml` — keys, machine rules, governance
|
|
2
|
+
|
|
3
|
+
Load this when writing or editing a `workflow.yaml`. Field-level detail and
|
|
4
|
+
precedence edge cases: [`workflow-schema.md`](workflow-schema.md).
|
|
5
|
+
|
|
6
|
+
## Every key
|
|
7
|
+
|
|
8
|
+
The schema is strict: an unknown key anywhere is a load error naming it. This
|
|
9
|
+
skeleton is the complete vocabulary; omit what you do not need.
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
title: Triage example # label only; identity is the directory slug
|
|
13
|
+
instructions: |- # standing direction for the whole run (warning if absent)
|
|
14
|
+
Records live in skills/order-data/assets/orders.json; write artifacts under scratchpad/.
|
|
15
|
+
|
|
16
|
+
Answer only from records you have read; never fabricate.
|
|
17
|
+
runtime: { engine: archmax-harness, version: "2" }
|
|
18
|
+
disabled: false # true = out of service: no new turns; parked runs still drain
|
|
19
|
+
settings: # sandbox limits: timeoutMs, memoryLimitBytes, maxPtcCalls,
|
|
20
|
+
timeoutMs: 15000 # maxResultChars, prompt_cache: { enabled, ttl }
|
|
21
|
+
model: gpt-5-mini # MODEL ID ONLY — every state runs on it; overrides ARCHMAX_MODEL
|
|
22
|
+
tests: # case suite config (no tests.config.js exists)
|
|
23
|
+
maxConcurrency: 1 # only 1 accepted
|
|
24
|
+
caseTimeoutMs: 120000 # per-case wall clock, distinct from settings.timeoutMs
|
|
25
|
+
judge: {} # required for grade:; {} = env default model, or { model, modelOptions }
|
|
26
|
+
metadata: { canvas: {} } # host data, never read; accepted at root, state, rubric — leave as found
|
|
27
|
+
extensions: { hooks: [] } # host-registered hook kinds; rarely authored
|
|
28
|
+
|
|
29
|
+
tools: # tool governance, workflow level
|
|
30
|
+
allow_always: [] # granted in EVERY state; a state's allow adds to it
|
|
31
|
+
forbid_always: [archmax_reset] # denied in EVERY state; beats every grant; "*" = every tool
|
|
32
|
+
skills: # skill governance, workflow level — SLUGS, never paths
|
|
33
|
+
allow_always: [order-data] # enabled in every state; [] = every capability is state-specific
|
|
34
|
+
forbid_always: [] # out of reach everywhere
|
|
35
|
+
|
|
36
|
+
states:
|
|
37
|
+
triage: # key = slug = identity (^[a-z0-9]+(-[a-z0-9]+)*$)
|
|
38
|
+
type: agent # agent (default) | human
|
|
39
|
+
title: Triage request # label for people/hosts; reaches no model call
|
|
40
|
+
summary: Record the refund decision for review. # ditto: an authoring/host label, NOT in the prompt
|
|
41
|
+
triggers: # a trigger is declared on the state it ENTERS; key = id
|
|
42
|
+
manual: # CLI / caller / host entry; the prompt is its input
|
|
43
|
+
email_reply: # any other id = host event; the payload arrives as locked variables
|
|
44
|
+
session: conversation_id # dotted path over variables: which conversation a firing joins
|
|
45
|
+
message: false # or a dotted path to the message text; false = firings carry none
|
|
46
|
+
connection: mail # host access connection; other keys preserved with a warning
|
|
47
|
+
description: Record one refund decision. # for CALLERS (delegating model, MCP client); never the agent
|
|
48
|
+
requires: # variables a firing must supply (a caller's required params):
|
|
49
|
+
- order_id # a bare name is untyped (any value, null included)
|
|
50
|
+
- { name: due, type: date, description: The day it is due. } # typed: held at the boundary
|
|
51
|
+
returns: [result_file] # variables the run guarantees on completion (a caller's result)
|
|
52
|
+
# entry types: string integer number boolean date date-time object array
|
|
53
|
+
instructions: >-
|
|
54
|
+
Record the decision in scratchpad/refund.json, then archmax_advance to
|
|
55
|
+
review; never close the case yourself.
|
|
56
|
+
before: # gates entry: ok/veto only; a veto here rejects the run
|
|
57
|
+
- script: hooks/check-requester.js
|
|
58
|
+
after: # gates the archmax_advance out (or completion, when terminal)
|
|
59
|
+
- script: hooks/check-policy.js
|
|
60
|
+
max_iterations: 2 # how many `correct`s before a hard veto (per hook; default 0)
|
|
61
|
+
- rubric: # LLM grader, inline; the agent never sees it
|
|
62
|
+
instructions: |- # the only required key: criteria + verdict (ok/correct/veto + reason)
|
|
63
|
+
Judge the tone of the last assistant message …
|
|
64
|
+
max_iterations: 1
|
|
65
|
+
model: gpt-4.1-mini # optional override
|
|
66
|
+
tools:
|
|
67
|
+
allow: # adds to allow_always; entry = name | { tool, args: {<arg>: [globs]} } | { tool, paths }
|
|
68
|
+
- lookup_customer
|
|
69
|
+
- { tool: archmax_run, paths: ["skills/refund-policy/scripts/**"] } # narrows an always-on tool, within an enabled skill
|
|
70
|
+
- { tool: write_file, args: { file_path: ["reports/${{case_id}}/*.json"] } } # variable-bound guard
|
|
71
|
+
- archmax_workflow_enrich-order # runs a sibling workflow
|
|
72
|
+
forbid: [archmax_wait] # denies here; beats every grant incl. the always-on surface
|
|
73
|
+
skills:
|
|
74
|
+
allow: [refund-policy] # adds to allow_always (bundle readable + scripts runnable, no path entries)
|
|
75
|
+
forbid: [order-data] # subtracts, even from allow_always ([] never denies)
|
|
76
|
+
requires: [case_id] # variables that must be set before this state may be left
|
|
77
|
+
model: claude-opus-5 # MODEL ID ONLY — this state's turns; beats settings.model
|
|
78
|
+
budget: { maxTurns: 12, timeoutMs: 60000, maxParks: 5 } # bounds the state's segment
|
|
79
|
+
on_error: closed # where a terminal failure of this segment routes
|
|
80
|
+
metadata: { x: 40, y: 180 }
|
|
81
|
+
transitions: # none => terminal state
|
|
82
|
+
- to: review # slug; must exist
|
|
83
|
+
description: The decision is recorded and needs a person's approval. # REQUIRED, non-empty:
|
|
84
|
+
# the ONLY thing the agent knows about this edge (on a human
|
|
85
|
+
# state, also the button label). Say WHEN, never where.
|
|
86
|
+
type: none # approve | reject | refine | none — semantic on human states
|
|
87
|
+
|
|
88
|
+
review: # human state: parks with a decision record; a PERSON picks the edge
|
|
89
|
+
type: human
|
|
90
|
+
title: Review refund
|
|
91
|
+
summary: A person approves the recorded decision or sends it back. # label only
|
|
92
|
+
instructions: >- # required on human states: the decision to present
|
|
93
|
+
Confirm scratchpad/refund.json matches policy.
|
|
94
|
+
approvers: [finance@acme.com]
|
|
95
|
+
evidence: [scratchpad/refund.json] # plus whatever archmax_advance attached via `evidence:`
|
|
96
|
+
transitions: # required; no self-edge; one target per labeled type
|
|
97
|
+
- { to: closed, type: approve, description: Approve and close. }
|
|
98
|
+
- { to: triage, type: refine, description: Send back with a comment. }
|
|
99
|
+
|
|
100
|
+
closed: # bare key = terminal state with no declarations
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Machine rules
|
|
104
|
+
|
|
105
|
+
- **Movement.** The agent moves only by `archmax_advance({ to, reason, evidence? })`
|
|
106
|
+
— one per message, edge validated, then the leaving state's `after` hooks,
|
|
107
|
+
the target's `before` hooks, then the target's tools unlock. A state with no
|
|
108
|
+
transitions is terminal; the run ends when the agent finishes there. Human
|
|
109
|
+
decisions and `on_error` are the other two movers. `archmax_reset({ reason })`
|
|
110
|
+
returns to the entry state (forbid it via `forbid_always` when a run must
|
|
111
|
+
never restart). No fan-out: sequence states; concurrency lives in
|
|
112
|
+
sub-workflow calls.
|
|
113
|
+
- **Entry.** At least one state declares `triggers`; one state per trigger id;
|
|
114
|
+
`manual` is the entry the CLI, a host firing and a caller all use. A
|
|
115
|
+
re-invoked finished session is a **new turn in the state it ended in**, not
|
|
116
|
+
at the entry — give a state re-entry transitions if a conversation must move
|
|
117
|
+
on, or park instead of finishing.
|
|
118
|
+
- **Variables.** One flat checkpointed `name → value` store, never declared.
|
|
119
|
+
Host seeds (`--variables`, `createAgent({ variables })`, a delivery) are
|
|
120
|
+
**locked**; the agent uses `archmax_get_variables`/`archmax_set_variables`
|
|
121
|
+
(`lock: true` to freeze). Reserved: `trigger` (current trigger id, always
|
|
122
|
+
locked) and `title` (the run's one-line task label, agent-owned). A
|
|
123
|
+
`${{name}}` / `${{a.b.-1.c}}` reference works in `tools.allow` globs
|
|
124
|
+
(guard) and inside any text tool argument the model writes (exact
|
|
125
|
+
substitution, no retyping) — but not in scripts, which read
|
|
126
|
+
`args.variables`. Name a guarded variable in an earlier state's `requires`
|
|
127
|
+
or seed it; an unresolvable guard reference fails the run, an unresolvable
|
|
128
|
+
argument reference is refused and retried. An `archmax_set_variables` typo
|
|
129
|
+
creates a new variable; `requires` is what surfaces it.
|
|
130
|
+
- **Models.** `settings.model` names the id every state runs on and a state's
|
|
131
|
+
`model` names its own — an **id only**, over the endpoint the assembly is
|
|
132
|
+
already configured with (a block with `temperature` is a load error;
|
|
133
|
+
sampling is env/host configuration). Precedence: state → workflow →
|
|
134
|
+
`ARCHMAX_MODEL`. Author it where the work differs in kind: small models for
|
|
135
|
+
classification, extraction and other mechanical states; leave drafting,
|
|
136
|
+
judgement and any state with a big tool surface or several transitions on
|
|
137
|
+
the default, since a small model that routes badly costs more in retries
|
|
138
|
+
than it saves. Repeating the workflow's id on a state is a validate warning.
|
|
139
|
+
A rubric keeps its own `model`, a sub-workflow uses its own spec's, and a
|
|
140
|
+
host that passes an explicit `model` instance to `createAgent` outranks
|
|
141
|
+
every declared id (it must pass `modelFactory` to honour them).
|
|
142
|
+
- **Parking.** A state that needs an event or a time calls
|
|
143
|
+
`archmax_wait({ reason, until? })` (`until`: `30m`/`2h`/`1d` or ISO instant →
|
|
144
|
+
`resumeAt` for the host's scheduler; the runtime holds no timer). Any
|
|
145
|
+
delivered trigger resumes **that same state** with the payload as locked
|
|
146
|
+
variables and an `[event]` runtime note; the state then picks its own edge.
|
|
147
|
+
Bound a polling loop with `budget.maxParks` + `on_error`. `archmax_wait` is
|
|
148
|
+
in every state, terminal ones included. Use a **human state** when a person
|
|
149
|
+
picks the edge, `archmax_wait` when an event or time resumes the work.
|
|
150
|
+
- **Human states** park with a record built from `instructions`, `evidence`
|
|
151
|
+
and transition `type`s. The handoff spends one reply-only model call so the
|
|
152
|
+
park carries a message — do **not** instruct the agent to announce the
|
|
153
|
+
handoff (two messages). While parked, `archmax reply` is answered on a
|
|
154
|
+
tool-free turn and re-parks unchanged; only `decide` moves the run. A
|
|
155
|
+
`refine` edge delivers the reviewer's comment as correction.
|
|
156
|
+
- **Hooks.** `before`: ok/veto (veto rejects entry; on a start state, the
|
|
157
|
+
run; routed through `on_error` if declared). `after`: ok/correct/veto on
|
|
158
|
+
the outgoing advance or on completion of a terminal state; `correct` sends
|
|
159
|
+
the agent back with the reason, bounded per hook by `max_iterations`
|
|
160
|
+
(default 0 → a correct is a hard veto). A list runs in order and stops at
|
|
161
|
+
the first non-ok. Errors and missing files veto (fail-closed). Hooks run
|
|
162
|
+
on runtime authority: the state's `allow`/`forbid`/`skills` do not bind
|
|
163
|
+
them; `forbid_always` and safety rules do. Human states may not declare
|
|
164
|
+
hooks.
|
|
165
|
+
- **Budgets and `on_error`.** `budget.maxTurns`/`timeoutMs`/`maxParks` bound
|
|
166
|
+
a state's segment. A terminal failure (hook execution error, exhausted
|
|
167
|
+
corrections, exhausted budget, exhausted parks) routes to `on_error`
|
|
168
|
+
instead of failing the run; recoverable rejections (invalid edge, veto,
|
|
169
|
+
in-budget correct) keep the agent in place. Declare `on_error` on states
|
|
170
|
+
with strict `after` hooks or bounded loops. A **tool that throws** is
|
|
171
|
+
neither: the call is answered with an error-status tool message carrying
|
|
172
|
+
the error, the agent stays in the state and can retry or switch tools, and
|
|
173
|
+
`on_error` never sees it (a park or a cancelled run still propagates).
|
|
174
|
+
- **Sub-workflows are tools.** Allow `archmax_workflow_<slug>` (slug verbatim)
|
|
175
|
+
and the sibling `workflows/<slug>/` is callable: its `manual` trigger's
|
|
176
|
+
`requires` are the parameters (typed where the entries are typed; a
|
|
177
|
+
mistyped argument is refused `invalid-param`, and `"${{count}}"` alone
|
|
178
|
+
passes the value with its own type), its `returns` the result
|
|
179
|
+
(`{ message, returns }`; a mistyped typed return fails `invalid-return`).
|
|
180
|
+
The trigger's `description` leads the tool description. Nothing captures the result — the caller records
|
|
181
|
+
what it needs with `archmax_set_variables`, made mandatory by `requires`.
|
|
182
|
+
The child is a separate session with a fresh transcript, sees only its
|
|
183
|
+
arguments (as locked variables), and shares no prose. One call = one
|
|
184
|
+
sub-run; fan out with parallel tool calls or `Promise.all` in a script
|
|
185
|
+
(bounds: depth 3, 4 concurrent). A child's human state parks the child;
|
|
186
|
+
a script's delegation cannot park and fails closed.
|
|
187
|
+
- **Choosing a mechanism.** Deterministic work with no model → an `archmax_run`
|
|
188
|
+
script. Delegated work that is itself a process or reused across workflows
|
|
189
|
+
→ a sub-workflow. Judging work already done → a rubric hook (never
|
|
190
|
+
callable by the agent; `task` is disclosed nowhere and not grantable).
|
|
191
|
+
- **Prompt economy.** The static prompt + tool schemas are re-sent every
|
|
192
|
+
model call. The static part carries the workflow's `title` + `instructions`
|
|
193
|
+
and **no state of the graph**; a state's `instructions` and its **own**
|
|
194
|
+
outgoing edges are sent only while it is active. So: workflow-wide
|
|
195
|
+
conventions go in the root `instructions` (paid once, cached); everything
|
|
196
|
+
else goes in the state that needs it, and splitting a broad state into
|
|
197
|
+
several narrow ones costs nothing in the prefix. `title` and `summary` are
|
|
198
|
+
free — neither reaches a model call. `WORKFLOW.md` carries only what the spec
|
|
199
|
+
cannot say; reader diagrams go inside an HTML comment (stripped from the
|
|
200
|
+
prompt).
|
|
201
|
+
|
|
202
|
+
## What the agent sees of the graph
|
|
203
|
+
|
|
204
|
+
Only the state it is in. Every model call is handed that state's outgoing edges
|
|
205
|
+
and nothing else of the machine — no other state's slug, `title`, `summary`,
|
|
206
|
+
hooks or transitions, and no count of the states:
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
Transitions — choose one with `archmax_advance` when this state's work is done:
|
|
210
|
+
- to `review` (human decision node) — The decision is recorded and needs a person's approval.
|
|
211
|
+
- to `closed` (terminal) — Nothing to refund; the case is answered.
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Two rules follow, and they are the ones authors get wrong:
|
|
215
|
+
|
|
216
|
+
1. **Write the `description` for the source state, not the target.** The agent
|
|
217
|
+
reading it knows nothing about where it is going. Say *when* the edge applies:
|
|
218
|
+
`The refund is over $50, or the customer has asked twice.` Not `Go to
|
|
219
|
+
refund-review.` — that tells a reader who already knows the graph something
|
|
220
|
+
they cannot use, and tells this reader nothing.
|
|
221
|
+
2. **Routing conditions cannot live downstream.** A target's `summary` or
|
|
222
|
+
`instructions` is never disclosed to the state routing into it. If the
|
|
223
|
+
condition is "when the order is over 30 days old", it belongs on the edge.
|
|
224
|
+
|
|
225
|
+
A missing or blank `description` is a **load error**. There is no fallback, and
|
|
226
|
+
no way to see more of the graph.
|
|
227
|
+
|
|
228
|
+
## Governance
|
|
229
|
+
|
|
230
|
+
Closed by default; **deny beats allow, and no narrower level widens a denial.**
|
|
231
|
+
|
|
232
|
+
- **Always on, never declared**: `ls`, `read_file`, `write_file`,
|
|
233
|
+
`edit_file`, `glob`, `grep`, `write_todos`, `archmax_eval`, `archmax_run`,
|
|
234
|
+
and the controls `archmax_advance`, `archmax_reset`, `archmax_wait`,
|
|
235
|
+
`archmax_get_variables`, `archmax_set_variables`. Any other tool needs an
|
|
236
|
+
`allow`/`allow_always` entry. An entry naming an always-on tool **narrows**
|
|
237
|
+
it. `task` is ungrantable (error).
|
|
238
|
+
- **Entries**: bare name; `{ tool, args: { <arg>: ["glob", …] } }` (matches
|
|
239
|
+
if any glob matches; `${{var}}` allowed); `{ tool, paths: [...] }`
|
|
240
|
+
(shorthand for `args.file_path`). `"*"` as the tool is legal only in a
|
|
241
|
+
forbid list. Globs match dot-segments (`secrets/**` covers `secrets/.env`).
|
|
242
|
+
- **Skills** are governed by **slug**, never path. Enabling a bundle makes
|
|
243
|
+
`skills/<slug>/**` readable and its scripts runnable with no `tools.allow`
|
|
244
|
+
entry; a disabled bundle is unreachable **and invisible** (not listed, not
|
|
245
|
+
in the prompt). `allow` adds, `forbid` subtracts, `allow: []` denies
|
|
246
|
+
nothing. A `tools.allow` entry may still narrow *within* an enabled bundle.
|
|
247
|
+
A state enabling no skill can run no script at all.
|
|
248
|
+
- **Scripts' `tools.*` calls** are governed per call: an `archmax_run` script
|
|
249
|
+
is bound like the model (grant every tool it calls); a hook is exempt from
|
|
250
|
+
the state's lists. Neither may read `workflows/**` or write a mount. A
|
|
251
|
+
refused call throws inside the script.
|