@kuralle-agents/core 0.3.9 → 0.3.11

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/dist/index.d.ts CHANGED
@@ -71,7 +71,7 @@ export { buildToolSet, toolToAiSdk, ToolApprovalDeniedError } from './tools/effe
71
71
  export type { Tool as EffectTool } from './types/effectTool.js';
72
72
  export type { AgentRoute } from './types/processors.js';
73
73
  export type { AgentConfig, Instructions } from './types/agentConfig.js';
74
- export type { Flow, FlowNode, Transition, CollectNode, DecideNode, ConfirmGate } from './types/flow.js';
74
+ export type { Flow, FlowNode, Transition, CollectNode, DecideNode, ConfirmGate, NodeGrounding, } from './types/flow.js';
75
75
  export { parseConfirmation } from './flow/confirmParse.js';
76
76
  export type { ConfirmVerdict } from './flow/confirmParse.js';
77
77
  export type { Route } from './types/route.js';
@@ -80,7 +80,7 @@ export type { HarnessStreamPart } from './types/stream.js';
80
80
  export type { ChoiceOption, ResolvedSelection } from './types/selection.js';
81
81
  export type { RunState, StepRecord } from './runtime/durable/types.js';
82
82
  export type { RunStore } from './runtime/durable/RunStore.js';
83
- export type { ChannelDriver, TextDriver, VoiceDriver } from './runtime/channels/index.js';
83
+ export type { ChannelDriver, TextDriver } from './runtime/channels/index.js';
84
84
  export type { TurnResult } from './types/channel.js';
85
85
  export type { RunContext } from './types/run-context.js';
86
86
  export { createRuntime, Runtime, type HarnessConfig, type RunOptions, } from './runtime/Runtime.js';
@@ -6,7 +6,7 @@ import { consumePendingUserInput } from './inputBuffer.js';
6
6
  import { runSilentExtraction } from './extractionTurn.js';
7
7
  import { applyPreTurnPolicies, applyPostTurnPolicies } from '../policies/agentTurn.js';
8
8
  import { resolveMaxSteps } from '../policies/limits.js';
9
- import { appendGatherBlocks, runGatherPhase } from '../grounding/index.js';
9
+ import { appendGatherBlocks, resolveNodeGatherScope, runGatherPhase } from '../grounding/index.js';
10
10
  export class TextDriver {
11
11
  toolDefs;
12
12
  maxSteps;
@@ -26,7 +26,8 @@ export class TextDriver {
26
26
  ctx.emit({ type: 'turn-end' });
27
27
  return { text: blocked, toolResults: [] };
28
28
  }
29
- const gather = await runGatherPhase(ctx);
29
+ const scope = resolveNodeGatherScope(replyNode, ctx.runState.state, ctx.runState.messages);
30
+ const gather = await runGatherPhase(ctx, scope);
30
31
  const out = { text: '', toolResults: [] };
31
32
  const model = replyNode.model ?? ctx.model;
32
33
  const nodeSystem = node.prompt || buildNodePrompt(replyNode, ctx.runState.state);
@@ -4,7 +4,7 @@ import { buildNodePrompt, resolveInstructions, composeSystem } from '../../flow/
4
4
  import { executeModelToolCall } from './executeModelTool.js';
5
5
  import { applyPreTurnPolicies, applyPostTurnPolicies } from '../policies/agentTurn.js';
6
6
  import { resolveMaxSteps } from '../policies/limits.js';
7
- import { appendGatherBlocks, runGatherPhase } from '../grounding/index.js';
7
+ import { appendGatherBlocks, resolveNodeGatherScope, runGatherPhase } from '../grounding/index.js';
8
8
  import { resolveVoiceGeminiTools } from './voiceTools.js';
9
9
  export class VoiceDriver {
10
10
  client;
@@ -29,7 +29,8 @@ export class VoiceDriver {
29
29
  ctx.emit({ type: 'turn-end' });
30
30
  return { text: blocked, toolResults: [] };
31
31
  }
32
- const gather = await runGatherPhase(ctx);
32
+ const scope = resolveNodeGatherScope(replyNode, ctx.runState.state, ctx.runState.messages);
33
+ const gather = await runGatherPhase(ctx, scope);
33
34
  const out = { text: '', toolResults: [] };
34
35
  const nodeSystem = node.prompt || buildNodePrompt(replyNode, ctx.runState.state);
35
36
  const baseSystem = composeSystem(ctx.baseInstructions, nodeSystem, ctx.runState.state);
@@ -64,8 +65,15 @@ export class VoiceDriver {
64
65
  return out;
65
66
  }
66
67
  async runStructured(node, ctx) {
67
- const system = composeSystem(ctx.baseInstructions, resolveInstructions(node.instructions, ctx.runState.state), ctx.runState.state);
68
+ const base = composeSystem(ctx.baseInstructions, resolveInstructions(node.instructions, ctx.runState.state), ctx.runState.state);
68
69
  const schema = node.schema;
70
+ // Parity with TextDriver: when the node offers choices, constrain the model
71
+ // to a single option id so an unconstrained string can't stall the decide.
72
+ const system = node.choices?.length
73
+ ? `${base}\n\nYou MUST pick exactly ONE option by its id. Valid ids: ${node.choices
74
+ .map((c) => c.id)
75
+ .join(', ')}. Respond with only the chosen id, nothing else.`
76
+ : base;
69
77
  const { object } = await generateObject({
70
78
  model: ctx.model,
71
79
  schema,
@@ -1,4 +1,8 @@
1
1
  export { TextDriver, buildNodePrompt } from './TextDriver.js';
2
+ // PAUSED: the realtime VoiceDriver is not on the primary (text) path and is not
3
+ // re-exported from the package's headline API. It stays here for the realtime
4
+ // stack (`@kuralle-agents/realtime-audio`) via the `/runtime` subpath. Text is
5
+ // the primary primitive; cascaded voice runs over text (see livekit-plugin).
2
6
  export { VoiceDriver } from './VoiceDriver.js';
3
7
  export { resolveVoiceGeminiTools, v2ToolsToGemini } from './voiceTools.js';
4
8
  export { setPendingUserInput, consumePendingUserInput, peekPendingUserInput } from './inputBuffer.js';
@@ -1,6 +1,7 @@
1
- import type { RunContext } from '../../types/run-context.js';
1
+ import type { GatherScope, RunContext } from '../../types/run-context.js';
2
+ export type { GatherScope } from '../../types/run-context.js';
2
3
  export interface GatherResult {
3
4
  retrievalBlock?: string;
4
5
  memoryBlock?: string;
5
6
  }
6
- export declare function runGatherPhase(ctx: RunContext): Promise<GatherResult>;
7
+ export declare function runGatherPhase(ctx: RunContext, scope?: GatherScope): Promise<GatherResult>;
@@ -1,5 +1,9 @@
1
- export async function runGatherPhase(ctx) {
2
- const retrievalBlock = ctx.autoRetrieve ? await ctx.autoRetrieve.retrieve(ctx) : undefined;
3
- const memoryBlock = ctx.memoryService?.preload ? await ctx.memoryService.preload(ctx) : undefined;
1
+ export async function runGatherPhase(ctx, scope) {
2
+ const retrievalBlock = ctx.autoRetrieve && scope?.knowledge?.autoRetrieve !== false
3
+ ? await ctx.autoRetrieve.retrieve(ctx, scope)
4
+ : undefined;
5
+ const memoryBlock = ctx.memoryService?.preload && scope?.memory?.preload !== false
6
+ ? await ctx.memoryService.preload(ctx, scope)
7
+ : undefined;
4
8
  return { retrievalBlock, memoryBlock };
5
9
  }
@@ -1,4 +1,5 @@
1
1
  export { appendGatherBlocks, buildAutoRetrieveProvider, buildKnowledgeProvider, } from './knowledge.js';
2
2
  export { buildMemoryService, resetMissingUserIdWarningsForTests, runMemoryIngest, warnMissingUserId, } from './memory.js';
3
- export { runGatherPhase, type GatherResult } from './gather.js';
3
+ export { runGatherPhase, type GatherResult, type GatherScope } from './gather.js';
4
+ export { resolveNodeGatherScope } from './nodeScope.js';
4
5
  export { createInMemoryKnowledgeConfig, createInMemoryKnowledgeRetriever, type InMemoryKnowledgeDocument, } from './inMemoryKnowledge.js';
@@ -1,4 +1,5 @@
1
1
  export { appendGatherBlocks, buildAutoRetrieveProvider, buildKnowledgeProvider, } from './knowledge.js';
2
2
  export { buildMemoryService, resetMissingUserIdWarningsForTests, runMemoryIngest, warnMissingUserId, } from './memory.js';
3
3
  export { runGatherPhase } from './gather.js';
4
+ export { resolveNodeGatherScope } from './nodeScope.js';
4
5
  export { createInMemoryKnowledgeConfig, createInMemoryKnowledgeRetriever, } from './inMemoryKnowledge.js';
@@ -35,22 +35,23 @@ export function buildAutoRetrieveProvider(provider, agent) {
35
35
  return undefined;
36
36
  }
37
37
  const overrides = agent.knowledge;
38
- const maxOutputTokens = provider.resolveConfig(overrides).maxOutputTokens;
39
38
  return {
40
- retrieve: async (ctx) => {
41
- const query = latestUserMessage(ctx);
39
+ retrieve: async (ctx, scope) => {
40
+ const query = scope?.query ?? latestUserMessage(ctx);
41
+ const merged = scope?.knowledge ? { ...overrides, ...scope.knowledge } : overrides;
42
42
  const cache = undefined;
43
- const { results, events } = await provider.retrieve(query || ' ', cache, overrides, false);
43
+ const { results, events } = await provider.retrieve(query || ' ', cache, merged, false);
44
44
  for (const event of events) {
45
45
  ctx.emit(event);
46
46
  }
47
- const compiled = provider.getCompiledKnowledge(overrides);
47
+ const compiled = provider.getCompiledKnowledge(merged);
48
48
  const retrievalResults = results.length > 0 ? results : [];
49
49
  const combined = [
50
50
  ...(compiled ? [{ text: compiled }] : []),
51
51
  ...retrievalResults.map((result) => ({ text: result.text })),
52
52
  ];
53
- return formatRetrievalBlock(combined, maxOutputTokens * 4);
53
+ const maxChars = provider.resolveConfig(merged).maxOutputTokens * 4;
54
+ return formatRetrievalBlock(combined, maxChars);
54
55
  },
55
56
  };
56
57
  }
@@ -31,16 +31,16 @@ export function buildMemoryService(service, agent) {
31
31
  }
32
32
  return {
33
33
  preload: preloadEnabled
34
- ? async (ctx) => {
34
+ ? async (ctx, scope) => {
35
35
  if (!ctx.session.userId) {
36
36
  warnMissingUserId(ctx.session.id);
37
37
  return undefined;
38
38
  }
39
- const userInput = latestUserMessage(ctx);
39
+ const userInput = scope?.query ?? latestUserMessage(ctx);
40
40
  if (!userInput.trim()) {
41
41
  return undefined;
42
42
  }
43
- const budget = agent.memory?.preload?.tokenBudget ?? 500;
43
+ const budget = scope?.memory?.tokenBudget ?? agent.memory?.preload?.tokenBudget ?? 500;
44
44
  const block = await preloadMemoryContext(service, ctx.session, userInput, budget);
45
45
  return block ? `\n\n${block}` : undefined;
46
46
  }
@@ -0,0 +1,4 @@
1
+ import type { ModelMessage } from 'ai';
2
+ import type { FlowState, ReplyNode } from '../../types/flow.js';
3
+ import type { GatherScope } from '../../types/run-context.js';
4
+ export declare function resolveNodeGatherScope(node: ReplyNode, state: FlowState, history: ModelMessage[]): GatherScope | undefined;
@@ -0,0 +1,11 @@
1
+ export function resolveNodeGatherScope(node, state, history) {
2
+ const g = node.grounding;
3
+ if (!g) {
4
+ return undefined;
5
+ }
6
+ return {
7
+ query: typeof g.query === 'function' ? g.query(state, history) : g.query,
8
+ knowledge: g.knowledge,
9
+ memory: g.memory,
10
+ };
11
+ }
@@ -1,5 +1,6 @@
1
- import type { LanguageModel, ToolSet } from 'ai';
1
+ import type { LanguageModel, ModelMessage, ToolSet } from 'ai';
2
2
  import type { Instructions } from './agentConfig.js';
3
+ import type { AgentKnowledgeOverrides } from './voice.js';
3
4
  import type { StandardSchemaV1 } from './standard-schema.js';
4
5
  import type { ContextStrategy } from './context.js';
5
6
  import type { TurnResult } from './channel.js';
@@ -17,6 +18,25 @@ export interface Flow {
17
18
  maxOscillations?: number;
18
19
  }
19
20
  export type FlowNode = ReplyNode | CollectNode | ActionNode | DecideNode;
21
+ /** Per-node retrieval/memory scoping (W3). All optional; omitting `grounding`
22
+ * entirely leaves the node grounded exactly as the agent-wide default. */
23
+ export interface NodeGrounding {
24
+ /** Retrieval/memory query for this node. A fixed topic string, or a function
25
+ * over current flow state + message history. Defaults to the last user message. */
26
+ query?: string | ((state: FlowState, history: ModelMessage[]) => string);
27
+ /** Node-scoped knowledge overrides, merged OVER the agent's (node wins). Most
28
+ * useful: `filter` (restrict to a node-specific doc subset) and `autoRetrieve:false`
29
+ * to skip retrieval for this node. `topK`/`maxOutputTokens` also honored. */
30
+ knowledge?: AgentKnowledgeOverrides & {
31
+ autoRetrieve?: boolean;
32
+ };
33
+ /** Node-scoped memory: `preload:false` skips memory preload for this node;
34
+ * `tokenBudget` overrides the agent default for this node only. */
35
+ memory?: {
36
+ preload?: boolean;
37
+ tokenBudget?: number;
38
+ };
39
+ }
20
40
  export type Transition = FlowNode | (() => FlowNode) | {
21
41
  goto: FlowNode | (() => FlowNode);
22
42
  data?: Record<string, unknown>;
@@ -35,6 +55,7 @@ export interface ReplyNode {
35
55
  tools?: ToolSet | ((state: FlowState) => ToolSet);
36
56
  model?: LanguageModel;
37
57
  context?: ContextStrategy;
58
+ grounding?: NodeGrounding;
38
59
  next?: (turn: TurnResult, state: FlowState) => Transition | Promise<Transition>;
39
60
  }
40
61
  export interface CollectNode {
@@ -9,6 +9,17 @@ import type { ValidationCapability } from '../capabilities/ValidationCapability.
9
9
  import type { Limits } from './guardrails.js';
10
10
  import type { AnyTool } from './effectTool.js';
11
11
  import type { Instructions } from './agentConfig.js';
12
+ import type { AgentKnowledgeOverrides } from './voice.js';
13
+ export interface GatherScope {
14
+ query?: string;
15
+ knowledge?: AgentKnowledgeOverrides & {
16
+ autoRetrieve?: boolean;
17
+ };
18
+ memory?: {
19
+ preload?: boolean;
20
+ tokenBudget?: number;
21
+ };
22
+ }
12
23
  export interface EffectToolExecutor {
13
24
  execute(args: {
14
25
  name: string;
@@ -23,10 +34,10 @@ export interface EffectToolExecutor {
23
34
  getTool?(name: string): AnyTool | undefined;
24
35
  }
25
36
  export interface AutoRetrieveProvider {
26
- retrieve(ctx: RunContext): Promise<string | undefined>;
37
+ retrieve(ctx: RunContext, scope?: GatherScope): Promise<string | undefined>;
27
38
  }
28
39
  export interface MemoryService {
29
- preload?(ctx: RunContext): Promise<string | undefined>;
40
+ preload?(ctx: RunContext, scope?: GatherScope): Promise<string | undefined>;
30
41
  ingest?(ctx: RunContext): Promise<void>;
31
42
  }
32
43
  export interface HookRunner {
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.3.9",
9
+ "version": "0.3.11",
10
10
  "description": "A framework for structured conversational AI agents",
11
11
  "publishConfig": {
12
12
  "access": "public"
@@ -97,7 +97,7 @@
97
97
  "dotenv": "^16.4.0",
98
98
  "typescript": "^5.3.0",
99
99
  "zod": "^3.23.0",
100
- "@kuralle-agents/realtime-audio": "0.3.9"
100
+ "@kuralle-agents/realtime-audio": "0.3.11"
101
101
  },
102
102
  "dependencies": {
103
103
  "chrono-node": "^2.6.0",