@directive-run/knowledge 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 +21 -0
- package/README.md +63 -0
- package/ai/ai-adapters.md +250 -0
- package/ai/ai-agents-streaming.md +269 -0
- package/ai/ai-budget-resilience.md +235 -0
- package/ai/ai-communication.md +281 -0
- package/ai/ai-debug-observability.md +243 -0
- package/ai/ai-guardrails-memory.md +332 -0
- package/ai/ai-mcp-rag.md +288 -0
- package/ai/ai-multi-agent.md +274 -0
- package/ai/ai-orchestrator.md +227 -0
- package/ai/ai-security.md +293 -0
- package/ai/ai-tasks.md +261 -0
- package/ai/ai-testing-evals.md +378 -0
- package/api-skeleton.md +5 -0
- package/core/anti-patterns.md +382 -0
- package/core/constraints.md +263 -0
- package/core/core-patterns.md +228 -0
- package/core/error-boundaries.md +322 -0
- package/core/multi-module.md +315 -0
- package/core/naming.md +283 -0
- package/core/plugins.md +344 -0
- package/core/react-adapter.md +262 -0
- package/core/resolvers.md +357 -0
- package/core/schema-types.md +262 -0
- package/core/system-api.md +271 -0
- package/core/testing.md +257 -0
- package/core/time-travel.md +238 -0
- package/dist/index.cjs +111 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +102 -0
- package/dist/index.js.map +1 -0
- package/examples/ab-testing.ts +385 -0
- package/examples/ai-checkpoint.ts +509 -0
- package/examples/ai-guardrails.ts +319 -0
- package/examples/ai-orchestrator.ts +589 -0
- package/examples/async-chains.ts +287 -0
- package/examples/auth-flow.ts +371 -0
- package/examples/batch-resolver.ts +341 -0
- package/examples/checkers.ts +589 -0
- package/examples/contact-form.ts +176 -0
- package/examples/counter.ts +393 -0
- package/examples/dashboard-loader.ts +512 -0
- package/examples/debounce-constraints.ts +105 -0
- package/examples/dynamic-modules.ts +293 -0
- package/examples/error-boundaries.ts +430 -0
- package/examples/feature-flags.ts +220 -0
- package/examples/form-wizard.ts +347 -0
- package/examples/fraud-analysis.ts +663 -0
- package/examples/goal-heist.ts +341 -0
- package/examples/multi-module.ts +57 -0
- package/examples/newsletter.ts +241 -0
- package/examples/notifications.ts +210 -0
- package/examples/optimistic-updates.ts +317 -0
- package/examples/pagination.ts +260 -0
- package/examples/permissions.ts +337 -0
- package/examples/provider-routing.ts +403 -0
- package/examples/server.ts +316 -0
- package/examples/shopping-cart.ts +422 -0
- package/examples/sudoku.ts +630 -0
- package/examples/theme-locale.ts +204 -0
- package/examples/time-machine.ts +225 -0
- package/examples/topic-guard.ts +306 -0
- package/examples/url-sync.ts +333 -0
- package/examples/websocket.ts +404 -0
- package/package.json +65 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# AI Budget and Resilience
|
|
2
|
+
|
|
3
|
+
Budget wrappers, retry policies, fallback chains, circuit breakers, health monitors, semantic caching, and constraint-driven provider routing for AI runners.
|
|
4
|
+
|
|
5
|
+
## Decision Tree: "How do I protect my AI calls?"
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
What failure mode are you guarding against?
|
|
9
|
+
├── Cost overruns → withBudget(runner, { budgets: [...] })
|
|
10
|
+
├── Transient errors → withRetry(runner, { maxRetries, backoff })
|
|
11
|
+
├── Provider outage → withFallback(primaryRunner, fallbackRunner)
|
|
12
|
+
├── Repeated failures → createCircuitBreaker({ failureThreshold })
|
|
13
|
+
├── Fleet monitoring → createHealthMonitor({ agents: {...} })
|
|
14
|
+
│
|
|
15
|
+
Want to avoid redundant LLM calls?
|
|
16
|
+
├── Yes, similar inputs → createSemanticCache({ embedder, similarity })
|
|
17
|
+
│
|
|
18
|
+
Need dynamic provider selection?
|
|
19
|
+
└── Yes → createConstraintRouter({ providers: [...], constraints: [...] })
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Budget Wrapping
|
|
23
|
+
|
|
24
|
+
Wrap any runner with cost tracking and enforcement per time window:
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { withBudget } from "@directive-run/ai";
|
|
28
|
+
|
|
29
|
+
const budgetRunner = withBudget(baseRunner, {
|
|
30
|
+
// Hard cap per single LLM call
|
|
31
|
+
maxCostPerCall: 0.10,
|
|
32
|
+
|
|
33
|
+
// Time-window budgets (multiple allowed)
|
|
34
|
+
budgets: [
|
|
35
|
+
{
|
|
36
|
+
window: "hour",
|
|
37
|
+
maxCost: 1.0,
|
|
38
|
+
pricing: { inputPerMillion: 3, outputPerMillion: 15 },
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
window: "day",
|
|
42
|
+
maxCost: 10.0,
|
|
43
|
+
pricing: { inputPerMillion: 3, outputPerMillion: 15 },
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
|
|
47
|
+
// Callback when approaching limit (0-1 percentage)
|
|
48
|
+
budgetWarningThreshold: 0.8,
|
|
49
|
+
onWarning: (usage) => {
|
|
50
|
+
console.warn(`Budget at ${(usage.percentage * 100).toFixed(0)}%`);
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Anti-Pattern #29: budgetWarningThreshold out of range
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// WRONG — threshold must be a 0-1 percentage
|
|
59
|
+
const budgetRunner = withBudget(baseRunner, {
|
|
60
|
+
budgetWarningThreshold: 80, // Not a percentage!
|
|
61
|
+
budgets: [{ window: "hour", maxCost: 1.0, pricing: { inputPerMillion: 3, outputPerMillion: 15 } }],
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// CORRECT — use a decimal between 0 and 1
|
|
65
|
+
const budgetRunner = withBudget(baseRunner, {
|
|
66
|
+
budgetWarningThreshold: 0.8, // 80%
|
|
67
|
+
budgets: [{ window: "hour", maxCost: 1.0, pricing: { inputPerMillion: 3, outputPerMillion: 15 } }],
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Retry Policies
|
|
72
|
+
|
|
73
|
+
Wrap a runner with automatic retry on transient failures:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { withRetry } from "@directive-run/ai";
|
|
77
|
+
|
|
78
|
+
const retryRunner = withRetry(baseRunner, {
|
|
79
|
+
maxRetries: 3,
|
|
80
|
+
backoff: "exponential", // "exponential" | "linear" | "none"
|
|
81
|
+
baseDelayMs: 100,
|
|
82
|
+
|
|
83
|
+
// Only retry specific errors
|
|
84
|
+
shouldRetry: (error) => {
|
|
85
|
+
return error.status === 429 || error.status >= 500;
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Fallback Chains
|
|
91
|
+
|
|
92
|
+
Automatically switch to a backup runner when the primary fails:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import { withFallback } from "@directive-run/ai";
|
|
96
|
+
import { createAnthropicRunner } from "@directive-run/ai/anthropic";
|
|
97
|
+
import { createOpenAIRunner } from "@directive-run/ai/openai";
|
|
98
|
+
|
|
99
|
+
const primary = createAnthropicRunner({ apiKey: process.env.ANTHROPIC_API_KEY });
|
|
100
|
+
const backup = createOpenAIRunner({ apiKey: process.env.OPENAI_API_KEY });
|
|
101
|
+
|
|
102
|
+
// Falls back to backup when primary throws
|
|
103
|
+
const resilientRunner = withFallback(primary, backup);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Circuit Breaker
|
|
107
|
+
|
|
108
|
+
Prevent repeated calls to a failing provider. Opens after N failures, resets after a timeout:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { createCircuitBreaker } from "@directive-run/ai";
|
|
112
|
+
|
|
113
|
+
const breaker = createCircuitBreaker({
|
|
114
|
+
failureThreshold: 3, // Open after 3 consecutive failures
|
|
115
|
+
resetTimeout: 30000, // Try again after 30s (half-open state)
|
|
116
|
+
halfOpenMaxAttempts: 1, // Allow 1 test request in half-open
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Use with a runner
|
|
120
|
+
const protectedRunner = breaker.wrap(baseRunner);
|
|
121
|
+
|
|
122
|
+
// Check state
|
|
123
|
+
console.log(breaker.state); // "closed" | "open" | "half-open"
|
|
124
|
+
breaker.reset(); // Force back to closed
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Anti-Pattern #28: Sharing a CircuitBreaker across unrelated agents
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
// WRONG — one failing agent opens the breaker for all agents
|
|
131
|
+
const sharedBreaker = createCircuitBreaker({ failureThreshold: 3, resetTimeout: 30000 });
|
|
132
|
+
const researchRunner = sharedBreaker.wrap(baseRunner);
|
|
133
|
+
const writerRunner = sharedBreaker.wrap(baseRunner); // Same breaker!
|
|
134
|
+
|
|
135
|
+
// CORRECT — each agent gets its own breaker instance
|
|
136
|
+
const researchBreaker = createCircuitBreaker({ failureThreshold: 3, resetTimeout: 30000 });
|
|
137
|
+
const writerBreaker = createCircuitBreaker({ failureThreshold: 3, resetTimeout: 30000 });
|
|
138
|
+
|
|
139
|
+
const researchRunner = researchBreaker.wrap(baseRunner);
|
|
140
|
+
const writerRunner = writerBreaker.wrap(baseRunner);
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Health Monitor
|
|
144
|
+
|
|
145
|
+
Monitor agent health across the system, track circuit breaker states, and report status:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { createHealthMonitor } from "@directive-run/ai";
|
|
149
|
+
|
|
150
|
+
const monitor = createHealthMonitor({
|
|
151
|
+
agents: {
|
|
152
|
+
researcher: { runner: researchRunner, circuitBreaker: researchBreaker },
|
|
153
|
+
writer: { runner: writerRunner, circuitBreaker: writerBreaker },
|
|
154
|
+
},
|
|
155
|
+
checkInterval: 60000, // Health check every 60s
|
|
156
|
+
|
|
157
|
+
onStatusChange: (agent, status) => {
|
|
158
|
+
console.log(`${agent}: ${status}`); // "healthy" | "degraded" | "unhealthy"
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
monitor.start();
|
|
163
|
+
const report = monitor.getReport();
|
|
164
|
+
monitor.stop();
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Semantic Cache
|
|
168
|
+
|
|
169
|
+
Cache LLM responses by semantic similarity to avoid redundant calls:
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
import { createSemanticCache } from "@directive-run/ai";
|
|
173
|
+
import { createOpenAIEmbedder } from "@directive-run/ai/openai";
|
|
174
|
+
|
|
175
|
+
const cache = createSemanticCache({
|
|
176
|
+
embedder: createOpenAIEmbedder({ apiKey: process.env.OPENAI_API_KEY }),
|
|
177
|
+
similarity: 0.98, // Minimum cosine similarity to count as a hit
|
|
178
|
+
maxSize: 1000, // Max cached entries (LRU eviction)
|
|
179
|
+
ttl: 3600000, // Cache entry TTL in ms (1 hour)
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Wrap a runner with caching
|
|
183
|
+
const cachedRunner = cache.wrap(baseRunner);
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Constraint-Driven Provider Routing
|
|
187
|
+
|
|
188
|
+
Route LLM calls to different providers based on runtime constraints:
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
import { createConstraintRouter } from "@directive-run/ai";
|
|
192
|
+
|
|
193
|
+
const router = createConstraintRouter({
|
|
194
|
+
providers: [
|
|
195
|
+
{ name: "anthropic", runner: anthropicRunner, costPerMillion: 3 },
|
|
196
|
+
{ name: "openai", runner: openaiRunner, costPerMillion: 5 },
|
|
197
|
+
{ name: "ollama", runner: ollamaRunner, costPerMillion: 0 },
|
|
198
|
+
],
|
|
199
|
+
constraints: [
|
|
200
|
+
// Use cheapest provider when budget is low
|
|
201
|
+
{ when: (context) => context.budgetRemaining < 1.0, prefer: "ollama" },
|
|
202
|
+
// Use best provider for high-priority tasks
|
|
203
|
+
{ when: (context) => context.priority === "high", prefer: "anthropic" },
|
|
204
|
+
],
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Combining Wrappers
|
|
209
|
+
|
|
210
|
+
Wrappers compose — apply them inside-out (innermost runs first):
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
import { withBudget, withRetry, withFallback } from "@directive-run/ai";
|
|
214
|
+
|
|
215
|
+
// Order: retry → budget → fallback
|
|
216
|
+
const resilientRunner = withFallback(
|
|
217
|
+
withBudget(
|
|
218
|
+
withRetry(primaryRunner, { maxRetries: 3, backoff: "exponential", baseDelayMs: 100 }),
|
|
219
|
+
{ budgets: [{ window: "hour", maxCost: 1.0, pricing: { inputPerMillion: 3, outputPerMillion: 15 } }] },
|
|
220
|
+
),
|
|
221
|
+
fallbackRunner,
|
|
222
|
+
);
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Quick Reference
|
|
226
|
+
|
|
227
|
+
| Utility | Purpose | Key Options |
|
|
228
|
+
|---|---|---|
|
|
229
|
+
| `withBudget` | Cost caps per time window | `budgets`, `maxCostPerCall` |
|
|
230
|
+
| `withRetry` | Retry transient failures | `maxRetries`, `backoff`, `shouldRetry` |
|
|
231
|
+
| `withFallback` | Switch to backup runner | primary, fallback runners |
|
|
232
|
+
| `createCircuitBreaker` | Stop calling failing providers | `failureThreshold`, `resetTimeout` |
|
|
233
|
+
| `createHealthMonitor` | Fleet health tracking | `agents`, `checkInterval` |
|
|
234
|
+
| `createSemanticCache` | Avoid redundant LLM calls | `similarity`, `maxSize`, `ttl` |
|
|
235
|
+
| `createConstraintRouter` | Dynamic provider selection | `providers`, `constraints` |
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# AI Communication
|
|
2
|
+
|
|
3
|
+
Cross-agent messaging, agent networks, shared state via derivations, scratchpad coordination, and handoff patterns.
|
|
4
|
+
|
|
5
|
+
## Decision Tree: "How do agents communicate?"
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
What kind of communication?
|
|
9
|
+
├── Fire-and-forget notification → bus.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
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Message Bus
|
|
26
|
+
|
|
27
|
+
The message bus enables structured communication between agents:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { createMessageBus } from "@directive-run/ai";
|
|
31
|
+
|
|
32
|
+
const bus = createMessageBus();
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Publishing Messages
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// Fire-and-forget notification
|
|
39
|
+
bus.publish({
|
|
40
|
+
type: "INFORM",
|
|
41
|
+
from: "researcher",
|
|
42
|
+
to: "writer",
|
|
43
|
+
content: "Found 5 relevant sources",
|
|
44
|
+
metadata: { sourceCount: 5, topics: ["AI", "ML"] },
|
|
45
|
+
});
|
|
46
|
+
|
|
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
|
|
56
|
+
|
|
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: "..." }
|
|
70
|
+
```
|
|
71
|
+
|
|
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
|
+
```
|
|
115
|
+
|
|
116
|
+
## Agent Network
|
|
117
|
+
|
|
118
|
+
Higher-level abstraction for capability-based agent discovery:
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { createAgentNetwork } from "@directive-run/ai";
|
|
122
|
+
|
|
123
|
+
const network = createAgentNetwork({
|
|
124
|
+
bus,
|
|
125
|
+
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
|
+
},
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Find agents by capability
|
|
139
|
+
const writers = network.findByCapability("draft");
|
|
140
|
+
// ["writer"]
|
|
141
|
+
|
|
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", {
|
|
147
|
+
claim: "GPT-4 has 1.8T parameters",
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Cross-Agent State via Derivations
|
|
152
|
+
|
|
153
|
+
Agents can read each other's facts and derivations through the shared system:
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
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
|
+
},
|
|
169
|
+
runner,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
orchestrator.start();
|
|
173
|
+
|
|
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;
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Scratchpad Coordination
|
|
183
|
+
|
|
184
|
+
The scratchpad is an ephemeral key-value store scoped to a single pattern execution. Tasks and agents in the same pattern share it:
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
// In a task — write to scratchpad
|
|
188
|
+
tasks: {
|
|
189
|
+
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;
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
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 });
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Handoff Patterns
|
|
211
|
+
|
|
212
|
+
### One-Time Delegation
|
|
213
|
+
|
|
214
|
+
```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 },
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// Agent B returns the result
|
|
225
|
+
bus.publish({
|
|
226
|
+
type: "DELEGATION_RESULT",
|
|
227
|
+
from: "researcher",
|
|
228
|
+
to: "coordinator",
|
|
229
|
+
correlationId: originalMessage.id,
|
|
230
|
+
content: "Research findings: ...",
|
|
231
|
+
metadata: { sourcesFound: 12 },
|
|
232
|
+
});
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Supervisor Reroute
|
|
236
|
+
|
|
237
|
+
In a supervisor pattern, the supervisor can reroute work mid-execution:
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
const managed = supervisor("editor", ["researcher", "writer"], {
|
|
241
|
+
onReroute: (from, to, reason) => {
|
|
242
|
+
console.log(`Rerouting from ${from} to ${to}: ${reason}`);
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Message Bus with Orchestrator
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
import { createMultiAgentOrchestrator, createMessageBus } from "@directive-run/ai";
|
|
251
|
+
|
|
252
|
+
const bus = createMessageBus();
|
|
253
|
+
|
|
254
|
+
const orchestrator = createMultiAgentOrchestrator({
|
|
255
|
+
agents: { researcher, writer },
|
|
256
|
+
runner,
|
|
257
|
+
bus, // Attach the message bus
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
orchestrator.start();
|
|
261
|
+
|
|
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
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Quick Reference
|
|
272
|
+
|
|
273
|
+
| API | Purpose | Key Options |
|
|
274
|
+
|---|---|---|
|
|
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 |
|