@archmax-ai/harness 0.0.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.
Files changed (143) hide show
  1. package/README.md +64 -47
  2. package/dist/agent.d.ts +0 -11
  3. package/dist/agent.js.map +1 -1
  4. package/dist/assembly/compose.js +3 -5
  5. package/dist/assembly/compose.js.map +1 -1
  6. package/dist/assembly/delegation-registry.js +4 -2
  7. package/dist/assembly/delegation-registry.js.map +1 -1
  8. package/dist/assembly/index.d.ts +1 -1
  9. package/dist/assembly/index.js +0 -1
  10. package/dist/assembly/index.js.map +1 -1
  11. package/dist/authoring-skill/archmax-harness/README.md +24 -18
  12. package/dist/authoring-skill/archmax-harness/SKILL.md +7 -1
  13. package/dist/authoring-skill/archmax-harness/references/backend-integration.md +66 -11
  14. package/dist/authoring-skill/archmax-harness/references/cases.md +1 -1
  15. package/dist/authoring-skill/archmax-harness/references/hook-and-test-scripts.md +2 -2
  16. package/dist/authoring-skill/archmax-harness/references/wiring.md +1 -1
  17. package/dist/authoring-skill/archmax-harness/references/workflow-schema.md +66 -20
  18. package/dist/authoring-skill/archmax-harness/references/workflow-yaml.md +14 -4
  19. package/dist/cli/bootstrap.d.ts +7 -3
  20. package/dist/cli/bootstrap.js.map +1 -1
  21. package/dist/cli/state-flow.js +3 -3
  22. package/dist/cli/state-flow.js.map +1 -1
  23. package/dist/cli/turn.d.ts +9 -4
  24. package/dist/cli/turn.js +36 -9
  25. package/dist/cli/turn.js.map +1 -1
  26. package/dist/cli.js +9 -10
  27. package/dist/cli.js.map +1 -1
  28. package/dist/core/events.d.ts +4 -9
  29. package/dist/core/events.js +5 -5
  30. package/dist/core/events.js.map +1 -1
  31. package/dist/core/workspace-context.d.ts +0 -2
  32. package/dist/core/workspace-context.js +0 -2
  33. package/dist/core/workspace-context.js.map +1 -1
  34. package/dist/core/zones.d.ts +0 -2
  35. package/dist/core/zones.js +0 -2
  36. package/dist/core/zones.js.map +1 -1
  37. package/dist/env.d.ts +0 -2
  38. package/dist/env.js +0 -2
  39. package/dist/env.js.map +1 -1
  40. package/dist/index.d.ts +15 -13
  41. package/dist/index.js +18 -7
  42. package/dist/index.js.map +1 -1
  43. package/dist/machine/machine.d.ts +9 -0
  44. package/dist/machine/machine.js +13 -0
  45. package/dist/machine/machine.js.map +1 -1
  46. package/dist/machine/signature.d.ts +104 -0
  47. package/dist/machine/signature.js +210 -0
  48. package/dist/machine/signature.js.map +1 -0
  49. package/dist/machine/spec-schema.d.ts +158 -8
  50. package/dist/machine/spec-schema.js +78 -6
  51. package/dist/machine/spec-schema.js.map +1 -1
  52. package/dist/machine/triggers.d.ts +6 -2
  53. package/dist/machine/triggers.js +7 -2
  54. package/dist/machine/triggers.js.map +1 -1
  55. package/dist/machine/types.d.ts +7 -1
  56. package/dist/machine/types.js.map +1 -1
  57. package/dist/machine/variables.d.ts +2 -0
  58. package/dist/machine/variables.js +4 -0
  59. package/dist/machine/variables.js.map +1 -1
  60. package/dist/public/spec.d.ts +20 -3
  61. package/dist/public/spec.js +18 -2
  62. package/dist/public/spec.js.map +1 -1
  63. package/dist/sandbox/executor.js.map +1 -1
  64. package/dist/sessions/resolve.d.ts +0 -2
  65. package/dist/sessions/resolve.js +1 -4
  66. package/dist/sessions/resolve.js.map +1 -1
  67. package/dist/sessions/resume.d.ts +4 -8
  68. package/dist/sessions/resume.js +10 -5
  69. package/dist/sessions/resume.js.map +1 -1
  70. package/dist/sessions/summary.d.ts +0 -2
  71. package/dist/sessions/summary.js +1 -1
  72. package/dist/sessions/summary.js.map +1 -1
  73. package/dist/testing/assertions.js +1 -5
  74. package/dist/testing/assertions.js.map +1 -1
  75. package/dist/testing/case-document.d.ts +1 -7
  76. package/dist/testing/case-document.js.map +1 -1
  77. package/dist/testing/case-schema.d.ts +2 -2
  78. package/dist/testing/case-schema.js +8 -21
  79. package/dist/testing/case-schema.js.map +1 -1
  80. package/dist/testing/driver.d.ts +4 -2
  81. package/dist/testing/driver.js +1 -1
  82. package/dist/testing/driver.js.map +1 -1
  83. package/dist/testing/grade.d.ts +2 -2
  84. package/dist/testing/grade.js +1 -1
  85. package/dist/testing/interpreter.d.ts +4 -2
  86. package/dist/testing/interpreter.js.map +1 -1
  87. package/dist/testing/runner.d.ts +13 -9
  88. package/dist/testing/runner.js +0 -1
  89. package/dist/testing/runner.js.map +1 -1
  90. package/dist/testing/target.d.ts +4 -2
  91. package/dist/workflow/control-tools.d.ts +7 -2
  92. package/dist/workflow/control-tools.js +27 -2
  93. package/dist/workflow/control-tools.js.map +1 -1
  94. package/dist/workflow/governance.js +28 -19
  95. package/dist/workflow/governance.js.map +1 -1
  96. package/dist/workflow/middleware.js +5 -10
  97. package/dist/workflow/middleware.js.map +1 -1
  98. package/dist/workflow/on-error.js +0 -1
  99. package/dist/workflow/on-error.js.map +1 -1
  100. package/dist/workflow/parks.js +9 -13
  101. package/dist/workflow/parks.js.map +1 -1
  102. package/dist/workflow/paths.d.ts +0 -1
  103. package/dist/workflow/paths.js +0 -1
  104. package/dist/workflow/paths.js.map +1 -1
  105. package/dist/workflow/prompt-cache.d.ts +0 -2
  106. package/dist/workflow/prompt-cache.js +0 -7
  107. package/dist/workflow/prompt-cache.js.map +1 -1
  108. package/dist/workflow/render-prompt.js +28 -7
  109. package/dist/workflow/render-prompt.js.map +1 -1
  110. package/dist/workflow/session-artifacts.d.ts +1 -3
  111. package/dist/workflow/session-artifacts.js +1 -2
  112. package/dist/workflow/session-artifacts.js.map +1 -1
  113. package/dist/workflow/session-view.d.ts +1 -1
  114. package/dist/workflow/signature-checks.d.ts +22 -0
  115. package/dist/workflow/signature-checks.js +52 -0
  116. package/dist/workflow/signature-checks.js.map +1 -0
  117. package/dist/workflow/state.d.ts +2 -14
  118. package/dist/workflow/state.js +3 -10
  119. package/dist/workflow/state.js.map +1 -1
  120. package/dist/workflow/sub-workflow.d.ts +23 -16
  121. package/dist/workflow/sub-workflow.js +78 -14
  122. package/dist/workflow/sub-workflow.js.map +1 -1
  123. package/dist/workflow/tool-service.js +3 -5
  124. package/dist/workflow/tool-service.js.map +1 -1
  125. package/dist/workflow/turn-boundary.js +5 -6
  126. package/dist/workflow/turn-boundary.js.map +1 -1
  127. package/dist/workflow/workflow-tools.d.ts +17 -9
  128. package/dist/workflow/workflow-tools.js +30 -17
  129. package/dist/workflow/workflow-tools.js.map +1 -1
  130. package/package.json +5 -4
  131. package/skills/archmax-harness/README.md +82 -0
  132. package/skills/archmax-harness/SKILL.md +247 -0
  133. package/skills/archmax-harness/references/backend-integration.md +681 -0
  134. package/skills/archmax-harness/references/cases.md +41 -0
  135. package/skills/archmax-harness/references/diagnostics.md +51 -0
  136. package/skills/archmax-harness/references/hook-and-test-scripts.md +1070 -0
  137. package/skills/archmax-harness/references/hooks-and-scripts.md +82 -0
  138. package/skills/archmax-harness/references/wiring.md +101 -0
  139. package/skills/archmax-harness/references/workflow-schema.md +1677 -0
  140. package/skills/archmax-harness/references/workflow-yaml.md +251 -0
  141. package/dist/behaviour/support.d.ts +0 -232
  142. package/dist/behaviour/support.js +0 -339
  143. package/dist/behaviour/support.js.map +0 -1
@@ -0,0 +1,82 @@
1
+ # Hook scripts and `archmax_run` scripts — contracts
2
+
3
+ Load this when writing a `workflows/<slug>/hooks/*.js` gate or a
4
+ `skills/<cap>/scripts/*.js` source. Worked examples and the sandbox details:
5
+ [`hook-and-test-scripts.md`](hook-and-test-scripts.md) Part 1.
6
+
7
+ ## Hook script contract
8
+
9
+ Default-export function; its return value is the verdict. Verdict helpers are
10
+ globals (typed import optional: `import { ok, veto, correct } from "@archmax-ai/harness/sandbox";`,
11
+ placed below the JSDoc; no other import is allowed).
12
+
13
+ ```js
14
+ /**
15
+ * Requester gate — the request must name a requester the order records know.
16
+ *
17
+ * Reads the first user message, extracts the first email address, loads
18
+ * skills/order-data/assets/orders.json through the bridge and matches the
19
+ * address against every record's `email`, case-insensitively.
20
+ *
21
+ * Vetoes when the file cannot be parsed, no address is present, or none
22
+ * matches — each with the reason. Otherwise ok.
23
+ */
24
+ export default async function hook({ state, phase, trigger, variables, messages, tools, from, to, reason }) {
25
+ let orders;
26
+ try { orders = JSON.parse(await tools.readFile({ file_path: "skills/order-data/assets/orders.json" })); }
27
+ catch { return veto("Could not load skills/order-data/assets/orders.json."); }
28
+ const text = messages.find((m) => m.role === "user")?.text ?? "";
29
+ const email = text.match(/[\w.+-]+@[\w.-]+\.\w{2,}/)?.[0];
30
+ if (!email) return veto("Include your email address so we can look up your account.");
31
+ if (!orders.some((o) => o.email?.toLowerCase() === email.toLowerCase())) return veto(`"${email}" is not in the order database.`);
32
+ return ok();
33
+ }
34
+ ```
35
+
36
+ | Input | Meaning |
37
+ | --- | --- |
38
+ | `state`, `phase` | state slug; `"before"` or `"after"` |
39
+ | `trigger` | trigger id string (payload is in `variables`) |
40
+ | `variables` | read-only `name → value` map incl. `trigger`; structured values whole |
41
+ | `messages` | last 24 messages, newest last, `{ role, text, toolCalls? }`; roles `user`/`assistant`/`tool`/`system`/`runtime` (a runtime note; match `role === "runtime"`, never a `[bracket]`) |
42
+ | `from`, `to`, `reason` | `after` only: the attempted transition and the agent's reason |
43
+ | `tools` | async camelCased bridge: `tools.readFile`, `tools.writeFile`, `tools.archmaxWorkflowEnrichOrder(...)`; no variable tools, no `task` |
44
+
45
+ | Return | Effect |
46
+ | --- | --- |
47
+ | `ok()`, nothing, `true`/number/string | proceed |
48
+ | `veto(reason)` | block; the reason is shown to the agent — make it actionable |
49
+ | `correct(reason)` | `after` only: retry with the reason, within `max_iterations` |
50
+ | `false` | veto ("precondition not met") |
51
+ | any other object, a throw | veto with keys/error named — never silently ok |
52
+
53
+ `console.log` goes to the trail. A file with no default export uses its
54
+ completion value as the verdict. Parse inputs defensively; turn a failure that
55
+ should not block into an explicit `ok()`.
56
+
57
+ ## `archmax_run` script contract
58
+
59
+ `skills/<cap>/scripts/<x>.js`: plain script, top-level `await`, `tools.*`,
60
+ `console.log`; the **last expression** is the tool result. Input is the global
61
+ `args`: the arguments the model passed plus `args.variables` (read-only snapshot).
62
+ No `archmax_advance`, `archmax_set_variables` or `task`. Fan-out shape:
63
+
64
+ ```js
65
+ /** One sub-run per requested order, all in flight at once; returns [{ order_id, ...returns }]. */
66
+ const orders = args.variables?.orders_to_enrich ?? [];
67
+ await Promise.all(orders.map(async (o) => {
68
+ const answer = await tools.archmaxWorkflowEnrichOrder({ order_id: o.order_id });
69
+ return { order_id: o.order_id, ...JSON.parse(answer).returns };
70
+ }));
71
+ ```
72
+
73
+ The calling state must allow every tool the script calls (here
74
+ `archmax_workflow_enrich-order`).
75
+
76
+ **Return into a variable.** A script's result reaches the model as one tool
77
+ message and no further — it is not written to the variable store, and a script
78
+ cannot write there itself. Return structured data and name the variable in the
79
+ calling state's `requires:`; the model then records it with
80
+ `archmax_set_variables`, which is what makes it survive the state, guard a
81
+ `tools.allow` glob, and be quotable downstream as `${{name}}` instead of retyped.
82
+ A result worth computing is worth naming.
@@ -0,0 +1,101 @@
1
+ # Wiring the runtime into a backend — the short form
2
+
3
+ Load this when embedding `@archmax-ai/harness` in a service. Every option shape, the
4
+ event catalogue and recipes: [`backend-integration.md`](backend-integration.md).
5
+
6
+
7
+ ```ts
8
+ import { createAgent, WORKFLOW_STATUSES, createUsageTracker } from "@archmax-ai/harness";
9
+
10
+ const usage = createUsageTracker();
11
+ const agent = await createAgent({
12
+ workflow: "order-lookup",
13
+ workspace: { rootDir, /* mounts?, sessionStore? */ },
14
+ onEvent: (e) => { usage.handler(e); publish(e); }, // omit for the console subscriber
15
+ // model | modelFactory, tools, systemPrompt, middleware, backend, checkpointer,
16
+ // trigger, variables, promptCache, pricing, hookExecutors …
17
+ });
18
+ const sessionId = `run-${runId}`;
19
+ const result = await agent.invoke(
20
+ { messages: [{ role: "user", content: prompt }] },
21
+ { configurable: { thread_id: sessionId }, recursionLimit: 50 },
22
+ );
23
+ if (result.status === WORKFLOW_STATUSES.awaitingDecision) {
24
+ // result.pendingDecision.transitions → show to a person, then:
25
+ // await agent.workflow.decide(sessionId, { target, comment });
26
+ }
27
+ usage.totals(sessionId); // tokens (+ costUsd only when pricing is configured)
28
+ agent.dispose(sessionId);
29
+ ```
30
+
31
+ - **Install**: `npm i @archmax-ai/harness`; pre-publish, `npm pack` and install the
32
+ tarball (a real copy, not a symlink). This skill ships in the package at
33
+ `BUNDLED_AUTHORING_SKILL_DIR` (`<dir>/archmax-harness/SKILL.md`).
34
+ - **Mounts**: `workspace.mounts` is a route table — key → backend, or
35
+ `{ backend, readOnly, governed }`. Mark a mount `governed: true` to hand the
36
+ spec's `mounts` block the decision of which **states** reach it (closed by
37
+ default, like a skill bundle) and whether each may write there; a mount not so
38
+ declared is visible in every state, so an existing table behaves unchanged.
39
+ `readOnly` is a ceiling the spec cannot lift.
40
+
41
+ ```ts
42
+ workspace: {
43
+ rootDir,
44
+ mounts: {
45
+ ...defaultMounts(rootDir), // skills/, .platform/, AGENTS.md
46
+ "/reference/": { backend: mountSubtree(store, "reference"), governed: true },
47
+ "/shared/": { backend: mountSubtree(store, "shared"), readOnly: false, governed: true },
48
+ },
49
+ },
50
+ ```
51
+
52
+ Then in `workflow.yaml`: `mounts: { allow_always: [reference] }`, a state's
53
+ `mounts: { allow: [{ mount: shared, access: read }] }`, and
54
+ `mounts.forbid_always` for what no state (or child session) may reach. Pass the
55
+ same table to `validateWorkflow({ mounts })` so `archmax validate` checks the
56
+ spec's names against the wiring.
57
+ - **Storage**: default filesystem store at `<root>/sessions`; else
58
+ `createFilesystemSessionStore({ dir })`, `createBackendSessionStore({ backend, prefix? })`,
59
+ `createMemorySessionStore()`. A custom `backend` without `sessionStore`
60
+ throws `SessionStoreRequiredError`. Delete sessions via `agent.sessions.delete(id)`.
61
+ - **Agent**: Deep Agents' `invoke`/`stream`/`streamEvents` + checkpoint
62
+ surface; `sessions.list/get/delete`; `emitRunArtifacts`; `dispose`.
63
+ Governance under `agent.workflow`: `machine`, `resolveTrigger`,
64
+ `send(sessionId, input)` → one `Outcome` (`decide`/`reply`/`deliver` are
65
+ conveniences), `resolveSession`.
66
+ - **Multi-turn**: re-invoke the same `thread_id` with only the new message;
67
+ never concatenate a conversation into one string. A spec edit is adopted,
68
+ not refused: the next turn runs under the current `workflow.yaml` (the
69
+ recorded `specHash` says which version each turn ran under), and a state
70
+ the edit removed reopens at the entry state.
71
+ - **Human-in-the-loop**: `status === "awaiting_decision"` →
72
+ `pendingDecision.transitions` → `decide`. `reply(sessionId, text)`
73
+ answers a message without moving the run. An `archmax_wait` park is
74
+ `awaiting_input` → `deliver(sessionId, { trigger, variables, message? })` — the
75
+ person's message travels with the firing; a park with `resumeAt` is the host
76
+ scheduler's cue. A delegated child's park is named on `Outcome.delegation`.
77
+ - **Events**: `onEvent` receives the one typed stream — `state-enter/leave`,
78
+ `advance`, `hook-verdict`, `parked/decided`, `tool-called/result/blocked`
79
+ (every governed call, `archmax_advance` included, grouped by `callId`),
80
+ `agent-text-delta`/`agent-text` (streaming by default, `messageId` groups
81
+ chunks), `model-usage` (provider counts per turn), `title-set`, warnings.
82
+ There is no run-end event; `invoke` returning is the end. Metrics are a
83
+ fold over this stream; only cost is checkpointed (`sessions.get(id).usage`).
84
+ - **Artifacts** per session under `sessions/<id>/artifacts/`: `graph.json`/`.mmd`,
85
+ `trajectory.json`, `trail.json`, `variables.json`, `metadata.json` (incl.
86
+ usage); the agent's files under `scratchpad/`.
87
+ - **Cases against your agent**: `runTests({ workflow, rootDir, createTarget?, sessionIdForCase? })`
88
+ from `@archmax-ai/harness/testing`; wire `createToolMockMiddleware()` into your
89
+ target or a case with `mocks:` is refused before running.
90
+ - **Model/caching**: `ARCHMAX_API_BASE_URL`, `ARCHMAX_API_KEY`, `ARCHMAX_MODEL`
91
+ (+ `ARCHMAX_TEMPERATURE`, `ARCHMAX_MAX_TOKENS`, `ARCHMAX_STREAMING`) for any
92
+ OpenAI-compatible endpoint, or pass `model`. Prompt prefix is cache-marked
93
+ by default (`promptCache: { enabled, ttl: "5m" | "1h" }`); watch
94
+ `cacheReadTokens`. Pricing via `pricing: { "<model-substring>": { input, output, cacheRead, cacheWrite } }`
95
+ or `ARCHMAX_PRICE_*`; without it report tokens, never a guessed cost. A keyed
96
+ table works behind a proxy that echoes no model back — the id falls back to the
97
+ one the runtime was configured to run. Cached tokens are charged once, at the
98
+ cache rate, not at the input rate as well.
99
+
100
+ Typical split: worker = `createAgent` + `invoke` + `decide`/`deliver`; API =
101
+ artifacts + event subscription; frontend = graph + trajectory + live stream.