@directive-run/knowledge 1.13.0 → 1.15.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 (43) hide show
  1. package/README.md +50 -15
  2. package/ai/ai-adapters.md +2 -0
  3. package/ai/ai-agents-streaming.md +182 -149
  4. package/ai/ai-budget-resilience.md +299 -132
  5. package/ai/ai-communication.md +215 -197
  6. package/ai/ai-debug-observability.md +253 -173
  7. package/ai/ai-guardrails-memory.md +185 -153
  8. package/ai/ai-mcp-rag.md +199 -199
  9. package/ai/ai-multi-agent.md +247 -153
  10. package/ai/ai-orchestrator.md +344 -114
  11. package/ai/ai-security.md +282 -180
  12. package/ai/ai-tasks.md +3 -1
  13. package/ai/ai-testing-evals.md +357 -256
  14. package/core/anti-patterns.md +9 -2
  15. package/core/constraints.md +6 -2
  16. package/core/core-patterns.md +2 -0
  17. package/core/error-boundaries.md +2 -0
  18. package/core/history.md +2 -0
  19. package/core/multi-module.md +8 -3
  20. package/core/naming.md +15 -6
  21. package/core/plugins.md +2 -0
  22. package/core/react-adapter.md +250 -174
  23. package/core/resolvers.md +2 -0
  24. package/core/schema-types.md +2 -0
  25. package/core/system-api.md +2 -0
  26. package/core/testing.md +251 -143
  27. package/examples/checkers.ts +15 -16
  28. package/examples/contact-form.ts +2 -2
  29. package/examples/counter-react.ts +1 -1
  30. package/examples/counter-svelte.ts +1 -1
  31. package/examples/counter-vue.ts +1 -1
  32. package/examples/counter.ts +1 -1
  33. package/examples/data-triggers.ts +4 -4
  34. package/examples/feature-flags.ts +2 -2
  35. package/examples/form-wizard.ts +2 -2
  36. package/examples/newsletter.ts +2 -2
  37. package/examples/server.ts +2 -2
  38. package/examples/shopping-cart.ts +1 -1
  39. package/examples/topic-guard.ts +1 -1
  40. package/package.json +3 -3
  41. package/sitemap.md +26 -3
  42. package/examples/debounce-constraints.ts +0 -95
  43. package/examples/multi-module.ts +0 -58
@@ -1,281 +1,299 @@
1
- # AI Communication
1
+ # AI inter-agent communication
2
2
 
3
- Cross-agent messaging, agent networks, shared state via derivations, scratchpad coordination, and handoff patterns.
3
+ > Covers `@directive-run/ai` `createMessageBus`, `createAgentNetwork`, request/response patterns, scratchpad, cross-agent fact reads.
4
4
 
5
- ## Decision Tree: "How do agents communicate?"
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
6
8
 
7
9
  ```
8
- What kind of communication?
9
- ├── Fire-and-forget notificationbus.publish({ type: "INFORM", ... })
10
- ├── Request-response (await reply)bus.request({ type: "REQUEST", ... })
11
- ├── Delegate work to another agent bus.publish({ type: "DELEGATION", ... })
12
- ├── Subscribe to ongoing updates bus.publish({ type: "SUBSCRIBE", ... })
13
-
14
- How do agents share state?
15
- ├── Read another agent's facts → orchestrator.system.facts.agentName.key
16
- ├── Cross-agent derivations → orchestrator.derive.agentName.derivation
17
- ├── Ephemeral key-value store → context.scratchpad
18
-
19
- How do agents hand off work?
20
- ├── One-time transfer → handoff pattern (DELEGATION + await DELEGATION_RESULT)
21
- ├── Ongoing collaboration → agent network with capabilities
22
- └── Conditional routing → reroute in supervisor pattern
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
23
16
  ```
24
17
 
25
- ## Message Bus
18
+ ## `createMessageBus(config?)`
26
19
 
27
- The message bus enables structured communication between agents:
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.
28
21
 
29
22
  ```typescript
30
- import { createMessageBus } from "@directive-run/ai";
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
+ });
31
32
 
32
- const bus = createMessageBus();
33
- ```
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
+ });
34
37
 
35
- ## Publishing Messages
38
+ // Later
39
+ sub.unsubscribe();
36
40
 
37
- ```typescript
38
- // Fire-and-forget notification
39
- bus.publish({
40
- type: "INFORM",
41
+ // Publish — returns the message id (sync)
42
+ const id = bus.publish({
43
+ type: "DELEGATION",
41
44
  from: "researcher",
42
45
  to: "writer",
43
- content: "Found 5 relevant sources",
44
- metadata: { sourceCount: 5, topics: ["AI", "ML"] },
46
+ task: "Summarize the findings",
47
+ context: { sources: 12 },
45
48
  });
46
49
 
47
- // Broadcast to all agents (omit "to")
48
- bus.publish({
49
- type: "INFORM",
50
- from: "coordinator",
51
- content: "System entering maintenance mode",
52
- });
53
- ```
54
-
55
- ## Request-Response Pattern
50
+ // Inspect
51
+ bus.getHistory({ from: "researcher" }, 50);
52
+ bus.getMessage(id);
53
+ bus.getPending("writer"); // messages queued for an offline subscriber
56
54
 
57
- ```typescript
58
- // Send a request and await the response
59
- const response = await bus.request({
60
- type: "REQUEST",
61
- from: "writer",
62
- to: "researcher",
63
- action: "verify_claim",
64
- payload: { claim: "Transformers were invented in 2017" },
65
- timeout: 5000, // Throws after 5s if no response
66
- });
67
-
68
- console.log(response.content); // "Verified: correct"
69
- console.log(response.metadata); // { confidence: 0.95, source: "..." }
55
+ bus.clear();
56
+ bus.destroy();
70
57
  ```
71
58
 
72
- ## Message Types
73
-
74
- All 11 message types in the system:
75
-
76
- | Type | Direction | Purpose |
77
- |---|---|---|
78
- | `REQUEST` | Agent-to-agent | Ask another agent to do something |
79
- | `RESPONSE` | Agent-to-agent | Reply to a REQUEST |
80
- | `DELEGATION` | Agent-to-agent | Hand off a task to another agent |
81
- | `DELEGATION_RESULT` | Agent-to-agent | Return result of delegated work |
82
- | `QUERY` | Agent-to-agent | Ask for information without side effects |
83
- | `INFORM` | Agent-to-agent/all | Share information, no response expected |
84
- | `SUBSCRIBE` | Agent-to-agent | Request ongoing updates on a topic |
85
- | `UNSUBSCRIBE` | Agent-to-agent | Stop receiving updates |
86
- | `UPDATE` | Agent-to-subscriber | Push update to a subscriber |
87
- | `ACK` | Agent-to-agent | Acknowledge receipt |
88
- | `NACK` | Agent-to-agent | Reject or refuse a message |
89
-
90
- ## Subscribing to Messages
91
-
92
- ```typescript
93
- // Subscribe to all messages for an agent
94
- const unsubscribe = bus.subscribe("writer", (message) => {
95
- switch (message.type) {
96
- case "INFORM":
97
- console.log(`Info from ${message.from}: ${message.content}`);
98
- break;
99
- case "REQUEST":
100
- // Handle and respond
101
- bus.publish({
102
- type: "RESPONSE",
103
- from: "writer",
104
- to: message.from,
105
- correlationId: message.id,
106
- content: "Done",
107
- });
108
- break;
109
- }
110
- });
111
-
112
- // Clean up
113
- unsubscribe();
114
- ```
59
+ There is no `bus.request(...)` method — request/response lives on the AgentNetwork (next section).
115
60
 
116
- ## Agent Network
61
+ ## `createAgentNetwork({ bus, agents? })`
117
62
 
118
- Higher-level abstraction for capability-based agent discovery:
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.
119
64
 
120
65
  ```typescript
121
- import { createAgentNetwork } from "@directive-run/ai";
66
+ import { createAgentNetwork, type AgentInfo } from "@directive-run/ai";
122
67
 
123
68
  const network = createAgentNetwork({
124
69
  bus,
125
70
  agents: {
126
- researcher: {
127
- capabilities: ["search", "verify", "cite"],
128
- },
129
- writer: {
130
- capabilities: ["draft", "edit", "summarize"],
131
- },
132
- analyst: {
133
- capabilities: ["analyze", "chart", "report"],
134
- },
71
+ researcher: { capabilities: ["search", "verify", "cite"] },
72
+ writer: { capabilities: ["draft", "edit", "summarize"] },
73
+ analyst: { capabilities: ["analyze", "chart", "report"] },
135
74
  },
75
+ defaultTimeout: 30_000,
76
+ onAgentOnline: (id) => log(`${id} online`),
77
+ onAgentOffline: (id) => log(`${id} offline`),
136
78
  });
137
79
 
138
- // Find agents by capability
139
- const writers = network.findByCapability("draft");
140
- // ["writer"]
80
+ // Find returns AgentInfo[], not string[]
81
+ const verifiers: AgentInfo[] = network.findByCapability("verify");
82
+ verifiers.forEach((info) => console.log(info.id, info.capabilities));
141
83
 
142
- const verifiers = network.findByCapability("verify");
143
- // ["researcher"]
144
-
145
- // Route a request to the best agent for a capability
146
- const result = await network.route("verify", {
84
+ // Request/response with timeout
85
+ const reply = await network.request("coordinator", "researcher", "verify-claim", {
147
86
  claim: "GPT-4 has 1.8T parameters",
148
- });
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 });
149
122
  ```
150
123
 
151
- ## Cross-Agent State via Derivations
124
+ ## Cross-agent state via facts + derivations
152
125
 
153
- Agents can read each other's facts and derivations through the shared system:
126
+ Each agent in a multi-agent orchestrator becomes a namespaced module, so its facts and derivations are readable on `orchestrator.system`.
154
127
 
155
128
  ```typescript
156
129
  const orchestrator = createMultiAgentOrchestrator({
157
- agents: {
158
- researcher: {
159
- name: "researcher",
160
- instructions: "...",
161
- model: "claude-sonnet-4-5",
162
- },
163
- writer: {
164
- name: "writer",
165
- instructions: "...",
166
- model: "claude-sonnet-4-5",
167
- },
168
- },
130
+ agents: { researcher, writer },
169
131
  runner,
170
132
  });
171
133
 
172
134
  orchestrator.start();
173
135
 
174
- // Read another agent's facts (read-only)
175
- const researchStatus = orchestrator.system.facts.researcher.status;
176
- const writerOutput = orchestrator.system.facts.writer.lastOutput;
177
-
178
- // Cross-agent derivations react to fact changes
179
- const isReady = orchestrator.system.derive.researcher.isComplete;
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;
180
140
  ```
181
141
 
182
- ## Scratchpad Coordination
142
+ For a deeper treatment of multi-module fact access + cross-module dependencies, see `multi-module.md`.
183
143
 
184
- The scratchpad is an ephemeral key-value store scoped to a single pattern execution. Tasks and agents in the same pattern share it:
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.
185
147
 
186
148
  ```typescript
187
- // In a task – write to scratchpad
188
149
  tasks: {
189
150
  gather: {
190
- run: async (input, context) => {
191
- const data = JSON.parse(input);
192
- context.scratchpad.researchData = data;
193
- context.scratchpad.timestamp = Date.now();
194
-
195
- return input;
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) });
196
155
  },
197
156
  },
198
157
  format: {
199
- run: async (input, context) => {
200
- // Read from scratchpad set by earlier task
201
- const data = context.scratchpad.researchData;
202
- const ts = context.scratchpad.timestamp as number;
203
-
204
- return JSON.stringify({ data, processedAt: ts });
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 });
205
163
  },
206
164
  },
207
165
  },
208
166
  ```
209
167
 
210
- ## Handoff Patterns
168
+ For full task surface, see `ai-tasks.md`.
211
169
 
212
- ### One-Time Delegation
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.
213
173
 
214
174
  ```typescript
215
- // Agent A delegates work to Agent B
216
- bus.publish({
217
- type: "DELEGATION",
218
- from: "coordinator",
219
- to: "researcher",
220
- content: "Research the topic: quantum computing",
221
- metadata: { priority: "high", deadline: Date.now() + 60000 },
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
+ },
222
200
  });
223
201
 
224
- // Agent B returns the result
202
+ orchestrator.start();
203
+
204
+ // External systems can publish to the bus directly
225
205
  bus.publish({
226
- type: "DELEGATION_RESULT",
227
- from: "researcher",
228
- to: "coordinator",
229
- correlationId: originalMessage.id,
230
- content: "Research findings: ...",
231
- metadata: { sourcesFound: 12 },
206
+ type: "INFORM",
207
+ from: "external-pipeline",
208
+ to: "researcher",
209
+ content: "New corpus available",
232
210
  });
233
211
  ```
234
212
 
235
- ### Supervisor Reroute
213
+ ## Anti-patterns
236
214
 
237
- In a supervisor pattern, the supervisor can reroute work mid-execution:
215
+ ### `bus.request(...)`
238
216
 
239
217
  ```typescript
240
- const managed = supervisor("editor", ["researcher", "writer"], {
241
- onReroute: (from, to, reason) => {
242
- console.log(`Rerouting from ${from} to ${to}: ${reason}`);
243
- },
244
- });
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);
245
223
  ```
246
224
 
247
- ## Message Bus with Orchestrator
225
+ ### `const unsub = bus.subscribe(...); unsub();`
248
226
 
249
227
  ```typescript
250
- import { createMultiAgentOrchestrator, createMessageBus } from "@directive-run/ai";
228
+ // WRONG — subscribe returns a Subscription object, not the unsubscribe function
229
+ const unsub = bus.subscribe("writer", handler);
230
+ unsub();
251
231
 
252
- const bus = createMessageBus();
232
+ // CORRECT call .unsubscribe() on the returned Subscription
233
+ const sub = bus.subscribe("writer", handler);
234
+ sub.unsubscribe();
235
+ ```
253
236
 
254
- const orchestrator = createMultiAgentOrchestrator({
255
- agents: { researcher, writer },
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,
256
270
  runner,
257
- bus, // Attach the message bus
271
+ hooks: {
272
+ onAgentComplete: (e) => bus.publish({ type: "AGENT_COMPLETE", from: "orchestrator", to: e.agentName, output: e.output }),
273
+ },
258
274
  });
275
+ ```
259
276
 
260
- orchestrator.start();
277
+ ### Mutating `context.scratchpad`
261
278
 
262
- // External systems can also publish to the bus
263
- bus.publish({
264
- type: "INFORM",
265
- from: "external",
266
- to: "researcher",
267
- content: "New data available",
268
- });
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() });
269
286
  ```
270
287
 
271
- ## Quick Reference
288
+ ## Quick reference
272
289
 
273
- | API | Purpose | Key Options |
290
+ | API | Purpose | Notes |
274
291
  |---|---|---|
275
- | `createMessageBus()` | Agent-to-agent messaging | subscribe, publish, request |
276
- | `createAgentNetwork()` | Capability-based discovery | agents with capabilities |
277
- | `bus.publish()` | Fire-and-forget message | type, from, to, content |
278
- | `bus.request()` | Request-response with timeout | action, payload, timeout |
279
- | `bus.subscribe()` | Listen for messages | agentName, callback |
280
- | `network.findByCapability()` | Find agents by skill | capability string |
281
- | `network.route()` | Route work to capable agent | capability, payload |
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 |