@directive-run/knowledge 1.14.0 → 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 (43) hide show
  1. package/README.md +50 -15
  2. package/ai/ai-adapters.md +7 -0
  3. package/ai/ai-agents-streaming.md +187 -149
  4. package/ai/ai-budget-resilience.md +305 -132
  5. package/ai/ai-communication.md +220 -197
  6. package/ai/ai-debug-observability.md +259 -173
  7. package/ai/ai-guardrails-memory.md +191 -153
  8. package/ai/ai-mcp-rag.md +204 -199
  9. package/ai/ai-multi-agent.md +254 -153
  10. package/ai/ai-orchestrator.md +353 -114
  11. package/ai/ai-security.md +287 -180
  12. package/ai/ai-tasks.md +8 -1
  13. package/ai/ai-testing-evals.md +363 -256
  14. package/core/anti-patterns.md +18 -2
  15. package/core/constraints.md +13 -2
  16. package/core/core-patterns.md +12 -0
  17. package/core/error-boundaries.md +8 -0
  18. package/core/history.md +7 -0
  19. package/core/multi-module.md +15 -3
  20. package/core/naming.md +128 -90
  21. package/core/plugins.md +7 -0
  22. package/core/react-adapter.md +256 -174
  23. package/core/resolvers.md +10 -0
  24. package/core/schema-types.md +8 -0
  25. package/core/system-api.md +10 -0
  26. package/core/testing.md +257 -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 +17 -11
  42. package/examples/debounce-constraints.ts +0 -95
  43. package/examples/multi-module.ts +0 -58
@@ -1,215 +1,215 @@
1
- # AI Guardrails and Memory
1
+ # AI guardrails and memory
2
2
 
3
- Built-in guardrails validate/transform input and output. Memory strategies manage conversation history with configurable summarization.
3
+ > Covers `@directive-run/ai/guardrails` and `@directive-run/ai` input/output/toolCall guardrails (PII, prompt-injection, length, schema, content filter), memory strategies, summarizers.
4
4
 
5
- ## Decision Tree: "Which guardrail do I need?"
5
+ Guardrails validate and transform input, output, and tool calls. Memory strategies manage conversation history with configurable summarization. Both plug into `createAgentOrchestrator` and `createMultiAgentOrchestrator`.
6
+
7
+ Import guardrail factories from the subpath barrel — the main `@directive-run/ai` re-exports them with `@deprecated` notices for v2.
8
+
9
+ ```typescript
10
+ import {
11
+ createPIIGuardrail,
12
+ createModerationGuardrail,
13
+ createRateLimitGuardrail,
14
+ createToolGuardrail,
15
+ createOutputSchemaGuardrail,
16
+ createOutputTypeGuardrail,
17
+ createLengthGuardrail,
18
+ createContentFilterGuardrail,
19
+ } from "@directive-run/ai/guardrails";
20
+ ```
21
+
22
+ ## Decision tree
6
23
 
7
24
  ```
8
25
  What are you guarding against?
9
- ├── PII in input/output → createPIIGuardrail()
26
+ ├── PII in input → createPIIGuardrail()
10
27
  ├── Harmful content → createModerationGuardrail()
11
28
  ├── Rate limits → createRateLimitGuardrail()
12
- ├── Unauthorized tool use → createToolGuardrail()
13
- ├── Output format validation → createOutputSchemaGuardrail()
14
- ├── Output type checking → createOutputTypeGuardrail()
15
- ├── Response length → createLengthGuardrail()
16
- └── Banned words/patterns → createContentFilterGuardrail()
29
+ ├── Unauthorized tool calls → createToolGuardrail()
30
+ ├── Output schema mismatch → createOutputSchemaGuardrail()
31
+ ├── Output type / shape → createOutputTypeGuardrail()
32
+ ├── Response length → createLengthGuardrail()
33
+ └── Banned words/patterns → createContentFilterGuardrail()
17
34
  ```
18
35
 
19
- ## GuardrailResult Shape
36
+ ## `GuardrailResult` shape
20
37
 
21
38
  Every guardrail returns this shape:
22
39
 
23
40
  ```typescript
24
41
  interface GuardrailResult {
25
- // Did the input/output pass?
26
42
  passed: boolean;
27
-
28
- // Why it failed (when passed: false)
29
- reason?: string;
30
-
31
- // Modified data – guardrail can transform the input/output
32
- transformed?: unknown;
43
+ reason?: string; // populated when passed: false
44
+ transformed?: unknown; // when set, replaces the original value downstream
33
45
  }
34
46
  ```
35
47
 
36
- When `transformed` is set, the modified value replaces the original for downstream processing.
48
+ When `transformed` is set, the modified value replaces the original for downstream processing — that's how redaction and sanitization work.
37
49
 
38
- ## Built-In Guardrails
50
+ ## Built-in guardrails
39
51
 
40
- ### PII Detection and Redaction
52
+ ### PII detection + redaction
41
53
 
42
54
  ```typescript
43
- import { createPIIGuardrail } from "@directive-run/ai";
44
-
45
55
  const piiGuardrail = createPIIGuardrail({
46
- // Additional regex patterns beyond defaults
47
- patterns: [/CUSTOM-\d{8}/g],
48
-
49
- // Redact instead of blocking (default: false)
50
- redact: true,
51
-
52
- // Replacement string (default: "[REDACTED]")
53
- redactReplacement: "***",
56
+ patterns: [/CUSTOM-\d{8}/g], // extra regex patterns beyond defaults (SSN / credit card / email)
57
+ redact: true, // redact in place instead of blocking (default: false)
58
+ redactReplacement: "***", // string to substitute (default: "[REDACTED]")
54
59
  });
55
60
  ```
56
61
 
57
- ### Content Moderation
62
+ ### Content moderation (user check function)
58
63
 
59
64
  ```typescript
60
- import { createModerationGuardrail } from "@directive-run/ai";
61
-
62
- const moderationGuardrail = createModerationGuardrail({
63
- // Custom check function – return true if content is safe
64
- checkFn: async (content) => {
65
- const result = await moderationAPI.check(content);
66
-
67
- return result.safe;
65
+ const moderation = createModerationGuardrail({
66
+ checkFn: async (text) => {
67
+ const result = await moderationAPI.check(text);
68
+ return result.flagged; // return TRUE when content should be flagged/blocked
68
69
  },
69
-
70
- // Custom rejection message
71
- message: "Content flagged by moderation",
70
+ message: "Content flagged by moderation", // optional override
72
71
  });
73
72
  ```
74
73
 
75
- ### Rate Limiting
74
+ The check function may be sync or async. Returning `true` causes the guardrail to block.
76
75
 
77
- ```typescript
78
- import { createRateLimitGuardrail } from "@directive-run/ai";
76
+ ### Rate limiting (sliding window)
79
77
 
80
- const rateLimitGuardrail = createRateLimitGuardrail({
81
- maxTokensPerMinute: 50000,
82
- maxRequestsPerMinute: 10,
78
+ ```typescript
79
+ const rateLimit = createRateLimitGuardrail({
80
+ maxTokensPerMinute: 50_000, // default 100_000
81
+ maxRequestsPerMinute: 30, // default 60
83
82
  });
83
+
84
+ // Test helper — included on the returned guardrail
85
+ rateLimit.reset();
84
86
  ```
85
87
 
86
- ### Tool Allowlist
88
+ ### Tool allow/deny lists
87
89
 
88
90
  ```typescript
89
- import { createToolGuardrail } from "@directive-run/ai";
90
-
91
- const toolGuardrail = createToolGuardrail({
92
- allowedTools: ["search", "calculator", "readFile"],
93
- // Any tool not in this list is blocked
91
+ const tools = createToolGuardrail({
92
+ allowlist: ["search", "calculator", "readFile"], // only these tools are allowed
93
+ denylist: ["dangerous-tool"], // any of these are blocked
94
+ caseSensitive: false, // default false
94
95
  });
95
96
  ```
96
97
 
97
- ### Output Schema Validation
98
+ Use `allowlist` OR `denylist` (or both). A tool not on the allowlist is blocked; a tool on the denylist is blocked.
99
+
100
+ ### Output schema validation
101
+
102
+ The validator is a function — Directive does NOT take a raw JSON schema. Use any validation library that has a `safeParse`-style API; pass an adapter.
98
103
 
99
104
  ```typescript
100
- import { createOutputSchemaGuardrail } from "@directive-run/ai";
105
+ import { z } from "zod";
101
106
 
102
107
  const schemaGuardrail = createOutputSchemaGuardrail({
103
- schema: {
104
- type: "object",
105
- properties: {
106
- title: { type: "string" },
107
- score: { type: "number", minimum: 0, maximum: 100 },
108
- },
109
- required: ["title", "score"],
110
- },
108
+ validate: (value) => {
109
+ const result = z.object({ title: z.string(), score: z.number() }).safeParse(value);
111
110
 
112
- // Retry with schema feedback if validation fails (default: 0)
113
- retries: 2,
111
+ return {
112
+ valid: result.success,
113
+ errors: result.success ? undefined : result.error.issues.map((i) => i.message),
114
+ };
115
+ },
116
+ errorPrefix: "Output schema validation failed", // default — prepended to error.reason
114
117
  });
115
118
  ```
116
119
 
117
- ### Output Type Guard
120
+ For automatic schema-retry on the agent's behalf, configure `outputSchema` + `maxSchemaRetries` on the orchestrator instead (see `ai-orchestrator.md` → Structured output).
118
121
 
119
- ```typescript
120
- import { createOutputTypeGuardrail } from "@directive-run/ai";
122
+ ### Output type guard (no schema library needed)
121
123
 
122
- const typeGuardrail = createOutputTypeGuardrail({
123
- type: "object", // "string" | "number" | "boolean" | "object" | "array"
124
+ ```typescript
125
+ const typeGuard = createOutputTypeGuardrail({
126
+ type: "object", // "string" | "number" | "boolean" | "object" | "array"
127
+ requiredFields: ["id", "name"], // object keys that must exist
128
+ minLength: 1, // for arrays
129
+ maxLength: 100, // for arrays
130
+ minStringLength: 1, // for strings
131
+ maxStringLength: 5000, // for strings
124
132
  });
125
133
  ```
126
134
 
127
- ### Length Constraints
135
+ ### Length constraints
128
136
 
129
137
  ```typescript
130
- import { createLengthGuardrail } from "@directive-run/ai";
131
-
132
138
  const lengthGuardrail = createLengthGuardrail({
133
- minChars: 100,
134
- maxChars: 5000,
135
- minTokens: 50,
136
- maxTokens: 1000,
139
+ maxCharacters: 5000,
140
+ maxTokens: 1200,
141
+ estimateTokens: (text) => Math.ceil(text.length / 4), // default: chars / 4
137
142
  });
138
143
  ```
139
144
 
140
- ### Content Filter
145
+ There is NO `minChars`, `maxChars`, or `minTokens` option — only `maxCharacters` (note the spelling) and `maxTokens`.
141
146
 
142
- ```typescript
143
- import { createContentFilterGuardrail } from "@directive-run/ai";
147
+ ### Content filter (banned patterns)
144
148
 
149
+ ```typescript
145
150
  const contentFilter = createContentFilterGuardrail({
146
- patterns: [/badword/i, /sensitive-term/gi],
147
-
148
- // "block" (default) or "redact"
149
- action: "redact",
150
-
151
- // Replacement for redact mode
152
- replacement: "[FILTERED]",
151
+ blockedPatterns: [/\bpassword\b/i, /\bsecret\b/i, "internal-only"],
152
+ caseSensitive: false,
153
153
  });
154
154
  ```
155
155
 
156
- ## Applying Guardrails
156
+ Strings are escaped and compiled to RegExp; RegExp instances pass through. There is NO `action: "redact"` mode — this guardrail blocks only. For redaction, use `createPIIGuardrail({ redact: true, patterns: [...] })`.
157
+
158
+ ## Applying guardrails
157
159
 
158
160
  ```typescript
159
161
  const orchestrator = createAgentOrchestrator({
160
162
  runner,
161
163
  guardrails: {
162
- // Run before the agent receives the prompt
163
- input: [piiGuardrail, rateLimitGuardrail],
164
-
165
- // Run after the agent produces output
166
- output: [lengthGuardrail, schemaGuardrail, contentFilter],
164
+ input: [piiGuardrail, rateLimit],
165
+ output: [lengthGuardrail, schemaGuardrail, contentFilter],
166
+ toolCall: [tools],
167
167
  },
168
168
  });
169
169
  ```
170
170
 
171
- ## Anti-Pattern #25: Catching Error Instead of GuardrailError
171
+ ## Catching `GuardrailError`
172
172
 
173
173
  ```typescript
174
- // WRONG loses guardrail-specific metadata
175
- try {
176
- const result = await orchestrator.run(agent, prompt);
177
- } catch (error) {
178
- if (error instanceof Error) {
179
- console.log(error.message); // No guardrail context
180
- }
181
- }
182
-
183
- // CORRECT – catch GuardrailError for full context
184
- import { GuardrailError } from "@directive-run/ai";
174
+ import { GuardrailError, isGuardrailError } from "@directive-run/ai";
185
175
 
186
176
  try {
187
- const result = await orchestrator.run(agent, prompt);
177
+ await orchestrator.run(agent, prompt);
188
178
  } catch (error) {
189
- if (error instanceof GuardrailError) {
190
- console.log(error.guardrailName); // "pii-detection"
191
- console.log(error.errorCode); // "GUARDRAIL_INPUT_BLOCKED"
192
- console.log(error.reason); // "PII detected in input"
179
+ if (isGuardrailError(error)) {
180
+ console.log(error.code); // "INPUT_GUARDRAIL_FAILED" | "OUTPUT_GUARDRAIL_FAILED" | "TOOL_CALL_GUARDRAIL_FAILED" | …
181
+ console.log(error.guardrailName); // "pii-detection"
182
+ console.log(error.guardrailType); // "input" | "output" | "toolCall"
183
+ console.log(error.userMessage); // safe-to-show user-facing message
184
+ console.log(error.agentName); // which agent triggered it
185
+ // error.input and error.data are non-enumerable to prevent accidental log leakage
193
186
  }
194
187
  }
195
188
  ```
196
189
 
197
- ---
190
+ There is no `errorCode` field — it's `code`. There is no `reason` field on `GuardrailError` — the rejection reason from the guardrail's result becomes the error's `message` (or `userMessage` for safe display).
198
191
 
199
- ## Memory Strategies
192
+ `GuardrailErrorCode` union:
193
+ - `INPUT_GUARDRAIL_FAILED` / `OUTPUT_GUARDRAIL_FAILED` / `TOOL_CALL_GUARDRAIL_FAILED`
194
+ - `APPROVAL_REJECTED`
195
+ - `BUDGET_EXCEEDED`
196
+ - `RATE_LIMIT_EXCEEDED`
197
+ - `AGENT_ERROR`
200
198
 
201
- Memory strategies control how conversation history is managed when it grows too large.
199
+ ---
202
200
 
203
- ## Decision Tree: "Which memory strategy?"
201
+ ## Memory strategies
202
+
203
+ Memory strategies control how conversation history is trimmed as it grows.
204
204
 
205
205
  ```
206
206
  How should history be trimmed?
207
207
  ├── Keep N most recent messages → createSlidingWindowStrategy()
208
208
  ├── Keep within token budget → createTokenBasedStrategy()
209
- └── Both constraints → createHybridStrategy()
209
+ └── Both at once → createHybridStrategy()
210
210
  ```
211
211
 
212
- ### Sliding Window
212
+ ### Sliding window (message count)
213
213
 
214
214
  ```typescript
215
215
  import { createAgentMemory, createSlidingWindowStrategy } from "@directive-run/ai";
@@ -217,17 +217,15 @@ import { createAgentMemory, createSlidingWindowStrategy } from "@directive-run/a
217
217
  const memory = createAgentMemory({
218
218
  strategy: createSlidingWindowStrategy({
219
219
  maxMessages: 50,
220
-
221
- // Always keep the N most recent (default: 5)
222
- preserveRecentCount: 10,
220
+ preserveRecentCount: 10, // always keep the N most recent (default 5)
223
221
  }),
224
222
  });
225
223
  ```
226
224
 
227
- ### Token-Based
225
+ ### Token-based
228
226
 
229
227
  ```typescript
230
- import { createAgentMemory, createTokenBasedStrategy } from "@directive-run/ai";
228
+ import { createTokenBasedStrategy } from "@directive-run/ai";
231
229
 
232
230
  const memory = createAgentMemory({
233
231
  strategy: createTokenBasedStrategy({
@@ -237,57 +235,54 @@ const memory = createAgentMemory({
237
235
  });
238
236
  ```
239
237
 
240
- ### Hybrid (Both Constraints)
238
+ ### Hybrid (both constraints)
241
239
 
242
240
  ```typescript
243
- import { createAgentMemory, createHybridStrategy } from "@directive-run/ai";
241
+ import { createHybridStrategy } from "@directive-run/ai";
244
242
 
245
243
  const memory = createAgentMemory({
246
244
  strategy: createHybridStrategy({
247
245
  maxMessages: 100,
248
- maxTokens: 16000,
246
+ maxTokens: 16_000,
249
247
  }),
250
248
  });
251
249
  ```
252
250
 
253
251
  ## Summarizers
254
252
 
255
- When messages are evicted, a summarizer condenses them:
253
+ When messages are evicted, a summarizer condenses them.
256
254
 
257
- ### Truncation (Default)
255
+ ### Truncation (drop, no summary)
258
256
 
259
257
  ```typescript
260
258
  import { createTruncationSummarizer } from "@directive-run/ai";
261
259
 
262
- // Simply drops old messages – no summary generated
263
260
  const summarizer = createTruncationSummarizer();
264
261
  ```
265
262
 
266
- ### Key Points Extraction
263
+ ### Key-points (rule-based, no LLM call)
267
264
 
268
265
  ```typescript
269
266
  import { createKeyPointsSummarizer } from "@directive-run/ai";
270
267
 
271
- // Extracts bullet points from evicted messages (rule-based, no LLM)
272
268
  const summarizer = createKeyPointsSummarizer();
273
269
  ```
274
270
 
275
- ### LLM-Based Summarization
271
+ ### LLM-based (async)
276
272
 
277
273
  ```typescript
278
274
  import { createLLMSummarizer } from "@directive-run/ai";
279
275
 
280
- // Uses the runner to summarize evicted messages via LLM
281
276
  const summarizer = createLLMSummarizer(runner);
282
277
  ```
283
278
 
284
- ### Applying to Memory
279
+ ### Wiring memory to an orchestrator
285
280
 
286
281
  ```typescript
287
282
  const memory = createAgentMemory({
288
283
  strategy: createSlidingWindowStrategy({ maxMessages: 50 }),
289
284
  summarizer: createKeyPointsSummarizer(),
290
- autoManage: true, // Automatically trim + summarize (default: true)
285
+ autoManage: true, // automatically trim + summarize after each run (default: true)
291
286
  });
292
287
 
293
288
  const orchestrator = createAgentOrchestrator({
@@ -296,37 +291,80 @@ const orchestrator = createAgentOrchestrator({
296
291
  });
297
292
  ```
298
293
 
299
- ## Anti-Pattern #31: Async Summarizer Without autoManage: false
294
+ ## Anti-patterns
295
+
296
+ ### Calling guardrails by old field names
300
297
 
301
298
  ```typescript
302
- // WRONG LLM summarizer is async but autoManage runs synchronously
303
- const memory = createAgentMemory({
299
+ // WRONG these option names don't exist
300
+ createOutputSchemaGuardrail({ schema: jsonSchema, retries: 2 }) // → use validate + outputSchema on the orchestrator for retry
301
+ createToolGuardrail({ allowedTools: [...] }) // → use allowlist
302
+ createLengthGuardrail({ minChars, maxChars, minTokens, maxTokens })// → only maxCharacters + maxTokens
303
+ createContentFilterGuardrail({ patterns, action: "redact" }) // → blockedPatterns; this guardrail BLOCKS only
304
+ ```
305
+
306
+ ### Catching `Error` instead of `GuardrailError`
307
+
308
+ ```typescript
309
+ // WRONG — loses code, guardrailName, guardrailType, userMessage
310
+ try { await orch.run(agent, prompt); } catch (e) {
311
+ if (e instanceof Error) console.log(e.message);
312
+ }
313
+
314
+ // CORRECT — narrow to GuardrailError
315
+ try { await orch.run(agent, prompt); } catch (e) {
316
+ if (e instanceof GuardrailError) {
317
+ console.log(e.code, e.guardrailName, e.userMessage);
318
+ } else {
319
+ throw e;
320
+ }
321
+ }
322
+ ```
323
+
324
+ ### Reading the wrong error fields
325
+
326
+ ```typescript
327
+ // WRONG — these don't exist on GuardrailError
328
+ error.errorCode // → use error.code
329
+ error.reason // → use error.message (or userMessage for user-facing)
330
+ ```
331
+
332
+ ### LLM summarizer with `autoManage: true`
333
+
334
+ ```typescript
335
+ // WRONG — autoManage runs synchronously; the async summarizer is never awaited
336
+ createAgentMemory({
304
337
  strategy: createSlidingWindowStrategy({ maxMessages: 20 }),
305
338
  summarizer: createLLMSummarizer(runner),
306
- autoManage: true, // Will not await the summarizer properly
307
- });
339
+ autoManage: true,
340
+ })
308
341
 
309
- // CORRECT disable autoManage, call memory.manage() manually
342
+ // CORRECT disable autoManage, call memory.manage() after each run
310
343
  const memory = createAgentMemory({
311
344
  strategy: createSlidingWindowStrategy({ maxMessages: 20 }),
312
345
  summarizer: createLLMSummarizer(runner),
313
346
  autoManage: false,
314
347
  });
315
348
 
316
- // After each run, manually manage memory
317
- const result = await orchestrator.run(agent, prompt);
318
- await memory.manage(); // Awaits the async summarizer
349
+ await orchestrator.run(agent, prompt);
350
+ await memory.manage(); // awaits the async summarizer
319
351
  ```
320
352
 
321
- ## Quick Reference
353
+ ## Quick reference
322
354
 
323
- | Guardrail | Input/Output | Key Option |
355
+ | Guardrail | Phase | Required options |
324
356
  |---|---|---|
325
- | `createPIIGuardrail` | Both | `redact`, `patterns` |
326
- | `createModerationGuardrail` | Both | `checkFn` |
327
- | `createRateLimitGuardrail` | Input | `maxTokensPerMinute` |
328
- | `createToolGuardrail` | Input | `allowedTools` |
329
- | `createOutputSchemaGuardrail` | Output | `schema`, `retries` |
330
- | `createOutputTypeGuardrail` | Output | `type` |
331
- | `createLengthGuardrail` | Output | `minChars`, `maxChars` |
332
- | `createContentFilterGuardrail` | Both | `patterns`, `action` |
357
+ | `createPIIGuardrail` | input | (none sensible defaults) |
358
+ | `createModerationGuardrail` | input + output | `checkFn` |
359
+ | `createRateLimitGuardrail` | input | (none — defaults 100K tokens/60 requests per minute) |
360
+ | `createToolGuardrail` | toolCall | `allowlist` or `denylist` |
361
+ | `createOutputSchemaGuardrail` | output | `validate` (`SchemaValidator<T>`) |
362
+ | `createOutputTypeGuardrail` | output | `type` |
363
+ | `createLengthGuardrail` | output | one of `maxCharacters` or `maxTokens` |
364
+ | `createContentFilterGuardrail` | output | `blockedPatterns` |
365
+
366
+ ## See also
367
+
368
+ - [`ai-orchestrator.md`](./ai-orchestrator.md) — `guardrails: { input, output, toolCall }` and `memory:` options on `createAgentOrchestrator`
369
+ - [`ai-security.md`](./ai-security.md) — `createPIIGuardrail`, `createPromptInjectionGuardrail`, `createAuditTrail`, `createCompliance` — the security-flavored guardrails
370
+ - [`ai-budget-resilience.md`](./ai-budget-resilience.md) — `createSemanticCache` pairs with `createSemanticCacheGuardrail` for caching-as-guardrail