@directive-run/claude-plugin 1.16.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 (83) hide show
  1. package/.claude-plugin/plugin.json +6 -0
  2. package/LICENSE +26 -0
  3. package/README.md +113 -0
  4. package/dist/index.cjs +2 -0
  5. package/dist/index.cjs.map +1 -0
  6. package/dist/index.d.cts +66 -0
  7. package/dist/index.d.ts +66 -0
  8. package/dist/index.js +2 -0
  9. package/dist/index.js.map +1 -0
  10. package/package.json +64 -0
  11. package/skills/README.md +9 -0
  12. package/skills/building-ai-agents/SKILL.md +414 -0
  13. package/skills/building-ai-agents/ai-adapters.md +257 -0
  14. package/skills/building-ai-agents/ai-agents-streaming.md +307 -0
  15. package/skills/building-ai-agents/ai-communication.md +304 -0
  16. package/skills/building-ai-agents/api-skeleton.md +5 -0
  17. package/skills/building-ai-agents/examples.md +849 -0
  18. package/skills/building-ai-orchestrators/SKILL.md +401 -0
  19. package/skills/building-ai-orchestrators/ai-multi-agent.md +375 -0
  20. package/skills/building-ai-orchestrators/ai-orchestrator.md +466 -0
  21. package/skills/building-ai-orchestrators/ai-tasks.md +268 -0
  22. package/skills/building-ai-orchestrators/api-skeleton.md +5 -0
  23. package/skills/building-ai-orchestrators/examples.md +1609 -0
  24. package/skills/building-directive-systems/SKILL.md +499 -0
  25. package/skills/building-directive-systems/api-skeleton.md +5 -0
  26. package/skills/building-directive-systems/examples.md +4149 -0
  27. package/skills/building-directive-systems/multi-module.md +327 -0
  28. package/skills/building-directive-systems/plugins.md +361 -0
  29. package/skills/building-directive-systems/react-adapter.md +376 -0
  30. package/skills/building-directive-systems/system-api.md +463 -0
  31. package/skills/getting-started-with-directive/SKILL.md +198 -0
  32. package/skills/getting-started-with-directive/api-skeleton.md +5 -0
  33. package/skills/getting-started-with-directive/core-patterns.md +240 -0
  34. package/skills/getting-started-with-directive/examples.md +87 -0
  35. package/skills/getting-started-with-directive/sitemap.md +202 -0
  36. package/skills/hardening-ai-systems/SKILL.md +377 -0
  37. package/skills/hardening-ai-systems/ai-budget-resilience.md +408 -0
  38. package/skills/hardening-ai-systems/ai-guardrails-memory.md +370 -0
  39. package/skills/hardening-ai-systems/ai-security.md +400 -0
  40. package/skills/hardening-ai-systems/api-skeleton.md +5 -0
  41. package/skills/hardening-ai-systems/examples.md +628 -0
  42. package/skills/migrating-to-directive/SKILL.md +249 -0
  43. package/skills/migrating-to-directive/anti-patterns.md +382 -0
  44. package/skills/migrating-to-directive/api-skeleton.md +5 -0
  45. package/skills/migrating-to-directive/core-patterns.md +240 -0
  46. package/skills/migrating-to-directive/examples.md +511 -0
  47. package/skills/migrating-to-directive/schema-types.md +273 -0
  48. package/skills/reviewing-directive-code/SKILL.md +232 -0
  49. package/skills/reviewing-directive-code/anti-patterns.md +382 -0
  50. package/skills/reviewing-directive-code/api-skeleton.md +5 -0
  51. package/skills/reviewing-directive-code/core-patterns.md +240 -0
  52. package/skills/reviewing-directive-code/examples.md +463 -0
  53. package/skills/reviewing-directive-code/naming.md +321 -0
  54. package/skills/scaffolding-directive-modules/SKILL.md +218 -0
  55. package/skills/scaffolding-directive-modules/api-skeleton.md +5 -0
  56. package/skills/scaffolding-directive-modules/core-patterns.md +240 -0
  57. package/skills/scaffolding-directive-modules/examples.md +1386 -0
  58. package/skills/scaffolding-directive-modules/naming.md +321 -0
  59. package/skills/scaffolding-directive-modules/schema-types.md +273 -0
  60. package/skills/testing-ai-systems/SKILL.md +398 -0
  61. package/skills/testing-ai-systems/ai-debug-observability.md +329 -0
  62. package/skills/testing-ai-systems/ai-mcp-rag.md +293 -0
  63. package/skills/testing-ai-systems/ai-testing-evals.md +485 -0
  64. package/skills/testing-ai-systems/api-skeleton.md +5 -0
  65. package/skills/testing-ai-systems/examples.md +1261 -0
  66. package/skills/testing-directive-code/SKILL.md +479 -0
  67. package/skills/testing-directive-code/api-skeleton.md +5 -0
  68. package/skills/testing-directive-code/examples.md +156 -0
  69. package/skills/testing-directive-code/history.md +351 -0
  70. package/skills/testing-directive-code/testing.md +412 -0
  71. package/skills/writing-directive-constraints/SKILL.md +486 -0
  72. package/skills/writing-directive-constraints/api-skeleton.md +5 -0
  73. package/skills/writing-directive-constraints/constraints.md +274 -0
  74. package/skills/writing-directive-constraints/error-boundaries.md +330 -0
  75. package/skills/writing-directive-constraints/examples.md +1361 -0
  76. package/skills/writing-directive-constraints/resolvers.md +367 -0
  77. package/skills/writing-directive-modules/SKILL.md +342 -0
  78. package/skills/writing-directive-modules/anti-patterns.md +382 -0
  79. package/skills/writing-directive-modules/api-skeleton.md +5 -0
  80. package/skills/writing-directive-modules/core-patterns.md +240 -0
  81. package/skills/writing-directive-modules/examples.md +1639 -0
  82. package/skills/writing-directive-modules/naming.md +321 -0
  83. package/skills/writing-directive-modules/schema-types.md +273 -0
@@ -0,0 +1,307 @@
1
+ # AI agents + streaming
2
+
3
+ > Covers `@directive-run/ai` — `AgentLike`, `RunResult`, `StreamChunk`, backpressure, `createStreamingRunner`, `createSSETransport`.
4
+
5
+ Defines the `AgentLike` shape, the `RunResult` returned by every runner, the `StreamChunk` discriminated union, backpressure strategies, the streaming runner wrapper, and the SSE transport for piping tokens to a browser.
6
+
7
+ ## Decision tree
8
+
9
+ ```
10
+ Need the complete result?
11
+ ├── Yes → orchestrator.run(agent, prompt) → Promise<RunResult>
12
+ └── No, need incremental output
13
+ ├── Async-iterator stream → orchestrator.runStream(agent, prompt) → { stream, result, abort }
14
+ ├── Wrap a base runner → createStreamingRunner(baseRunner, opts) → StreamRunner
15
+ └── Server-Sent Events to HTTP → createSSETransport(config) → { toResponse, toStream }
16
+
17
+ Backpressure concern?
18
+ ├── Consumer is slow → backpressure: "buffer" (default)
19
+ ├── Need every token → backpressure: "block"
20
+ └── Real-time, can drop → backpressure: "drop"
21
+ ```
22
+
23
+ ## `AgentLike`
24
+
25
+ ```typescript
26
+ interface AgentLike {
27
+ name: string; // required — unique identifier
28
+ instructions?: string; // system prompt
29
+ model?: string; // adapter-specific model id
30
+ tools?: unknown[]; // tools the agent can call
31
+ }
32
+
33
+ const agent: AgentLike = {
34
+ name: "analyst",
35
+ instructions: "You analyze data and provide insights.",
36
+ model: "claude-sonnet-4-5",
37
+ tools: [searchTool, calculatorTool],
38
+ };
39
+ ```
40
+
41
+ ## `RunResult<T>`
42
+
43
+ Every runner — wrapped, mocked, real — resolves to this shape.
44
+
45
+ ```typescript
46
+ interface RunResult<T = unknown> {
47
+ output: T; // the agent's final output
48
+ messages: Message[]; // full message history from this run
49
+ toolCalls: ToolCall[]; // tool calls executed during this run
50
+ totalTokens: number; // cumulative tokens
51
+ tokenUsage?: TokenUsage; // optional input/output breakdown when the provider supplies it
52
+ isCached?: boolean; // true when served from a semantic cache hit
53
+ }
54
+
55
+ interface TokenUsage {
56
+ inputTokens: number;
57
+ outputTokens: number;
58
+ // Note: NO `total` field — sum `inputTokens + outputTokens` when needed, or use `result.totalTokens`.
59
+ }
60
+ ```
61
+
62
+ ## `StreamChunk` discriminated union
63
+
64
+ ```typescript
65
+ type StreamChunk =
66
+ | { type: "token"; data: string; tokenCount: number } // a text token from the model
67
+ | { type: "tool_start"; tool: string; toolCallId: string; arguments: string } // tool started executing
68
+ | { type: "tool_end"; tool: string; toolCallId: string; result: string } // tool finished
69
+ | { type: "message"; message: Message } // a complete message added to history
70
+ | { type: "guardrail_triggered"; guardrailName: string; reason: string; stopped: boolean } // a guardrail fired during streaming
71
+ | { type: "progress"; phase: "starting" | "generating" | "tool_calling" | "finishing" } // coarse progress
72
+ | { type: "done"; totalTokens: number; duration: number; droppedTokens: number } // stream complete
73
+ | { type: "error"; error: Error }; // stream aborted with an error
74
+ ```
75
+
76
+ ## Consuming `runStream`
77
+
78
+ `orchestrator.runStream(agent, input, options?)` returns **`OrchestratorStreamResult<T>`** — a `{ stream, result, abort }` triple, NOT an `AsyncIterable` directly. Destructure it before iterating.
79
+
80
+ ```typescript
81
+ const { stream, result, abort } = orchestrator.runStream(agent, "Write a report");
82
+
83
+ for await (const chunk of stream) {
84
+ switch (chunk.type) {
85
+ case "token":
86
+ process.stdout.write(chunk.data);
87
+ break;
88
+ case "tool_start":
89
+ console.log(`\ntool: ${chunk.tool}`);
90
+ break;
91
+ case "tool_end":
92
+ console.log(`result: ${chunk.result.slice(0, 100)}`);
93
+ break;
94
+ case "guardrail_triggered":
95
+ console.warn(`guardrail ${chunk.guardrailName}: ${chunk.reason}`);
96
+ if (chunk.stopped) {
97
+ console.error("stream stopped by guardrail");
98
+ }
99
+ break;
100
+ case "done":
101
+ console.log(`\ntokens: ${chunk.totalTokens}, ${chunk.duration}ms`);
102
+ break;
103
+ case "error":
104
+ console.error("stream error:", chunk.error);
105
+ break;
106
+ }
107
+ }
108
+
109
+ const final = await result; // RunResult<T>
110
+
111
+ // Cancel mid-stream
112
+ abort();
113
+ ```
114
+
115
+ ## Backpressure strategies
116
+
117
+ Configure how the stream behaves when the consumer can't keep up. Pass via `runStream`'s `options` (orchestrator-side) or via the `StreamRunOptions` if you're calling a `StreamRunner` directly.
118
+
119
+ ```typescript
120
+ const { stream, result } = orchestrator.runStream(agent, "Generate a long report", {
121
+ signal: abortController.signal,
122
+ backpressure: "buffer", // default — buffer all tokens
123
+ // backpressure: "block" // pause generation until consumer catches up
124
+ // backpressure: "drop" // drop unprocessed tokens; `done.droppedTokens` reports the count
125
+ bufferSize: 1000,
126
+ stopOnGuardrail: true,
127
+ guardrailCheckInterval: 100,
128
+ });
129
+ ```
130
+
131
+ | Strategy | Behavior | Use when |
132
+ |---|---|---|
133
+ | `"buffer"` | Buffers all tokens in memory | Consumer is slightly slow; memory is available |
134
+ | `"block"` | Pauses model generation | Consumer must process every token |
135
+ | `"drop"` | Drops unprocessed tokens | Real-time display; some loss acceptable |
136
+
137
+ ## `createStreamingRunner(baseRunner, options?)`
138
+
139
+ Wrap a base streaming runner (a `StreamingCallbackRunner` — the callback-based adapter interface) into a `StreamRunner` that produces the async-iterator chunks shown above. The factory is `createStreamingRunner`, NOT `createStreamingCallbackRunner` (the "callback" form is the INPUT to this wrapper, not a separate factory).
140
+
141
+ ```typescript
142
+ import { createStreamingRunner, type StreamingCallbackRunner } from "@directive-run/ai";
143
+
144
+ // The base runner is callback-driven. You supply this from your provider adapter.
145
+ const callbackBased: StreamingCallbackRunner = (agent, input, { onToken, onToolStart, onToolEnd, onComplete, signal }) => {
146
+ // … call your provider's streaming API; invoke the callbacks as tokens arrive
147
+ };
148
+
149
+ const streamRunner = createStreamingRunner(callbackBased, {
150
+ streamingGuardrails: [],
151
+ });
152
+
153
+ // Use it directly — returns the same { stream, result, abort } shape
154
+ const { stream, result, abort } = streamRunner(agent, "prompt", { backpressure: "buffer" });
155
+ ```
156
+
157
+ ## `createSSETransport(config?)`
158
+
159
+ Pipes a token stream to Server-Sent Events for browser consumption. `createSSEResponse` does NOT exist — the real factory is `createSSETransport`, and it returns `{ toResponse, toStream }`.
160
+
161
+ ```typescript
162
+ import { createSSETransport } from "@directive-run/ai";
163
+
164
+ const sse = createSSETransport({
165
+ maxResponseChars: 50_000,
166
+ truncationMessage: "\n\n*[Response truncated]*",
167
+ heartbeatIntervalMs: 15_000,
168
+ headers: { "X-AI-Service": "directive" },
169
+ errorMessages: { rate_limit: "Service busy — please retry shortly." },
170
+ });
171
+
172
+ // Modern web frameworks (Hono, Next.js, Bun, Deno) — return a Response directly
173
+ export async function POST(req: Request) {
174
+ const { prompt } = await req.json();
175
+ const { stream } = orchestrator.runStream(agent, prompt);
176
+
177
+ return sse.toResponse(stream, agent.name, prompt);
178
+ }
179
+
180
+ // Express / Koa — write to res via the ReadableStream
181
+ app.post("/api/chat", async (req, res) => {
182
+ const { stream } = orchestrator.runStream(agent, req.body.prompt);
183
+ const readable = sse.toStream(stream, agent.name, req.body.prompt);
184
+
185
+ res.setHeader("Content-Type", "text/event-stream");
186
+ res.setHeader("Cache-Control", "no-cache");
187
+ res.setHeader("Connection", "keep-alive");
188
+
189
+ for await (const chunk of readable) {
190
+ res.write(chunk);
191
+ }
192
+ res.end();
193
+ });
194
+ ```
195
+
196
+ Client-side consumption is plain SSE:
197
+
198
+ ```typescript
199
+ const eventSource = new EventSource("/api/chat");
200
+
201
+ eventSource.addEventListener("text", (event) => {
202
+ const data = JSON.parse(event.data) as { type: "text"; text: string };
203
+ appendToDisplay(data.text);
204
+ });
205
+
206
+ eventSource.addEventListener("done", () => eventSource.close());
207
+ ```
208
+
209
+ ## Anti-patterns
210
+
211
+ ### Iterating `runStream` directly
212
+
213
+ ```typescript
214
+ // WRONG — runStream returns { stream, result, abort }, not the iterator itself
215
+ const stream = orchestrator.runStream(agent, prompt);
216
+ for await (const chunk of stream) { /* won't iterate the right thing */ }
217
+
218
+ // CORRECT — destructure
219
+ const { stream, result, abort } = orchestrator.runStream(agent, prompt);
220
+ for await (const chunk of stream) { /* … */ }
221
+ const final = await result;
222
+ ```
223
+
224
+ ### Importing `createStreamingCallbackRunner`
225
+
226
+ ```typescript
227
+ // WRONG — no factory by that name
228
+ import { createStreamingCallbackRunner } from "@directive-run/ai";
229
+
230
+ // CORRECT — wrap a callback-based runner with createStreamingRunner
231
+ import { createStreamingRunner } from "@directive-run/ai";
232
+ const wrapped = createStreamingRunner(callbackBasedRunner, { streamingGuardrails: [] });
233
+ ```
234
+
235
+ ### Importing `createSSEResponse`
236
+
237
+ ```typescript
238
+ // WRONG — no factory by that name
239
+ import { createSSEResponse } from "@directive-run/ai";
240
+ const sse = createSSEResponse(stream);
241
+
242
+ // CORRECT — createSSETransport returns { toResponse, toStream }
243
+ import { createSSETransport } from "@directive-run/ai";
244
+ const sse = createSSETransport({ heartbeatIntervalMs: 15_000 });
245
+ return sse.toResponse(stream, agentId, prompt);
246
+ ```
247
+
248
+ ### Reading `tokenUsage.total`
249
+
250
+ ```typescript
251
+ // WRONG — TokenUsage has no `total` field
252
+ console.log(result.tokenUsage?.total);
253
+
254
+ // CORRECT — sum, or use the top-level totalTokens
255
+ console.log(result.totalTokens);
256
+ console.log((result.tokenUsage?.inputTokens ?? 0) + (result.tokenUsage?.outputTokens ?? 0));
257
+ ```
258
+
259
+ ### Not checking `chunk.type` before accessing fields
260
+
261
+ ```typescript
262
+ // WRONG — not every chunk has .data; this is undefined for tool_start/done/etc.
263
+ for await (const chunk of stream) {
264
+ console.log(chunk.data);
265
+ }
266
+
267
+ // CORRECT — switch / narrow on chunk.type
268
+ for await (const chunk of stream) {
269
+ if (chunk.type === "token") {
270
+ process.stdout.write(chunk.data);
271
+ }
272
+ }
273
+ ```
274
+
275
+ ### Ignoring `guardrail_triggered.stopped`
276
+
277
+ ```typescript
278
+ // WRONG — continuing after a stopping guardrail
279
+ case "guardrail_triggered":
280
+ console.log("guardrail fired, continuing…");
281
+ break;
282
+
283
+ // CORRECT — `stopped: true` means the stream was terminated by the guardrail
284
+ case "guardrail_triggered":
285
+ if (chunk.stopped) {
286
+ console.error(`stopped by ${chunk.guardrailName}: ${chunk.reason}`);
287
+ abort();
288
+ return;
289
+ }
290
+ break;
291
+ ```
292
+
293
+ ## Quick reference
294
+
295
+ | Type / API | Path | Purpose |
296
+ |---|---|---|
297
+ | `AgentLike` | `@directive-run/ai` | `{ name, instructions?, model?, tools? }` — what runners receive |
298
+ | `RunResult<T>` | `@directive-run/ai` | `{ output, messages, toolCalls, totalTokens, tokenUsage?, isCached? }` |
299
+ | `StreamChunk` | `@directive-run/ai` | 8-way discriminated union for streaming output |
300
+ | `orchestrator.runStream(agent, input, opts?)` | instance method | Returns `{ stream, result, abort }` |
301
+ | `createStreamingRunner(baseRunner, opts?)` | `@directive-run/ai` | Wrap a `StreamingCallbackRunner` into a `StreamRunner` |
302
+ | `createSSETransport(config?)` | `@directive-run/ai` | `{ toResponse, toStream }` for piping a stream to SSE |
303
+
304
+ ## See also
305
+
306
+ - [`ai-adapters.md`](./ai-adapters.md) — provider runners that produce the streams this file consumes
307
+ - [`ai-orchestrator.md`](./ai-orchestrator.md) — `orchestrator.runStream(agent, input)` returns the `{ stream, result, abort }` triple this file documents
@@ -0,0 +1,304 @@
1
+ # AI inter-agent communication
2
+
3
+ > Covers `@directive-run/ai` — `createMessageBus`, `createAgentNetwork`, request/response patterns, scratchpad, cross-agent fact reads.
4
+
5
+ `createMessageBus` for typed pub/sub, `createAgentNetwork` for capability-based discovery and request/response patterns, plus the scratchpad and cross-agent state access available via a multi-agent orchestrator. Import from `@directive-run/ai`.
6
+
7
+ ## Decision tree
8
+
9
+ ```
10
+ What do you need?
11
+ ├── Typed pub/sub between agents → createMessageBus(config?)
12
+ ├── Capability lookup + request/response → createAgentNetwork({ bus, agents? })
13
+ ├── Per-pattern ephemeral state → context.scratchpad inside tasks/agents
14
+ ├── Reading another agent's facts → orchestrator.system.facts[agentId].x
15
+ └── Cross-agent derived state → orchestrator.system.derive[agentId].x
16
+ ```
17
+
18
+ ## `createMessageBus(config?)`
19
+
20
+ Fire-and-forget pub/sub keyed by `agentId`. `publish()` is synchronous — it returns the message id before delivery completes. Use `onDelivery` / `onDeliveryError` in config to observe delivery status.
21
+
22
+ ```typescript
23
+ import { createMessageBus, type TypedAgentMessage } from "@directive-run/ai";
24
+
25
+ const bus = createMessageBus({
26
+ maxHistory: 1000,
27
+ defaultTtlMs: 60 * 60 * 1000,
28
+ maxPendingPerAgent: 100,
29
+ onDelivery: (msg, recipients) => log("delivered", msg.id, recipients),
30
+ onDeliveryError: (msg, error) => log("delivery_failed", msg.id, error.message),
31
+ });
32
+
33
+ // Subscribe — returns a Subscription object with an .unsubscribe() method
34
+ const sub = bus.subscribe("writer", (message: TypedAgentMessage) => {
35
+ console.log(`writer received: ${message.type}`);
36
+ });
37
+
38
+ // Later
39
+ sub.unsubscribe();
40
+
41
+ // Publish — returns the message id (sync)
42
+ const id = bus.publish({
43
+ type: "DELEGATION",
44
+ from: "researcher",
45
+ to: "writer",
46
+ task: "Summarize the findings",
47
+ context: { sources: 12 },
48
+ });
49
+
50
+ // Inspect
51
+ bus.getHistory({ from: "researcher" }, 50);
52
+ bus.getMessage(id);
53
+ bus.getPending("writer"); // messages queued for an offline subscriber
54
+
55
+ bus.clear();
56
+ bus.destroy();
57
+ ```
58
+
59
+ There is no `bus.request(...)` method — request/response lives on the AgentNetwork (next section).
60
+
61
+ ## `createAgentNetwork({ bus, agents? })`
62
+
63
+ Wraps a MessageBus with registry, capability lookup, and request/response patterns. **Capability lookup returns `AgentInfo[]`, not `string[]`.** Request/response shapes — `request`, `delegate`, `query`, `broadcast`, `listen`, `send` — are all on the network, NOT on the bus.
64
+
65
+ ```typescript
66
+ import { createAgentNetwork, type AgentInfo } from "@directive-run/ai";
67
+
68
+ const network = createAgentNetwork({
69
+ bus,
70
+ agents: {
71
+ researcher: { capabilities: ["search", "verify", "cite"] },
72
+ writer: { capabilities: ["draft", "edit", "summarize"] },
73
+ analyst: { capabilities: ["analyze", "chart", "report"] },
74
+ },
75
+ defaultTimeout: 30_000,
76
+ onAgentOnline: (id) => log(`${id} online`),
77
+ onAgentOffline: (id) => log(`${id} offline`),
78
+ });
79
+
80
+ // Find — returns AgentInfo[], not string[]
81
+ const verifiers: AgentInfo[] = network.findByCapability("verify");
82
+ verifiers.forEach((info) => console.log(info.id, info.capabilities));
83
+
84
+ // Request/response with timeout
85
+ const reply = await network.request("coordinator", "researcher", "verify-claim", {
86
+ claim: "GPT-4 has 1.8T parameters",
87
+ }, 10_000);
88
+ console.log(reply.payload);
89
+
90
+ // Delegate a task and await its result
91
+ const result = await network.delegate(
92
+ "coordinator",
93
+ "writer",
94
+ "Draft a 200-word summary",
95
+ { source: reply.payload },
96
+ );
97
+
98
+ // Question / answer
99
+ const answer = await network.query("coordinator", "analyst", "What's the median latency?", { window: "24h" });
100
+
101
+ // Broadcast to all agents
102
+ network.broadcast("coordinator", { type: "INFORM", content: "Cache cleared" });
103
+
104
+ // Plain fire-and-forget through the network (returns message id)
105
+ network.send("coordinator", "writer", { type: "INFORM", content: "Starting batch" });
106
+
107
+ // Subscribe an agent — same as bus.subscribe but registered through the network
108
+ const sub = network.listen("writer", (msg) => console.log(msg.type));
109
+ sub.unsubscribe();
110
+
111
+ network.destroy();
112
+ ```
113
+
114
+ There is no `network.route(capability, payload)` — pick the agent yourself via `findByCapability(...)`, then call `request` / `delegate` against the chosen `agent.id`.
115
+
116
+ ```typescript
117
+ const candidates = network.findByCapability("verify");
118
+ if (candidates.length === 0) throw new Error("no verifier available");
119
+ const target = candidates[0];
120
+
121
+ const result = await network.request("coordinator", target.id, "verify", { claim });
122
+ ```
123
+
124
+ ## Cross-agent state via facts + derivations
125
+
126
+ Each agent in a multi-agent orchestrator becomes a namespaced module, so its facts and derivations are readable on `orchestrator.system`.
127
+
128
+ ```typescript
129
+ const orchestrator = createMultiAgentOrchestrator({
130
+ agents: { researcher, writer },
131
+ runner,
132
+ });
133
+
134
+ orchestrator.start();
135
+
136
+ // Read another agent's namespaced state (read-only outside its own resolvers)
137
+ const status = orchestrator.system.facts.researcher.status;
138
+ const output = orchestrator.system.facts.writer.output;
139
+ const isReady = orchestrator.system.derive.researcher.isComplete;
140
+ ```
141
+
142
+ For a deeper treatment of multi-module fact access + cross-module dependencies, see `multi-module.md`.
143
+
144
+ ## Scratchpad — per-pattern ephemeral state
145
+
146
+ The scratchpad is a read-only context object shared across tasks/agents inside a single pattern execution. **You cannot mutate `context.scratchpad` directly** — pass updates back through the task's return value, or use `network.send` / `bus.publish` for messages that outlive the pattern.
147
+
148
+ ```typescript
149
+ tasks: {
150
+ gather: {
151
+ run: async (input, signal, context) => {
152
+ // context.scratchpad is Readonly — do NOT do context.scratchpad.x = …
153
+ const seed = context.scratchpad.seed;
154
+ return JSON.stringify({ seed, data: await fetchData(seed) });
155
+ },
156
+ },
157
+ format: {
158
+ run: async (input, signal, context) => {
159
+ // Read scratchpad written by the pattern config
160
+ const region = context.scratchpad.region;
161
+ const parsed = JSON.parse(input);
162
+ return JSON.stringify({ ...parsed, region });
163
+ },
164
+ },
165
+ },
166
+ ```
167
+
168
+ For full task surface, see `ai-tasks.md`.
169
+
170
+ ## Message bus + orchestrator wiring
171
+
172
+ The orchestrator does NOT accept a `bus:` option directly — wire the bus alongside the orchestrator and route messages explicitly.
173
+
174
+ ```typescript
175
+ import { createMultiAgentOrchestrator } from "@directive-run/ai/multi-agent";
176
+ import { createMessageBus, createAgentNetwork } from "@directive-run/ai";
177
+
178
+ const bus = createMessageBus({ maxHistory: 1000 });
179
+ const network = createAgentNetwork({
180
+ bus,
181
+ agents: {
182
+ researcher: { capabilities: ["search", "verify"] },
183
+ writer: { capabilities: ["draft", "edit"] },
184
+ },
185
+ });
186
+
187
+ const orchestrator = createMultiAgentOrchestrator({
188
+ agents: { researcher, writer },
189
+ runner,
190
+ hooks: {
191
+ onAgentStart: (e) => {
192
+ bus.publish({
193
+ type: "AGENT_START",
194
+ from: "orchestrator",
195
+ to: e.agentName,
196
+ input: e.input,
197
+ });
198
+ },
199
+ },
200
+ });
201
+
202
+ orchestrator.start();
203
+
204
+ // External systems can publish to the bus directly
205
+ bus.publish({
206
+ type: "INFORM",
207
+ from: "external-pipeline",
208
+ to: "researcher",
209
+ content: "New corpus available",
210
+ });
211
+ ```
212
+
213
+ ## Anti-patterns
214
+
215
+ ### `bus.request(...)`
216
+
217
+ ```typescript
218
+ // WRONG — there is no request method on MessageBus
219
+ const reply = await bus.request({ type: "REQUEST", from: "a", to: "b", action: "verify", timeout: 10_000 });
220
+
221
+ // CORRECT — request/response is on AgentNetwork
222
+ const reply = await network.request("a", "b", "verify", { /* payload */ }, 10_000);
223
+ ```
224
+
225
+ ### `const unsub = bus.subscribe(...); unsub();`
226
+
227
+ ```typescript
228
+ // WRONG — subscribe returns a Subscription object, not the unsubscribe function
229
+ const unsub = bus.subscribe("writer", handler);
230
+ unsub();
231
+
232
+ // CORRECT — call .unsubscribe() on the returned Subscription
233
+ const sub = bus.subscribe("writer", handler);
234
+ sub.unsubscribe();
235
+ ```
236
+
237
+ ### `network.findByCapability(...)` returning strings
238
+
239
+ ```typescript
240
+ // WRONG — assumes the return is string[]
241
+ const writers = network.findByCapability("draft"); // ["writer"]?
242
+ network.send("coordinator", writers[0], message); // passes a string where AgentInfo was expected? No — but we lost capabilities/metadata
243
+
244
+ // CORRECT — it returns AgentInfo[]; use .id when you need the string
245
+ const candidates = network.findByCapability("draft");
246
+ network.send("coordinator", candidates[0].id, message);
247
+ ```
248
+
249
+ ### `network.route(capability, payload)`
250
+
251
+ ```typescript
252
+ // WRONG — no such method
253
+ await network.route("verify", { claim });
254
+
255
+ // CORRECT — pick an agent via findByCapability, then request/delegate
256
+ const verifiers = network.findByCapability("verify");
257
+ if (verifiers.length === 0) throw new Error("no verifier");
258
+ const result = await network.request("coordinator", verifiers[0].id, "verify", { claim });
259
+ ```
260
+
261
+ ### `createMultiAgentOrchestrator({ bus })`
262
+
263
+ ```typescript
264
+ // WRONG — bus is not an option on MultiAgentOrchestratorOptions
265
+ createMultiAgentOrchestrator({ agents, runner, bus })
266
+
267
+ // CORRECT — wire the bus separately and publish through hooks/handlers
268
+ createMultiAgentOrchestrator({
269
+ agents,
270
+ runner,
271
+ hooks: {
272
+ onAgentComplete: (e) => bus.publish({ type: "AGENT_COMPLETE", from: "orchestrator", to: e.agentName, output: e.output }),
273
+ },
274
+ });
275
+ ```
276
+
277
+ ### Mutating `context.scratchpad`
278
+
279
+ ```typescript
280
+ // WRONG — context.scratchpad is Readonly
281
+ context.scratchpad.researchData = data;
282
+ context.scratchpad.timestamp = Date.now();
283
+
284
+ // CORRECT — return new state from the task; or use bus.publish to broadcast
285
+ return JSON.stringify({ ...JSON.parse(input), researchData: data, timestamp: Date.now() });
286
+ ```
287
+
288
+ ## Quick reference
289
+
290
+ | API | Purpose | Notes |
291
+ |---|---|---|
292
+ | `createMessageBus(config?)` | Pub/sub primitive | `publish` / `subscribe` / `getHistory` / `getMessage` / `getPending` / `clear` / `destroy` |
293
+ | `bus.subscribe(id, handler, filter?)` | Subscribe | returns a `Subscription` — call `sub.unsubscribe()` |
294
+ | `createAgentNetwork({ bus, agents? })` | Capability-aware coordination | `request` / `delegate` / `query` / `broadcast` / `send` / `listen` / `findByCapability` |
295
+ | `network.findByCapability(cap)` | Discovery | returns `AgentInfo[]` (NOT `string[]`) |
296
+ | `network.request(from, to, action, payload, timeout?)` | Request/response | `Promise<ResponseMessage>` |
297
+ | `network.delegate(from, to, task, context)` | Delegated task with result | `Promise<DelegationResultMessage>` |
298
+ | `orchestrator.system.facts[agentId].x` | Cross-agent fact read | each agent is a namespaced module |
299
+ | `context.scratchpad` | Per-pattern ephemeral state | Readonly inside tasks/agents |
300
+
301
+ ## See also
302
+
303
+ - [`ai-multi-agent.md`](./ai-multi-agent.md) — the orchestrator that owns the namespaced facts this file's primitives read
304
+ - [`ai-tasks.md`](./ai-tasks.md) — tasks share the scratchpad surface this file documents
@@ -0,0 +1,5 @@
1
+ # API Skeleton
2
+
3
+ > Auto-generated. Do not edit.
4
+
5
+ > Source JSON not found. Build website docs first.