@framers/agentos-ext-topicality 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.
@@ -0,0 +1,426 @@
1
+ /**
2
+ * @fileoverview IGuardrailService implementation for topicality enforcement.
3
+ *
4
+ * `TopicalityGuardrail` evaluates user input (and optionally agent output)
5
+ * against configured allowed and forbidden topic sets using semantic
6
+ * embedding similarity. It enforces three independent policy checks:
7
+ *
8
+ * 1. **Forbidden topics** — Messages that score above `forbiddenThreshold`
9
+ * against any forbidden topic are blocked (or flagged).
10
+ * 2. **Off-topic detection** — Messages that score below `allowedThreshold`
11
+ * against *all* allowed topics are flagged (or blocked/redirected).
12
+ * 3. **Session drift** — An EMA-based tracker flags sustained drift away
13
+ * from allowed topics across consecutive messages.
14
+ *
15
+ * ### Lazy initialisation
16
+ * Embedding indices are built on the **first evaluation call**, not at
17
+ * construction time. This keeps instantiation cheap and defers the
18
+ * potentially expensive batch embedding call until the agent actually
19
+ * receives its first message.
20
+ *
21
+ * ### Fail-open semantics
22
+ * All evaluation methods wrap their logic in try/catch. If the embedding
23
+ * function throws, or any other unexpected error occurs, the guardrail
24
+ * logs a warning and returns `null` (pass) to avoid blocking legitimate
25
+ * traffic due to infrastructure failures.
26
+ *
27
+ * @module topicality/TopicalityGuardrail
28
+ */
29
+ import { GuardrailAction } from '@framers/agentos';
30
+ import { DEFAULT_DRIFT_CONFIG } from './types';
31
+ import { TopicEmbeddingIndex } from './TopicEmbeddingIndex';
32
+ import { TopicDriftTracker } from './TopicDriftTracker';
33
+ // ---------------------------------------------------------------------------
34
+ // Reason codes emitted by this guardrail
35
+ // ---------------------------------------------------------------------------
36
+ /**
37
+ * Machine-readable reason code for messages matching a forbidden topic.
38
+ * @internal
39
+ */
40
+ const REASON_FORBIDDEN = 'TOPICALITY_FORBIDDEN';
41
+ /**
42
+ * Machine-readable reason code for messages that do not match any allowed topic.
43
+ * @internal
44
+ */
45
+ const REASON_OFF_TOPIC = 'TOPICALITY_OFF_TOPIC';
46
+ /**
47
+ * Machine-readable reason code for sustained session-level topic drift.
48
+ * @internal
49
+ */
50
+ const REASON_DRIFT = 'TOPICALITY_DRIFT';
51
+ // ---------------------------------------------------------------------------
52
+ // TopicalityGuardrail
53
+ // ---------------------------------------------------------------------------
54
+ /**
55
+ * Guardrail that enforces topicality constraints via semantic embeddings.
56
+ *
57
+ * Implements {@link IGuardrailService} with Phase 2 (parallel) semantics:
58
+ * `evaluateStreamingChunks: false` and `canSanitize: false`. The guardrail
59
+ * never modifies content — it only blocks or flags.
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * const guardrail = new TopicalityGuardrail(registry, {
64
+ * allowedTopics: TOPIC_PRESETS.customerSupport,
65
+ * forbiddenTopics: TOPIC_PRESETS.commonUnsafe,
66
+ * forbiddenAction: 'block',
67
+ * offTopicAction: 'flag',
68
+ * }, embeddingFn);
69
+ *
70
+ * const result = await guardrail.evaluateInput(payload);
71
+ * if (result?.action === GuardrailAction.BLOCK) {
72
+ * // Reject the message
73
+ * }
74
+ * ```
75
+ */
76
+ export class TopicalityGuardrail {
77
+ // -------------------------------------------------------------------------
78
+ // IGuardrailService config
79
+ // -------------------------------------------------------------------------
80
+ /**
81
+ * Guardrail pipeline configuration.
82
+ *
83
+ * - `evaluateStreamingChunks: false` — topicality evaluation requires
84
+ * complete text, not partial deltas.
85
+ * - `canSanitize: false` — this guardrail only blocks or flags; it never
86
+ * modifies content, so it runs in Phase 2 (parallel) of the pipeline.
87
+ */
88
+ config = {
89
+ evaluateStreamingChunks: false,
90
+ canSanitize: false,
91
+ };
92
+ // -------------------------------------------------------------------------
93
+ // Private state
94
+ // -------------------------------------------------------------------------
95
+ /** Shared service registry provided by the extension manager. */
96
+ services;
97
+ /** Resolved pack options with caller overrides. */
98
+ options;
99
+ /** Caller-supplied or registry-backed embedding function. */
100
+ embeddingFn;
101
+ /**
102
+ * Embedding index for allowed topics. Lazily built on the first
103
+ * evaluation call. `null` until built or if no allowed topics are
104
+ * configured.
105
+ */
106
+ allowedIndex = null;
107
+ /**
108
+ * Embedding index for forbidden topics. Lazily built on the first
109
+ * evaluation call. `null` until built or if no forbidden topics are
110
+ * configured.
111
+ */
112
+ forbiddenIndex = null;
113
+ /**
114
+ * Session-level EMA drift tracker. Only instantiated when
115
+ * `enableDriftDetection` is `true` (default). `null` otherwise.
116
+ */
117
+ driftTracker = null;
118
+ /**
119
+ * Which side of the conversation to evaluate.
120
+ * - `'input'` — only user messages
121
+ * - `'output'` — only agent responses
122
+ * - `'both'` — both directions
123
+ */
124
+ scope;
125
+ /**
126
+ * Minimum similarity to any allowed topic for the message to be
127
+ * considered on-topic.
128
+ */
129
+ allowedThreshold;
130
+ /**
131
+ * Similarity above which a forbidden topic match triggers action.
132
+ */
133
+ forbiddenThreshold;
134
+ /**
135
+ * Whether the lazy initialisation of embedding indices has been
136
+ * performed. Prevents redundant build calls.
137
+ */
138
+ indicesBuilt = false;
139
+ // -------------------------------------------------------------------------
140
+ // Constructor
141
+ // -------------------------------------------------------------------------
142
+ /**
143
+ * Creates a new `TopicalityGuardrail`.
144
+ *
145
+ * @param services - Shared service registry for heavyweight resource sharing.
146
+ * @param options - Pack-level configuration (topics, thresholds, actions).
147
+ * @param embeddingFn - Optional explicit embedding function. When omitted,
148
+ * the guardrail falls back to requesting an EmbeddingManager from the
149
+ * shared service registry at evaluation time.
150
+ */
151
+ constructor(services, options, embeddingFn) {
152
+ this.services = services;
153
+ this.options = options;
154
+ // Resolve embedding function: prefer explicit argument, then fall back
155
+ // to the shared service registry.
156
+ this.embeddingFn = embeddingFn ?? this.createRegistryEmbeddingFn();
157
+ // Resolve scope and thresholds from options with sensible defaults.
158
+ this.scope = options.guardrailScope ?? 'input';
159
+ this.allowedThreshold = options.allowedThreshold ?? 0.35;
160
+ this.forbiddenThreshold = options.forbiddenThreshold ?? 0.65;
161
+ // Instantiate drift tracker if enabled (default: true).
162
+ const driftEnabled = options.enableDriftDetection !== false;
163
+ if (driftEnabled) {
164
+ const driftConfig = { ...DEFAULT_DRIFT_CONFIG, ...(options.drift ?? {}) };
165
+ this.driftTracker = new TopicDriftTracker(driftConfig);
166
+ }
167
+ }
168
+ /**
169
+ * Clears any session-level drift-tracking state held by this guardrail.
170
+ *
171
+ * Called by the topicality pack's `onDeactivate` hook so long-lived agents
172
+ * do not retain per-session EMA state after the pack is removed or the
173
+ * agent shuts down.
174
+ */
175
+ clearSessionState() {
176
+ this.driftTracker?.clear();
177
+ }
178
+ // -------------------------------------------------------------------------
179
+ // IGuardrailService — evaluateInput
180
+ // -------------------------------------------------------------------------
181
+ /**
182
+ * Evaluates a user input message against configured topic constraints.
183
+ *
184
+ * When `scope` is `'output'`, this method immediately returns `null`
185
+ * because input evaluation is disabled.
186
+ *
187
+ * @param payload - The input payload containing the user message text and
188
+ * session context.
189
+ * @returns A guardrail evaluation result (BLOCK or FLAG), or `null` if
190
+ * the message passes all topic checks. Returns `null` on any error
191
+ * (fail-open).
192
+ */
193
+ async evaluateInput(payload) {
194
+ // If scope is output-only, skip input evaluation entirely.
195
+ if (this.scope === 'output') {
196
+ return null;
197
+ }
198
+ try {
199
+ // Extract the text content from the input payload.
200
+ const text = payload.input.textInput;
201
+ if (!text || text.trim().length === 0) {
202
+ // No text to evaluate — pass through.
203
+ return null;
204
+ }
205
+ // Lazy-build embedding indices on the first call.
206
+ await this.ensureIndicesBuilt();
207
+ // Embed the user's text once — reuse the vector for all checks.
208
+ const [embedding] = await this.embeddingFn([text]);
209
+ // Run the core evaluation pipeline on the embedded vector.
210
+ return this.evaluateEmbedding(embedding, payload.context.sessionId);
211
+ }
212
+ catch (error) {
213
+ // Fail-open: log the error but let the message through.
214
+ console.warn('[TopicalityGuardrail] evaluateInput failed (fail-open):', error instanceof Error ? error.message : error);
215
+ return null;
216
+ }
217
+ }
218
+ // -------------------------------------------------------------------------
219
+ // IGuardrailService — evaluateOutput
220
+ // -------------------------------------------------------------------------
221
+ /**
222
+ * Evaluates an agent output chunk against configured topic constraints.
223
+ *
224
+ * When `scope` is `'input'`, this method immediately returns `null`
225
+ * because output evaluation is disabled.
226
+ *
227
+ * For output evaluation, the guardrail extracts text from the response
228
+ * chunk's `finalResponseText` field (since `evaluateStreamingChunks` is
229
+ * `false`, only FINAL_RESPONSE chunks are seen).
230
+ *
231
+ * @param payload - The output payload containing the response chunk and
232
+ * session context.
233
+ * @returns A guardrail evaluation result (BLOCK or FLAG), or `null` if
234
+ * the output passes all topic checks. Returns `null` on any error
235
+ * (fail-open).
236
+ */
237
+ async evaluateOutput(payload) {
238
+ // If scope is input-only, skip output evaluation entirely.
239
+ if (this.scope === 'input') {
240
+ return null;
241
+ }
242
+ try {
243
+ // Extract text from the chunk. Since evaluateStreamingChunks is false,
244
+ // we receive FINAL_RESPONSE chunks with finalResponseText.
245
+ const chunk = payload.chunk;
246
+ const text = chunk.textDelta ??
247
+ chunk.finalResponseText ??
248
+ '';
249
+ if (!text || text.trim().length === 0) {
250
+ return null;
251
+ }
252
+ // Lazy-build embedding indices on the first call.
253
+ await this.ensureIndicesBuilt();
254
+ // Embed the output text once.
255
+ const [embedding] = await this.embeddingFn([text]);
256
+ // Run the core evaluation pipeline.
257
+ return this.evaluateEmbedding(embedding, payload.context.sessionId);
258
+ }
259
+ catch (error) {
260
+ // Fail-open: log and pass through.
261
+ console.warn('[TopicalityGuardrail] evaluateOutput failed (fail-open):', error instanceof Error ? error.message : error);
262
+ return null;
263
+ }
264
+ }
265
+ // -------------------------------------------------------------------------
266
+ // Core evaluation pipeline
267
+ // -------------------------------------------------------------------------
268
+ /**
269
+ * Runs the three-stage topicality evaluation pipeline on a pre-computed
270
+ * embedding vector.
271
+ *
272
+ * Evaluation order:
273
+ * 1. Forbidden topic check (highest priority — immediate block/flag)
274
+ * 2. Off-topic check against allowed topics
275
+ * 3. Session drift check (only if drift detection is enabled and allowed
276
+ * topics are configured)
277
+ *
278
+ * @param embedding - Pre-computed embedding vector for the text.
279
+ * @param sessionId - Session identifier for drift tracking.
280
+ * @returns A {@link GuardrailEvaluationResult} if any check triggers, or
281
+ * `null` if all checks pass.
282
+ *
283
+ * @internal
284
+ */
285
+ evaluateEmbedding(embedding, sessionId) {
286
+ // ------------------------------------------------------------------
287
+ // Step 1: Check forbidden topics
288
+ // ------------------------------------------------------------------
289
+ if (this.forbiddenIndex) {
290
+ const forbiddenMatches = this.forbiddenIndex.matchByVector(embedding);
291
+ // Check if any forbidden topic exceeds the threshold.
292
+ for (const match of forbiddenMatches) {
293
+ if (match.similarity > this.forbiddenThreshold) {
294
+ // Determine action: 'block' (default) or 'flag'.
295
+ const action = this.options.forbiddenAction === 'flag'
296
+ ? GuardrailAction.FLAG
297
+ : GuardrailAction.BLOCK;
298
+ return {
299
+ action,
300
+ reason: `Message matches forbidden topic: ${match.topicName}`,
301
+ reasonCode: REASON_FORBIDDEN,
302
+ metadata: {
303
+ matchedTopic: match.topicId,
304
+ matchedTopicName: match.topicName,
305
+ similarity: match.similarity,
306
+ },
307
+ };
308
+ }
309
+ }
310
+ }
311
+ // ------------------------------------------------------------------
312
+ // Step 2: Check allowed topics (off-topic detection)
313
+ // ------------------------------------------------------------------
314
+ if (this.allowedIndex) {
315
+ const isOnTopic = this.allowedIndex.isOnTopicByVector(embedding, this.allowedThreshold);
316
+ if (!isOnTopic) {
317
+ // Get the nearest topic for metadata, even though it's below threshold.
318
+ const allMatches = this.allowedIndex.matchByVector(embedding);
319
+ const nearestTopic = allMatches.length > 0 ? allMatches[0] : null;
320
+ // Determine action based on offTopicAction option.
321
+ let action;
322
+ switch (this.options.offTopicAction) {
323
+ case 'block':
324
+ action = GuardrailAction.BLOCK;
325
+ break;
326
+ case 'redirect':
327
+ // Redirect maps to FLAG with metadata indicating redirection intent.
328
+ action = GuardrailAction.FLAG;
329
+ break;
330
+ default:
331
+ // Default: 'flag'
332
+ action = GuardrailAction.FLAG;
333
+ break;
334
+ }
335
+ return {
336
+ action,
337
+ reason: nearestTopic
338
+ ? `Message is off-topic. Nearest topic: ${nearestTopic.topicName} (similarity: ${nearestTopic.similarity.toFixed(3)})`
339
+ : 'Message is off-topic. No matching topics found.',
340
+ reasonCode: REASON_OFF_TOPIC,
341
+ metadata: {
342
+ nearestTopic: nearestTopic?.topicId ?? null,
343
+ nearestTopicName: nearestTopic?.topicName ?? null,
344
+ nearestSimilarity: nearestTopic?.similarity ?? 0,
345
+ },
346
+ };
347
+ }
348
+ }
349
+ // ------------------------------------------------------------------
350
+ // Step 3: Check session drift (only when drift detection is enabled
351
+ // and we have allowed topics to compare against)
352
+ // ------------------------------------------------------------------
353
+ if (this.driftTracker && this.allowedIndex) {
354
+ const driftResult = this.driftTracker.update(sessionId, embedding, this.allowedIndex);
355
+ if (driftResult.driftLimitExceeded) {
356
+ return {
357
+ // Drift is always a FLAG — it represents a gradual trend, not
358
+ // an immediate policy violation.
359
+ action: GuardrailAction.FLAG,
360
+ reason: `Session has drifted off-topic for ${driftResult.driftStreak} consecutive messages.`,
361
+ reasonCode: REASON_DRIFT,
362
+ metadata: {
363
+ driftStreak: driftResult.driftStreak,
364
+ currentSimilarity: driftResult.currentSimilarity,
365
+ nearestTopic: driftResult.nearestTopic?.topicId ?? null,
366
+ nearestTopicName: driftResult.nearestTopic?.topicName ?? null,
367
+ },
368
+ };
369
+ }
370
+ }
371
+ // All checks passed — no action needed.
372
+ return null;
373
+ }
374
+ // -------------------------------------------------------------------------
375
+ // Lazy index building
376
+ // -------------------------------------------------------------------------
377
+ /**
378
+ * Ensures that the allowed and forbidden embedding indices have been built.
379
+ *
380
+ * Called once before the first evaluation. Subsequent calls are no-ops
381
+ * (guarded by the `indicesBuilt` flag).
382
+ *
383
+ * @internal
384
+ */
385
+ async ensureIndicesBuilt() {
386
+ if (this.indicesBuilt) {
387
+ return;
388
+ }
389
+ // Build the forbidden-topic index if any forbidden topics are configured.
390
+ if (this.options.forbiddenTopics && this.options.forbiddenTopics.length > 0) {
391
+ this.forbiddenIndex = new TopicEmbeddingIndex(this.embeddingFn);
392
+ await this.forbiddenIndex.build(this.options.forbiddenTopics);
393
+ }
394
+ // Build the allowed-topic index if any allowed topics are configured.
395
+ if (this.options.allowedTopics && this.options.allowedTopics.length > 0) {
396
+ this.allowedIndex = new TopicEmbeddingIndex(this.embeddingFn);
397
+ await this.allowedIndex.build(this.options.allowedTopics);
398
+ }
399
+ this.indicesBuilt = true;
400
+ }
401
+ // -------------------------------------------------------------------------
402
+ // Registry-based embedding fallback
403
+ // -------------------------------------------------------------------------
404
+ /**
405
+ * Creates an embedding function that retrieves an EmbeddingManager from
406
+ * the shared service registry at call time.
407
+ *
408
+ * This fallback is used when no explicit `embeddingFn` is provided to
409
+ * the constructor. It throws if the EmbeddingManager service is not
410
+ * available in the registry.
411
+ *
412
+ * @returns An async embedding function.
413
+ * @internal
414
+ */
415
+ createRegistryEmbeddingFn() {
416
+ return async (texts) => {
417
+ // Attempt to retrieve the EmbeddingManager from the shared registry.
418
+ const em = await this.services.getOrCreate('agentos:topicality:embedding-manager', async () => {
419
+ throw new Error('EmbeddingManager not available in shared service registry. ' +
420
+ 'Provide an explicit embeddingFn or register an EmbeddingManager.');
421
+ });
422
+ return em.generateEmbeddings(texts);
423
+ };
424
+ }
425
+ }
426
+ //# sourceMappingURL=TopicalityGuardrail.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TopicalityGuardrail.js","sourceRoot":"","sources":["../src/TopicalityGuardrail.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * @fileoverview Pack factory for the Topicality Guardrail Extension Pack.
3
+ *
4
+ * Exports the main `createTopicalityPack()` factory that assembles the
5
+ * {@link TopicalityGuardrail} and the {@link CheckTopicTool} into a single
6
+ * {@link ExtensionPack} ready for registration with the AgentOS extension
7
+ * manager.
8
+ *
9
+ * Also exports a `createExtensionPack()` bridge function that conforms to
10
+ * the AgentOS manifest factory convention, delegating to
11
+ * `createTopicalityPack()` with options extracted from the
12
+ * {@link ExtensionPackContext}.
13
+ *
14
+ * ### Default behaviour (zero-config)
15
+ * When called without arguments, no topics are configured so the guardrail
16
+ * and tool are effectively no-ops. Callers should provide at least
17
+ * `allowedTopics` or `forbiddenTopics` for meaningful enforcement.
18
+ *
19
+ * ### Activation lifecycle
20
+ * Components are built eagerly at pack creation time for direct programmatic
21
+ * use. When the extension manager activates the pack, `onActivate` rebuilds
22
+ * all components with the manager's shared service registry so heavyweight
23
+ * resources (embedding models) are shared across the agent.
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * import { createTopicalityPack, TOPIC_PRESETS } from './topicality';
28
+ *
29
+ * const pack = createTopicalityPack({
30
+ * allowedTopics: TOPIC_PRESETS.customerSupport,
31
+ * forbiddenTopics: TOPIC_PRESETS.commonUnsafe,
32
+ * });
33
+ * ```
34
+ *
35
+ * @module agentos/extensions/packs/topicality
36
+ */
37
+ import type { ExtensionPack, ExtensionPackContext } from '@framers/agentos';
38
+ import type { TopicalityPackOptions } from './types';
39
+ /**
40
+ * Re-export all types from the topicality type definitions so consumers
41
+ * can import everything from a single entry point:
42
+ * ```ts
43
+ * import { createTopicalityPack, TOPIC_PRESETS } from './topicality';
44
+ * ```
45
+ */
46
+ export * from './types';
47
+ /**
48
+ * Create an {@link ExtensionPack} that bundles:
49
+ * - The {@link TopicalityGuardrail} guardrail (evaluates input & output
50
+ * against allowed/forbidden topics and drift detection).
51
+ * - The {@link CheckTopicTool} `check_topic` tool (on-demand topic analysis).
52
+ *
53
+ * @param options - Optional pack-level configuration. All properties have
54
+ * sensible defaults; see {@link TopicalityPackOptions}.
55
+ * @returns A fully-configured {@link ExtensionPack} with one guardrail
56
+ * descriptor and one tool descriptor.
57
+ */
58
+ export declare function createTopicalityPack(options?: TopicalityPackOptions): ExtensionPack;
59
+ /**
60
+ * AgentOS manifest factory function.
61
+ *
62
+ * Conforms to the convention expected by the extension loader when resolving
63
+ * packs from manifests. Extracts `options` from the {@link ExtensionPackContext}
64
+ * and delegates to {@link createTopicalityPack}.
65
+ *
66
+ * @param context - Manifest context containing optional pack options, secret
67
+ * resolver, and shared service registry.
68
+ * @returns A fully-configured {@link ExtensionPack}.
69
+ *
70
+ * @example Manifest entry:
71
+ * ```json
72
+ * {
73
+ * "packs": [
74
+ * {
75
+ * "module": "./topicality",
76
+ * "options": {
77
+ * "allowedTopics": [...],
78
+ * "forbiddenTopics": [...],
79
+ * "allowedThreshold": 0.4
80
+ * }
81
+ * }
82
+ * ]
83
+ * }
84
+ * ```
85
+ */
86
+ export declare function createExtensionPack(context: ExtensionPackContext): ExtensionPack;
87
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAG5E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAQrD;;;;;;GAMG;AACH,cAAc,SAAS,CAAC;AAMxB;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,aAAa,CAgMnF;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,aAAa,CAEhF"}