@kuralle-agents/core 0.11.0 → 0.13.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 (99) hide show
  1. package/README.md +85 -0
  2. package/dist/capabilities/LivePromptAssembler.js +1 -0
  3. package/dist/flow/collectDigression.d.ts +5 -2
  4. package/dist/flow/collectDigression.js +48 -10
  5. package/dist/flow/collectUntilComplete.js +9 -1
  6. package/dist/flow/extraction.d.ts +1 -0
  7. package/dist/flow/extraction.js +4 -0
  8. package/dist/flow/runFlow.js +63 -5
  9. package/dist/index.d.ts +9 -1
  10. package/dist/index.js +5 -0
  11. package/dist/prompts/PromptBuilder.js +7 -0
  12. package/dist/prompts/types.d.ts +2 -0
  13. package/dist/runtime/InMemoryRetrievalCache.d.ts +25 -0
  14. package/dist/runtime/InMemoryRetrievalCache.js +59 -0
  15. package/dist/runtime/KnowledgeProvider.js +6 -1
  16. package/dist/runtime/Runtime.d.ts +44 -0
  17. package/dist/runtime/Runtime.js +196 -13
  18. package/dist/runtime/TokenAccumulator.d.ts +7 -0
  19. package/dist/runtime/TokenAccumulator.js +7 -0
  20. package/dist/runtime/TraceRecorder.d.ts +33 -0
  21. package/dist/runtime/TraceRecorder.js +290 -0
  22. package/dist/runtime/buildAgentToolSurface.js +4 -0
  23. package/dist/runtime/channels/TextDriver.js +45 -22
  24. package/dist/runtime/channels/executeModelTool.d.ts +8 -1
  25. package/dist/runtime/channels/executeModelTool.js +70 -1
  26. package/dist/runtime/channels/inputBuffer.d.ts +1 -0
  27. package/dist/runtime/channels/inputBuffer.js +9 -0
  28. package/dist/runtime/closeRun.js +11 -8
  29. package/dist/runtime/compaction.d.ts +2 -0
  30. package/dist/runtime/compaction.js +3 -2
  31. package/dist/runtime/ctx.js +114 -36
  32. package/dist/runtime/durable/RunStore.d.ts +16 -0
  33. package/dist/runtime/durable/RunStore.js +6 -0
  34. package/dist/runtime/durable/SessionRunStore.d.ts +7 -2
  35. package/dist/runtime/durable/SessionRunStore.js +136 -34
  36. package/dist/runtime/durable/idempotency.d.ts +1 -0
  37. package/dist/runtime/durable/idempotency.js +3 -0
  38. package/dist/runtime/durable/replay.js +7 -2
  39. package/dist/runtime/durable/types.d.ts +11 -0
  40. package/dist/runtime/goals.d.ts +18 -0
  41. package/dist/runtime/goals.js +158 -0
  42. package/dist/runtime/grounding/knowledge.js +1 -1
  43. package/dist/runtime/handoffContinuation.d.ts +20 -0
  44. package/dist/runtime/handoffContinuation.js +30 -0
  45. package/dist/runtime/handoffOscillation.d.ts +6 -0
  46. package/dist/runtime/handoffOscillation.js +17 -0
  47. package/dist/runtime/hostLoop.js +11 -0
  48. package/dist/runtime/index.d.ts +3 -1
  49. package/dist/runtime/index.js +1 -0
  50. package/dist/runtime/openRun.d.ts +2 -0
  51. package/dist/runtime/openRun.js +46 -17
  52. package/dist/runtime/policies/limits.d.ts +1 -0
  53. package/dist/runtime/policies/limits.js +3 -0
  54. package/dist/runtime/resolveAgentWorkspace.d.ts +3 -0
  55. package/dist/runtime/resolveAgentWorkspace.js +6 -2
  56. package/dist/runtime/select.d.ts +5 -1
  57. package/dist/runtime/select.js +30 -1
  58. package/dist/runtime/turnTokenUsage.d.ts +28 -0
  59. package/dist/runtime/turnTokenUsage.js +70 -0
  60. package/dist/session/SessionStore.d.ts +6 -0
  61. package/dist/session/SessionStore.js +12 -1
  62. package/dist/session/stores/MemoryStore.d.ts +1 -1
  63. package/dist/session/stores/MemoryStore.js +16 -2
  64. package/dist/session/testing.d.ts +6 -1
  65. package/dist/session/testing.js +34 -0
  66. package/dist/session/utils.d.ts +3 -0
  67. package/dist/session/utils.js +23 -0
  68. package/dist/tools/effect/ToolExecutor.js +10 -2
  69. package/dist/tools/effect/defineTool.d.ts +3 -0
  70. package/dist/tools/effect/defineTool.js +3 -0
  71. package/dist/tools/fs/caps.d.ts +21 -0
  72. package/dist/tools/fs/caps.js +59 -0
  73. package/dist/tools/fs/createFsTool.js +68 -7
  74. package/dist/tools/fs/createShellTool.d.ts +7 -0
  75. package/dist/tools/fs/createShellTool.js +90 -0
  76. package/dist/tracing/MemoryTraceStore.d.ts +16 -0
  77. package/dist/tracing/MemoryTraceStore.js +56 -0
  78. package/dist/tracing/OtelTraceSink.d.ts +127 -0
  79. package/dist/tracing/OtelTraceSink.js +101 -0
  80. package/dist/tracing/TraceStore.d.ts +19 -0
  81. package/dist/tracing/TraceStore.js +32 -0
  82. package/dist/tracing/index.d.ts +3 -0
  83. package/dist/tracing/index.js +3 -0
  84. package/dist/tracing/testing.d.ts +3 -0
  85. package/dist/tracing/testing.js +45 -0
  86. package/dist/types/agentConfig.d.ts +4 -1
  87. package/dist/types/channel.d.ts +14 -1
  88. package/dist/types/effectTool.d.ts +6 -0
  89. package/dist/types/index.d.ts +1 -0
  90. package/dist/types/index.js +1 -0
  91. package/dist/types/run-context.d.ts +15 -2
  92. package/dist/types/session.d.ts +10 -0
  93. package/dist/types/shell.d.ts +15 -0
  94. package/dist/types/shell.js +1 -0
  95. package/dist/types/stream.d.ts +8 -0
  96. package/dist/types/trace.d.ts +50 -0
  97. package/dist/types/trace.js +1 -0
  98. package/guides/EXAMPLE_VERIFICATION.md +4 -4
  99. package/package.json +14 -4
@@ -0,0 +1,127 @@
1
+ import type { AgentSpan } from '../types/trace.js';
2
+ import type { TraceSink } from './TraceStore.js';
3
+ export interface OtelTraceSinkOptions {
4
+ endpoint: string;
5
+ headers?: Record<string, string>;
6
+ serviceName?: string;
7
+ batchSize?: number;
8
+ fetch?: typeof fetch;
9
+ }
10
+ export declare class OtelTraceSink implements TraceSink {
11
+ private readonly options;
12
+ private readonly pending;
13
+ private exporting?;
14
+ constructor(options: OtelTraceSinkOptions);
15
+ write(span: AgentSpan): Promise<void> | void;
16
+ flush(): Promise<void>;
17
+ private export;
18
+ }
19
+ export declare function otelSink(options: OtelTraceSinkOptions): OtelTraceSink;
20
+ export interface LangfuseSinkOptions {
21
+ endpoint?: string;
22
+ publicKey: string;
23
+ secretKey: string;
24
+ serviceName?: string;
25
+ batchSize?: number;
26
+ fetch?: typeof fetch;
27
+ }
28
+ export declare function langfuseSink(options: LangfuseSinkOptions): OtelTraceSink;
29
+ export declare function toOtlpPayload(spans: AgentSpan[], serviceName?: string): {
30
+ resourceSpans: {
31
+ resource: {
32
+ attributes: {
33
+ key: string;
34
+ value: {
35
+ stringValue: string;
36
+ };
37
+ }[];
38
+ };
39
+ scopeSpans: {
40
+ scope: {
41
+ name: string;
42
+ };
43
+ spans: {
44
+ name: string;
45
+ kind: number;
46
+ startTimeUnixNano: string;
47
+ endTimeUnixNano: string;
48
+ attributes: ({
49
+ key: string;
50
+ value: {
51
+ boolValue: boolean;
52
+ intValue?: undefined;
53
+ doubleValue?: undefined;
54
+ stringValue?: undefined;
55
+ };
56
+ } | {
57
+ key: string;
58
+ value: {
59
+ intValue: string;
60
+ boolValue?: undefined;
61
+ doubleValue?: undefined;
62
+ stringValue?: undefined;
63
+ };
64
+ } | {
65
+ key: string;
66
+ value: {
67
+ doubleValue: number;
68
+ boolValue?: undefined;
69
+ intValue?: undefined;
70
+ stringValue?: undefined;
71
+ };
72
+ } | {
73
+ key: string;
74
+ value: {
75
+ stringValue: string;
76
+ boolValue?: undefined;
77
+ intValue?: undefined;
78
+ doubleValue?: undefined;
79
+ };
80
+ })[];
81
+ events: {
82
+ name: string;
83
+ timeUnixNano: string;
84
+ attributes: ({
85
+ key: string;
86
+ value: {
87
+ boolValue: boolean;
88
+ intValue?: undefined;
89
+ doubleValue?: undefined;
90
+ stringValue?: undefined;
91
+ };
92
+ } | {
93
+ key: string;
94
+ value: {
95
+ intValue: string;
96
+ boolValue?: undefined;
97
+ doubleValue?: undefined;
98
+ stringValue?: undefined;
99
+ };
100
+ } | {
101
+ key: string;
102
+ value: {
103
+ doubleValue: number;
104
+ boolValue?: undefined;
105
+ intValue?: undefined;
106
+ stringValue?: undefined;
107
+ };
108
+ } | {
109
+ key: string;
110
+ value: {
111
+ stringValue: string;
112
+ boolValue?: undefined;
113
+ intValue?: undefined;
114
+ doubleValue?: undefined;
115
+ };
116
+ })[];
117
+ }[];
118
+ status: {
119
+ code: number;
120
+ };
121
+ parentSpanId?: string | undefined;
122
+ traceId: string;
123
+ spanId: string;
124
+ }[];
125
+ }[];
126
+ }[];
127
+ };
@@ -0,0 +1,101 @@
1
+ export class OtelTraceSink {
2
+ options;
3
+ pending = [];
4
+ exporting;
5
+ constructor(options) {
6
+ this.options = options;
7
+ }
8
+ write(span) {
9
+ this.pending.push(structuredClone(span));
10
+ if (this.pending.length >= (this.options.batchSize ?? 32))
11
+ return this.flush();
12
+ }
13
+ async flush() {
14
+ if (this.exporting)
15
+ await this.exporting;
16
+ if (this.pending.length === 0)
17
+ return;
18
+ const spans = this.pending.splice(0);
19
+ this.exporting = this.export(spans).finally(() => { this.exporting = undefined; });
20
+ await this.exporting;
21
+ }
22
+ async export(spans) {
23
+ const transport = this.options.fetch ?? globalThis.fetch;
24
+ const response = await transport(otlpUrl(this.options.endpoint), {
25
+ method: 'POST',
26
+ headers: { 'content-type': 'application/json', ...this.options.headers },
27
+ body: JSON.stringify(toOtlpPayload(spans, this.options.serviceName)),
28
+ });
29
+ if (!response.ok)
30
+ throw new Error(`OTLP trace export failed: ${response.status}`);
31
+ }
32
+ }
33
+ export function otelSink(options) {
34
+ return new OtelTraceSink(options);
35
+ }
36
+ export function langfuseSink(options) {
37
+ return new OtelTraceSink({
38
+ endpoint: options.endpoint ?? 'https://cloud.langfuse.com/api/public/otel',
39
+ headers: {
40
+ Authorization: `Basic ${btoa(`${options.publicKey}:${options.secretKey}`)}`,
41
+ 'x-langfuse-ingestion-version': '4',
42
+ },
43
+ serviceName: options.serviceName,
44
+ batchSize: options.batchSize,
45
+ fetch: options.fetch,
46
+ });
47
+ }
48
+ export function toOtlpPayload(spans, serviceName = 'kuralle-agent') {
49
+ return {
50
+ resourceSpans: [{
51
+ resource: { attributes: [{ key: 'service.name', value: { stringValue: serviceName } }] },
52
+ scopeSpans: [{
53
+ scope: { name: '@kuralle-agents/core' },
54
+ spans: spans.map(toOtlpSpan),
55
+ }],
56
+ }],
57
+ };
58
+ }
59
+ function toOtlpSpan(span) {
60
+ return {
61
+ traceId: normalizeHex(span.traceId, 32),
62
+ spanId: normalizeHex(span.spanId, 16),
63
+ ...(span.parentSpanId ? { parentSpanId: normalizeHex(span.parentSpanId, 16) } : {}),
64
+ name: span.name,
65
+ kind: 1,
66
+ startTimeUnixNano: millisecondsToNanos(span.startTime),
67
+ endTimeUnixNano: millisecondsToNanos(span.endTime ?? span.startTime),
68
+ attributes: [
69
+ otlpAttribute('kuralle.kind', span.kind),
70
+ ...Object.entries(span.attributes)
71
+ .filter(([, value]) => value !== undefined)
72
+ .map(([key, value]) => otlpAttribute(`kuralle.${key}`, value)),
73
+ ],
74
+ events: (span.events ?? []).map((event) => ({
75
+ name: event.name,
76
+ timeUnixNano: millisecondsToNanos(event.time),
77
+ attributes: Object.entries(event.attributes ?? {}).map(([key, value]) => otlpAttribute(`kuralle.${key}`, value)),
78
+ })),
79
+ status: { code: span.status === 'error' ? 2 : 1 },
80
+ };
81
+ }
82
+ function otlpAttribute(key, value) {
83
+ if (typeof value === 'boolean')
84
+ return { key, value: { boolValue: value } };
85
+ if (typeof value === 'number' && Number.isInteger(value))
86
+ return { key, value: { intValue: String(value) } };
87
+ if (typeof value === 'number')
88
+ return { key, value: { doubleValue: value } };
89
+ return { key, value: { stringValue: typeof value === 'string' ? value : JSON.stringify(value) } };
90
+ }
91
+ function normalizeHex(value, length) {
92
+ const hex = value.toLowerCase().replace(/[^0-9a-f]/g, '');
93
+ return hex.padStart(length, '0').slice(-length);
94
+ }
95
+ function millisecondsToNanos(value) {
96
+ return (BigInt(Math.trunc(value)) * 1000000n).toString();
97
+ }
98
+ function otlpUrl(endpoint) {
99
+ const trimmed = endpoint.replace(/\/$/, '');
100
+ return trimmed.endsWith('/v1/traces') ? trimmed : `${trimmed}/v1/traces`;
101
+ }
@@ -0,0 +1,19 @@
1
+ import type { AgentSpan, AgentTrace } from '../types/trace.js';
2
+ export interface TraceListWindow {
3
+ from?: Date;
4
+ to?: Date;
5
+ limit?: number;
6
+ }
7
+ export interface TraceSink {
8
+ write(span: AgentSpan): void | Promise<void>;
9
+ flush?(): Promise<void>;
10
+ }
11
+ export interface TraceStore extends TraceSink {
12
+ putSpan(span: AgentSpan): void | Promise<void>;
13
+ getTrace(traceId: string): Promise<AgentTrace | null>;
14
+ listTraces(sessionId: string, window?: TraceListWindow): Promise<AgentTrace[]>;
15
+ cleanup?(maxAgeMs: number): Promise<number>;
16
+ }
17
+ export declare function isTraceStore(sink: TraceSink): sink is TraceStore;
18
+ export declare function traceFromSpans(spans: AgentSpan[]): AgentTrace | null;
19
+ export declare function cloneSpan(span: AgentSpan): AgentSpan;
@@ -0,0 +1,32 @@
1
+ export function isTraceStore(sink) {
2
+ const value = sink;
3
+ return typeof value.putSpan === 'function' &&
4
+ typeof value.getTrace === 'function' &&
5
+ typeof value.listTraces === 'function';
6
+ }
7
+ export function traceFromSpans(spans) {
8
+ if (spans.length === 0)
9
+ return null;
10
+ const ordered = spans.map(cloneSpan).sort((a, b) => a.startTime - b.startTime);
11
+ const root = ordered.find((span) => span.kind === 'turn') ?? ordered[0];
12
+ const tools = ordered.filter((span) => span.kind === 'tool');
13
+ return {
14
+ traceId: root.traceId,
15
+ sessionId: root.attributes.sessionId,
16
+ spans: ordered,
17
+ answer: typeof root.attributes.output === 'string' ? root.attributes.output : '',
18
+ usedTool: tools.length > 0,
19
+ toolCalls: tools.map((span) => ({
20
+ name: span.attributes.toolName ?? span.name,
21
+ args: span.attributes.input ?? null,
22
+ })),
23
+ toolResults: tools
24
+ .filter((span) => span.attributes.output !== undefined)
25
+ .map((span) => ({ name: span.attributes.toolName ?? span.name, result: span.attributes.output })),
26
+ startedAt: root.startTime,
27
+ ...(root.endTime !== undefined ? { endedAt: root.endTime } : {}),
28
+ };
29
+ }
30
+ export function cloneSpan(span) {
31
+ return structuredClone(span);
32
+ }
@@ -0,0 +1,3 @@
1
+ export { MemoryTraceStore, type MemoryTraceStoreOptions } from './MemoryTraceStore.js';
2
+ export { isTraceStore, traceFromSpans, type TraceListWindow, type TraceSink, type TraceStore, } from './TraceStore.js';
3
+ export { OtelTraceSink, langfuseSink, otelSink, toOtlpPayload, type LangfuseSinkOptions, type OtelTraceSinkOptions, } from './OtelTraceSink.js';
@@ -0,0 +1,3 @@
1
+ export { MemoryTraceStore } from './MemoryTraceStore.js';
2
+ export { isTraceStore, traceFromSpans, } from './TraceStore.js';
3
+ export { OtelTraceSink, langfuseSink, otelSink, toOtlpPayload, } from './OtelTraceSink.js';
@@ -0,0 +1,3 @@
1
+ import type { TraceStore } from './TraceStore.js';
2
+ export type TraceStoreFactory = () => TraceStore | Promise<TraceStore>;
3
+ export declare function runTraceStoreContract(factory: TraceStoreFactory): void;
@@ -0,0 +1,45 @@
1
+ /// <reference types="bun-types" />
2
+ import { beforeEach, describe, expect, test } from 'bun:test';
3
+ export function runTraceStoreContract(factory) {
4
+ describe('TraceStore contract', () => {
5
+ let store;
6
+ beforeEach(async () => { store = await factory(); });
7
+ test('writes spans and reconstructs a JSON-safe trace', async () => {
8
+ await store.putSpan(span('trace-a', 'session-a', 'root', 10, 'turn'));
9
+ await store.putSpan(span('trace-a', 'session-a', 'tool', 11, 'tool'));
10
+ const trace = await store.getTrace('trace-a');
11
+ expect(trace?.spans.map((entry) => entry.spanId)).toEqual(['root', 'tool']);
12
+ expect(trace?.sessionId).toBe('session-a');
13
+ expect(() => JSON.stringify(trace)).not.toThrow();
14
+ });
15
+ test('returns null for a missing trace', async () => {
16
+ expect(await store.getTrace('missing')).toBeNull();
17
+ });
18
+ test('lists newest traces for only the requested session and window', async () => {
19
+ await store.putSpan(span('old', 'session-a', 'old-root', 10, 'turn'));
20
+ await store.putSpan(span('new', 'session-a', 'new-root', 30, 'turn'));
21
+ await store.putSpan(span('other', 'session-b', 'other-root', 40, 'turn'));
22
+ const traces = await store.listTraces('session-a', { from: new Date(20), limit: 1 });
23
+ expect(traces.map((trace) => trace.traceId)).toEqual(['new']);
24
+ });
25
+ test('upserts a span by trace and span id', async () => {
26
+ const root = span('trace-a', 'session-a', 'root', 10, 'turn');
27
+ await store.write(root);
28
+ await store.putSpan({ ...root, endTime: 25 });
29
+ expect((await store.getTrace('trace-a'))?.spans).toHaveLength(1);
30
+ expect((await store.getTrace('trace-a'))?.endedAt).toBe(25);
31
+ });
32
+ });
33
+ }
34
+ function span(traceId, sessionId, spanId, startTime, kind) {
35
+ return {
36
+ traceId,
37
+ spanId,
38
+ name: kind,
39
+ kind,
40
+ startTime,
41
+ endTime: startTime + 5,
42
+ status: 'ok',
43
+ attributes: { sessionId },
44
+ };
45
+ }
@@ -8,9 +8,11 @@ import type { RefinementCapability } from '../capabilities/RefinementCapability.
8
8
  import type { ValidationCapability } from '../capabilities/ValidationCapability.js';
9
9
  import type { AnyTool } from './effectTool.js';
10
10
  import type { FileSystem } from './filesystem.js';
11
+ import type { Shell } from './shell.js';
11
12
  import type { SkillSource } from './skills.js';
12
13
  export type AgentWorkspaceConfig = FileSystem | {
13
14
  fs: FileSystem;
15
+ shell?: Shell;
14
16
  readOnly?: boolean;
15
17
  };
16
18
  export type Instructions = string | AgentPrompt | ((ctx: {
@@ -48,7 +50,8 @@ export interface AgentConfig {
48
50
  /** Pre-turn refinement policies. Default: none. */
49
51
  refine?: RefinementCapability[];
50
52
  experimental?: {
51
- /** Flow reply nodes: silo flow-transition control tools + deterministic evaluator (ADR 0003 H1). Default OFF. */
53
+ /** Flow reply nodes: silo flow-transition control tools + deterministic evaluator (ADR 0003 H1).
54
+ * Default ON when the agent declares `flows`; OFF for answering-only agents. Override explicitly to opt out. */
52
55
  outOfBandControl?: boolean;
53
56
  };
54
57
  /** Portable workspace filesystem; auto-registers the durable `workspace` tool when set.
@@ -1,4 +1,4 @@
1
- import type { ToolSet } from 'ai';
1
+ import type { ModelMessage, ToolSet } from 'ai';
2
2
  import type { UserInputContent } from '../runtime/userInput.js';
3
3
  import type { RunContext } from './run-context.js';
4
4
  import type { FlowNode } from './flow.js';
@@ -34,6 +34,14 @@ export interface ChannelDriver {
34
34
  * back to runAgentTurn, whose text the engine then discards. */
35
35
  runExtraction?(node: ResolvedNode, ctx: RunContext): Promise<TurnResult>;
36
36
  }
37
+ export interface TurnUsageSnapshot {
38
+ inputTokens: number;
39
+ outputTokens: number;
40
+ totalTokens: number;
41
+ cacheReadTokens?: number;
42
+ /** Input tokens on the final model call, i.e. current context-window occupancy. */
43
+ contextTokens?: number;
44
+ }
37
45
  export interface TurnResult {
38
46
  text: string;
39
47
  toolResults: ToolResultRecord[];
@@ -43,6 +51,11 @@ export interface TurnResult {
43
51
  confidence?: number;
44
52
  /** Native realtime post-hoc gate: provider audio already played; gate is advisory only. */
45
53
  gateScope?: 'advisory';
54
+ /** AI-SDK tool round-trip messages (assistant tool-call + tool-result) produced this turn,
55
+ * so the host loop can persist them to history in free conversation (G18). */
56
+ toolMessages?: ModelMessage[];
57
+ /** Real token usage from the AI SDK when the driver captured it. */
58
+ usage?: TurnUsageSnapshot;
46
59
  }
47
60
  export type UserSignal = {
48
61
  type: 'message';
@@ -10,6 +10,12 @@ export interface Tool<TInput = unknown, TOutput = unknown> {
10
10
  interim?: string;
11
11
  interimAfterMs?: number;
12
12
  timeoutMs?: number;
13
+ /** When false, the durable journal always re-executes this tool instead of returning a cached step result — for observation/mutation tools (fs, shell) whose result must be fresh. Default true. */
14
+ replay?: boolean;
15
+ /** When true, the tool may run concurrently with other parallel-safe tools in the same model turn. */
16
+ parallelSafe?: boolean;
17
+ /** Override the auto-derived effect key (`idempotencyKey(logicalRunId, callsite, {name,args})`). Use when args are not a stable identity (e.g. a nonce). */
18
+ idempotencyKey?: (args: TInput) => string;
13
19
  execute: (args: TInput, ctx?: ToolContext) => Promise<TOutput> | AsyncIterable<TOutput>;
14
20
  }
15
21
  export type AnyTool = Tool<any, any>;
@@ -10,6 +10,7 @@ export * from './tool.js';
10
10
  export * from './effectTool.js';
11
11
  export * from './voice.js';
12
12
  export * from './runtime.js';
13
+ export * from './trace.js';
13
14
  export type * from '../audit/types.js';
14
15
  export type * from '../outcomes/index.js';
15
16
  export type * from '../channels/index.js';
@@ -10,3 +10,4 @@ export * from './tool.js';
10
10
  export * from './effectTool.js';
11
11
  export * from './voice.js';
12
12
  export * from './runtime.js';
13
+ export * from './trace.js';
@@ -10,7 +10,7 @@ import type { Limits } from './guardrails.js';
10
10
  import type { AnyTool } from './effectTool.js';
11
11
  import type { FileSystem } from './filesystem.js';
12
12
  import type { Instructions } from './agentConfig.js';
13
- import type { AgentKnowledgeOverrides, SourceRef } from './voice.js';
13
+ import type { AgentKnowledgeOverrides, SourceRef, RetrievalCacheAdapter } from './voice.js';
14
14
  export interface GatherScope {
15
15
  query?: string;
16
16
  knowledge?: AgentKnowledgeOverrides & {
@@ -80,6 +80,13 @@ export interface RunContext {
80
80
  turnInputConsumed?: boolean;
81
81
  /** Citations from the latest gather-phase retrieval on this turn. */
82
82
  lastRetrievalCitations?: SourceRef[];
83
+ /**
84
+ * Session retrieval cache (G6): created once per run by the KnowledgeProvider,
85
+ * persists across in-session agent handoffs (this RunContext survives the
86
+ * handoff branch). Keyed by query embedding; RAG-only, undefined without a
87
+ * configured knowledge provider + embedder.
88
+ */
89
+ retrievalCache?: RetrievalCacheAdapter;
83
90
  /** Agent base layer (ADR 0001), set when entering a flow. `baseInstructions`
84
91
  * is composed as a prefix into every node turn's system prompt (persona /
85
92
  * safety / grounding floor); `globalTools` are safe tools made model-visible
@@ -98,6 +105,10 @@ export interface RunContext {
98
105
  toolCallId?: string;
99
106
  def?: AnyTool;
100
107
  toolCtx?: ToolContext;
108
+ /** Pre-reserved callsite ordinal for parallel-safe tool batches (G9). */
109
+ callsite?: string;
110
+ /** Pre-reserved journal index for parallel-safe tool batches (G9). */
111
+ index?: number;
101
112
  }): Promise<unknown>;
102
113
  approve(req: {
103
114
  title: string;
@@ -116,7 +127,9 @@ export interface RunContext {
116
127
  * a flow's durable callsites are anchored to the flow — identical on fresh entry
117
128
  * (after an answering turn) and on resume (where that turn does not re-run). */
118
129
  resetCallsites(): void;
130
+ /** Reserve N contiguous effect callsite ordinals for parallel-safe tool batches (G9). */
131
+ reserveCallsites(count: number): string[];
119
132
  }
120
133
  export type ActionContext = Pick<RunContext, 'tool' | 'approve' | 'signal' | 'now' | 'uuid' | 'emit' | 'fs'>;
121
- export type ToolContext = Pick<RunContext, 'session' | 'runState' | 'tool' | 'now' | 'uuid' | 'emit' | 'fs'>;
134
+ export type ToolContext = Pick<RunContext, 'session' | 'runState' | 'tool' | 'now' | 'uuid' | 'emit' | 'fs' | 'abortSignal'>;
122
135
  export type { ModelMessage };
@@ -6,6 +6,14 @@ import type { ConversationOutcomeRecord, CsatRecord } from '../outcomes/types.js
6
6
  import type { PersonaExperimentMetadata } from '../persona/types.js';
7
7
  import type { ConversationAuditEntry } from '../audit/types.js';
8
8
  export type ChannelId = 'web' | 'email' | 'sms' | 'voice' | 'api' | 'slack' | 'discord' | (string & {});
9
+ /** Structured goal/thread entry on session.workingMemory.__goals (G5). */
10
+ export type GoalStatus = 'open' | 'resolved';
11
+ export interface TrackedGoal {
12
+ topic: string;
13
+ status: GoalStatus;
14
+ lastTurn: number;
15
+ note?: string;
16
+ }
9
17
  export interface WorkingMemory {
10
18
  get<T>(key: string): T | undefined;
11
19
  set<T>(key: string, value: T): void;
@@ -42,6 +50,8 @@ export interface Session {
42
50
  pendingRefinement?: RefinementStageResult;
43
51
  /** @internal Pending key-facts extraction promises. Awaited before session save. */
44
52
  __pendingExtractions?: Promise<void>[];
53
+ /** Optimistic-concurrency version; must match the stored row on save (C2 CAS). */
54
+ version?: number;
45
55
  }
46
56
  export interface SessionMetadata {
47
57
  createdAt: Date;
@@ -0,0 +1,15 @@
1
+ export interface ShellResult {
2
+ stdout: string;
3
+ stderr: string;
4
+ exitCode: number;
5
+ }
6
+ export interface ShellExecOptions {
7
+ cwd?: string;
8
+ env?: Record<string, string>;
9
+ timeoutMs?: number;
10
+ signal?: AbortSignal;
11
+ }
12
+ export interface Shell {
13
+ exec(command: string, options?: ShellExecOptions): Promise<ShellResult>;
14
+ cwd?: string;
15
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -116,6 +116,14 @@ export type HarnessStreamPart = {
116
116
  } | {
117
117
  type: 'done';
118
118
  sessionId: string;
119
+ /** Per-turn token usage: `inputTokens`/`outputTokens` are THIS turn's
120
+ * consumption (deltas, for cost attribution); `contextTokens` is the current
121
+ * context-window occupancy (last prompt tokens, a snapshot). */
122
+ usage?: {
123
+ inputTokens?: number;
124
+ outputTokens?: number;
125
+ contextTokens?: number;
126
+ };
119
127
  };
120
128
  export interface TurnHandle extends Promise<import('./channel.js').TurnResult> {
121
129
  readonly events: AsyncIterable<HarnessStreamPart>;
@@ -0,0 +1,50 @@
1
+ export type SpanKind = 'turn' | 'flow' | 'node' | 'tool' | 'handoff' | 'llm';
2
+ export interface AgentSpan {
3
+ traceId: string;
4
+ spanId: string;
5
+ parentSpanId?: string;
6
+ name: string;
7
+ kind: SpanKind;
8
+ startTime: number;
9
+ endTime?: number;
10
+ status: 'ok' | 'error';
11
+ events?: Array<{
12
+ name: string;
13
+ time: number;
14
+ attributes?: Record<string, unknown>;
15
+ }>;
16
+ attributes: {
17
+ sessionId: string;
18
+ activeFlow?: string;
19
+ nodeId?: string;
20
+ toolName?: string;
21
+ handoffFrom?: string;
22
+ handoffTo?: string;
23
+ input?: unknown;
24
+ output?: unknown;
25
+ error?: string;
26
+ /** Input tokens consumed by this turn (delta — for cost attribution). */
27
+ tokensIn?: number;
28
+ /** Output tokens generated by this turn (delta — for cost attribution). */
29
+ tokensOut?: number;
30
+ /** Context-window occupancy at this turn (last prompt tokens — a snapshot, not a delta). */
31
+ contextTokens?: number;
32
+ };
33
+ }
34
+ export interface AgentTrace {
35
+ traceId: string;
36
+ sessionId: string;
37
+ spans: AgentSpan[];
38
+ answer: string;
39
+ usedTool: boolean;
40
+ toolCalls: Array<{
41
+ name: string;
42
+ args: unknown;
43
+ }>;
44
+ toolResults: Array<{
45
+ name: string;
46
+ result: unknown;
47
+ }>;
48
+ startedAt: number;
49
+ endedAt?: number;
50
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -8,8 +8,8 @@ This verification was run after runtime policy-profile changes to check for regr
8
8
 
9
9
  Covered suites:
10
10
 
11
- 1. `packages/kuralle-core/examples/agents` (Line parity set)
12
- 2. `packages/kuralle-core/examples/flows` (Pipecat parity set)
11
+ 1. `packages/core/examples/agents` (Line parity set)
12
+ 2. `packages/core/examples/flows` (Pipecat parity set)
13
13
  3. Extended package examples (`kuralle-tools`, `kuralle-redis-store`)
14
14
 
15
15
  ## Results
@@ -30,8 +30,8 @@ Interpretation:
30
30
 
31
31
  Non-pass items:
32
32
 
33
- 1. `packages/kuralle-redis-store/examples/local-redis/test.ts` -> `SKIPPED`
34
- 2. `packages/kuralle-redis-store/examples/local-redis/multi-turn.ts` -> `SKIPPED`
33
+ 1. `packages/redis-store/examples/local-redis/test.ts` -> `SKIPPED`
34
+ 2. `packages/redis-store/examples/local-redis/multi-turn.ts` -> `SKIPPED`
35
35
  - Cause: local Redis was not started.
36
36
 
37
37
  ## Verdict
package/package.json CHANGED
@@ -4,9 +4,9 @@
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/kuralle/kuralle-agents.git",
7
- "directory": "packages/kuralle-core"
7
+ "directory": "packages/core"
8
8
  },
9
- "version": "0.11.0",
9
+ "version": "0.13.0",
10
10
  "description": "A framework for structured conversational AI agents",
11
11
  "publishConfig": {
12
12
  "access": "public"
@@ -48,6 +48,14 @@
48
48
  "types": "./dist/session/testing.d.ts",
49
49
  "default": "./dist/session/testing.js"
50
50
  },
51
+ "./tracing": {
52
+ "types": "./dist/tracing/index.d.ts",
53
+ "default": "./dist/tracing/index.js"
54
+ },
55
+ "./tracing/testing": {
56
+ "types": "./dist/tracing/testing.d.ts",
57
+ "default": "./dist/tracing/testing.js"
58
+ },
51
59
  "./testing": {
52
60
  "types": "./dist/testing/index.d.ts",
53
61
  "default": "./dist/testing/index.js"
@@ -94,8 +102,8 @@
94
102
  "zod": "^4.0.0"
95
103
  },
96
104
  "devDependencies": {
97
- "@cloudflare/vitest-pool-workers": "^0.12.7",
98
105
  "@ai-sdk/openai": "^3.0.0",
106
+ "@cloudflare/vitest-pool-workers": "^0.12.7",
99
107
  "@types/node": "^20.11.0",
100
108
  "ai": "^6.0.0",
101
109
  "bun-types": "^1.3.0",
@@ -103,7 +111,8 @@
103
111
  "typescript": "^5.3.0",
104
112
  "vitest": "^3.2.4",
105
113
  "zod": "^4.0.0",
106
- "@kuralle-agents/realtime-audio": "0.11.0"
114
+ "@kuralle-agents/fs": "0.13.0",
115
+ "@kuralle-agents/realtime-audio": "0.13.0"
107
116
  },
108
117
  "dependencies": {
109
118
  "chrono-node": "^2.6.0"
@@ -111,6 +120,7 @@
111
120
  "scripts": {
112
121
  "prebuild": "rm -rf dist",
113
122
  "build": "tsc -p tsconfig.json",
123
+ "typecheck:audit": "tsc --noEmit -p tsconfig.audit.json",
114
124
  "typecheck:examples": "tsc --noEmit -p tsconfig.examples.json",
115
125
  "clean": "rm -rf dist",
116
126
  "test": "bun test ./test && vitest run --config vitest.config.ts",