@archmax-ai/harness 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +11 -4
  2. package/dist/assembly/compose.d.ts +5 -3
  3. package/dist/assembly/compose.js +8 -5
  4. package/dist/assembly/compose.js.map +1 -1
  5. package/dist/assembly/index.d.ts +3 -2
  6. package/dist/assembly/index.js.map +1 -1
  7. package/dist/authoring-skill/archmax-harness/SKILL.md +9 -5
  8. package/dist/authoring-skill/archmax-harness/references/backend-integration.md +3 -2
  9. package/dist/authoring-skill/archmax-harness/references/diagnostics.md +5 -3
  10. package/dist/authoring-skill/archmax-harness/references/hook-and-test-scripts.md +5 -4
  11. package/dist/authoring-skill/archmax-harness/references/hooks-and-scripts.md +4 -1
  12. package/dist/authoring-skill/archmax-harness/references/workflow-schema.md +78 -46
  13. package/dist/authoring-skill/archmax-harness/references/workflow-yaml.md +30 -14
  14. package/dist/core/platform-prompt.generated.js +1 -1
  15. package/dist/core/platform-prompt.generated.js.map +1 -1
  16. package/dist/core/workspace-prompt.js +2 -1
  17. package/dist/core/workspace-prompt.js.map +1 -1
  18. package/dist/kernel/kernel.js +12 -8
  19. package/dist/kernel/kernel.js.map +1 -1
  20. package/dist/machine/lint-spec.js +62 -29
  21. package/dist/machine/lint-spec.js.map +1 -1
  22. package/dist/machine/machine.d.ts +33 -17
  23. package/dist/machine/machine.js +48 -28
  24. package/dist/machine/machine.js.map +1 -1
  25. package/dist/machine/spec-schema.d.ts +15 -7
  26. package/dist/machine/spec-schema.js +43 -15
  27. package/dist/machine/spec-schema.js.map +1 -1
  28. package/dist/machine/tool-names.d.ts +10 -7
  29. package/dist/machine/tool-names.js +11 -8
  30. package/dist/machine/tool-names.js.map +1 -1
  31. package/dist/machine/tool-paths.d.ts +4 -3
  32. package/dist/machine/tool-paths.js +4 -3
  33. package/dist/machine/tool-paths.js.map +1 -1
  34. package/dist/machine/types.d.ts +14 -8
  35. package/dist/machine/types.js.map +1 -1
  36. package/dist/public/spec.d.ts +3 -3
  37. package/dist/public/spec.js +3 -3
  38. package/dist/public/spec.js.map +1 -1
  39. package/dist/sandbox/executor.js +16 -4
  40. package/dist/sandbox/executor.js.map +1 -1
  41. package/dist/sandbox/prelude.js +20 -7
  42. package/dist/sandbox/prelude.js.map +1 -1
  43. package/dist/workflow/render-prompt.d.ts +2 -1
  44. package/dist/workflow/render-prompt.js +24 -5
  45. package/dist/workflow/render-prompt.js.map +1 -1
  46. package/package.json +1 -1
  47. package/skills/archmax-harness/SKILL.md +9 -5
  48. package/skills/archmax-harness/references/backend-integration.md +3 -2
  49. package/skills/archmax-harness/references/diagnostics.md +5 -3
  50. package/skills/archmax-harness/references/hook-and-test-scripts.md +5 -4
  51. package/skills/archmax-harness/references/hooks-and-scripts.md +4 -1
  52. package/skills/archmax-harness/references/workflow-schema.md +78 -46
  53. package/skills/archmax-harness/references/workflow-yaml.md +30 -14
@@ -80,8 +80,8 @@ instructions?: string # the workflow's STANDING instructions — free-fo
80
80
  # simply ignored; use `instructions:`, rereading the
81
81
  # text as direction, not narration.
82
82
  # A test case's `description` is UNAFFECTED. A
83
- # TRANSITION's `description` is now REQUIRED and
84
- # non-empty (see `transitions` below).
83
+ # TRANSITION's `description` is what the agent routes
84
+ # on (see `transitions` below).
85
85
 
86
86
  disabled?: boolean # `true` = this machine STARTS NO NEW TURNS. Bounds
87
87
  # STARTING, not finishing.
@@ -146,7 +146,13 @@ tests?: # cases suite config (declarative data;
146
146
  # factory's `judge` role; other keys are errors
147
147
 
148
148
  tools?:
149
- allow_always?: AllowEntry[] # grants added to every state's allowlist
149
+ allow_always?: AllowEntry[] # the WORKFLOW-WIDE GRANT, in every state. ABSENT = the
150
+ # default tools (ls, read_file, write_file, edit_file,
151
+ # glob, grep, copy_file, move_file, remove_file,
152
+ # write_todos, archmax_eval, archmax_run). DECLARED
153
+ # ([] included) = exactly what it lists: it REPLACES
154
+ # the default tools, so list each one you want. A
155
+ # new workflow lists all twelve.
150
156
  forbid_always?: ForbidEntry[]
151
157
  # DENIED in EVERY state, and absolute: nothing reaches
152
158
  # past it — no state grant, allow_always entry,
@@ -312,8 +318,9 @@ states:
312
318
  # NO PART OF THE PROMPT — the model never reads it,
313
319
  # so it is free, and it can NOT carry routing logic.
314
320
  # The routing text the agent reads is the SOURCE
315
- # state's transition `description`s; nothing about a
316
- # target state is disclosed at all.
321
+ # state's transition `description`s, or a target's
322
+ # `instructions` where an edge is undescribed; a
323
+ # target's summary is never disclosed.
317
324
 
318
325
  instructions?: string # behavior guidance.
319
326
  # agent node: surfaced to the LLM while active.
@@ -328,13 +335,14 @@ states:
328
335
  # for a terminal state)
329
336
 
330
337
  tools?:
331
- allow?: AllowEntry[] # grants beyond the always-on tools (plus
332
- # allow_always grants and the always-allowed
333
- # archmax_advance); an entry naming
334
- # an always-on tool NARROWS it.
338
+ allow?: AllowEntry[] # grants beyond the workflow-wide grant (plus the
339
+ # always-allowed archmax_advance); an entry naming
340
+ # a tool that grant includes NARROWS it, and one
341
+ # naming a default tool a declared allow_always
342
+ # leaves out GRANTS it here.
335
343
  forbid?: ForbidEntry[] # what this state DENIES while active — beating the
336
- # workflow's allow_always, this state's own allow,
337
- # and the always-on surface alike. Deny beats allow.
344
+ # workflow-wide grant, this state's own allow,
345
+ # and a host's essentialTools alike. Deny beats allow.
338
346
  # Not inherited by a sub-workflow; a lifecycle hook
339
347
  # is exempt from it (runtime authority).
340
348
 
@@ -421,25 +429,35 @@ states:
421
429
  #
422
430
  # Transition = {
423
431
  # to: string # a declared state's SLUG
424
- # description: string # REQUIRED, non-empty
432
+ # description?: string # what the agent routes on
425
433
  # type?: "approve" | "reject" | "refine" | "none"
426
434
  # }
427
435
  #
428
436
  # THE AGENT SEES THIS STATE'S EDGES AND NOTHING
429
437
  # ELSE OF THE GRAPH — no other state's slug, title,
430
438
  # summary, hooks or transitions, and no count of
431
- # the states. So `description` is the whole of what
432
- # it knows about where an edge leads, and a missing
433
- # or blank one is a LOAD ERROR naming
434
- # states.<slug>.transitions.<i>.description.
439
+ # the states. So `description` is what it knows
440
+ # about where an edge leads. An UNDESCRIBED edge
441
+ # (absent/empty/blank) routes on the TARGET's
442
+ # `instructions` instead: the edge's line carries
443
+ # them, quoted, and nothing else of the target.
444
+ # LOAD ERRORS (states.<slug>.transitions.<i>.description):
445
+ # an undescribed edge whose target has no
446
+ # instructions; an undescribed transition of a
447
+ # HUMAN state (it is the button label); a
448
+ # non-string. An undescribed edge INTO a human
449
+ # state discloses that state's reviewer-facing
450
+ # instructions to the agent — describe it to keep
451
+ # them private.
435
452
  #
436
453
  # Write it for a reader standing in THIS state,
437
454
  # which knows nothing about the target:
438
455
  # GOOD: "The refund is over $50." (when)
439
456
  # BAD: "Go to refund-review." (where)
440
457
  # A routing condition CANNOT live in the target's
441
- # summary/instructions — those are never disclosed
442
- # to the state routing into it.
458
+ # summary, which is never disclosed; it lives in the
459
+ # target's instructions only when the edge is left
460
+ # undescribed.
443
461
  #
444
462
  # The rendered line marks a target that is a human
445
463
  # decision node or terminal (each changes the call
@@ -666,11 +684,12 @@ an entry naming an unprefixed variant governs a tool that does not exist,
666
684
  silently granting nothing. A host tool may never claim the prefix. Deep Agents' built-ins
667
685
  (`read_file`, `task`, …) keep their own names.
668
686
 
669
- - A state with **no `tools` block** gets the **always-on tools only**
670
- (`ls`, `read_file`, `write_file`, `edit_file`, `copy_file`, `move_file`, `remove_file`, `glob`,
671
- `grep`, `write_todos`, `archmax_eval`, `archmax_run`, plus the always-allowed `archmax_advance` and `allow_always`
672
- grants — minus
673
- whatever a `forbid` list denies there, and read-only-zone protection).
687
+ - A state with **no `tools` block** gets the **workflow-wide grant only**: what
688
+ `tools.allow_always` lists, or the default tools (`ls`, `read_file`, `write_file`,
689
+ `edit_file`, `copy_file`, `move_file`, `remove_file`, `glob`, `grep`, `write_todos`,
690
+ `archmax_eval`, `archmax_run`) when the key is absent, plus the always-allowed
691
+ `archmax_advance` and a host's `essentialTools`, minus whatever a `forbid` list
692
+ denies there, and read-only-zone protection.
674
693
  Governance is closed by default; there is no way to declare a fully open state.
675
694
  - `tools.forbid` on a state **denies** while that state is active, beating every
676
695
  grant that would otherwise reach it (see [Tool governance](#tool-governance)).
@@ -712,7 +731,8 @@ success.
712
731
  ```yaml
713
732
  transitions:
714
733
  - to: string # REQUIRED target state SLUG (must exist); never a title
715
- description?: string # shown to the agent (or human) to pick the edge
734
+ description?: string # shown to the agent (or human) to pick the edge; absent =
735
+ # the agent is shown the target's instructions instead
716
736
  type?: "approve" | "reject" | "refine" | "none" # default "none"
717
737
  ```
718
738
 
@@ -746,22 +766,30 @@ platform-provided tool resolves through when its collection has several) and
746
766
  `source` (any string: a host's own label for where the tool came from) — the
747
767
  runtime matches governance by `tool` name alone and never reads `source`.
748
768
 
749
- **The model is closed by default, and a denial beats every grant.** Every state always has the
750
- **always-on tools** (`ls`, `read_file`, `write_file`, `edit_file`, `copy_file`, `move_file`,
751
- `remove_file`, `glob`, `grep`, `write_todos`, `archmax_eval`, `archmax_run`). One built-in is **never
769
+ **The model is closed by default, and a denial beats every grant.** Every state has the
770
+ **workflow-wide grant**: what `tools.allow_always` lists, or, when the workflow does not
771
+ declare that key, the **default tools** (`ls`, `read_file`, `write_file`, `edit_file`,
772
+ `copy_file`, `move_file`, `remove_file`, `glob`, `grep`, `write_todos`, `archmax_eval`,
773
+ `archmax_run`). A declared list **replaces** the default tools, never adds to them:
774
+ `allow_always: []` grants no tool workflow-wide, and a list without `write_file` leaves
775
+ 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 the always-allowed controls and
777
+ any tool the host passes as `essentialTools`, whatever the workflow declares. One built-in is **never
752
778
  disclosed** and grantable by nothing: `task`, the runtime's own dispatch for
753
779
  grading rubrics — a rubric grades the agent rather than serving it, so naming
754
780
  `task` in an allow entry is a `validate` error and the kernel refuses the call.
755
781
  The planning scratchpad `write_todos` and the sandbox
756
- `archmax_eval` are standard equipment — always disclosed, always permitted,
757
- no setting; only a `forbid` list refuses them. The interpreter is safe as a
782
+ `archmax_eval` are default tools: granted wherever the workflow-wide grant includes
783
+ them (everywhere, in a workflow without `allow_always`). `write_todos`'s prompt
784
+ guidance appears only when some state is granted it, and hooks run in the sandbox
785
+ whether or not the agent is granted `archmax_eval`. The interpreter is safe as a
758
786
  default because the `tools.*` calls its code makes are decided against the active
759
787
  state by the same kernel: it adds computation over what the state permits, not
760
- access. `archmax_run` — executing an authored script **file** — is always on on
788
+ access. `archmax_run` — executing an authored script **file** — is a default tool on
761
789
  the same terms (the script is a file the state could already read); declare it
762
790
  per state to **narrow** which files may run there. The file operations —
763
791
  `copy_file({ source, destination, overwrite? })`, `move_file` (same arguments)
764
- and `remove_file({ file_path })` — are always on on the same terms: every tool
792
+ and `remove_file({ file_path })` — are default tools on the same terms: every tool
765
793
  **declares its path arguments** with how the call uses each (`read`, `list`,
766
794
  `search`, `write`, `remove`, `execute`), and every path rule reads that
767
795
  declaration. `copy_file` reads `source` and writes `destination`; `move_file`
@@ -774,16 +802,19 @@ entry guards every declared path argument: a grant needs all of them to match
774
802
  declares its paths (`toolPaths`, or a descriptor's `paths`) is governed the same
775
803
  way; an argument may hold a list of paths, each one governed (and each one
776
804
  tested by a `paths:` guard), and any other shape is refused
777
- (`tool.path-argument`). `scratchpad/` is open in every state to the always-on
778
- tools only — a host tool a state must grant stays refused where it is not
779
- granted, whatever its paths. Any other tool
805
+ (`tool.path-argument`). `scratchpad/` is open in every state to each default file tool the state
806
+ is granted, whatever the granting entry narrows, and to a host's `essentialTools`.
807
+ A file tool the state is not granted gets nothing from it, and a host tool a state
808
+ must grant stays refused where it is not granted, whatever its paths. Any other tool
780
809
  must be declared in the state's `tools.allow` or workflow-level
781
- `tools.allow_always`. A `tools.allow` entry naming an always-on tool **narrows**
782
- it for that state.
810
+ `tools.allow_always`. A `tools.allow` entry naming a tool the workflow-wide grant
811
+ includes **narrows** it for that state. A guard on an `allow_always` entry is
812
+ enforced like any other (`{ tool: write_file, paths: [reports/**] }` binds in every
813
+ state that does not name `write_file`).
783
814
  Denial is declared beside the grant, in the same two positions and with the same
784
815
  entries: `tools.forbid_always` at the root denies in every state,
785
816
  `tools.forbid` on a state denies while it is active — subtracting from the
786
- workflow's grant, the state's own `allow` and the always-on surface alike.
817
+ workflow-wide grant, the state's own `allow` and a host's `essentialTools` alike.
787
818
  **Deny beats allow, and no narrower level widens a denial.** A forbid entry may
788
819
  name the tool `*` (every tool); `*` in an allow list is a load error. A lifecycle
789
820
  hook is exempt from the state's `allow` **and** its `forbid` — runtime authority,
@@ -803,17 +834,18 @@ Precedence (highest first):
803
834
  (and regardless of always-allowed): deny beats allow, and no narrower level
804
835
  widens a denial.
805
836
  2. **Always allowed**: `archmax_advance` (baked in, every state).
806
- 3. **Per-state `tools.allow`** — the listed tools/arg-shapes. An entry for an
807
- always-on tool (or one also in `allow_always`) takes precedence
808
- (**narrower wins** — the state's arg globs apply, not the broader grant).
809
- 4. **Essential built-ins** — always permitted when no state entry narrows
810
- them. Listing one in `allow_always` is inert (`archmax validate` warns).
811
- 5. **Workflow `tools.allow_always`** — grants tools that are not always on in every
812
- state.
837
+ 3. **Per-state `tools.allow`** — the listed tools/arg-shapes. An entry for a
838
+ tool the workflow-wide grant includes takes precedence (**narrower wins** —
839
+ the state's arg globs apply, not the broader grant); an entry for a default
840
+ tool a declared `allow_always` omits grants it in this state.
841
+ 4. **The workflow-wide grant** — the declared `tools.allow_always`, guards
842
+ included, or the default tools, unconstrained, when the key is absent.
843
+ 5. **The host's `essentialTools`** — always permitted when no state entry
844
+ narrows them, whatever the workflow declares.
813
845
  6. **Default**: everything else is blocked.
814
846
 
815
847
  **Progressive disclosure.** The model is only shown the active state's tool
816
- surface: always-on tools + `allow_always` + the state's entries + `archmax_advance`
848
+ surface: the workflow-wide grant + a host's `essentialTools` + the state's entries + `archmax_advance`
817
849
  (hidden in terminal states), minus every tool denied there by name. Disclosure is
818
850
  name-level (arg-constrained tools stay visible; the kernel enforces args at
819
851
  call time) and never replaces enforcement. **Skills are disclosed the same way**:
@@ -1839,7 +1871,7 @@ them. Its shape:
1839
1871
  - `orders-question` (terminal) answers from `skills/order-data/assets/orders.json`; an
1840
1872
  `after: { rubric: order-reply-tone }` grades the reply at completion.
1841
1873
  - `refund-request` records a decision to `scratchpad/refund.json` (no allow entry
1842
- narrows it — the working area is open in every state; what holds the agent to a
1874
+ narrows it — the working area is open to `write_file` wherever it is granted; what holds the agent to a
1843
1875
  correct file is the hook), runs `after: check-refund.js`, and pairs
1844
1876
  `budget.maxTurns: 24` with `on_error: escalation` so a terminal failure
1845
1877
  (hook error, exhausted corrections, exhausted budget) routes to escalation
@@ -27,7 +27,19 @@ metadata: { canvas: {} } # host data, never read; accepted at root, stat
27
27
  extensions: { hooks: [] } # host-registered hook kinds; rarely authored
28
28
 
29
29
  tools: # tool governance, workflow level
30
- allow_always: [] # granted in EVERY state; a state's allow adds to it
30
+ allow_always: # granted in EVERY state; a state's allow adds to or narrows it.
31
+ - ls # The DEFAULT TOOLS: declared, this list REPLACES them (absent,
32
+ - read_file # all twelve are granted), so a new workflow lists all twelve and
33
+ - write_file # removes only what it must not have. Workflow-wide host tools
34
+ - edit_file # go below them; state-specific ones under a state's tools.allow.
35
+ - glob
36
+ - grep
37
+ - copy_file
38
+ - move_file
39
+ - remove_file
40
+ - write_todos
41
+ - archmax_eval
42
+ - archmax_run
31
43
  forbid_always: [archmax_reset] # denied in EVERY state; beats every grant; "*" = every tool
32
44
  skills: # skill governance, workflow level — SLUGS, never paths
33
45
  allow_always: [order-data] # enabled in every state; [] = every capability is state-specific
@@ -67,10 +79,10 @@ states:
67
79
  tools:
68
80
  allow: # adds to allow_always; entry = name | { tool, args: {<arg>: [globs]} } | { tool, paths }
69
81
  - lookup_customer
70
- - { tool: archmax_run, paths: ["skills/refund-policy/scripts/**"] } # narrows an always-on tool, within an enabled skill
82
+ - { tool: archmax_run, paths: ["skills/refund-policy/scripts/**"] } # narrows the workflow-wide grant, within an enabled skill
71
83
  - { tool: write_file, args: { file_path: ["reports/${{case_id}}/*.json"] } } # variable-bound guard
72
84
  - archmax_workflow_enrich-order # runs a sibling workflow
73
- forbid: [archmax_wait] # denies here; beats every grant incl. the always-on surface
85
+ forbid: [archmax_wait] # denies here; beats every grant incl. allow_always
74
86
  skills:
75
87
  allow: [refund-policy] # adds to allow_always (bundle readable + scripts runnable, no path entries)
76
88
  forbid: [order-data] # subtracts, even from allow_always ([] never denies)
@@ -81,9 +93,10 @@ states:
81
93
  metadata: { x: 40, y: 180 }
82
94
  transitions: # none => terminal state
83
95
  - to: review # slug; must exist
84
- description: The decision is recorded and needs a person's approval. # REQUIRED, non-empty:
85
- # the ONLY thing the agent knows about this edge (on a human
86
- # state, also the button label). Say WHEN, never where.
96
+ description: The decision is recorded and needs a person's approval. # what the agent routes
97
+ # on. Say WHEN, never where. Omit it to route on the target's
98
+ # instructions instead (target must have some); REQUIRED on a
99
+ # human state's transitions, where it is the button label.
87
100
  type: none # approve | reject | refine | none — semantic on human states
88
101
 
89
102
  review: # human state: parks with a decision record; a PERSON picks the edge
@@ -252,14 +265,17 @@ no way to see more of the graph.
252
265
 
253
266
  Closed by default; **deny beats allow, and no narrower level widens a denial.**
254
267
 
255
- - **Always on, never declared**: `ls`, `read_file`, `write_file`,
256
- `edit_file`, `copy_file`, `move_file`, `remove_file`, `glob`, `grep`,
257
- `write_todos`, `archmax_eval`, `archmax_run`,
258
- and the controls `archmax_advance`, `archmax_reset`, `archmax_wait`,
259
- `archmax_raise`, `archmax_get_variables`, `archmax_set_variables`. Any other
260
- tool needs an
261
- `allow`/`allow_always` entry. An entry naming an always-on tool **narrows**
262
- it. `task` is ungrantable (error).
268
+ - **The workflow-wide grant**: `tools.allow_always`, in every state. Absent,
269
+ it is the **default tools**: `ls`, `read_file`, `write_file`, `edit_file`,
270
+ `copy_file`, `move_file`, `remove_file`, `glob`, `grep`, `write_todos`,
271
+ `archmax_eval`, `archmax_run`. Declared (`[]` included), it is exactly what it
272
+ lists and **replaces** the default tools, so a new workflow lists all twelve
273
+ (as in the skeleton) and removes only what it must not have. Never declared:
274
+ the controls `archmax_advance`, `archmax_reset`, `archmax_wait`,
275
+ `archmax_raise`, `archmax_get_variables`, `archmax_set_variables`, and a host's
276
+ `essentialTools`. Any other tool needs an `allow`/`allow_always` entry. A
277
+ state entry naming a granted tool **narrows** it there, and one naming a
278
+ default tool the list omits grants it there. `task` is ungrantable (error).
263
279
  - **Entries**: bare name; `{ tool, args: { <arg>: ["glob", …] } }` (matches
264
280
  if any glob matches; `${{var}}` allowed); `{ tool, paths: [...] }`
265
281
  (guards every path argument the tool declares — all must match to grant,