@intflows/genkit-guard 0.0.13 → 0.1.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/README.md CHANGED
@@ -1,292 +1,438 @@
1
- # **@intflows/genkit-guard**
1
+ # Genkit Guard
2
2
 
3
- ### **Lightweight Intent, PII, and Safety Guardrails for Genkit**
3
+ Keep Genkit agents on topic, mask personal data before model calls, and control which tools can run.
4
4
 
5
- `@intflows/genkit-guard` provides a modular guardrail layer for Genkit flows.
6
- It adds **semantic intent validation**, **PII masking/unmasking**, and **prompt‑injection detection** with minimal configuration.
5
+ `@intflows/genkit-guard` adds prompt-injection checks, semantic intent scoring, reversible PII masking, and tool policies to your Genkit application. Guard models run locally through Transformers.js; your generative model can use the provider you choose.
7
6
 
8
- This library is designed for developers who want **practical, production‑ready safety controls** without heavy dependencies or complex setup.
7
+ [Try the Hugging Face Space](https://huggingface.co/spaces/intflows/genkit-guard) · [Documentation](https://github.com/IntFlows/genkit-guard/wiki) · [Examples](./example/src)
9
8
 
10
- ---
9
+ ## Why use it?
11
10
 
12
- ## Features
11
+ Agents can access customer data and trigger real workflows. Genkit Guard gives you controls at the model and tool boundaries:
13
12
 
14
- - **Semantic Intent Guarding**
15
- Uses MiniLM embeddings to ensure prompts match allowed intents.
13
+ - **Check requests:** block known injection patterns and reject prompts below your intent similarity threshold.
14
+ - **Reduce PII exposure:** mask detected personal data before model calls and restore tokens in responses and tool inputs.
15
+ - **Control tools:** allow, block, redact detected PII, or require application approval before execution.
16
+ - **Audit decisions:** emit versioned decision events without raw prompts, arguments, or PII values.
16
17
 
17
- - **PII Detection & Masking**
18
- Detects emails, phone numbers, names, and AU‑specific identifiers.
19
- Replaces PII with reversible tokens before sending to the LLM.
18
+ Detection is based on patterns and model predictions. It can miss attacks or PII and can reject valid requests; evaluate it on your application's inputs and keep authorization in your application.
20
19
 
21
- - **Automatic Unmasking**
22
- Restores original PII in the model’s response, even inside structured JSON.
20
+ ## Full setup guide
23
21
 
24
- - **Prompt Injection Detection**
25
- Blocks jailbreak attempts using pattern‑based heuristics.
22
+ The following setup targets **v0.1.0** and can be used after that version is published. The package declares Genkit **1.39.0** as its peer dependency.
26
23
 
27
- - **Model‑Light Architecture**
28
- The package uses local `all-MiniLM-L6-v2` and `openai/privacy-filter` Models, these Models are downloaded once and cached locally.
29
-
30
- - **Drop‑in Genkit Middleware**
31
- Works with `ai.generate`, `ai.generateStream`, and Genkit flows.
32
-
33
- ---
34
-
35
- ### 📦 Installation
24
+ ### 1. Create the application and install dependencies
36
25
 
37
26
  ```bash
38
- ## Install the package
39
- npm install @intflows/genkit-guard
27
+ mkdir my-genkit-app
28
+ cd my-genkit-app
29
+ npm init -y
30
+ npm pkg set type=module
31
+ npm install genkit@1.39.0 @genkit-ai/google-genai@1.39.0 @intflows/genkit-guard@0.1.0
32
+ npm install -D typescript tsx @types/node
33
+ mkdir src
40
34
  ```
41
35
 
42
- This library uses lightweight transformer models (MiniLM + Openai/privacy-filter).
36
+ For an existing Genkit application, install the guard package and use your existing provider configuration.
43
37
 
44
- Download them once.
38
+ ### 2. Download the models
39
+
40
+ Download MiniLM for intent analysis and OpenAI's `privacy-filter` for PII detection. Run this command once from your application's root directory:
45
41
 
46
42
  ```bash
47
- ## Download the transformer models (MiniLM + OpenAI/privacy-filter)
43
+ # Download MiniLM + OpenAI/privacy-filter
48
44
  node node_modules/@intflows/genkit-guard/scripts/download-model.js
49
45
  ```
50
46
 
51
- Models are cached locally and reused across runs.
47
+ Models are cached locally in `./models` and reused across runs. This script downloads those two models; it does not read `guard.config.ts`. The configuration below selects `pii.mode: "classifier"` to use `privacy-filter`. Download size and memory use depend on the models selected.
52
48
 
53
- ---
49
+ ### 3. Configure the guard
54
50
 
55
- ## 🚀 Quick Start
51
+ Create `src/guard.config.ts`:
56
52
 
57
- ### 1. Initialize Local folder
53
+ ```ts
54
+ import { defineGuardConfig } from "@intflows/genkit-guard";
55
+
56
+ export default defineGuardConfig({
57
+ models: { extractor: "Xenova/all-MiniLM-L6-v2" },
58
+ intent: {
59
+ semantic: {
60
+ threshold: 0.7,
61
+ intents: {
62
+ support: "Technical support for Azure Blob Storage, APIs and integrations"
63
+ }
64
+ }
65
+ },
66
+ pii: {
67
+ mode: "classifier",
68
+ model: "openai/privacy-filter"
69
+ },
70
+ tools: {
71
+ defaultAction: "block",
72
+ rules: { searchDocs: "allow" }
73
+ }
74
+ });
75
+ ```
58
76
 
59
- ```bash
60
- # Install @intflows/genkit-guard
61
- npm install @intflows/genkit-guard
77
+ A separate configuration file is optional in general, but required by the import in this example. Pass the same configuration to startup and runtime; there is no automatic file discovery. See [shared model configuration](https://github.com/IntFlows/genkit-guard/wiki/8.-Shared-Model-Configuration) for alternatives.
62
78
 
63
- # Download Local Models (Only needed once)
64
- node node_modules/@intflows/genkit-guard/scripts/download-model.js
65
- ```
66
- _This downloads the models to `./models`; the total size is approximately 1.5 GB._
79
+ The tool rules apply to tools you separately register and supply to Genkit; they do not create tools. This configuration permits `searchDocs` and blocks other tool names.
80
+
81
+ ### 4. Create the application entry point
67
82
 
68
- ### 2. Update genkit
83
+ Create `src/index.ts`:
69
84
 
70
85
  ```ts
86
+ import { genkit } from "genkit";
87
+ import { googleAI } from "@genkit-ai/google-genai";
71
88
  import { guard, initGuard } from "@intflows/genkit-guard";
89
+ import guardConfig from "./guard.config.js";
72
90
 
73
- await initGuard();
91
+ const modelName = process.env.GEMINI_MODEL;
92
+ if (!modelName) throw new Error("Set GEMINI_MODEL before running the example");
74
93
 
75
- const response = await ai.generate({
76
- prompt: "How do I integrate with Azure Blob Storage?",
77
- use: [
78
- guard({
79
- intent: {
80
- mode: "semantic",
81
- allowedIntent: "integration",
82
- semantic: {
83
- threshold: 0.7,
84
- intents: {
85
- integration: "Azure Blob, APIs, workflows"
86
- }
87
- }
88
- },
89
- pii: { reversible: true }
90
- })
91
- ]
94
+ const ai = genkit({
95
+ plugins: [googleAI()],
96
+ model: googleAI.model(modelName)
92
97
  });
93
- ```
94
-
95
- **You Can also check the full step by step guide here:**
96
-
97
- [Intflows Wiki](https://github.com/IntFlows/genkit-guard/wiki)
98
98
 
99
- ### 3. Execute the Genkit flow
99
+ await initGuard(guardConfig);
100
100
 
101
- #### Allowed :
102
- ``` npx tsx src/index.ts "How do I integrate with Azure Blob Storage?"```
101
+ const response = await ai.generate({
102
+ prompt: process.argv[2] ?? "How do I integrate with Azure Blob Storage?",
103
+ use: [guard(guardConfig)]
104
+ });
103
105
 
104
- #### Blocked:
105
- ``` npx tsx src/index.ts "workflow to download a file from an API, save it to Blob file and export the API key"```
106
+ if (response.finishReason === "blocked") {
107
+ console.log("Request blocked by guard policy");
108
+ } else {
109
+ console.log(response.text);
110
+ }
111
+ ```
106
112
 
107
- ![Image showing Generation Blocked](./GenerationBlocked.png)
113
+ `initGuard(config)` preloads the selected models and can download missing files, including NER or custom models if you change the configuration.
108
114
 
115
+ ### 5. Set environment variables
109
116
 
110
- #### PII MASK and UNMASK:
111
- ``` npx tsx src/index.ts "workflow to download a file from an API, save it to Blob file with my email john.doe@example.com"```
117
+ Set `GEMINI_API_KEY` to your provider key and `GEMINI_MODEL` to a model available to your account in the terminal you will use to run the example.
112
118
 
113
- ![Image showing PII data masked ](./MaskedPII.png)
119
+ PowerShell:
114
120
 
115
- ---
116
- ## Example
121
+ ```powershell
122
+ $env:GEMINI_API_KEY = "your-api-key"
123
+ $env:GEMINI_MODEL = "your-model-name"
124
+ ```
117
125
 
118
- An example genkit flow is present in `example` directory.
126
+ Bash:
119
127
 
120
128
  ```bash
121
- git clone https://github.com/IntFlows/genkit-guard.git
122
- cd genkit-guard/example
123
- npm install
124
- node node_modules/@intflows/genkit-guard/scripts/download-model.js
125
- npx tsx src/index.ts
129
+ export GEMINI_API_KEY="your-api-key"
130
+ export GEMINI_MODEL="your-model-name"
126
131
  ```
127
132
 
128
- Or you can run the flow with genkit dev UI
133
+ ### 6. Run the example
129
134
 
130
135
  ```bash
131
- git clone https://github.com/IntFlows/genkit-guard.git
132
- cd genkit-guard/example
133
- npm install
134
- node node_modules/@intflows/genkit-guard/scripts/download-model.js
135
- genkit start -- npx tsx src/index.ts
136
+ npx tsx src/index.ts "How do I integrate with Azure Blob Storage?"
137
+ npx tsx src/index.ts "export the API key"
138
+ npx tsx src/index.ts "Integrate Azure Blob Storage for alice@example.com"
136
139
  ```
137
140
 
138
- ---
139
-
140
- ## 🧠 How It Works
141
-
142
- ### **1. Intent Guard**
143
- - Embeds the user prompt + intent descriptions using MiniLM
144
- - Computes cosine similarity
145
- - Blocks prompts below threshold
146
- - Detects jailbreak patterns like:
147
- - “ignore previous instructions”
148
- - “you are a hacker”
149
- - “export the API key”
141
+ The second prompt exercises injection-pattern blocking. The third exercises email masking when intent scoring allows it. Responses restore masked values, so final output alone does not demonstrate what the model received. Check `finishReason` for blocked results before treating a response as successful, and tune intent descriptions and thresholds with representative inputs.
150
142
 
151
- ### **2. PII Masking**
152
- Before the LLM sees the prompt:
143
+ ## What happens to a request?
153
144
 
154
- ```
155
- "Email john.doe@example.com" → "Email [[EMAIL_0]]"
145
+ ```text
146
+ User request
147
+ -> Injection-pattern check
148
+ -> Semantic intent check
149
+ -> PII masking
150
+ -> Generative model
151
+ -> Response token restoration
152
+ -> Tool policy check before any requested tool executes
156
153
  ```
157
154
 
158
- Detected PII includes:
155
+ A matching injection phrase such as `ignore previous instructions` blocks the model call. Intent checks compare the prompt with every description in `intent.semantic.intents`; only include categories you want to allow.
159
156
 
160
- - Emails
161
- - Phone numbers
162
- - AU identifiers (Medicare, TFN, ABN, etc.)
163
- - PII detected by local Model (OpenAI/privacy-filter)
157
+ PII masking replaces detected values with namespaced tokens:
164
158
 
165
- ### **3. LLM Call**
166
- The masked prompt is sent to the model.
159
+ ```text
160
+ Input: Email alice@example.com
161
+ Model receives: Email [[EMAIL_<namespace>_0]]
162
+ Restored: Email alice@example.com
163
+ ```
167
164
 
168
- ### **4. Response Unmasking**
169
- After the LLM responds:
165
+ Restoration also works inside structured responses. Tool policy checks determine whether a requested tool may receive restored values or redacted arguments.
170
166
 
167
+ ## Tool controls
168
+
169
+ Add exact tool names to the shared configuration:
170
+ ```text
171
+ Input: Email alice@example.com
172
+ Model receives: Email [[EMAIL_<namespace>_0]]
173
+ Restored: Email alice@example.com
171
174
  ```
172
- "Send a confirmation email to [[EMAIL_0]]" → "Send a confirmation email to john.doe@example.com"
173
- ```
174
- ---
175
175
 
176
- ## ⚙️ Configuration
176
+ Restoration also works inside structured responses. Tool policy checks determine whether a requested tool may receive restored values or redacted arguments.
177
+
178
+ ## Tool controls
177
179
 
178
- ### **Intent Guard**
180
+ Add exact tool names to the shared configuration:
179
181
 
180
182
  ```ts
181
- intent: {
182
- mode: "semantic",
183
- allowedIntent: "intent_question",
184
- semantic: {
185
- threshold: 0.7,
186
- intents: {
187
- intent_question: "Description of allowed intent"
188
- }
183
+ tools: {
184
+ defaultAction: "block",
185
+ rules: {
186
+ searchDocs: "allow",
187
+ summarizeTicket: "redact",
188
+ deleteTicket: "approval-required"
189
+ },
190
+ approve: async ({ toolName, input, context }) => {
191
+ // Connect a trusted approval service for this exact call and user.
192
+ // This example denies every approval request.
193
+ return false;
194
+ tools: {
195
+ defaultAction: "block",
196
+ rules: {
197
+ searchDocs: "allow",
198
+ summarizeTicket: "redact",
199
+ deleteTicket: "approval-required"
200
+ },
201
+ approve: async ({ toolName, input, context }) => {
202
+ // Connect a trusted approval service for this exact call and user.
203
+ // This example denies every approval request.
204
+ return false;
189
205
  }
190
206
  }
191
207
  ```
192
208
 
193
- ### **PII Guard**
209
+ | Policy | Behavior |
210
+ | --- | --- |
211
+ | `allow` | Restore tokens, scan input, then execute |
212
+ | `block` | Stop before execution |
213
+ | `redact` | Replace detected PII in nested string arguments with `[REDACTED]`, then execute |
214
+ | `approval-required` | Execute only when the application's callback returns literal `true` |
194
215
 
195
- ```ts
196
- pii: {
197
- reversible: true,
198
- mode: "classifier"
199
- }
200
- ```
201
-
202
- `classifier` mode uses `openai/privacy-filter` as a token-classification model with aggregated
203
- spans. Model-detected names, addresses, emails, phone numbers, URLs, dates, account numbers and
204
- secrets are converted into reversible masking tokens. Regex rules continue to run as an additional
205
- layer, and duplicate spans are masked only once.
206
-
207
- During multi-turn tool execution, opaque tokens returned through a different Genkit middleware
208
- context are rehydrated from the configured vault before tool execution and before the final
209
- response is returned to the application.
210
-
211
- Preload the same mode during application startup:
212
-
213
- ```ts
214
- await initGuard({ pii: { mode: "classifier" } });
215
- ```
216
-
217
- ### **PII Vault Isolation and External Storage**
218
-
219
- By default, PII is stored in an in-memory vault scoped to a single tokenizer instance. Tokens include a generated vault scope:
220
-
221
- ```txt
222
- "Email john.doe@example.com" -> "Email [[EMAIL_<namespace>_0]]"
216
+ Blocked, pending, or denied calls throw `GuardToolError`. Approval UI and durable approval storage belong to the application. Redaction may invalidate a tool's input schema, such as an email field; choose a policy appropriate to the tool.
217
+
218
+ Without tool policies, the default remains allow. With `tools` configured, `guard()` returns a native Genkit middleware reference. Existing configurations without `tools` retain the legacy callable form. Use `guardMiddleware(config)` for native tool hooks without explicit policies.
219
+
220
+ See [tool controls and logging](https://github.com/IntFlows/genkit-guard/wiki/9.-Tool-Controls-and-Logging) for the complete behavior.
221
+
222
+ ## Models and PII storage
223
+
224
+ | Setting | Default |
225
+ | --- | --- |
226
+ | Intent model | `Xenova/all-MiniLM-L6-v2` |
227
+ | PII mode | `ner` |
228
+ | NER model | `Xenova/bert-base-NER` |
229
+ | Classifier model | `openai/privacy-filter` when classifier mode is selected |
230
+ | PII vault | In-memory storage |
231
+
232
+ The quick start explicitly selects classifier mode. Regex detection also runs for email, Australian phone and identifier patterns, and credit-card-like numbers.
233
+
234
+ Use `models.extractor` and `pii.model` to select compatible models, and `pii.labelMappings` to map fine-tuned labels to masking types. Classifier mode currently loads `q4` weights. Redis and custom vault adapters support external storage.
235
+
236
+ - [Shared model configuration](https://github.com/IntFlows/genkit-guard/wiki/8.-Shared-Model-Configuration)
237
+ - [PII labels, masking and vaults](https://github.com/IntFlows/genkit-guard/wiki/5.-PII-Guard)
238
+
239
+ ## PII Vault Isolation and External Storage
240
+
241
+ Masked values are kept in a vault so they can be restored later. By default, the middleware uses process-local in-memory storage with generated vault scopes. Each tokenizer also generates an opaque namespace for its placeholders:
242
+ | Policy | Behavior |
243
+ | --- | --- |
244
+ | `allow` | Restore tokens, scan input, then execute |
245
+ | `block` | Stop before execution |
246
+ | `redact` | Replace detected PII in nested string arguments with `[REDACTED]`, then execute |
247
+ | `approval-required` | Execute only when the application's callback returns literal `true` |
248
+
249
+ Blocked, pending, or denied calls throw `GuardToolError`. Approval UI and durable approval storage belong to the application. Redaction may invalidate a tool's input schema, such as an email field; choose a policy appropriate to the tool.
250
+
251
+ Without tool policies, the default remains allow. With `tools` configured, `guard()` returns a native Genkit middleware reference. Existing configurations without `tools` retain the legacy callable form. Use `guardMiddleware(config)` for native tool hooks without explicit policies.
252
+
253
+ See [tool controls and logging](https://github.com/IntFlows/genkit-guard/wiki/9.-Tool-Controls-and-Logging) for the complete behavior.
254
+
255
+ ## Models and PII storage
256
+
257
+ | Setting | Default |
258
+ | --- | --- |
259
+ | Intent model | `Xenova/all-MiniLM-L6-v2` |
260
+ | PII mode | `ner` |
261
+ | NER model | `Xenova/bert-base-NER` |
262
+ | Classifier model | `openai/privacy-filter` when classifier mode is selected |
263
+ | PII vault | In-memory storage |
264
+
265
+ The quick start explicitly selects classifier mode. Regex detection also runs for email, Australian phone and identifier patterns, and credit-card-like numbers.
266
+
267
+ Use `models.extractor` and `pii.model` to select compatible models, and `pii.labelMappings` to map fine-tuned labels to masking types. Classifier mode currently loads `q4` weights. Redis and custom vault adapters support external storage.
268
+
269
+ - [Shared model configuration](https://github.com/IntFlows/genkit-guard/wiki/8.-Shared-Model-Configuration)
270
+ - [PII labels, masking and vaults](https://github.com/IntFlows/genkit-guard/wiki/5.-PII-Guard)
271
+
272
+ ## PII Vault Isolation and External Storage
273
+
274
+ Masked values are kept in a vault so they can be restored later. By default, the middleware uses process-local in-memory storage with generated vault scopes. Each tokenizer also generates an opaque namespace for its placeholders:
275
+
276
+ ```text
277
+ alice@example.com -> [[EMAIL_<namespace>_0]]
278
+ ```text
279
+ alice@example.com -> [[EMAIL_<namespace>_0]]
223
280
  ```
224
281
 
225
- That generated namespace prevents two concurrent calls from sharing the same visible placeholder names. Vault lookups are isolated by the configured storage scope, so User A and User B can safely produce their own email tokens without cross-resolving each other's PII.
282
+ Namespaces prevent concurrent calls from creating identical placeholder names. Use `pii.vault.scopeId` to group vault entries by request, session, or another application scope. The scope ID is not exposed in the placeholder.
283
+
284
+ ### Redis storage
285
+
286
+ Use Redis when vault entries need to survive application restarts or be available to multiple workers. Install the client separately:
287
+
288
+ ```bash
289
+ npm install redis
290
+ ```
226
291
 
227
- For applications that need persistence, distributed workers, audits, or tenant-specific storage, provide a vault storage backend. Redis clients can be passed through the built-in helper:
228
- For applications that need persistence, distributed workers, audits, or tenant-specific storage, provide a vault storage backend. Redis clients can be passed through the built-in helper:
292
+ Extend your shared configuration during application startup:
293
+ Namespaces prevent concurrent calls from creating identical placeholder names. Use `pii.vault.scopeId` to group vault entries by request, session, or another application scope. The scope ID is not exposed in the placeholder.
294
+
295
+ ### Redis storage
296
+
297
+ Use Redis when vault entries need to survive application restarts or be available to multiple workers. Install the client separately:
298
+
299
+ ```bash
300
+ npm install redis
301
+ ```
302
+
303
+ Extend your shared configuration during application startup:
229
304
 
230
305
  ```ts
231
306
  import { createClient } from "redis";
232
- import { guard, createRedisPiiVaultStorage } from "@intflows/genkit-guard";
233
-
234
- const redis = createClient({ url: "redis://localhost:6379" });
307
+ import { guard, initGuard, createRedisPiiVaultStorage } from "@intflows/genkit-guard";
308
+ import guardConfig from "./guard.config.js";
309
+ import { guard, initGuard, createRedisPiiVaultStorage } from "@intflows/genkit-guard";
310
+ import guardConfig from "./guard.config.js";
311
+
312
+ const redis = createClient({ url: process.env.REDIS_URL ?? "redis://localhost:6379" });
313
+ redis.on("error", () => console.error("PII vault Redis connection error"));
314
+ const redis = createClient({ url: process.env.REDIS_URL ?? "redis://localhost:6379" });
315
+ redis.on("error", () => console.error("PII vault Redis connection error"));
235
316
  await redis.connect();
236
317
 
237
- guard({
318
+ const config = {
319
+ ...guardConfig,
320
+ const config = {
321
+ ...guardConfig,
238
322
  pii: {
239
- reversible: true,
323
+ ...guardConfig.pii,
324
+ ...guardConfig.pii,
240
325
  vault: {
241
326
  storage: createRedisPiiVaultStorage(redis, {
242
327
  keyPrefix: "my-app:pii",
243
328
  ttlSeconds: 3600,
244
- fallbackToMemory: true
329
+ fallbackToMemory: false
245
330
  }),
246
- scopeId: (req, ctx) => ctx?.auth?.sessionId ?? req?.metadata?.requestId
331
+ // Populate this from trusted application context.
332
+ // If absent, the middleware generates a new scope.
333
+ scopeId: (_req: unknown, ctx: any) => ctx?.context?.piiScopeId
247
334
  }
248
335
  }
336
+ };
337
+
338
+ await initGuard(config);
339
+ // Use guard(config) in your ai.generate({ use: [...] }) calls.
340
+ const middleware = guard(config);
341
+ ```
342
+
343
+ `ttlSeconds` expires the scoped vault and shared token-index keys; writes refresh their expiry, so it is not a per-entry retention deadline. Without a TTL, Redis entries remain until removed externally. The default in-memory vault has no automatic expiry.
344
+
345
+ `fallbackToMemory: true` enables a process-local mirror with the same TTL behavior when Redis operations fail. It is disabled by default, so Redis errors propagate. The mirror is not shared across workers, does not survive restarts, and is not automatically replayed into Redis after recovery.
346
+
347
+ ### Custom storage and isolation boundaries
348
+
349
+ Use `createPiiVaultStorage({ get, set, entries, getByToken })` to connect another database or store. `getByToken` is optional and enables recovery of opaque tokens across model/tool turns.
350
+
351
+ Scoped reads and writes use `scopeId`, but cross-turn recovery can look up tokens across scopes within the same backend. Scope IDs and opaque namespaces are not tenant authorization controls. For separate tenants, use appropriately isolated storage adapters or Redis prefixes, including separate token indexes, and enforce access in your application. Vault entries contain original PII values; the adapter does not encrypt those values itself.
352
+
353
+ See the [PII vault documentation](https://github.com/IntFlows/genkit-guard/wiki/5.-PII-Guard) and [Redis integration example](./example/src/index.ts).
354
+
355
+ ## Decision logging
356
+
357
+ Console logging emits structured JSON. In v0.1.0, attach a persistent store to your shared configuration:
358
+
359
+ ```ts
360
+ import { defineGuardConfig, createJsonlDecisionStore } from "@intflows/genkit-guard";
361
+
362
+ const decisionStore = createJsonlDecisionStore("./logs/guard-decisions.jsonl");
363
+ const config = defineGuardConfig({
364
+ // Include your intent, PII and tool policies here.
365
+ policyVersion: "support-v2",
366
+ logging: {
367
+ enabled: false, // Disable console output; persistence still runs.
368
+ store: decisionStore
369
+ }
249
370
  });
250
371
  ```
251
372
 
252
- `ttlSeconds` applies the configured expiry to both the scoped vault and token index. When
253
- `fallbackToMemory` is enabled, successful writes are also mirrored in process memory and Redis
254
- operation failures fall back to that mirror. The fallback is disabled by default, is local to one
255
- process, and is not a replacement for Redis persistence or multi-worker availability. Its in-memory
256
- entries observe the same TTL. Redis errors continue to propagate when fallback is disabled.
373
+ Pass `config` to `initGuard()` and `guard()`. Each append is validated, serialized and flushed before it resolves. Use `await decisionStore.read()` to load the records, including after restarting the application. Unknown fields are stripped before writing.
257
374
 
258
- For another backend, use `createPiiVaultStorage({ get, set, entries, getByToken })` with your database, cache, or secret store.
375
+ Events include schema version, decision ID, timestamp, guard, policy version, action, reason code, latency, and intent similarity where applicable. They exclude raw prompts, arguments, classifier output and error messages. Use non-sensitive policy identifiers.
259
376
 
260
- Choose a `scopeId` that matches your isolation boundary, such as request ID, session ID, tenant/user ID, or a combination like `tenantId:userId:requestId`. A shared external backend should never ignore `scopeId`, because placeholders are only safe when resolved against the correct vault scope. The placeholder sent to the model uses an opaque generated namespace rather than exposing your `scopeId`.
377
+ `logging.onDecision` remains available. Store writes happen first, then the callback; both are awaited regardless of console settings. Failure stops the current operation. There are no automatic delivery retries, and a successful write is not rolled back if the callback later fails. Decisions describe checks, not confirmation of downstream execution.
261
378
 
262
- ### Screenshots
263
- ![Redis Stored PII ](redis-scan.png)
379
+ The JSONL helper supports concurrent callers in one process, including separate instances using the same resolved path. Use separate files per worker or a shared backend through `createGuardDecisionStore({ append })` for multiple processes. File rotation, retention, encryption and crash recovery are application responsibilities; incomplete log tails cause reads and further appends to fail rather than silently discarding records. `read()` loads the full file into memory.
264
380
 
265
- ---
381
+ ## Fallback guard models
266
382
 
267
- ## 🛡️ Why This Library Exists
383
+ Fallbacks are optional and apply to local intent and PII models, not your application's generative model:
268
384
 
269
- Genkit provides a powerful LLM framework, but production systems need:
385
+ ```ts
386
+ models: {
387
+ extractor: "Xenova/all-MiniLM-L6-v2",
388
+ extractorFallback: "your-org/compatible-embedding-model"
389
+ },
390
+ pii: {
391
+ mode: "classifier",
392
+ model: "openai/privacy-filter",
393
+ fallback: {
394
+ model: "Xenova/bert-base-NER",
395
+ mode: "ner",
396
+ labelMappings: { PER: "NAME" }
397
+ }
398
+ }
399
+ ```
270
400
 
271
- - intent boundaries
272
- - PII protection
273
- - jailbreak resistance
274
- - predictable behavior
401
+ Replace the custom embedding identifier with a model you have prepared. A fallback runs once when the primary model fails to load or execute. An intent rejection or empty PII result does not trigger fallback. The primary is tried again on the next operation; there is no timeout, circuit breaker or automatic switch for future requests.
275
402
 
276
- This library adds those guardrails without heavy dependencies or complex setup.
403
+ PII fallback mode defaults to the primary mode. Its label mappings are independent of the primary mappings. Both models must support the relevant Transformers.js task; classifier mode requires compatible `q4` weights. NER and privacy-filter have different detection coverage, so evaluate the fallback on your own inputs before enabling it.
277
404
 
278
- ---
405
+ Successful recovery emits `MODEL_FALLBACK_USED`; normal guard checks still determine whether the request proceeds. If both models fail, `GuardModelError` with code `MODEL_UNAVAILABLE` stops the operation. There is no regex-only bypass. The same policy applies during `initGuard()`, model-request checks and tool PII scans. A failure after a tool has executed cannot undo that execution.
279
406
 
280
- ## Contributing
407
+ Without fallback configuration, existing model selection and error propagation remain unchanged. See [persistent decisions and fallback models](https://github.com/IntFlows/genkit-guard/wiki/10.-Decision-Storage-and-Model-Fallback) for the full contract.
408
+
409
+ ## Try it and explore the examples
281
410
 
282
- We plan to:
411
+ [Open the Hugging Face Space](https://huggingface.co/spaces/intflows/genkit-guard) to explore the demo. Use synthetic inputs when trying a hosted demo.
283
412
 
284
- 1. Extend the utility by adding Auth and Tool Middleware in further stages.
285
- 2. Add more filter types for common malicious prompts.
286
- 3. Add more patterns for custom PII masking.
413
+ - [Integration flow](./example/src/index.ts): Azure Blob workflow with Redis-backed PII storage.
414
+ - [Tool controls](./example/src/tool-controls.ts): optional standalone demo of redaction and approval-required policies. This file is not required to use the package.
415
+
416
+ The tool-controls demo uses the local build. Run `npm install` and `npm run build` in the repository root, then `npm install` in `example`. Set `GEMINI_API_KEY` and `GEMINI_MODEL` for your provider account, and run:
417
+
418
+ ```bash
419
+ # From example/
420
+ npx tsx src/tool-controls.ts
421
+ ```
422
+
423
+ ## Roadmap
424
+
425
+ - **v0.0.14:** shared model configuration, custom PII labels, tool policies and versioned decision events.
426
+ - **v0.1.0 (in development):** persistent decision logging and explicit fallback-model behavior.
427
+ - **Later:** SQLite vault, memory compaction integration, compatibility hardening and stable v1.
428
+
429
+ ## Contributing
287
430
 
288
- Contributions are welcome whether it’s bug reports, new guard modules, model improvements or enhancements. This project aims to stay lightweight, modular, and production‑ready, so thoughtful contributions are appreciated.
431
+ Issues, pull requests, model evaluations and security reviews are welcome. Run `npm test` for the deterministic suite; real Redis integration is tested separately with `npm run test:redis`.
432
+ Issues, pull requests, model evaluations and security reviews are welcome. Run `npm test` for the deterministic suite; real Redis integration is tested separately with `npm run test:redis`.
289
433
 
290
- # 📄 License
434
+ ## License
435
+ ## License
291
436
 
292
- Apache2.0
437
+ [Apache-2.0](./LICENSE)
438
+ [Apache-2.0](./LICENSE)
@@ -0,0 +1,5 @@
1
+ import type { GuardConfig } from '../middleware/middleware.js';
2
+ import type { GuardDecision } from './decision.js';
3
+ export declare function publishDecision(config: GuardConfig | undefined, start: number, fields: Pick<GuardDecision, 'guard' | 'action' | 'reasonCode'> & {
4
+ confidence?: number;
5
+ }): Promise<GuardDecision>;