@kuralle-agents/core 0.8.0 → 0.8.5

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 (39) hide show
  1. package/README.md +6 -0
  2. package/dist/capabilities/validators/groundingValidator.d.ts +21 -0
  3. package/dist/capabilities/validators/groundingValidator.js +109 -0
  4. package/dist/escalation/escalation.d.ts +20 -0
  5. package/dist/escalation/escalation.js +85 -0
  6. package/dist/escalation/types.d.ts +40 -0
  7. package/dist/eval/simulation.d.ts +99 -0
  8. package/dist/eval/simulation.js +168 -0
  9. package/dist/index.d.ts +22 -2
  10. package/dist/index.js +10 -0
  11. package/dist/memory/factMemoryService.d.ts +28 -0
  12. package/dist/memory/factMemoryService.js +137 -0
  13. package/dist/memory/index.d.ts +1 -0
  14. package/dist/memory/index.js +1 -0
  15. package/dist/processors/builtin/moderationGuard.d.ts +24 -0
  16. package/dist/processors/builtin/moderationGuard.js +101 -0
  17. package/dist/processors/builtin/piiGuard.d.ts +41 -0
  18. package/dist/processors/builtin/piiGuard.js +143 -0
  19. package/dist/processors/builtin/promptInjectionGuard.d.ts +16 -0
  20. package/dist/processors/builtin/promptInjectionGuard.js +28 -0
  21. package/dist/runtime/Runtime.d.ts +53 -0
  22. package/dist/runtime/Runtime.js +184 -10
  23. package/dist/runtime/channels/TextDriver.js +6 -0
  24. package/dist/runtime/channels/VoiceDriver.js +6 -0
  25. package/dist/runtime/closeRun.js +4 -0
  26. package/dist/runtime/compaction.d.ts +51 -0
  27. package/dist/runtime/compaction.js +111 -0
  28. package/dist/runtime/hostLoop.d.ts +2 -0
  29. package/dist/runtime/hostLoop.js +1 -1
  30. package/dist/runtime/openRun.d.ts +5 -0
  31. package/dist/runtime/openRun.js +17 -0
  32. package/dist/runtime/policies/agentTurn.d.ts +4 -0
  33. package/dist/runtime/policies/agentTurn.js +35 -4
  34. package/dist/scheduler/index.d.ts +89 -0
  35. package/dist/scheduler/index.js +104 -0
  36. package/dist/types/channel.d.ts +2 -0
  37. package/dist/types/stream.d.ts +29 -0
  38. package/guides/GUARDRAILS.md +44 -0
  39. package/package.json +2 -2
@@ -0,0 +1,89 @@
1
+ import { z } from 'zod';
2
+ import type { Tool } from '../types/effectTool.js';
3
+ import type { HarnessStreamPart, TurnHandle } from '../types/stream.js';
4
+ /**
5
+ * Deferred-work scheduling for proactive (agent-initiated) turns.
6
+ *
7
+ * One `Scheduler` contract across the framework: the engagement layer's
8
+ * broadcast/drip jobs and the runtime's wake turns ride the same interface.
9
+ * Backends: `createInProcessScheduler` (dev, timer-based), Cloudflare DO
10
+ * alarms via `@kuralle-agents/cf-agent`, or any queue (BullMQ, Cloud Tasks)
11
+ * implementing the two methods.
12
+ */
13
+ export interface ScheduledJob {
14
+ kind: string;
15
+ payload: Record<string, unknown>;
16
+ }
17
+ export interface Scheduler {
18
+ enqueue(job: ScheduledJob, opts?: {
19
+ delayMs?: number;
20
+ }): Promise<string>;
21
+ cancel(jobId: string): Promise<void>;
22
+ }
23
+ export type InjectableTimer = {
24
+ set(fn: () => void, ms: number): unknown;
25
+ clear(handle: unknown): void;
26
+ };
27
+ /**
28
+ * Default in-process scheduler (timer-based). For single-process/dev.
29
+ * Inject a durable adapter (DO alarms, BullMQ, Cloud Tasks) for
30
+ * multi-process / serverless.
31
+ */
32
+ export declare function createInProcessScheduler(opts: {
33
+ run: (job: ScheduledJob) => void | Promise<void>;
34
+ timer?: InjectableTimer;
35
+ }): Scheduler;
36
+ export declare const WAKE_JOB_KIND = "kuralle.wake";
37
+ export interface WakeOptions {
38
+ /** Why the agent is waking — composed into the wake note the model sees. */
39
+ reason: string;
40
+ /** Structured context for the wake turn (e.g. the abandoned cart id). */
41
+ payload?: Record<string, unknown>;
42
+ }
43
+ export interface WakeJobPayload extends WakeOptions {
44
+ sessionId: string;
45
+ }
46
+ export declare function wakeJob(wake: WakeJobPayload): ScheduledJob;
47
+ export declare function isWakeJob(job: ScheduledJob): boolean;
48
+ /** What a wake turn produced — handed to the host's delivery function. */
49
+ export interface WakeDelivery {
50
+ sessionId: string;
51
+ reason: string;
52
+ payload?: Record<string, unknown>;
53
+ /** Full stream of the wake turn (text, tool events, interactive parts…). */
54
+ parts: HarnessStreamPart[];
55
+ /** Concatenated assistant text of the wake turn. */
56
+ text: string;
57
+ }
58
+ /** The runtime surface a wake runner needs (satisfied by `Runtime`). */
59
+ export interface WakeRunnable {
60
+ run(opts: {
61
+ sessionId: string;
62
+ wake: WakeOptions;
63
+ }): TurnHandle;
64
+ }
65
+ /**
66
+ * Build the scheduler executor for wake jobs: runs the agent-initiated turn
67
+ * and hands the produced parts to `deliver` (e.g. the messaging outbound
68
+ * pipeline — which keeps the send window-safe). Compose with your own job
69
+ * kinds: `run: (job) => isWakeJob(job) ? runWake(job) : runMine(job)`.
70
+ */
71
+ export declare function createWakeJobRunner(runtime: WakeRunnable, opts: {
72
+ deliver: (delivery: WakeDelivery) => Promise<void>;
73
+ onError?: (error: unknown, job: ScheduledJob) => void;
74
+ }): (job: ScheduledJob) => Promise<void>;
75
+ declare const scheduleFollowupInput: z.ZodObject<{
76
+ delayMinutes: z.ZodNumber;
77
+ reason: z.ZodString;
78
+ }, z.core.$strip>;
79
+ /**
80
+ * Durable tool letting the agent schedule its own follow-up wake turn
81
+ * ("I'll check back in an hour"). Safe for `globalTools` — it only schedules;
82
+ * the wake turn itself goes through the full guard/window pipeline.
83
+ */
84
+ export declare function createScheduleFollowupTool(scheduler: Scheduler): Tool<z.infer<typeof scheduleFollowupInput>, {
85
+ scheduled: boolean;
86
+ jobId: string;
87
+ inMinutes: number;
88
+ }>;
89
+ export {};
@@ -0,0 +1,104 @@
1
+ import { z } from 'zod';
2
+ import { defineTool } from '../tools/effect/defineTool.js';
3
+ const defaultTimer = {
4
+ set: (fn, ms) => setTimeout(fn, ms),
5
+ clear: (handle) => clearTimeout(handle),
6
+ };
7
+ /**
8
+ * Default in-process scheduler (timer-based). For single-process/dev.
9
+ * Inject a durable adapter (DO alarms, BullMQ, Cloud Tasks) for
10
+ * multi-process / serverless.
11
+ */
12
+ export function createInProcessScheduler(opts) {
13
+ const timer = opts.timer ?? defaultTimer;
14
+ let nextJobId = 0;
15
+ const handles = new Map();
16
+ return {
17
+ async enqueue(job, options) {
18
+ const jobId = String(++nextJobId);
19
+ const delayMs = options?.delayMs ?? 0;
20
+ const handle = timer.set(() => {
21
+ handles.delete(jobId);
22
+ void opts.run(job);
23
+ }, delayMs);
24
+ handles.set(jobId, handle);
25
+ return jobId;
26
+ },
27
+ async cancel(jobId) {
28
+ const handle = handles.get(jobId);
29
+ if (handle === undefined)
30
+ return;
31
+ timer.clear(handle);
32
+ handles.delete(jobId);
33
+ },
34
+ };
35
+ }
36
+ // ── Wake jobs (agent-initiated turns) ────────────────────────────────────────
37
+ export const WAKE_JOB_KIND = 'kuralle.wake';
38
+ export function wakeJob(wake) {
39
+ return { kind: WAKE_JOB_KIND, payload: { ...wake } };
40
+ }
41
+ export function isWakeJob(job) {
42
+ return job.kind === WAKE_JOB_KIND;
43
+ }
44
+ /**
45
+ * Build the scheduler executor for wake jobs: runs the agent-initiated turn
46
+ * and hands the produced parts to `deliver` (e.g. the messaging outbound
47
+ * pipeline — which keeps the send window-safe). Compose with your own job
48
+ * kinds: `run: (job) => isWakeJob(job) ? runWake(job) : runMine(job)`.
49
+ */
50
+ export function createWakeJobRunner(runtime, opts) {
51
+ return async (job) => {
52
+ if (!isWakeJob(job)) {
53
+ return;
54
+ }
55
+ const { sessionId, reason, payload } = job.payload;
56
+ try {
57
+ const handle = runtime.run({ sessionId, wake: { reason, payload } });
58
+ const parts = [];
59
+ let text = '';
60
+ for await (const part of handle.events) {
61
+ parts.push(part);
62
+ if (part.type === 'text-delta') {
63
+ text += part.delta;
64
+ }
65
+ }
66
+ const result = await handle;
67
+ await opts.deliver({ sessionId, reason, payload, parts, text: text || result.text });
68
+ }
69
+ catch (error) {
70
+ if (opts.onError) {
71
+ opts.onError(error, job);
72
+ }
73
+ else {
74
+ console.warn(`[Kuralle] wake turn failed for session ${sessionId}:`, error);
75
+ }
76
+ }
77
+ };
78
+ }
79
+ const scheduleFollowupInput = z.object({
80
+ delayMinutes: z.number().min(1).describe('How many minutes from now to follow up'),
81
+ reason: z
82
+ .string()
83
+ .describe('Why the follow-up is needed, e.g. "user said they would decide after lunch"'),
84
+ });
85
+ /**
86
+ * Durable tool letting the agent schedule its own follow-up wake turn
87
+ * ("I'll check back in an hour"). Safe for `globalTools` — it only schedules;
88
+ * the wake turn itself goes through the full guard/window pipeline.
89
+ */
90
+ export function createScheduleFollowupTool(scheduler) {
91
+ return defineTool({
92
+ name: 'schedule_followup',
93
+ description: 'Schedule a follow-up message to the user after a delay. Use when the user asks you to check back later, or a pending task warrants a proactive follow-up.',
94
+ input: scheduleFollowupInput,
95
+ execute: async (args, ctx) => {
96
+ const sessionId = ctx?.session.id;
97
+ if (!sessionId) {
98
+ throw new Error('schedule_followup requires a session context');
99
+ }
100
+ const jobId = await scheduler.enqueue(wakeJob({ sessionId, reason: args.reason }), { delayMs: Math.round(args.delayMinutes * 60_000) });
101
+ return { scheduled: true, jobId, inMinutes: args.delayMinutes };
102
+ },
103
+ });
104
+ }
@@ -4,6 +4,7 @@ import type { RunContext } from './run-context.js';
4
4
  import type { FlowNode } from './flow.js';
5
5
  import type { AnyTool } from './effectTool.js';
6
6
  import type { DispatchMode } from '../runtime/dispatchMode.js';
7
+ import type { EscalationReason } from '../escalation/types.js';
7
8
  export type DriverOutputCapability = 'kuralle-controlled-text' | 'kuralle-controlled-tts' | 'native-realtime';
8
9
  export interface HostControlContext {
9
10
  dispatchMode: DispatchMode;
@@ -67,6 +68,7 @@ export type TurnControl = {
67
68
  } | {
68
69
  type: 'escalate';
69
70
  reason: string;
71
+ category?: EscalationReason;
70
72
  } | {
71
73
  type: 'recover';
72
74
  reason?: string;
@@ -1,5 +1,6 @@
1
1
  import type { ConversationOutcome } from '../outcomes/types.js';
2
2
  import type { ChoiceOption } from './selection.js';
3
+ import type { EscalationReason } from '../escalation/types.js';
3
4
  /**
4
5
  * Authoritative runtime stream union (`runFlow` / `Runtime` emit).
5
6
  * `types/voice.ts` defines a separate voice/realtime union that intentionally
@@ -77,6 +78,34 @@ export type HarnessStreamPart = {
77
78
  rationale: string;
78
79
  userFacingMessage: string;
79
80
  handlerOutcome?: 'queued' | 'connected' | 'failed';
81
+ } | {
82
+ /** An agent-initiated (scheduled wake) turn — there is no new user message. */
83
+ type: 'wake';
84
+ reason: string;
85
+ } | {
86
+ type: 'escalation';
87
+ reason: string;
88
+ category?: EscalationReason;
89
+ /** Result of the configured escalation handler. */
90
+ outcome: 'queued' | 'connected' | 'failed';
91
+ /** The LLM handoff brief included in the request, when generated. */
92
+ summary?: string;
93
+ } | {
94
+ type: 'context-compacted';
95
+ /** Estimated history tokens before/after compaction. */
96
+ beforeTokens: number;
97
+ afterTokens: number;
98
+ /** Number of older messages folded into the summary. */
99
+ summarizedCount: number;
100
+ } | {
101
+ type: 'compaction-skipped';
102
+ reason: string;
103
+ } | {
104
+ type: 'context-overflow-recovered';
105
+ /** Partial assistant/tool messages stripped from the failed turn. */
106
+ strippedCount: number;
107
+ /** Whether the forced post-recovery compaction actually compacted. */
108
+ compacted: boolean;
80
109
  } | {
81
110
  type: 'error';
82
111
  error: string;
@@ -65,6 +65,50 @@ const runtime = new Runtime({
65
65
  });
66
66
  ```
67
67
 
68
+ ## Built-in Guards (0.8.5)
69
+
70
+ Production-ready processors and validators ship with core — wire them into
71
+ `AgentConfig.guardrails` / `AgentConfig.validate`:
72
+
73
+ ```ts
74
+ import {
75
+ createPromptInjectionGuard,
76
+ createPiiInputGuard,
77
+ createPiiOutputGuard,
78
+ createModerationGuard,
79
+ createGroundingValidator,
80
+ } from '@kuralle-agents/core';
81
+
82
+ const agent = defineAgent({
83
+ id: 'shop',
84
+ instructions: '...',
85
+ guardrails: {
86
+ input: [
87
+ createPromptInjectionGuard(), // deterministic injection patterns → block
88
+ createPiiInputGuard(), // Luhn-checked cards + emails → redact (PCI default)
89
+ createModerationGuard({ model: controlModel }), // LLM policy classifier → block
90
+ ],
91
+ output: [createPiiOutputGuard()],
92
+ },
93
+ validate: [
94
+ // state-grounded "no invented actions" gate — rewrite-not-block
95
+ createGroundingValidator({ model: controlModel }),
96
+ ],
97
+ });
98
+ ```
99
+
100
+ - PII detectors default to `['credit-card', 'email']`; `phone`/`iban` are
101
+ opt-in (they collide with order ids). Cards are Luhn-validated, IBANs
102
+ checksum-validated — order numbers don't false-positive.
103
+ - The moderation guard fails **open** by default (`onError: 'block'` for
104
+ zero-tolerance deployments); deterministic guards still run during an
105
+ outage.
106
+ - The grounding validator flags completed-action claims unsupported by this
107
+ turn's tool calls, flow state, or citations and rewrites them out; if no
108
+ safe rewrite exists, it blocks.
109
+ - A pre-turn block emits a `safety-blocked` stream part with the moderator id
110
+ and rationale, then the user-facing message.
111
+
68
112
  ## Output Redaction
69
113
 
70
114
  `outputRedaction` is a defense-in-depth filter for streamed text.
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "url": "git+https://github.com/kuralle/kuralle-agents.git",
7
7
  "directory": "packages/kuralle-core"
8
8
  },
9
- "version": "0.8.0",
9
+ "version": "0.8.5",
10
10
  "description": "A framework for structured conversational AI agents",
11
11
  "publishConfig": {
12
12
  "access": "public"
@@ -103,7 +103,7 @@
103
103
  "typescript": "^5.3.0",
104
104
  "vitest": "^3.2.4",
105
105
  "zod": "^4.0.0",
106
- "@kuralle-agents/realtime-audio": "0.8.0"
106
+ "@kuralle-agents/realtime-audio": "0.8.5"
107
107
  },
108
108
  "dependencies": {
109
109
  "chrono-node": "^2.6.0"