@pattern-js/mod-agents 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pattern contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # @pattern-js/mod-agents
2
+
3
+ The **neutral** agents contracts layer for [Pattern](../../README.md) — plain-JSON
4
+ agent/tool/guardrail descriptors and the modality-agnostic turn event protocol,
5
+ with **no SDK dependency**. Provider mods reify the descriptors; apps consume the
6
+ events. Neither has to know about the other.
7
+
8
+ ```bash
9
+ npm install @pattern-js/mod-agents
10
+ ```
11
+
12
+ ## When to use
13
+
14
+ Install it whenever you want agents in your app — but never *alone*. It
15
+ contributes the `boundary.tool` pair, the toolset/guardrail ops, and the live
16
+ tool registry (`AGENTS_SERVICE`); it does **not** run an agent. Pair it with a
17
+ provider (`@pattern-js/mod-agents-openai`) that reifies the descriptors and emits
18
+ the turn events.
19
+
20
+ **When not:** a single model call with no tools, no agent loop, and no streaming
21
+ turn events — a provider op straight from the canvas is lighter than the agents
22
+ stack.
23
+
24
+ ## Prerequisites
25
+
26
+ Always ship it with a provider. Usually also `@pattern-js/mod-store` (history +
27
+ blobs) and `@pattern-js/mod-vault` (the API key).
28
+
29
+ ## Config
30
+
31
+ The contracts always ship paired with a provider — add both as strings in
32
+ `pattern.config.json`:
33
+
34
+ ```jsonc
35
+ { "mods": ["@pattern-js/mod-agents", "@pattern-js/mod-agents-openai"] }
36
+ ```
37
+
38
+ For options, export a local wrapper mod that calls the `agentsMod({...})` factory.
39
+
40
+ Full documentation: the **Agents (contracts)** chapter at `/docs` (served by
41
+ `@pattern-js/mod-docs`), or [the source](docs/index.md).
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @pattern-js/mod-agents — the boundary.tool pair.
3
+ *
4
+ * A tool is a WORKFLOW that starts with `boundary.tool` (name, description,
5
+ * params as JSON Schema — the SchemaBuilder UI in the editor) and ends with
6
+ * `boundary.tool.return`. That one shape buys, for free:
7
+ *
8
+ * - engine-side argument validation (`validate: true` → TriggerInputError
9
+ * before the body runs — an LLM's malformed args never reach your graph),
10
+ * - discovery (the registry scans for these triggers, like hook listeners),
11
+ * - run linkage (each call is a ctx.invoke sub-run: ↳ links + sampled I/O —
12
+ * the Runs page is the agent debugger).
13
+ *
14
+ * Guardrails reuse the SAME pair by convention: a guardrail is a tool
15
+ * workflow whose result is `{ tripwire: boolean, info?: unknown }`.
16
+ */
17
+ import { type OpDefinition } from "@pattern-js/core";
18
+ export declare const toolTrigger: OpDefinition;
19
+ export declare const toolReturn: OpDefinition;
20
+ export declare const agentBoundaryOps: OpDefinition[];
21
+ //# sourceMappingURL=boundaries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"boundaries.d.ts","sourceRoot":"","sources":["../src/boundaries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAML,KAAK,YAAY,EAElB,MAAM,kBAAkB,CAAC;AAI1B,eAAO,MAAM,WAAW,EAAE,YAwCzB,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,YAUxB,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,YAAY,EAA8B,CAAC"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * @pattern-js/mod-agents — the boundary.tool pair.
3
+ *
4
+ * A tool is a WORKFLOW that starts with `boundary.tool` (name, description,
5
+ * params as JSON Schema — the SchemaBuilder UI in the editor) and ends with
6
+ * `boundary.tool.return`. That one shape buys, for free:
7
+ *
8
+ * - engine-side argument validation (`validate: true` → TriggerInputError
9
+ * before the body runs — an LLM's malformed args never reach your graph),
10
+ * - discovery (the registry scans for these triggers, like hook listeners),
11
+ * - run linkage (each call is a ctx.invoke sub-run: ↳ links + sampled I/O —
12
+ * the Runs page is the agent debugger).
13
+ *
14
+ * Guardrails reuse the SAME pair by convention: a guardrail is a tool
15
+ * workflow whose result is `{ tripwire: boolean, info?: unknown }`.
16
+ */
17
+ import { jsonSchemaToZod, required, userInputSchema, value, z, } from "@pattern-js/core";
18
+ const jsonSchema = z.record(z.string(), z.unknown());
19
+ export const toolTrigger = {
20
+ type: "boundary.tool",
21
+ title: "boundary.tool",
22
+ description: "Declares this workflow as an agent-callable tool. config: { name, description, params (JSON Schema) }. " +
23
+ "Outputs { args (validated), user }.",
24
+ boundary: "trigger",
25
+ pair: "boundary.tool.return",
26
+ inputs: {},
27
+ // Wire a core.schema.define node into `params` and the resolve phase
28
+ // freezes it — same trick as http.request's body/query/params.
29
+ configInputs: {
30
+ name: value(z.string()),
31
+ description: value(z.string()),
32
+ params: value(jsonSchema),
33
+ },
34
+ outputs: (config) => ({
35
+ args: config.params
36
+ ? value(jsonSchemaToZod(config.params), { validate: true })
37
+ : value(z.record(z.string(), z.unknown())),
38
+ user: value(userInputSchema),
39
+ }),
40
+ config: z.object({
41
+ /** Tool name the model sees (unique per app; snake_case reads best). */
42
+ name: z.string().min(1),
43
+ /** What the model reads to decide when to call this tool. */
44
+ description: z.string().optional(),
45
+ /** JSON Schema for the arguments (engine-validated on every call). */
46
+ params: jsonSchema.optional(),
47
+ /** Pause for human approval before each call (HITL). */
48
+ needsApproval: z.boolean().optional(),
49
+ /**
50
+ * Reserve this tool for use as a GUARDRAIL (or other by-name invocation)
51
+ * only — it's still resolvable via `agents.guardrail`, but the default
52
+ * `agents.tools.workflows` toolset leaves it out, so the model is never
53
+ * offered it as a callable tool. Name it explicitly to include it anyway.
54
+ */
55
+ guardrail: z.boolean().optional(),
56
+ }),
57
+ execute: () => ({}),
58
+ };
59
+ export const toolReturn = {
60
+ type: "boundary.tool.return",
61
+ title: "boundary.tool.return",
62
+ description: "Tool out-gate: { result } goes back to the calling agent as the tool's output.",
63
+ boundary: "outgate",
64
+ pair: "boundary.tool",
65
+ reusable: false,
66
+ inputs: { result: required() },
67
+ outputs: {},
68
+ execute: async (ctx) => ({ result: await ctx.input.value("result") }),
69
+ };
70
+ export const agentBoundaryOps = [toolTrigger, toolReturn];
71
+ //# sourceMappingURL=boundaries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"boundaries.js","sourceRoot":"","sources":["../src/boundaries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EACL,eAAe,EACf,QAAQ,EACR,eAAe,EACf,KAAK,EACL,CAAC,GAGF,MAAM,kBAAkB,CAAC;AAE1B,MAAM,UAAU,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;AAErD,MAAM,CAAC,MAAM,WAAW,GAAiB;IACvC,IAAI,EAAE,eAAe;IACrB,KAAK,EAAE,eAAe;IACtB,WAAW,EACT,yGAAyG;QACzG,qCAAqC;IACvC,QAAQ,EAAE,SAAS;IACnB,IAAI,EAAE,sBAAsB;IAC5B,MAAM,EAAE,EAAE;IACV,qEAAqE;IACrE,+DAA+D;IAC/D,YAAY,EAAE;QACZ,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QACvB,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QAC9B,MAAM,EAAE,KAAK,CAAC,UAAU,CAAC;KAC1B;IACD,OAAO,EAAE,CAAC,MAA4B,EAAS,EAAE,CAAC,CAAC;QACjD,IAAI,EAAE,MAAM,CAAC,MAAM;YACjB,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC,MAAM,CAAC,MAAe,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;YACpE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QAC5C,IAAI,EAAE,KAAK,CAAC,eAAe,CAAC;KAC7B,CAAC;IACF,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,wEAAwE;QACxE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QACvB,6DAA6D;QAC7D,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAClC,sEAAsE;QACtE,MAAM,EAAE,UAAU,CAAC,QAAQ,EAAE;QAC7B,wDAAwD;QACxD,aAAa,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;QACrC;;;;;WAKG;QACH,SAAS,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;KAClC,CAAC;IACF,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC;CACpB,CAAC;AAEF,MAAM,CAAC,MAAM,UAAU,GAAiB;IACtC,IAAI,EAAE,sBAAsB;IAC5B,KAAK,EAAE,sBAAsB;IAC7B,WAAW,EAAE,gFAAgF;IAC7F,QAAQ,EAAE,SAAS;IACnB,IAAI,EAAE,eAAe;IACrB,QAAQ,EAAE,KAAK;IACf,MAAM,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE;IAC9B,OAAO,EAAE,EAAE;IACX,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;CACtE,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAmB,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC"}
@@ -0,0 +1,9 @@
1
+ /** @pattern-js/mod-agents — public surface (the neutral agent contracts). */
2
+ export { agentsMod } from "./mod.js";
3
+ export { default } from "./mod.js";
4
+ export { AGENTS_SERVICE, agentsService, workflowToolRef, type AgentsService, type OpToolRegistration, type WorkflowToolRegistration, } from "./well-known.js";
5
+ export { AgentsRegistry } from "./service.js";
6
+ export { agentBoundaryOps, toolTrigger, toolReturn } from "./boundaries.js";
7
+ export { agentsOps } from "./ops.js";
8
+ export { agentSchema, guardrailSchema, historySchema, messagePartSchema, toolRefSchema, toolsetSchema, turnEventSchema, type AgentDescriptor, type GuardrailDescriptor, type History, type MessagePart, type ToolRef, type ToolsetDescriptor, type TurnEvent, type TurnStopReason, } from "./types.js";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAE7E,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAEnC,OAAO,EACL,cAAc,EACd,aAAa,EACb,eAAe,EACf,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,GAC9B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC5E,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EACL,WAAW,EACX,eAAe,EACf,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,eAAe,EACf,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,OAAO,EACZ,KAAK,WAAW,EAChB,KAAK,OAAO,EACZ,KAAK,iBAAiB,EACtB,KAAK,SAAS,EACd,KAAK,cAAc,GACpB,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ /** @pattern-js/mod-agents — public surface (the neutral agent contracts). */
2
+ export { agentsMod } from "./mod.js";
3
+ export { default } from "./mod.js";
4
+ export { AGENTS_SERVICE, agentsService, workflowToolRef, } from "./well-known.js";
5
+ export { AgentsRegistry } from "./service.js";
6
+ export { agentBoundaryOps, toolTrigger, toolReturn } from "./boundaries.js";
7
+ export { agentsOps } from "./ops.js";
8
+ export { agentSchema, guardrailSchema, historySchema, messagePartSchema, toolRefSchema, toolsetSchema, turnEventSchema, } from "./types.js";
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAE7E,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAEnC,OAAO,EACL,cAAc,EACd,aAAa,EACb,eAAe,GAIhB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC5E,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EACL,WAAW,EACX,eAAe,EACf,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,eAAe,GAShB,MAAM,YAAY,CAAC"}
package/dist/mod.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @pattern-js/mod-agents — the mod. Registers the boundary.tool pair, the
3
+ * neutral toolset/guardrail ops, and the live tool registry (AGENTS_SERVICE).
4
+ */
5
+ import { type PatternMod } from "@pattern-js/core";
6
+ export declare function agentsMod(): PatternMod;
7
+ /** A ready-to-use agents-contracts mod (for `loadMods`/`engine.use`). */
8
+ declare const _default: PatternMod;
9
+ export default _default;
10
+ //# sourceMappingURL=mod.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../src/mod.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EAA0B,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAiB3E,wBAAgB,SAAS,IAAI,UAAU,CAUtC;AAED,yEAAyE;;AACzE,wBAA2B"}
package/dist/mod.js ADDED
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @pattern-js/mod-agents — the mod. Registers the boundary.tool pair, the
3
+ * neutral toolset/guardrail ops, and the live tool registry (AGENTS_SERVICE).
4
+ */
5
+ import { existsSync } from "node:fs";
6
+ import { fileURLToPath } from "node:url";
7
+ import { localFs, provideFilesystem } from "@pattern-js/runtime-node";
8
+ import { defineMod } from "@pattern-js/core";
9
+ import { agentBoundaryOps } from "./boundaries.js";
10
+ import { agentsOps } from "./ops.js";
11
+ import { AgentsRegistry } from "./service.js";
12
+ import { AGENTS_SERVICE } from "./well-known.js";
13
+ /** The packaged docs/ chapter (the `docs` contribution points at "agents-docs"). */
14
+ function packagedDocs(engine) {
15
+ try {
16
+ const dir = fileURLToPath(new URL("../docs", import.meta.url));
17
+ if (existsSync(dir))
18
+ provideFilesystem(engine, "agents-docs", localFs(dir));
19
+ }
20
+ catch {
21
+ /* packaged without docs — the contribution is simply skipped */
22
+ }
23
+ }
24
+ export function agentsMod() {
25
+ return defineMod({
26
+ name: "@pattern-js/mod-agents",
27
+ docs: { filesystem: "agents-docs", title: "Agents", order: 50 },
28
+ ops: [...agentBoundaryOps, ...agentsOps],
29
+ setup: (engine) => {
30
+ packagedDocs(engine);
31
+ engine.provideService(AGENTS_SERVICE, new AgentsRegistry(engine));
32
+ },
33
+ });
34
+ }
35
+ /** A ready-to-use agents-contracts mod (for `loadMods`/`engine.use`). */
36
+ export default agentsMod();
37
+ //# sourceMappingURL=mod.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mod.js","sourceRoot":"","sources":["../src/mod.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,SAAS,EAAgC,MAAM,kBAAkB,CAAC;AAC3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGjD,oFAAoF;AACpF,SAAS,YAAY,CAAC,MAAc;IAClC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAC/D,IAAI,UAAU,CAAC,GAAG,CAAC;YAAE,iBAAiB,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9E,CAAC;IAAC,MAAM,CAAC;QACP,gEAAgE;IAClE,CAAC;AACH,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,OAAO,SAAS,CAAC;QACf,IAAI,EAAE,wBAAwB;QAC9B,IAAI,EAAE,EAAE,UAAU,EAAE,aAAa,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,EAAE;QAC/D,GAAG,EAAE,CAAC,GAAG,gBAAgB,EAAE,GAAG,SAAS,CAAC;QACxC,KAAK,EAAE,CAAC,MAAc,EAAE,EAAE;YACxB,YAAY,CAAC,MAAM,CAAC,CAAC;YACrB,MAAM,CAAC,cAAc,CAAC,cAAc,EAAE,IAAI,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC;QACpE,CAAC;KACF,CAAC,CAAC;AACL,CAAC;AAED,yEAAyE;AACzE,eAAe,SAAS,EAAE,CAAC"}
package/dist/ops.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @pattern-js/mod-agents — neutral ops (toolset plumbing + guardrail refs).
3
+ *
4
+ * Provider-independent nodes: pick workflow tools into a toolset, merge
5
+ * toolsets from several sources, wrap a tool workflow as a guardrail
6
+ * descriptor. The agent/run ops live in provider mods.
7
+ */
8
+ import { type OpDefinition } from "@pattern-js/core";
9
+ export declare const agentsOps: OpDefinition[];
10
+ //# sourceMappingURL=ops.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ops.d.ts","sourceRoot":"","sources":["../src/ops.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAY,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAmH/D,eAAO,MAAM,SAAS,EAAE,YAAY,EAAsD,CAAC"}
package/dist/ops.js ADDED
@@ -0,0 +1,110 @@
1
+ /**
2
+ * @pattern-js/mod-agents — neutral ops (toolset plumbing + guardrail refs).
3
+ *
4
+ * Provider-independent nodes: pick workflow tools into a toolset, merge
5
+ * toolsets from several sources, wrap a tool workflow as a guardrail
6
+ * descriptor. The agent/run ops live in provider mods.
7
+ */
8
+ import { value, z } from "@pattern-js/core";
9
+ import { agentsService, workflowToolRef } from "./well-known.js";
10
+ import { toolsetSchema } from "./types.js";
11
+ const toolsWorkflows = {
12
+ type: "agents.tools.workflows",
13
+ title: "agents.tools.workflows",
14
+ description: 'Collect boundary.tool workflows into a toolset. config.tools: tool names to include, or empty/["*"] for all.',
15
+ config: z.object({
16
+ /** Tool names to include; empty (or ["*"]) = every discovered tool. */
17
+ tools: z.array(z.string()).default([]),
18
+ }),
19
+ inputs: {},
20
+ outputs: { toolset: value(toolsetSchema) },
21
+ execute: (ctx) => {
22
+ const svc = agentsService(ctx);
23
+ const wanted = ctx.config.tools.filter((t) => t !== "*");
24
+ const all = svc.listWorkflowTools();
25
+ const picked = wanted.length === 0
26
+ ? // Auto-pickup offers every tool to the model EXCEPT guardrail-only
27
+ // ones (a moderation classifier must not become a callable tool).
28
+ // Name one explicitly below to include it anyway.
29
+ all.filter((t) => !t.guardrail)
30
+ : wanted.map((name) => {
31
+ const reg = svc.getWorkflowTool(name);
32
+ if (!reg) {
33
+ const known = all.map((t) => t.name).join(", ") || "(none)";
34
+ throw new Error(`agents: no tool workflow named "${name}" — known tools: ${known}`);
35
+ }
36
+ return reg;
37
+ });
38
+ const toolset = { kind: "toolset", tools: picked.map(workflowToolRef) };
39
+ return { toolset };
40
+ },
41
+ };
42
+ const toolsOps = {
43
+ type: "agents.tools.ops",
44
+ title: "agents.tools.ops",
45
+ description: "Collect mod-contributed code tools into a toolset. config.tools: names to include, empty = all registered.",
46
+ config: z.object({ tools: z.array(z.string()).default([]) }),
47
+ inputs: {},
48
+ outputs: { toolset: value(toolsetSchema) },
49
+ execute: (ctx) => {
50
+ const svc = agentsService(ctx);
51
+ const wanted = ctx.config.tools;
52
+ const all = svc.listOpTools();
53
+ const picked = wanted.length === 0 ? all : all.filter((t) => wanted.includes(t.name));
54
+ const toolset = {
55
+ kind: "toolset",
56
+ tools: picked.map((t) => ({ origin: "op", name: t.name, needsApproval: t.needsApproval })),
57
+ };
58
+ return { toolset };
59
+ },
60
+ };
61
+ const toolsMerge = {
62
+ type: "agents.tools.merge",
63
+ title: "agents.tools.merge",
64
+ description: "Merge toolsets (workflow tools + MCP servers + op tools) into one. config.count sets the inputs.",
65
+ config: z.object({ count: z.number().int().min(2).max(8).default(2) }),
66
+ inputs: (config) => Object.fromEntries(Array.from({ length: config.count ?? 2 }, (_v, i) => [`tools${i}`, value(toolsetSchema)])),
67
+ outputs: { toolset: value(toolsetSchema) },
68
+ execute: async (ctx) => {
69
+ const count = ctx.config.count;
70
+ const sets = await Promise.all(Array.from({ length: count }, (_v, i) => ctx.input.has(`tools${i}`) ? ctx.input.value(`tools${i}`) : undefined));
71
+ const tools = sets.filter(Boolean).flatMap((s) => s.tools);
72
+ // De-dup by identity (a tool picked twice via different routes is once).
73
+ const seen = new Set();
74
+ const deduped = tools.filter((t) => {
75
+ const key = JSON.stringify(t);
76
+ if (seen.has(key))
77
+ return false;
78
+ seen.add(key);
79
+ return true;
80
+ });
81
+ return { toolset: { kind: "toolset", tools: deduped } };
82
+ },
83
+ };
84
+ const guardrail = {
85
+ type: "agents.guardrail",
86
+ title: "agents.guardrail",
87
+ description: "Wrap a boundary.tool workflow (returning { tripwire, info? }) as an input or output guardrail.",
88
+ config: z.object({
89
+ /** The tool workflow's declared name. */
90
+ tool: z.string().min(1),
91
+ direction: z.enum(["input", "output"]).default("input"),
92
+ }),
93
+ inputs: {},
94
+ outputs: { guardrail: value() },
95
+ execute: (ctx) => {
96
+ const cfg = ctx.config;
97
+ const reg = agentsService(ctx).getWorkflowTool(cfg.tool);
98
+ if (!reg)
99
+ throw new Error(`agents: no tool workflow named "${cfg.tool}" to use as a guardrail`);
100
+ const descriptor = {
101
+ kind: "guardrail",
102
+ direction: cfg.direction,
103
+ workflowId: reg.workflowId,
104
+ name: reg.name,
105
+ };
106
+ return { guardrail: descriptor };
107
+ },
108
+ };
109
+ export const agentsOps = [toolsWorkflows, toolsOps, toolsMerge, guardrail];
110
+ //# sourceMappingURL=ops.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ops.js","sourceRoot":"","sources":["../src/ops.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @pattern-js/mod-agents — the registry implementation.
3
+ *
4
+ * Workflow tools are DISCOVERED, never registered by hand: the service scans
5
+ * the engine's workflow registry for `boundary.tool` triggers and keeps the
6
+ * index live via `onWorkflowsChanged` (the same pattern hook listeners use) —
7
+ * deploy a tool workflow from the admin and the next agent run sees it.
8
+ */
9
+ import type { Engine } from "@pattern-js/core";
10
+ import type { AgentsService, OpToolRegistration, WorkflowToolRegistration } from "./well-known.js";
11
+ export declare class AgentsRegistry implements AgentsService {
12
+ private readonly engine;
13
+ private workflowTools;
14
+ private opTools;
15
+ private turns;
16
+ constructor(engine: Engine);
17
+ private rescan;
18
+ private index;
19
+ listWorkflowTools(): WorkflowToolRegistration[];
20
+ getWorkflowTool(name: string): WorkflowToolRegistration | undefined;
21
+ registerOpTool(reg: OpToolRegistration): void;
22
+ listOpTools(): OpToolRegistration[];
23
+ getOpTool(name: string): OpToolRegistration | undefined;
24
+ registerTurn(turnId: string, controller: AbortController): void;
25
+ releaseTurn(turnId: string): void;
26
+ abortTurn(turnId: string, reason?: unknown): boolean;
27
+ }
28
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAY,MAAM,kBAAkB,CAAC;AACzD,OAAO,KAAK,EACV,aAAa,EACb,kBAAkB,EAClB,wBAAwB,EACzB,MAAM,iBAAiB,CAAC;AAEzB,qBAAa,cAAe,YAAW,aAAa;IAKtC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJnC,OAAO,CAAC,aAAa,CAA+C;IACpE,OAAO,CAAC,OAAO,CAAyC;IACxD,OAAO,CAAC,KAAK,CAAsC;gBAEtB,MAAM,EAAE,MAAM;IAK3C,OAAO,CAAC,MAAM;IAKd,OAAO,CAAC,KAAK;IA8Bb,iBAAiB,IAAI,wBAAwB,EAAE;IAI/C,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,wBAAwB,GAAG,SAAS;IAInE,cAAc,CAAC,GAAG,EAAE,kBAAkB,GAAG,IAAI;IAO7C,WAAW,IAAI,kBAAkB,EAAE;IAInC,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS;IAIvD,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,eAAe,GAAG,IAAI;IAI/D,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAIjC,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,OAAO;CAOrD"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * @pattern-js/mod-agents — the registry implementation.
3
+ *
4
+ * Workflow tools are DISCOVERED, never registered by hand: the service scans
5
+ * the engine's workflow registry for `boundary.tool` triggers and keeps the
6
+ * index live via `onWorkflowsChanged` (the same pattern hook listeners use) —
7
+ * deploy a tool workflow from the admin and the next agent run sees it.
8
+ */
9
+ export class AgentsRegistry {
10
+ engine;
11
+ workflowTools = new Map();
12
+ opTools = new Map();
13
+ turns = new Map();
14
+ constructor(engine) {
15
+ this.engine = engine;
16
+ for (const wf of engine.workflows.list())
17
+ this.index(wf);
18
+ engine.onWorkflowsChanged(() => this.rescan());
19
+ }
20
+ rescan() {
21
+ this.workflowTools.clear();
22
+ for (const wf of this.engine.workflows.list())
23
+ this.index(wf);
24
+ }
25
+ index(workflow) {
26
+ for (const node of workflow.nodes) {
27
+ if (node.op !== "boundary.tool")
28
+ continue;
29
+ const cfg = (node.config ?? {});
30
+ if (!cfg.name)
31
+ continue;
32
+ const existing = this.workflowTools.get(cfg.name);
33
+ if (existing && existing.workflowId !== workflow.id) {
34
+ console.warn(`[pattern/mod-agents] tool name "${cfg.name}" is declared by both "${existing.workflowId}" and "${workflow.id}" — keeping the first`);
35
+ continue;
36
+ }
37
+ this.workflowTools.set(cfg.name, {
38
+ workflowId: workflow.id,
39
+ nodeId: node.id,
40
+ name: cfg.name,
41
+ description: cfg.description,
42
+ params: cfg.params,
43
+ needsApproval: cfg.needsApproval,
44
+ guardrail: cfg.guardrail,
45
+ });
46
+ }
47
+ }
48
+ listWorkflowTools() {
49
+ return [...this.workflowTools.values()].sort((a, b) => a.name.localeCompare(b.name));
50
+ }
51
+ getWorkflowTool(name) {
52
+ return this.workflowTools.get(name);
53
+ }
54
+ registerOpTool(reg) {
55
+ if (this.opTools.has(reg.name)) {
56
+ throw new Error(`[pattern/mod-agents] op tool "${reg.name}" is already registered`);
57
+ }
58
+ this.opTools.set(reg.name, reg);
59
+ }
60
+ listOpTools() {
61
+ return [...this.opTools.values()].sort((a, b) => a.name.localeCompare(b.name));
62
+ }
63
+ getOpTool(name) {
64
+ return this.opTools.get(name);
65
+ }
66
+ registerTurn(turnId, controller) {
67
+ this.turns.set(turnId, controller);
68
+ }
69
+ releaseTurn(turnId) {
70
+ this.turns.delete(turnId);
71
+ }
72
+ abortTurn(turnId, reason) {
73
+ const ctrl = this.turns.get(turnId);
74
+ if (!ctrl)
75
+ return false;
76
+ this.turns.delete(turnId);
77
+ ctrl.abort(reason ?? new Error("turn aborted"));
78
+ return true;
79
+ }
80
+ }
81
+ //# sourceMappingURL=service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,175 @@
1
+ /**
2
+ * @pattern-js/mod-agents — the neutral contracts.
3
+ *
4
+ * Two families of shapes, both PLAIN JSON (structured-cloneable: they flow on
5
+ * edges and may cross worker boundaries — provider SDK objects never do; a
6
+ * provider mod reifies descriptors into SDK instances at execute time):
7
+ *
8
+ * - **Descriptors** — what an agent/toolset/guardrail value IS on the canvas.
9
+ * - **Turn events** — the modality-agnostic stream a running agent emits
10
+ * (text deltas, tool activity, approval requests, a guaranteed terminal
11
+ * event). Chat UIs, stores and SSE responses all speak this one protocol;
12
+ * voice surfaces later consume the same stream (`audio.ref` is reserved).
13
+ *
14
+ * Message content is PARTS-based from day one (text | image_ref) — retrofitting
15
+ * parts into string messages is the painful version.
16
+ */
17
+ import { z } from "@pattern-js/core";
18
+ export declare const messagePartSchema: z.ZodUnion<readonly [z.ZodObject<{
19
+ type: z.ZodLiteral<"text">;
20
+ text: z.ZodString;
21
+ }, z.core.$strip>, z.ZodObject<{
22
+ type: z.ZodLiteral<"image_ref">;
23
+ blobId: z.ZodString;
24
+ mime: z.ZodOptional<z.ZodString>;
25
+ }, z.core.$strip>]>;
26
+ export type MessagePart = z.infer<typeof messagePartSchema>;
27
+ /**
28
+ * Conversation history: provider-shaped items, treated as OPAQUE by everything
29
+ * except the provider mod that produced them (pull from the store → agent run
30
+ * → push back). Keeping them opaque lets each provider use its native item
31
+ * format losslessly.
32
+ */
33
+ export declare const historySchema: z.ZodArray<z.ZodUnknown>;
34
+ export type History = unknown[];
35
+ export declare const toolRefSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
36
+ origin: z.ZodLiteral<"workflow">;
37
+ workflowId: z.ZodString;
38
+ name: z.ZodString;
39
+ description: z.ZodOptional<z.ZodString>;
40
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
41
+ needsApproval: z.ZodOptional<z.ZodBoolean>;
42
+ }, z.core.$strip>, z.ZodObject<{
43
+ origin: z.ZodLiteral<"mcp">;
44
+ transport: z.ZodEnum<{
45
+ http: "http";
46
+ stdio: "stdio";
47
+ }>;
48
+ url: z.ZodOptional<z.ZodString>;
49
+ headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
50
+ command: z.ZodOptional<z.ZodString>;
51
+ args: z.ZodOptional<z.ZodArray<z.ZodString>>;
52
+ env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
53
+ serverLabel: z.ZodOptional<z.ZodString>;
54
+ }, z.core.$strip>, z.ZodObject<{
55
+ origin: z.ZodLiteral<"op">;
56
+ name: z.ZodString;
57
+ needsApproval: z.ZodOptional<z.ZodBoolean>;
58
+ }, z.core.$strip>], "origin">;
59
+ export type ToolRef = z.infer<typeof toolRefSchema>;
60
+ export declare const toolsetSchema: z.ZodObject<{
61
+ kind: z.ZodLiteral<"toolset">;
62
+ tools: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
63
+ origin: z.ZodLiteral<"workflow">;
64
+ workflowId: z.ZodString;
65
+ name: z.ZodString;
66
+ description: z.ZodOptional<z.ZodString>;
67
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
68
+ needsApproval: z.ZodOptional<z.ZodBoolean>;
69
+ }, z.core.$strip>, z.ZodObject<{
70
+ origin: z.ZodLiteral<"mcp">;
71
+ transport: z.ZodEnum<{
72
+ http: "http";
73
+ stdio: "stdio";
74
+ }>;
75
+ url: z.ZodOptional<z.ZodString>;
76
+ headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
77
+ command: z.ZodOptional<z.ZodString>;
78
+ args: z.ZodOptional<z.ZodArray<z.ZodString>>;
79
+ env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
80
+ serverLabel: z.ZodOptional<z.ZodString>;
81
+ }, z.core.$strip>, z.ZodObject<{
82
+ origin: z.ZodLiteral<"op">;
83
+ name: z.ZodString;
84
+ needsApproval: z.ZodOptional<z.ZodBoolean>;
85
+ }, z.core.$strip>], "origin">>;
86
+ }, z.core.$strip>;
87
+ export type ToolsetDescriptor = z.infer<typeof toolsetSchema>;
88
+ export declare const guardrailSchema: z.ZodObject<{
89
+ kind: z.ZodLiteral<"guardrail">;
90
+ direction: z.ZodEnum<{
91
+ input: "input";
92
+ output: "output";
93
+ }>;
94
+ workflowId: z.ZodString;
95
+ name: z.ZodString;
96
+ }, z.core.$strip>;
97
+ export type GuardrailDescriptor = z.infer<typeof guardrailSchema>;
98
+ export interface AgentDescriptor {
99
+ kind: "agent";
100
+ name: string;
101
+ instructions: string;
102
+ model?: string;
103
+ modelSettings?: Record<string, unknown>;
104
+ tools?: ToolsetDescriptor;
105
+ guardrails?: GuardrailDescriptor[];
106
+ /** Other agents this one may hand off to. */
107
+ handoffs?: AgentDescriptor[];
108
+ handoffDescription?: string;
109
+ /** JSON Schema for structured final output (provider-enforced). */
110
+ outputSchema?: Record<string, unknown>;
111
+ }
112
+ export declare const agentSchema: z.ZodType<AgentDescriptor>;
113
+ export declare const turnEventSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
114
+ type: z.ZodLiteral<"text.delta">;
115
+ delta: z.ZodString;
116
+ turnId: z.ZodString;
117
+ runId: z.ZodString;
118
+ }, z.core.$strip>, z.ZodObject<{
119
+ type: z.ZodLiteral<"text.done">;
120
+ text: z.ZodString;
121
+ turnId: z.ZodString;
122
+ runId: z.ZodString;
123
+ }, z.core.$strip>, z.ZodObject<{
124
+ type: z.ZodLiteral<"tool.activity">;
125
+ toolName: z.ZodString;
126
+ callId: z.ZodOptional<z.ZodString>;
127
+ phase: z.ZodEnum<{
128
+ error: "error";
129
+ start: "start";
130
+ done: "done";
131
+ }>;
132
+ args: z.ZodOptional<z.ZodUnknown>;
133
+ result: z.ZodOptional<z.ZodUnknown>;
134
+ error: z.ZodOptional<z.ZodString>;
135
+ subRunId: z.ZodOptional<z.ZodString>;
136
+ turnId: z.ZodString;
137
+ runId: z.ZodString;
138
+ }, z.core.$strip>, z.ZodObject<{
139
+ type: z.ZodLiteral<"audio.ref">;
140
+ blobId: z.ZodString;
141
+ mime: z.ZodString;
142
+ turnId: z.ZodString;
143
+ runId: z.ZodString;
144
+ }, z.core.$strip>, z.ZodObject<{
145
+ type: z.ZodLiteral<"approval.request">;
146
+ interruption: z.ZodObject<{
147
+ id: z.ZodString;
148
+ toolName: z.ZodString;
149
+ args: z.ZodUnknown;
150
+ }, z.core.$strip>;
151
+ stateToken: z.ZodString;
152
+ turnId: z.ZodString;
153
+ runId: z.ZodString;
154
+ }, z.core.$strip>, z.ZodObject<{
155
+ type: z.ZodLiteral<"error">;
156
+ message: z.ZodString;
157
+ code: z.ZodOptional<z.ZodString>;
158
+ turnId: z.ZodString;
159
+ runId: z.ZodString;
160
+ }, z.core.$strip>, z.ZodObject<{
161
+ type: z.ZodLiteral<"done">;
162
+ stopReason: z.ZodEnum<{
163
+ error: "error";
164
+ complete: "complete";
165
+ interrupted: "interrupted";
166
+ cancelled: "cancelled";
167
+ }>;
168
+ turnId: z.ZodString;
169
+ runId: z.ZodString;
170
+ }, z.core.$strip>], "type">;
171
+ export type TurnEvent = z.infer<typeof turnEventSchema>;
172
+ export type TurnStopReason = Extract<TurnEvent, {
173
+ type: "done";
174
+ }>["stopReason"];
175
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,kBAAkB,CAAC;AAIrC,eAAO,MAAM,iBAAiB;;;;;;;mBAQ5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D;;;;;GAKG;AACH,eAAO,MAAM,aAAa,0BAAuB,CAAC;AAClD,MAAM,MAAM,OAAO,GAAG,OAAO,EAAE,CAAC;AAIhC,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;6BA2BxB,CAAC;AACH,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEpD,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;iBAGxB,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE9D,eAAO,MAAM,eAAe;;;;;;;;iBAM1B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAElE,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,UAAU,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACnC,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,eAAe,EAAE,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,mEAAmE;IACnE,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACxC;AAED,eAAO,MAAM,WAAW,EAAE,CAAC,CAAC,OAAO,CAAC,eAAe,CAapB,CAAC;AAMhC,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAoC1B,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AACxD,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,SAAS,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,YAAY,CAAC,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,126 @@
1
+ /**
2
+ * @pattern-js/mod-agents — the neutral contracts.
3
+ *
4
+ * Two families of shapes, both PLAIN JSON (structured-cloneable: they flow on
5
+ * edges and may cross worker boundaries — provider SDK objects never do; a
6
+ * provider mod reifies descriptors into SDK instances at execute time):
7
+ *
8
+ * - **Descriptors** — what an agent/toolset/guardrail value IS on the canvas.
9
+ * - **Turn events** — the modality-agnostic stream a running agent emits
10
+ * (text deltas, tool activity, approval requests, a guaranteed terminal
11
+ * event). Chat UIs, stores and SSE responses all speak this one protocol;
12
+ * voice surfaces later consume the same stream (`audio.ref` is reserved).
13
+ *
14
+ * Message content is PARTS-based from day one (text | image_ref) — retrofitting
15
+ * parts into string messages is the painful version.
16
+ */
17
+ import { z } from "@pattern-js/core";
18
+ /* ── message parts ─────────────────────────────────────────────────────── */
19
+ export const messagePartSchema = z.union([
20
+ z.object({ type: z.literal("text"), text: z.string() }),
21
+ z.object({
22
+ type: z.literal("image_ref"),
23
+ /** A blob id (mod-store) — providers inline it (e.g. base64 data URL). */
24
+ blobId: z.string(),
25
+ mime: z.string().optional(),
26
+ }),
27
+ ]);
28
+ /**
29
+ * Conversation history: provider-shaped items, treated as OPAQUE by everything
30
+ * except the provider mod that produced them (pull from the store → agent run
31
+ * → push back). Keeping them opaque lets each provider use its native item
32
+ * format losslessly.
33
+ */
34
+ export const historySchema = z.array(z.unknown());
35
+ /* ── descriptors ───────────────────────────────────────────────────────── */
36
+ export const toolRefSchema = z.discriminatedUnion("origin", [
37
+ z.object({
38
+ origin: z.literal("workflow"),
39
+ workflowId: z.string(),
40
+ name: z.string(),
41
+ description: z.string().optional(),
42
+ /** JSON Schema for the tool arguments (engine-enforced via boundary.tool). */
43
+ params: z.record(z.string(), z.unknown()).optional(),
44
+ /** Pause for human approval before each call (HITL). */
45
+ needsApproval: z.boolean().optional(),
46
+ }),
47
+ z.object({
48
+ origin: z.literal("mcp"),
49
+ transport: z.enum(["http", "stdio"]),
50
+ url: z.string().optional(),
51
+ headers: z.record(z.string(), z.string()).optional(),
52
+ command: z.string().optional(),
53
+ args: z.array(z.string()).optional(),
54
+ env: z.record(z.string(), z.string()).optional(),
55
+ serverLabel: z.string().optional(),
56
+ }),
57
+ z.object({
58
+ origin: z.literal("op"),
59
+ /** Name in the AGENTS_SERVICE op-tool registry (mods register in setup). */
60
+ name: z.string(),
61
+ needsApproval: z.boolean().optional(),
62
+ }),
63
+ ]);
64
+ export const toolsetSchema = z.object({
65
+ kind: z.literal("toolset"),
66
+ tools: z.array(toolRefSchema),
67
+ });
68
+ export const guardrailSchema = z.object({
69
+ kind: z.literal("guardrail"),
70
+ direction: z.enum(["input", "output"]),
71
+ /** A boundary.tool workflow returning { tripwire: boolean, info?: unknown }. */
72
+ workflowId: z.string(),
73
+ name: z.string(),
74
+ });
75
+ export const agentSchema = z.lazy(() => z.object({
76
+ kind: z.literal("agent"),
77
+ name: z.string(),
78
+ instructions: z.string(),
79
+ model: z.string().optional(),
80
+ modelSettings: z.record(z.string(), z.unknown()).optional(),
81
+ tools: toolsetSchema.optional(),
82
+ guardrails: z.array(guardrailSchema).optional(),
83
+ handoffs: z.array(agentSchema).optional(),
84
+ handoffDescription: z.string().optional(),
85
+ outputSchema: z.record(z.string(), z.unknown()).optional(),
86
+ }));
87
+ /* ── the turn event protocol ───────────────────────────────────────────── */
88
+ const base = { turnId: z.string(), runId: z.string() };
89
+ export const turnEventSchema = z.discriminatedUnion("type", [
90
+ /** Streaming text. */
91
+ z.object({ ...base, type: z.literal("text.delta"), delta: z.string() }),
92
+ /** A complete assistant message (also emitted after deltas). */
93
+ z.object({ ...base, type: z.literal("text.done"), text: z.string() }),
94
+ /** Tool lifecycle: start → done | error. Args/results may be elided/truncated. */
95
+ z.object({
96
+ ...base,
97
+ type: z.literal("tool.activity"),
98
+ toolName: z.string(),
99
+ callId: z.string().optional(),
100
+ phase: z.enum(["start", "done", "error"]),
101
+ args: z.unknown().optional(),
102
+ result: z.unknown().optional(),
103
+ error: z.string().optional(),
104
+ /** Set when the tool ran as a linked sub-run (deep-link into the admin). */
105
+ subRunId: z.string().optional(),
106
+ }),
107
+ /** Reserved for the voice round: a chunk of audio parked as a blob. */
108
+ z.object({ ...base, type: z.literal("audio.ref"), blobId: z.string(), mime: z.string() }),
109
+ /** HITL: the turn paused awaiting approval; resume via the provider mod. */
110
+ z.object({
111
+ ...base,
112
+ type: z.literal("approval.request"),
113
+ interruption: z.object({ id: z.string(), toolName: z.string(), args: z.unknown() }),
114
+ /** Opaque serialized run state — only the provider mod reads it. */
115
+ stateToken: z.string(),
116
+ }),
117
+ /** An error, as turn CONTENT (chat UIs render an inline card, not a toast). */
118
+ z.object({ ...base, type: z.literal("error"), message: z.string(), code: z.string().optional() }),
119
+ /** GUARANTEED terminal event — a consumer can always settle on `done`. */
120
+ z.object({
121
+ ...base,
122
+ type: z.literal("done"),
123
+ stopReason: z.enum(["complete", "interrupted", "error", "cancelled"]),
124
+ }),
125
+ ]);
126
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @pattern-js/mod-agents — the well-known service seam.
3
+ *
4
+ * Provider mods (mod-agents-openai…) and consumers (mod-chat) meet here:
5
+ * the tool registry lives on AGENTS_SERVICE; nobody imports a provider mod
6
+ * to find tools.
7
+ */
8
+ import type { OpContext } from "@pattern-js/core";
9
+ import type { ToolRef } from "./types.js";
10
+ export declare const AGENTS_SERVICE = "agentsService";
11
+ /** A boundary.tool workflow discovered in the registry. */
12
+ export interface WorkflowToolRegistration {
13
+ workflowId: string;
14
+ nodeId: string;
15
+ name: string;
16
+ description?: string;
17
+ params?: Record<string, unknown>;
18
+ needsApproval?: boolean;
19
+ /** Guardrail-only: resolvable by name, but excluded from the default toolset. */
20
+ guardrail?: boolean;
21
+ }
22
+ /** A code tool a mod contributes directly (registered in its `setup`). */
23
+ export interface OpToolRegistration {
24
+ name: string;
25
+ description: string;
26
+ /** JSON Schema for the arguments. */
27
+ params: Record<string, unknown>;
28
+ execute(args: Record<string, unknown>, ctx: OpContext): unknown | Promise<unknown>;
29
+ needsApproval?: boolean;
30
+ }
31
+ export interface AgentsService {
32
+ /** Live view of every boundary.tool workflow (registered ⇄ discovered). */
33
+ listWorkflowTools(): WorkflowToolRegistration[];
34
+ getWorkflowTool(name: string): WorkflowToolRegistration | undefined;
35
+ /** Mods contribute ready-made tools here from their setup. */
36
+ registerOpTool(reg: OpToolRegistration): void;
37
+ listOpTools(): OpToolRegistration[];
38
+ getOpTool(name: string): OpToolRegistration | undefined;
39
+ /**
40
+ * Turn-scoped cancellation. A STREAMING run settles for the engine as soon
41
+ * as its out-gates capture (the SSE tail flows after), so engine.cancelRun
42
+ * can't reach an in-flight agent turn — providers register an
43
+ * AbortController per turnId instead, and a Stop button calls `abortTurn`.
44
+ */
45
+ registerTurn(turnId: string, controller: AbortController): void;
46
+ releaseTurn(turnId: string): void;
47
+ abortTurn(turnId: string, reason?: unknown): boolean;
48
+ }
49
+ export declare function agentsService(ctx: OpContext): AgentsService;
50
+ /** ToolRef for a workflow tool registration (what flows on toolset edges). */
51
+ export declare function workflowToolRef(reg: WorkflowToolRegistration): ToolRef;
52
+ //# sourceMappingURL=well-known.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"well-known.d.ts","sourceRoot":"","sources":["../src/well-known.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,eAAO,MAAM,cAAc,kBAAkB,CAAC;AAE9C,2DAA2D;AAC3D,MAAM,WAAW,wBAAwB;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,iFAAiF;IACjF,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,0EAA0E;AAC1E,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,qCAAqC;IACrC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnF,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,2EAA2E;IAC3E,iBAAiB,IAAI,wBAAwB,EAAE,CAAC;IAChD,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,wBAAwB,GAAG,SAAS,CAAC;IACpE,8DAA8D;IAC9D,cAAc,CAAC,GAAG,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAC9C,WAAW,IAAI,kBAAkB,EAAE,CAAC;IACpC,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAAC;IACxD;;;;;OAKG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,eAAe,GAAG,IAAI,CAAC;IAChE,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;CACtD;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,SAAS,GAAG,aAAa,CAQ3D;AAED,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,GAAG,EAAE,wBAAwB,GAAG,OAAO,CAStE"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @pattern-js/mod-agents — the well-known service seam.
3
+ *
4
+ * Provider mods (mod-agents-openai…) and consumers (mod-chat) meet here:
5
+ * the tool registry lives on AGENTS_SERVICE; nobody imports a provider mod
6
+ * to find tools.
7
+ */
8
+ export const AGENTS_SERVICE = "agentsService";
9
+ export function agentsService(ctx) {
10
+ const svc = ctx.services[AGENTS_SERVICE];
11
+ if (!svc) {
12
+ throw new Error('agent ops need @pattern-js/mod-agents installed — add "@pattern-js/mod-agents" to your pattern.config.json mods');
13
+ }
14
+ return svc;
15
+ }
16
+ /** ToolRef for a workflow tool registration (what flows on toolset edges). */
17
+ export function workflowToolRef(reg) {
18
+ return {
19
+ origin: "workflow",
20
+ workflowId: reg.workflowId,
21
+ name: reg.name,
22
+ description: reg.description,
23
+ params: reg.params,
24
+ needsApproval: reg.needsApproval,
25
+ };
26
+ }
27
+ //# sourceMappingURL=well-known.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"well-known.js","sourceRoot":"","sources":["../src/well-known.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,MAAM,CAAC,MAAM,cAAc,GAAG,eAAe,CAAC;AA2C9C,MAAM,UAAU,aAAa,CAAC,GAAc;IAC1C,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC,cAAc,CAA8B,CAAC;IACtE,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,iHAAiH,CAClH,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,eAAe,CAAC,GAA6B;IAC3D,OAAO;QACL,MAAM,EAAE,UAAU;QAClB,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,aAAa,EAAE,GAAG,CAAC,aAAa;KACjC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,99 @@
1
+ ---
2
+ title: Authoring tools
3
+ order: 10
4
+ ---
5
+
6
+ # Authoring agent tools
7
+
8
+ A tool is just a workflow with a `boundary.tool` trigger and a
9
+ `boundary.tool.return` out-gate. Reach for one whenever the model needs to
10
+ *do* something — look data up, call an API, change state — rather than only
11
+ talk. Three things make it a good tool.
12
+
13
+ ## 1. A tight schema
14
+
15
+ The trigger's `params` is a JSON Schema the **engine validates** before your
16
+ graph runs — the model's arguments are checked at the door, so a hallucinated
17
+ shape becomes a clean `TriggerInputError`, never a malformed value deep in
18
+ your logic. Be specific: required fields, enums, and descriptions (the model
19
+ reads them to decide *when* and *how* to call).
20
+
21
+ ```jsonc
22
+ // workflows/tool-weather.json
23
+ { "id": "tool-weather",
24
+ "nodes": [
25
+ { "id": "in", "op": "boundary.tool",
26
+ "config": {
27
+ "name": "get_weather",
28
+ "description": "Current weather for a city.",
29
+ "params": {
30
+ "type": "object",
31
+ "properties": { "city": { "type": "string", "description": "City name, e.g. \"Lyon\"" } },
32
+ "required": ["city"] } } },
33
+ { "id": "fetch", "op": "…your graph…", "comment": "call the weather API" },
34
+ { "id": "out", "op": "boundary.tool.return" } ],
35
+ "edges": [
36
+ { "from": { "node": "in", "port": "args" }, "to": { "node": "fetch", "port": "in" } },
37
+ { "from": { "node": "fetch", "port": "result" }, "to": { "node": "out", "port": "result" } } ] }
38
+ ```
39
+
40
+ The trigger outputs `args` (validated against your schema) and `user` (the
41
+ calling principal). In the editor, `params` is a visual SchemaBuilder.
42
+
43
+ ## 2. A real return
44
+
45
+ Wire your result into `boundary.tool.return`'s `result` — that's exactly what
46
+ the model sees. Keep it small and structured; the model pays attention tokens
47
+ for every field. A weather tool returns `{ tempC, summary }`, not the raw API
48
+ envelope. `boundary.tool.return` is the only legal terminal for a tool
49
+ workflow, so every branch must converge on it.
50
+
51
+ ## 3. It's debuggable for free
52
+
53
+ Every tool call runs as a **linked sub-run** (`ctx.invoke`). Open the agent's
54
+ run in the admin and each invocation is a child run (↳) with its own node
55
+ waterfall and sampled I/O — you see exactly what the model passed and what
56
+ came back. No print-debugging an opaque function.
57
+
58
+ ## Three origins — and when to choose each
59
+
60
+ - **Workflow tools** — what we just built; `agents.tools.workflows` collects
61
+ every `boundary.tool` in the app (or a named subset). The default: visible,
62
+ debuggable, yours. With no names it auto-includes everything *except*
63
+ guardrail-only tools.
64
+ - **MCP servers** — `agents.mcp.server` exposes an existing MCP server's tools
65
+ (HTTP or stdio, pooled per process). Choose this to borrow tools you don't
66
+ want to re-implement.
67
+ - **Op tools** — a mod registers plain-function tools in its setup
68
+ (`AGENTS_SERVICE.registerOpTool`), picked by `agents.tools.ops`. Choose this
69
+ for tools a mod ships as code rather than a visible workflow.
70
+
71
+ Merge any combination with `agents.tools.merge` (config `count` = how many
72
+ toolset inputs) and wire the result into `agents.agent`'s `tools` input.
73
+
74
+ ## Gating a tool behind approval (HITL)
75
+
76
+ Set `needsApproval: true` on the `boundary.tool` config and the agent pauses
77
+ before the call, emitting an `approval.request` (with an opaque resume
78
+ `stateToken`). The chat app turns that into Approve/Deny buttons;
79
+ `agents.run.resume` continues the **same** turn with the decision. Use it for
80
+ tools with side effects — sending mail, spending money, deleting rows.
81
+
82
+ ## Authoring a guardrail
83
+
84
+ A guardrail reuses the **same** `boundary.tool` pair: a tool workflow whose
85
+ `result` is `{ tripwire: boolean, info? }`. The provider calls it as an input
86
+ or output guardrail (a linked sub-run, like any tool), and a `tripwire: true`
87
+ ends the turn cleanly with an inline card.
88
+
89
+ 1. Author the classifier as a `boundary.tool` workflow. Mark it
90
+ `guardrail: true` so `agents.tools.workflows` leaves it **out** of the
91
+ model's callable toolbox — a moderation classifier must not become a tool
92
+ the model can invoke.
93
+ 2. Wrap it with `agents.guardrail` (config: `tool` = the workflow's name,
94
+ `direction` = `input` | `output`).
95
+ 3. Wire the descriptor into `agents.agent`'s `guardrails` input (one or an
96
+ array).
97
+
98
+ Input guardrails vet the user's message before the model sees it; output
99
+ guardrails vet the model's answer before it reaches the user.
package/docs/index.md ADDED
@@ -0,0 +1,122 @@
1
+ # Agents (contracts)
2
+
3
+ `@pattern-js/mod-agents` is the **neutral** agents layer — plain-JSON
4
+ descriptors and the turn event protocol, with no SDK dependency. Provider
5
+ mods (like `@pattern-js/mod-agents-openai`) reify the descriptors into running
6
+ agents; apps (like `@pattern-js/mod-chat`) consume the events. Neither has to
7
+ know about the other.
8
+
9
+ ## When to use
10
+
11
+ Install this whenever you want agents in your app — but you never install it
12
+ *alone*. It contributes the `boundary.tool` pair, the toolset/guardrail ops,
13
+ and the live tool registry (`AGENTS_SERVICE`); it does **not** run an agent.
14
+ Pair it with a provider (`@pattern-js/mod-agents-openai`) for `agents.agent` /
15
+ `agents.run`, and usually with `@pattern-js/mod-store` (history + blobs) and
16
+ `@pattern-js/mod-vault` (the API key).
17
+
18
+ **When not:** if all you need is a single model call with no tools, no agent
19
+ loop, and no streaming turn events, a provider op straight from the canvas is
20
+ lighter than the agents stack. The agents layer earns its keep the moment
21
+ there are tools, handoffs, guardrails, or a live transcript.
22
+
23
+ ```jsonc
24
+ // pattern.config.json — the contracts always ship with a provider
25
+ { "mods": ["@pattern-js/mod-agents", "@pattern-js/mod-agents-openai"] }
26
+ ```
27
+
28
+ ## Tools are workflows
29
+
30
+ A tool is a **workflow** that starts with a `boundary.tool` trigger and ends
31
+ with `boundary.tool.return`. The trigger's config carries the `name` and
32
+ `description` the model reads, plus a JSON-Schema `params` the **engine**
33
+ validates before your graph runs — a model's hallucinated args become a clean
34
+ `TriggerInputError` at the door, never a malformed value deep in your logic.
35
+ That one shape also buys discovery (the registry scans for these triggers,
36
+ live) and debugging: every call is a **linked sub-run** (↳ in the Runs page,
37
+ with sampled I/O), so the admin's run view is your agent debugger.
38
+
39
+ ### Three tool origins — and when to choose each
40
+
41
+ | origin | op | reach for it when |
42
+ | --- | --- | --- |
43
+ | **workflow** | `agents.tools.workflows` | the tool is your own logic, and you want it visible/debuggable on the canvas (the default — drop a `boundary.tool` workflow in the app and every agent picks it up). |
44
+ | **MCP server** | `agents.mcp.server` (provider mod) | you want to expose an existing MCP server's tools (HTTP or stdio) without re-implementing them. |
45
+ | **op (code)** | `agents.tools.ops` | a mod ships a ready-made tool as a plain function (registered via `AGENTS_SERVICE.registerOpTool` in setup) rather than a visible workflow. |
46
+
47
+ `agents.tools.workflows` with no names auto-collects every `boundary.tool` in
48
+ the app **except** guardrail-only ones (`guardrail: true` on the trigger keeps
49
+ a moderation classifier from becoming a callable tool — name it explicitly to
50
+ include it anyway). Combine any origins with `agents.tools.merge` (config
51
+ `count` sets the input ports) and wire the result into `agents.agent`'s
52
+ `tools` input.
53
+
54
+ ## Guardrails reuse the tool shape
55
+
56
+ A guardrail is the same `boundary.tool` pair by convention — a tool workflow
57
+ whose `result` is `{ tripwire: boolean, info? }`. `agents.guardrail` wraps a
58
+ named tool workflow as an `input` or `output` guardrail descriptor; input
59
+ guardrails vet the user's message before the model sees it, output guardrails
60
+ vet the answer. A trip surfaces as an inline error card in the chat and the
61
+ turn ends cleanly with the reason — never a crash. Mark the underlying tool
62
+ `guardrail: true` so it stays out of the model's callable toolbox.
63
+
64
+ ## The turn event protocol
65
+
66
+ A running agent emits one **modality-agnostic** stream that every surface
67
+ speaks — SSE responses, the chat's persisted log, future voice surfaces:
68
+
69
+ - `text.delta` / `text.done` — streaming text, then the settled message.
70
+ - `tool.activity` — a tool's lifecycle (`start` → `done` | `error`), with an
71
+ optional `subRunId` deep-linking into the admin.
72
+ - `approval.request` — the turn paused for human approval (HITL), carrying an
73
+ opaque `stateToken` only the provider mod reads.
74
+ - `error` — errors are turn **content** (chats render an inline card, not a
75
+ toast).
76
+ - `audio.ref` — reserved for the voice round.
77
+ - `done { stopReason }` — a **guaranteed** terminal event
78
+ (`complete | interrupted | error | cancelled`), so a consumer can always
79
+ settle.
80
+
81
+ These are event-name constants on the wire, not ops. Descriptors and events
82
+ are both plain JSON (structured-cloneable) so they flow on edges and cross
83
+ worker boundaries; provider SDK objects never do — a provider mod reifies
84
+ descriptors into SDK instances only at execute time.
85
+
86
+ ## Integration
87
+
88
+ - **Provider** (`@pattern-js/mod-agents-openai`) — meets this mod at the
89
+ `AGENTS_SERVICE` service seam: it asks the registry for tools, nobody
90
+ imports a provider to find them. Reifies descriptors → SDK; emits the turn
91
+ events.
92
+ - **`@pattern-js/mod-store`** — history is opaque provider items pulled from a
93
+ store and pushed back; image-part tools resolve blob ids through it.
94
+ - **`@pattern-js/mod-chat`** — the product surface; its `chat.turn.pipeline`
95
+ wires `agents.tools.workflows` → `agents.agent` → `agents.run` and persists
96
+ the event stream.
97
+
98
+ ## The agentic shape
99
+
100
+ Request → extract the user's message → collect tools + define the agent →
101
+ run → stream the response. The canvas version, end to end:
102
+
103
+ ```workflow
104
+ { "id": "agent.turn",
105
+ "name": "Agent · one turn",
106
+ "nodes": [
107
+ { "id": "in", "op": "boundary.http.request", "config": { "method": "POST", "path": "/api/agent" } },
108
+ { "id": "msg", "op": "core.object.get", "config": { "path": "message" }, "comment": "the user's text out of the body" },
109
+ { "id": "tools", "op": "agents.tools.workflows", "comment": "every boundary.tool in the app; name a subset to narrow" },
110
+ { "id": "agent", "op": "agents.agent", "config": { "name": "assistant", "instructions": "You are a helpful assistant." } },
111
+ { "id": "run", "op": "agents.run", "comment": "streams turn events; needs an API key" },
112
+ { "id": "out", "op": "boundary.http.response" } ],
113
+ "edges": [
114
+ { "from": { "node": "in", "port": "body" }, "to": { "node": "msg", "port": "object" } },
115
+ { "from": { "node": "tools", "port": "toolset" }, "to": { "node": "agent", "port": "tools" } },
116
+ { "from": { "node": "agent", "port": "agent" }, "to": { "node": "run", "port": "agent" } },
117
+ { "from": { "node": "msg", "port": "out" }, "to": { "node": "run", "port": "input" } },
118
+ { "from": { "node": "run", "port": "events" }, "to": { "node": "out", "port": "stream" } } ] }
119
+ ```
120
+
121
+ For the full agentic app (history, persistence, approvals, Stop), use
122
+ `@pattern-js/mod-chat` — its turn pipeline is this shape, hardened.
@@ -0,0 +1,5 @@
1
+ Wrap a `boundary.tool` workflow (one that returns `{ tripwire, info? }`) as an
2
+ input or output guardrail and feed it into `agents.agent`'s guardrails input.
3
+ A tripped guardrail surfaces as an inline card in the chat — the run ends
4
+ cleanly with the reason, never a crash. Input guardrails vet the user's
5
+ message before the model sees it; output guardrails vet the model's answer.
@@ -0,0 +1,5 @@
1
+ Combine several toolsets into one — workflow tools (`agents.tools.workflows`),
2
+ MCP servers (`agents.mcp.server`), and op tools (`agents.tools.ops`) all
3
+ produce toolset values, and this merges them (config `count` sets how many
4
+ inputs). Wire the result into `agents.agent`'s tools. The three tool origins,
5
+ one toolbox.
@@ -0,0 +1,4 @@
1
+ Collect mod-contributed CODE tools (registered via
2
+ `AGENTS_SERVICE.registerOpTool`) into a toolset — config `tools` names which
3
+ to include, empty = all. The third tool origin alongside workflow tools and
4
+ MCP: for tools a mod ships as plain functions rather than visible workflows.
@@ -0,0 +1,3 @@
1
+ Collects `boundary.tool` workflows into a toolset descriptor. Default: every
2
+ tool workflow in the app ("drop a file in workflows/, the agent learns it").
3
+ Name specific workflows in config to narrow an agent's toolbox.
@@ -0,0 +1,5 @@
1
+ The trigger that makes a workflow callable by agents. Config: `name`,
2
+ `description`, `params` (JSON Schema — engine-validated, so your graph never
3
+ sees malformed args), optional `needsApproval` for human-in-the-loop. Outputs
4
+ `args` (validated) + `user` (the principal). Pair with `boundary.tool.return`
5
+ whose `result` becomes the tool's return value to the model.
@@ -0,0 +1 @@
1
+ The out-gate that closes a `boundary.tool` workflow: whatever you wire into `result` is exactly what the calling agent reads back as the tool's output. Keep it small and structured — the model spends attention tokens on every field, so return `{ tempC, summary }`, not a raw API envelope. For a guardrail tool, this is where `{ tripwire, info? }` goes. Pairs with `boundary.tool`; it's the only legal terminal for a tool workflow, so every branch must converge here.
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@pattern-js/mod-agents",
3
+ "version": "0.2.0",
4
+ "description": "Neutral agent contracts for Pattern: agent/tool/guardrail descriptors that flow on edges, the boundary.tool workflow pair (engine-validated params), the modality-agnostic turn event protocol, and the tool registry — no model-provider dependency. Pair with @pattern-js/mod-agents-openai (or a future provider mod).",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js"
11
+ }
12
+ },
13
+ "main": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "files": [
16
+ "dist",
17
+ "docs",
18
+ "README.md"
19
+ ],
20
+ "dependencies": {
21
+ "zod": "^4.0.0",
22
+ "@pattern-js/core": "0.2.0",
23
+ "@pattern-js/runtime-node": "0.2.0"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^22.0.0",
27
+ "typescript": "^5.6.0"
28
+ },
29
+ "engines": {
30
+ "node": ">=22.5"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/makkr-studio/pattern.git",
35
+ "directory": "packages/mod-agents"
36
+ },
37
+ "homepage": "https://github.com/makkr-studio/pattern#readme",
38
+ "bugs": "https://github.com/makkr-studio/pattern/issues",
39
+ "author": "Makkr Studio (https://github.com/makkr-studio)",
40
+ "scripts": {
41
+ "build": "tsc -p tsconfig.json",
42
+ "typecheck": "tsc -p tsconfig.json --noEmit"
43
+ }
44
+ }