@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.
- package/.claude-plugin/plugin.json +6 -0
- package/LICENSE +26 -0
- package/README.md +113 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +66 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -0
- package/skills/README.md +9 -0
- package/skills/building-ai-agents/SKILL.md +414 -0
- package/skills/building-ai-agents/ai-adapters.md +257 -0
- package/skills/building-ai-agents/ai-agents-streaming.md +307 -0
- package/skills/building-ai-agents/ai-communication.md +304 -0
- package/skills/building-ai-agents/api-skeleton.md +5 -0
- package/skills/building-ai-agents/examples.md +849 -0
- package/skills/building-ai-orchestrators/SKILL.md +401 -0
- package/skills/building-ai-orchestrators/ai-multi-agent.md +375 -0
- package/skills/building-ai-orchestrators/ai-orchestrator.md +466 -0
- package/skills/building-ai-orchestrators/ai-tasks.md +268 -0
- package/skills/building-ai-orchestrators/api-skeleton.md +5 -0
- package/skills/building-ai-orchestrators/examples.md +1609 -0
- package/skills/building-directive-systems/SKILL.md +499 -0
- package/skills/building-directive-systems/api-skeleton.md +5 -0
- package/skills/building-directive-systems/examples.md +4149 -0
- package/skills/building-directive-systems/multi-module.md +327 -0
- package/skills/building-directive-systems/plugins.md +361 -0
- package/skills/building-directive-systems/react-adapter.md +376 -0
- package/skills/building-directive-systems/system-api.md +463 -0
- package/skills/getting-started-with-directive/SKILL.md +198 -0
- package/skills/getting-started-with-directive/api-skeleton.md +5 -0
- package/skills/getting-started-with-directive/core-patterns.md +240 -0
- package/skills/getting-started-with-directive/examples.md +87 -0
- package/skills/getting-started-with-directive/sitemap.md +202 -0
- package/skills/hardening-ai-systems/SKILL.md +377 -0
- package/skills/hardening-ai-systems/ai-budget-resilience.md +408 -0
- package/skills/hardening-ai-systems/ai-guardrails-memory.md +370 -0
- package/skills/hardening-ai-systems/ai-security.md +400 -0
- package/skills/hardening-ai-systems/api-skeleton.md +5 -0
- package/skills/hardening-ai-systems/examples.md +628 -0
- package/skills/migrating-to-directive/SKILL.md +249 -0
- package/skills/migrating-to-directive/anti-patterns.md +382 -0
- package/skills/migrating-to-directive/api-skeleton.md +5 -0
- package/skills/migrating-to-directive/core-patterns.md +240 -0
- package/skills/migrating-to-directive/examples.md +511 -0
- package/skills/migrating-to-directive/schema-types.md +273 -0
- package/skills/reviewing-directive-code/SKILL.md +232 -0
- package/skills/reviewing-directive-code/anti-patterns.md +382 -0
- package/skills/reviewing-directive-code/api-skeleton.md +5 -0
- package/skills/reviewing-directive-code/core-patterns.md +240 -0
- package/skills/reviewing-directive-code/examples.md +463 -0
- package/skills/reviewing-directive-code/naming.md +321 -0
- package/skills/scaffolding-directive-modules/SKILL.md +218 -0
- package/skills/scaffolding-directive-modules/api-skeleton.md +5 -0
- package/skills/scaffolding-directive-modules/core-patterns.md +240 -0
- package/skills/scaffolding-directive-modules/examples.md +1386 -0
- package/skills/scaffolding-directive-modules/naming.md +321 -0
- package/skills/scaffolding-directive-modules/schema-types.md +273 -0
- package/skills/testing-ai-systems/SKILL.md +398 -0
- package/skills/testing-ai-systems/ai-debug-observability.md +329 -0
- package/skills/testing-ai-systems/ai-mcp-rag.md +293 -0
- package/skills/testing-ai-systems/ai-testing-evals.md +485 -0
- package/skills/testing-ai-systems/api-skeleton.md +5 -0
- package/skills/testing-ai-systems/examples.md +1261 -0
- package/skills/testing-directive-code/SKILL.md +479 -0
- package/skills/testing-directive-code/api-skeleton.md +5 -0
- package/skills/testing-directive-code/examples.md +156 -0
- package/skills/testing-directive-code/history.md +351 -0
- package/skills/testing-directive-code/testing.md +412 -0
- package/skills/writing-directive-constraints/SKILL.md +486 -0
- package/skills/writing-directive-constraints/api-skeleton.md +5 -0
- package/skills/writing-directive-constraints/constraints.md +274 -0
- package/skills/writing-directive-constraints/error-boundaries.md +330 -0
- package/skills/writing-directive-constraints/examples.md +1361 -0
- package/skills/writing-directive-constraints/resolvers.md +367 -0
- package/skills/writing-directive-modules/SKILL.md +342 -0
- package/skills/writing-directive-modules/anti-patterns.md +382 -0
- package/skills/writing-directive-modules/api-skeleton.md +5 -0
- package/skills/writing-directive-modules/core-patterns.md +240 -0
- package/skills/writing-directive-modules/examples.md +1639 -0
- package/skills/writing-directive-modules/naming.md +321 -0
- package/skills/writing-directive-modules/schema-types.md +273 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
# AI budget + resilience
|
|
2
|
+
|
|
3
|
+
> Covers `@directive-run/ai` — `withBudget`, `withRetry`, `withFallback`, `createCircuitBreaker`, `createHealthMonitor`, `createSemanticCache`, `createConstraintRouter`.
|
|
4
|
+
|
|
5
|
+
Runner wrappers and supporting utilities for cost control, retry, fallback, circuit breaking, fleet health tracking, semantic caching, and constraint-driven provider routing. Compose these around any `AgentRunner` before passing it to an orchestrator.
|
|
6
|
+
|
|
7
|
+
## Decision tree
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
What failure mode are you guarding against?
|
|
11
|
+
├── Cost overruns → withBudget(runner, config)
|
|
12
|
+
├── Transient errors → withRetry(runner, config)
|
|
13
|
+
├── Provider outage → withFallback([primary, backup], config?)
|
|
14
|
+
├── Repeated cascading fails → createCircuitBreaker(config) + breaker.execute(fn)
|
|
15
|
+
├── Fleet-wide health → createHealthMonitor() + recordSuccess/Failure
|
|
16
|
+
├── Redundant LLM calls → createSemanticCache(config) + createSemanticCacheGuardrail
|
|
17
|
+
└── Dynamic provider pick → createConstraintRouter({ providers, constraints })
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## `withBudget(runner, config)` — cost caps
|
|
21
|
+
|
|
22
|
+
`BudgetConfig` controls per-call and per-window cost limits. The `onBudgetExceeded` callback fires before the wrapper throws `BudgetExceededError`. There is NO `budgetWarningThreshold` or `onWarning` option on this wrapper — for percentage-based warnings, use `onBudgetWarning` on `createAgentOrchestrator` instead.
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { withBudget } from "@directive-run/ai";
|
|
26
|
+
|
|
27
|
+
const pricing = { inputPerMillion: 3, outputPerMillion: 15 };
|
|
28
|
+
|
|
29
|
+
const budgetRunner = withBudget(baseRunner, {
|
|
30
|
+
maxCostPerCall: 0.10,
|
|
31
|
+
pricing, // required when maxCostPerCall is set
|
|
32
|
+
estimatedOutputMultiplier: 1.0, // for summarization use 0.3; for generation use 3.0
|
|
33
|
+
charsPerToken: 4,
|
|
34
|
+
budgets: [
|
|
35
|
+
{ window: "hour", maxCost: 5.00, pricing },
|
|
36
|
+
{ window: "day", maxCost: 50.00, pricing },
|
|
37
|
+
],
|
|
38
|
+
onBudgetExceeded: ({ estimated, remaining, window }) => {
|
|
39
|
+
console.warn(`[budget] ${window} would overshoot — est $${estimated.toFixed(4)}, remaining $${remaining.toFixed(4)}`);
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Catching the throw:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { BudgetExceededError } from "@directive-run/ai";
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
await budgetRunner(agent, prompt);
|
|
51
|
+
} catch (err) {
|
|
52
|
+
if (err instanceof BudgetExceededError) {
|
|
53
|
+
console.log(err.window, err.estimated, err.remaining);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## `withRetry(runner, config)` — transient-error retry
|
|
59
|
+
|
|
60
|
+
Exponential backoff with jitter is hard-coded; `backoff: "linear"` / `"none"` do NOT exist. The retry decision goes through built-in HTTP-status checks (non-retryable: 400 / 401 / 403 / 404 / 422) then your `isRetryable` predicate.
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { withRetry } from "@directive-run/ai";
|
|
64
|
+
|
|
65
|
+
const retryRunner = withRetry(baseRunner, {
|
|
66
|
+
maxRetries: 3, // default 3
|
|
67
|
+
baseDelayMs: 1000, // default 1000
|
|
68
|
+
maxDelayMs: 30_000, // default 30000
|
|
69
|
+
isRetryable: (error) => {
|
|
70
|
+
const msg = error.message.toLowerCase();
|
|
71
|
+
return msg.includes("rate_limit") || msg.includes("503") || msg.includes("504");
|
|
72
|
+
},
|
|
73
|
+
onRetry: (attempt, error, delayMs) => {
|
|
74
|
+
console.log(`retry ${attempt} in ${delayMs}ms — ${error.message}`);
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Catching the exhausted throw:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { RetryExhaustedError } from "@directive-run/ai";
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
await retryRunner(agent, prompt);
|
|
86
|
+
} catch (err) {
|
|
87
|
+
if (err instanceof RetryExhaustedError) {
|
|
88
|
+
console.log(`gave up after ${err.retryCount} retries; last: ${err.lastError.message}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## `withFallback(runners[], config?)` — provider chain
|
|
94
|
+
|
|
95
|
+
**`withFallback` takes an ARRAY of runners as positional arg 1**, then an optional config. The chain tries each runner in order; the first success wins. When all fail, `AllProvidersFailedError` is thrown carrying all per-runner errors.
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
import { withFallback, withRetry } from "@directive-run/ai";
|
|
99
|
+
import { createAnthropicRunner } from "@directive-run/ai/anthropic";
|
|
100
|
+
import { createOpenAIRunner } from "@directive-run/ai/openai";
|
|
101
|
+
import { createOllamaRunner } from "@directive-run/ai/ollama";
|
|
102
|
+
|
|
103
|
+
const resilient = withFallback(
|
|
104
|
+
[
|
|
105
|
+
withRetry(createAnthropicRunner({ apiKey: process.env.ANTHROPIC_API_KEY! }), { maxRetries: 2 }),
|
|
106
|
+
withRetry(createOpenAIRunner({ apiKey: process.env.OPENAI_API_KEY! }), { maxRetries: 2 }),
|
|
107
|
+
createOllamaRunner({ baseUrl: "http://localhost:11434" }),
|
|
108
|
+
],
|
|
109
|
+
{
|
|
110
|
+
shouldFallback: (error) => !error.message.includes("invalid_api_key"),
|
|
111
|
+
onFallback: (fromIndex, toIndex, error) => {
|
|
112
|
+
console.log(`falling back from runner ${fromIndex} to ${toIndex}: ${error.message}`);
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
);
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Catching all-failed:
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { AllProvidersFailedError } from "@directive-run/ai";
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
await resilient(agent, prompt);
|
|
125
|
+
} catch (err) {
|
|
126
|
+
if (err instanceof AllProvidersFailedError) {
|
|
127
|
+
err.errors.forEach((e, i) => console.log(`[${i}] ${e.message}`));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## `createCircuitBreaker(config)` — failure isolation
|
|
133
|
+
|
|
134
|
+
The circuit breaker is from `@directive-run/core` (it's not AI-specific). Use `breaker.execute(fn)` to run an arbitrary async closure inside the breaker; there is NO `breaker.wrap(runner)` shortcut.
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
import { createCircuitBreaker, CircuitBreakerOpenError } from "@directive-run/core";
|
|
138
|
+
|
|
139
|
+
const breaker = createCircuitBreaker({
|
|
140
|
+
name: "openai",
|
|
141
|
+
failureThreshold: 5, // default 5
|
|
142
|
+
recoveryTimeMs: 30_000, // default 30000
|
|
143
|
+
halfOpenMaxRequests: 3, // default 3 — concurrent test requests when half-open
|
|
144
|
+
failureWindowMs: 60_000, // default 60000
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Use with a runner: wrap the call inside execute()
|
|
148
|
+
async function callAgent(agent: AgentLike, input: string) {
|
|
149
|
+
return await breaker.execute(() => baseRunner(agent, input));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Inspect state
|
|
153
|
+
console.log(breaker.getState()); // "CLOSED" | "OPEN" | "HALF_OPEN"
|
|
154
|
+
console.log(breaker.getStats()); // { successCount, failureCount, lastFailureTime, ... }
|
|
155
|
+
console.log(breaker.isAllowed()); // boolean — would a request be allowed now?
|
|
156
|
+
|
|
157
|
+
breaker.reset(); // force back to CLOSED, clear stats
|
|
158
|
+
breaker.forceState("OPEN"); // testing helper
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Catching the OPEN throw:
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
try {
|
|
165
|
+
await breaker.execute(() => baseRunner(agent, prompt));
|
|
166
|
+
} catch (err) {
|
|
167
|
+
if (err instanceof CircuitBreakerOpenError) {
|
|
168
|
+
console.log(err.code); // "CIRCUIT_OPEN"
|
|
169
|
+
console.log(err.state); // "OPEN" | "HALF_OPEN"
|
|
170
|
+
console.log(err.retryAfterMs); // wait this long before next attempt
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Don't share a breaker across unrelated runners
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
// WRONG — one failing runner trips the breaker for all of them
|
|
179
|
+
const shared = createCircuitBreaker({ name: "shared" });
|
|
180
|
+
async function research(agent, input) { return shared.execute(() => researchRunner(agent, input)); }
|
|
181
|
+
async function write(agent, input) { return shared.execute(() => writerRunner(agent, input)); }
|
|
182
|
+
|
|
183
|
+
// CORRECT — one breaker per failure domain
|
|
184
|
+
const researchBreaker = createCircuitBreaker({ name: "anthropic-research" });
|
|
185
|
+
const writerBreaker = createCircuitBreaker({ name: "openai-writer" });
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## `createHealthMonitor(config?)` — fleet health metrics
|
|
189
|
+
|
|
190
|
+
`createHealthMonitor` is a metrics tracker: you record successes/failures into it; it computes per-agent health scores you can use to drive routing or observability. It is NOT an auto-polling daemon. There is no `monitor.start()`, no `monitor.stop()`, no `monitor.getReport()`, no `agents:` config, no `checkInterval`, and no `onStatusChange` callback — those don't exist.
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
import { createHealthMonitor } from "@directive-run/ai";
|
|
194
|
+
|
|
195
|
+
const monitor = createHealthMonitor({
|
|
196
|
+
windowMs: 60_000, // rolling window for metrics (default 60s)
|
|
197
|
+
maxNormalLatencyMs: 5000, // above this counts as "slow" for the latency component
|
|
198
|
+
weights: { // weights for the composite health score
|
|
199
|
+
successRate: 0.5,
|
|
200
|
+
latency: 0.3,
|
|
201
|
+
circuitState: 0.2,
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Record outcomes from your runner calls
|
|
206
|
+
const start = Date.now();
|
|
207
|
+
try {
|
|
208
|
+
const result = await baseRunner(agent, input);
|
|
209
|
+
monitor.recordSuccess("researcher", Date.now() - start);
|
|
210
|
+
} catch (err) {
|
|
211
|
+
monitor.recordFailure("researcher", Date.now() - start, err as Error);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// If you also use a circuit breaker, mirror its state into the monitor
|
|
215
|
+
monitor.updateCircuitState("researcher", breaker.getState() === "OPEN" ? "open" : "closed");
|
|
216
|
+
|
|
217
|
+
// Read
|
|
218
|
+
console.log(monitor.getHealthScore("researcher")); // 0-100 (50 when no data)
|
|
219
|
+
console.log(monitor.getMetrics("researcher")); // { successRate, avgLatency, ... }
|
|
220
|
+
console.log(monitor.getAllMetrics()); // Record<agentId, metrics>
|
|
221
|
+
|
|
222
|
+
monitor.reset();
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## `createSemanticCache(config)` — embedding-based response cache
|
|
226
|
+
|
|
227
|
+
Semantic caching avoids redundant LLM calls when a new input is close enough to one already seen. Wire it into an orchestrator via the cache guardrail factory — there is NO `cache.wrap(runner)` method.
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
import { createSemanticCache, createSemanticCacheGuardrail, type EmbedderFn } from "@directive-run/ai";
|
|
231
|
+
|
|
232
|
+
// You supply the embedder — no createOpenAIEmbedder / createAnthropicEmbedder exists.
|
|
233
|
+
// `EmbedderFn = (text: string) => Promise<number[]>`
|
|
234
|
+
const embedder: EmbedderFn = async (text) => {
|
|
235
|
+
// call your embedding model (OpenAI, Voyage, local model, …)
|
|
236
|
+
return await myEmbedAPI(text);
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const cache = createSemanticCache({
|
|
240
|
+
embedder,
|
|
241
|
+
similarityThreshold: 0.95,
|
|
242
|
+
maxCacheSize: 1000,
|
|
243
|
+
ttlMs: 60 * 60 * 1000, // 1 hour
|
|
244
|
+
onHit: (entry, sim) => console.log(`cache hit @ ${(sim * 100).toFixed(1)}%`),
|
|
245
|
+
onMiss: (query) => console.log(`miss: ${query.slice(0, 40)}…`),
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const cacheGuardrail = createSemanticCacheGuardrail({ cache });
|
|
249
|
+
|
|
250
|
+
const orchestrator = createAgentOrchestrator({
|
|
251
|
+
runner: baseRunner,
|
|
252
|
+
guardrails: {
|
|
253
|
+
input: [cacheGuardrail], // short-circuits with the cached response when a hit fires
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
For testing, a `createTestEmbedder` is exported from `@directive-run/ai` that produces deterministic embeddings without API calls.
|
|
259
|
+
|
|
260
|
+
## `createConstraintRouter(config)` — constraint-driven provider selection
|
|
261
|
+
|
|
262
|
+
Routes each runner call to a provider based on runtime context.
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
import { createConstraintRouter } from "@directive-run/ai";
|
|
266
|
+
|
|
267
|
+
const router = createConstraintRouter({
|
|
268
|
+
providers: [
|
|
269
|
+
{ name: "ollama", runner: ollamaRunner, costPerMillion: 0 },
|
|
270
|
+
{ name: "anthropic", runner: anthropicRunner, costPerMillion: 3 },
|
|
271
|
+
{ name: "openai", runner: openaiRunner, costPerMillion: 5 },
|
|
272
|
+
],
|
|
273
|
+
constraints: [
|
|
274
|
+
{ when: (ctx) => ctx.budgetRemaining < 1.0, prefer: "ollama" },
|
|
275
|
+
{ when: (ctx) => ctx.priority === "high", prefer: "anthropic" },
|
|
276
|
+
],
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
`router` returns an `AgentRunner` you pass directly to an orchestrator. The router's context fields depend on your config; check the `ConstraintRouterConfig` type for the full surface.
|
|
281
|
+
|
|
282
|
+
## Composing wrappers
|
|
283
|
+
|
|
284
|
+
Wrappers are pure functions — compose them inside-out (innermost runs first per call).
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
import { withBudget, withRetry, withFallback } from "@directive-run/ai";
|
|
288
|
+
|
|
289
|
+
// Per call: retry first → budget cap on retried call → fallback to backup if all retries fail
|
|
290
|
+
const resilient = withFallback(
|
|
291
|
+
[
|
|
292
|
+
withBudget(
|
|
293
|
+
withRetry(primaryRunner, { maxRetries: 3 }),
|
|
294
|
+
{
|
|
295
|
+
pricing: { inputPerMillion: 3, outputPerMillion: 15 },
|
|
296
|
+
budgets: [{ window: "hour", maxCost: 5.0, pricing: { inputPerMillion: 3, outputPerMillion: 15 } }],
|
|
297
|
+
},
|
|
298
|
+
),
|
|
299
|
+
fallbackRunner,
|
|
300
|
+
],
|
|
301
|
+
);
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## Anti-patterns
|
|
305
|
+
|
|
306
|
+
### `withFallback(primary, backup)` (two positional args)
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
// WRONG — withFallback takes a single ARRAY of runners, then an optional config
|
|
310
|
+
withFallback(primary, backup)
|
|
311
|
+
|
|
312
|
+
// CORRECT
|
|
313
|
+
withFallback([primary, backup], { onFallback: (from, to, err) => {} })
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### `withRetry` with `backoff: "linear" | "none"` or `shouldRetry`
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
// WRONG — backoff is always exponential+jitter (not configurable); shouldRetry is named isRetryable
|
|
320
|
+
withRetry(runner, { maxRetries: 3, backoff: "exponential", shouldRetry: (e) => true })
|
|
321
|
+
|
|
322
|
+
// CORRECT
|
|
323
|
+
withRetry(runner, { maxRetries: 3, baseDelayMs: 500, isRetryable: (e) => /5\d{2}/.test(e.message) })
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### `createCircuitBreaker({ resetTimeout, halfOpenMaxAttempts })`
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
// WRONG — option names from a different library
|
|
330
|
+
createCircuitBreaker({ resetTimeout: 30_000, halfOpenMaxAttempts: 1 })
|
|
331
|
+
|
|
332
|
+
// CORRECT
|
|
333
|
+
createCircuitBreaker({ recoveryTimeMs: 30_000, halfOpenMaxRequests: 1 })
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### `breaker.wrap(runner)` / `breaker.state`
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
// WRONG — these don't exist on CircuitBreaker
|
|
340
|
+
const protected = breaker.wrap(baseRunner);
|
|
341
|
+
console.log(breaker.state);
|
|
342
|
+
|
|
343
|
+
// CORRECT
|
|
344
|
+
const result = await breaker.execute(() => baseRunner(agent, input));
|
|
345
|
+
console.log(breaker.getState());
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### `createHealthMonitor({ agents, checkInterval, onStatusChange })` / `monitor.start()` / `monitor.getReport()`
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
// WRONG — none of these exist
|
|
352
|
+
const monitor = createHealthMonitor({
|
|
353
|
+
agents: { researcher: { runner, circuitBreaker } },
|
|
354
|
+
checkInterval: 60_000,
|
|
355
|
+
onStatusChange: (id, s) => {},
|
|
356
|
+
});
|
|
357
|
+
monitor.start();
|
|
358
|
+
const report = monitor.getReport();
|
|
359
|
+
|
|
360
|
+
// CORRECT — push metrics in from your call sites
|
|
361
|
+
const monitor = createHealthMonitor({ windowMs: 60_000 });
|
|
362
|
+
monitor.recordSuccess("researcher", latencyMs);
|
|
363
|
+
monitor.recordFailure("researcher", latencyMs, error);
|
|
364
|
+
const score = monitor.getHealthScore("researcher");
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### `createOpenAIEmbedder` / `createAnthropicEmbedder`
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
// WRONG — no embedder factories ship in @directive-run/ai
|
|
371
|
+
import { createOpenAIEmbedder } from "@directive-run/ai/openai";
|
|
372
|
+
|
|
373
|
+
// CORRECT — pass your own EmbedderFn
|
|
374
|
+
const embedder: EmbedderFn = async (text) => {
|
|
375
|
+
const res = await myEmbedAPI.embed(text);
|
|
376
|
+
return res.embedding;
|
|
377
|
+
};
|
|
378
|
+
const cache = createSemanticCache({ embedder, similarityThreshold: 0.95 });
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### `cache.wrap(runner)`
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
// WRONG — SemanticCache doesn't have a wrap() method
|
|
385
|
+
const cachedRunner = cache.wrap(baseRunner);
|
|
386
|
+
|
|
387
|
+
// CORRECT — wire it in as a guardrail
|
|
388
|
+
const cacheGuardrail = createSemanticCacheGuardrail({ cache });
|
|
389
|
+
const orchestrator = createAgentOrchestrator({ runner: baseRunner, guardrails: { input: [cacheGuardrail] } });
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
## Quick reference
|
|
393
|
+
|
|
394
|
+
| Utility | Shape | Throws |
|
|
395
|
+
|---|---|---|
|
|
396
|
+
| `withBudget(runner, BudgetConfig)` | wraps an AgentRunner | `BudgetExceededError` |
|
|
397
|
+
| `withRetry(runner, RetryConfig)` | wraps an AgentRunner | `RetryExhaustedError` |
|
|
398
|
+
| `withFallback(runners[], config?)` | wraps an array of AgentRunners | `AllProvidersFailedError` |
|
|
399
|
+
| `createCircuitBreaker(config)` | returns `CircuitBreaker` with `execute(fn)` | `CircuitBreakerOpenError` |
|
|
400
|
+
| `createHealthMonitor(config?)` | returns `HealthMonitor`; push outcomes in via `recordSuccess/Failure` | — |
|
|
401
|
+
| `createSemanticCache(config)` | returns `SemanticCache`; use via `createSemanticCacheGuardrail({ cache })` | — |
|
|
402
|
+
| `createConstraintRouter(config)` | returns an `AgentRunner` that picks providers at call time | — |
|
|
403
|
+
|
|
404
|
+
## See also
|
|
405
|
+
|
|
406
|
+
- [`ai-orchestrator.md`](./ai-orchestrator.md) — `circuitBreaker` / `selfHealing` / `agentRetry` / `maxTokenBudget` options on `createAgentOrchestrator`
|
|
407
|
+
- [`ai-guardrails-memory.md`](./ai-guardrails-memory.md) — `createSemanticCacheGuardrail({ cache })` wires this file's `SemanticCache` into the orchestrator
|
|
408
|
+
- [`ai-debug-observability.md`](./ai-debug-observability.md) — recording retry storms, circuit-breaker state changes, and budget warnings in the debug timeline
|