@kuralle-agents/core 0.5.0 → 0.6.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 (64) hide show
  1. package/README.md +1 -1
  2. package/dist/flow/nodeBuilders.d.ts +1 -1
  3. package/dist/flow/nodeBuilders.js +5 -3
  4. package/dist/index.d.ts +12 -4
  5. package/dist/index.js +7 -2
  6. package/dist/memory/blocks/FilePersistentMemoryStore.d.ts +1 -1
  7. package/dist/memory/blocks/FilePersistentMemoryStore.js +9 -0
  8. package/dist/memory/blocks/InMemoryPersistentMemoryStore.d.ts +8 -0
  9. package/dist/memory/blocks/InMemoryPersistentMemoryStore.js +25 -0
  10. package/dist/memory/blocks/RoutedPersistentMemoryStore.d.ts +18 -0
  11. package/dist/memory/blocks/RoutedPersistentMemoryStore.js +35 -0
  12. package/dist/memory/blocks/TieredPersistentMemoryStore.d.ts +10 -0
  13. package/dist/memory/blocks/TieredPersistentMemoryStore.js +30 -0
  14. package/dist/memory/blocks/memoryBlockTool.d.ts +5 -5
  15. package/dist/memory/blocks/testing.d.ts +11 -0
  16. package/dist/memory/blocks/testing.js +59 -0
  17. package/dist/memory/index.d.ts +4 -0
  18. package/dist/memory/index.js +3 -0
  19. package/dist/runtime/Runtime.d.ts +3 -0
  20. package/dist/runtime/Runtime.js +47 -5
  21. package/dist/runtime/agentReply.js +2 -1
  22. package/dist/runtime/channels/TextDriver.js +3 -2
  23. package/dist/runtime/channels/VoiceDriver.js +3 -3
  24. package/dist/runtime/channels/extractionTurn.js +1 -1
  25. package/dist/runtime/ctx.d.ts +2 -0
  26. package/dist/runtime/ctx.js +1 -0
  27. package/dist/runtime/grounding/defaultStoreRegistry.d.ts +3 -0
  28. package/dist/runtime/grounding/defaultStoreRegistry.js +10 -0
  29. package/dist/runtime/grounding/index.d.ts +1 -0
  30. package/dist/runtime/grounding/index.js +1 -0
  31. package/dist/runtime/grounding/workingMemory.d.ts +19 -0
  32. package/dist/runtime/grounding/workingMemory.js +80 -0
  33. package/dist/runtime/resolveAgentWorkspace.d.ts +10 -0
  34. package/dist/runtime/resolveAgentWorkspace.js +9 -0
  35. package/dist/skills/SkillsCapability.d.ts +10 -0
  36. package/dist/skills/SkillsCapability.js +52 -0
  37. package/dist/skills/collectSkills.d.ts +17 -0
  38. package/dist/skills/collectSkills.js +56 -0
  39. package/dist/skills/index.d.ts +5 -0
  40. package/dist/skills/index.js +4 -0
  41. package/dist/skills/inlineSkillStore.d.ts +9 -0
  42. package/dist/skills/inlineSkillStore.js +37 -0
  43. package/dist/skills/wireAgentSkills.d.ts +10 -0
  44. package/dist/skills/wireAgentSkills.js +25 -0
  45. package/dist/tools/effect/defineTool.js +1 -1
  46. package/dist/tools/effect/index.d.ts +1 -0
  47. package/dist/tools/effect/index.js +1 -0
  48. package/dist/tools/effect/wrapAiSdkTool.d.ts +3 -0
  49. package/dist/tools/effect/wrapAiSdkTool.js +12 -0
  50. package/dist/tools/fs/createFsTool.d.ts +30 -0
  51. package/dist/tools/fs/createFsTool.js +200 -0
  52. package/dist/types/agentConfig.d.ts +16 -3
  53. package/dist/types/filesystem.d.ts +85 -0
  54. package/dist/types/filesystem.js +6 -0
  55. package/dist/types/grounding.d.ts +12 -0
  56. package/dist/types/index.d.ts +2 -0
  57. package/dist/types/index.js +2 -0
  58. package/dist/types/run-context.d.ts +11 -2
  59. package/dist/types/skills.d.ts +19 -0
  60. package/dist/types/skills.js +1 -0
  61. package/guides/AGENTS.md +2 -3
  62. package/guides/FLOWS.md +2 -2
  63. package/guides/TOOLS.md +69 -2
  64. package/package.json +12 -3
package/README.md CHANGED
@@ -41,7 +41,7 @@ const agent = defineAgent({
41
41
  instructions: 'You are a helpful support agent.',
42
42
  model: openai('gpt-4o-mini'),
43
43
  tools: buildToolSet({ echo }), // model-visible
44
- effectTools: { echo }, // durable executor
44
+ tools: { echo }, // durable executor
45
45
  });
46
46
 
47
47
  const runtime = createRuntime({ agents: [agent], defaultAgentId: 'support' });
@@ -8,7 +8,7 @@ export declare function buildNodePrompt(node: ReplyNode, state: FlowState): stri
8
8
  * agent's base instructions (persona / safety / grounding) prefix the node's
9
9
  * own instructions. Node instructions layer ON TOP — they never replace the
10
10
  * base. Base resolves against the current state so dynamic base prompts work. */
11
- export declare function composeSystem(base: Instructions | undefined, nodeSystem: string, state: FlowState): string;
11
+ export declare function composeSystem(base: Instructions | undefined, nodeSystem: string, state: FlowState, skillPrompt?: string, workingMemoryPrompt?: string): string;
12
12
  export declare function resolveReplyNode(node: ReplyNode, state: FlowState, options?: {
13
13
  freeConversation?: boolean;
14
14
  }): ResolvedNode;
@@ -19,9 +19,11 @@ export function buildNodePrompt(node, state) {
19
19
  * agent's base instructions (persona / safety / grounding) prefix the node's
20
20
  * own instructions. Node instructions layer ON TOP — they never replace the
21
21
  * base. Base resolves against the current state so dynamic base prompts work. */
22
- export function composeSystem(base, nodeSystem, state) {
22
+ export function composeSystem(base, nodeSystem, state, skillPrompt, workingMemoryPrompt) {
23
23
  const baseText = base ? resolveInstructions(base, state) : '';
24
- return [baseText, nodeSystem].filter((s) => s && s.trim()).join('\n\n');
24
+ return [baseText, skillPrompt, workingMemoryPrompt, nodeSystem]
25
+ .filter((s) => s && s.trim())
26
+ .join('\n\n');
25
27
  }
26
28
  function buildNodeTools(node, state) {
27
29
  if (!node.tools) {
@@ -39,7 +41,7 @@ export function resolveReplyNode(node, state, options) {
39
41
  prompt: buildNodePrompt(node, state),
40
42
  tools,
41
43
  // Recover the raw executors from the node's `buildToolSet` tools so they run
42
- // in-flow (with run context) — without also needing `agent.effectTools`.
44
+ // in-flow (with run context) — without also needing `agent.tools`.
43
45
  localTools: rawToolsFromSet(tools),
44
46
  ...(options?.freeConversation && { freeConversation: true }),
45
47
  };
package/dist/index.d.ts CHANGED
@@ -47,8 +47,11 @@ export type { MemoryEntry, SearchMemoryRequest, SearchMemoryResponse, MemoryInge
47
47
  export { InMemoryMemoryService } from './memory/index.js';
48
48
  export { preloadMemoryContext } from './memory/index.js';
49
49
  export { extractMemories } from './memory/index.js';
50
- export type { PersistentMemoryStore, PersistentMemoryBlock, PersistentMemoryConfig, MemoryBlockScope, } from './memory/index.js';
51
- export { FilePersistentMemoryStore, scanMemoryWrite, buildMemoryBlockTool, DEFAULT_BLOCK_CHAR_LIMIT, DEFAULT_AUTO_LOAD_BLOCKS, } from './memory/index.js';
50
+ export type { PersistentMemoryStore, PersistentMemoryBlock, PersistentMemoryConfig, MemoryBlockScope, WorkingMemoryBlockSpec, WorkingMemoryConfig, } from './memory/index.js';
51
+ export { FilePersistentMemoryStore, InMemoryPersistentMemoryStore, RoutedPersistentMemoryStore, TieredPersistentMemoryStore, scanMemoryWrite, buildMemoryBlockTool, DEFAULT_BLOCK_CHAR_LIMIT, DEFAULT_AUTO_LOAD_BLOCKS, } from './memory/index.js';
52
+ export type { RoutedPersistentMemoryStoreConfig, MemoryRouteFn, } from './memory/index.js';
53
+ export { wireWorkingMemory, loadWorkingMemoryBlocks, formatWorkingMemorySection, resolveWorkingMemoryStore, } from './runtime/grounding/workingMemory.js';
54
+ export { resolveAgentWorkspace, type ResolvedAgentWorkspace, } from './runtime/resolveAgentWorkspace.js';
52
55
  export { DEFAULT_CONTEXT_BUDGET, VOICE_CONTEXT_BUDGET, computeMessageHistoryBudget, truncateToTokenBudget, formatMemoryWithBudget, estimateTokenCount, ContextBudget, } from './runtime/ContextBudget.js';
53
56
  export type { ContextBudgetConfig } from './runtime/ContextBudget.js';
54
57
  export { TokenAccumulator } from './runtime/TokenAccumulator.js';
@@ -67,10 +70,15 @@ export type { Hooks } from './types/hooks.js';
67
70
  export type { HarnessHooks } from './types/runtime.js';
68
71
  export { defineAgent, defineFlow, reply, collect, action, decide, confirmGate, } from './authoring/index.js';
69
72
  export { defineTool } from './types/effectTool.js';
70
- export { buildToolSet, toolToAiSdk, ToolApprovalDeniedError, ToolTimeoutError, } from './tools/effect/index.js';
73
+ export { fsErrorCode } from './types/filesystem.js';
74
+ export { createFsTool } from './tools/fs/createFsTool.js';
75
+ export type { CreateFsToolOptions, GrepHit } from './tools/fs/createFsTool.js';
76
+ export { SkillsCapability, wireAgentSkills, collectRegisteredNames, validateSkillAllowedTools, prepareSkillStore, isSkillStore, InlineSkillStore, } from './skills/index.js';
77
+ export type { WiredAgentSkills, SkillWireAgent } from './skills/index.js';
78
+ export { buildToolSet, toolToAiSdk, wrapAiSdkTool, ToolApprovalDeniedError, ToolTimeoutError, } from './tools/effect/index.js';
71
79
  export type { Tool as EffectTool } from './types/effectTool.js';
72
80
  export type { AgentRoute } from './types/processors.js';
73
- export type { AgentConfig, Instructions } from './types/agentConfig.js';
81
+ export type { AgentConfig, AgentWorkspaceConfig, Instructions } from './types/agentConfig.js';
74
82
  export type { Flow, FlowNode, Transition, CollectNode, DecideNode, ConfirmGate, NodeGrounding, } from './types/flow.js';
75
83
  export { parseConfirmation } from './flow/confirmParse.js';
76
84
  export type { ConfirmVerdict } from './flow/confirmParse.js';
package/dist/index.js CHANGED
@@ -29,7 +29,9 @@ export { getTemplate, registerTemplate, listTemplates, getAllTemplates } from '.
29
29
  export { InMemoryMemoryService } from './memory/index.js';
30
30
  export { preloadMemoryContext } from './memory/index.js';
31
31
  export { extractMemories } from './memory/index.js';
32
- export { FilePersistentMemoryStore, scanMemoryWrite, buildMemoryBlockTool, DEFAULT_BLOCK_CHAR_LIMIT, DEFAULT_AUTO_LOAD_BLOCKS, } from './memory/index.js';
32
+ export { FilePersistentMemoryStore, InMemoryPersistentMemoryStore, RoutedPersistentMemoryStore, TieredPersistentMemoryStore, scanMemoryWrite, buildMemoryBlockTool, DEFAULT_BLOCK_CHAR_LIMIT, DEFAULT_AUTO_LOAD_BLOCKS, } from './memory/index.js';
33
+ export { wireWorkingMemory, loadWorkingMemoryBlocks, formatWorkingMemorySection, resolveWorkingMemoryStore, } from './runtime/grounding/workingMemory.js';
34
+ export { resolveAgentWorkspace, } from './runtime/resolveAgentWorkspace.js';
33
35
  export { DEFAULT_CONTEXT_BUDGET, VOICE_CONTEXT_BUDGET, computeMessageHistoryBudget, truncateToTokenBudget, formatMemoryWithBudget, estimateTokenCount, ContextBudget, } from './runtime/ContextBudget.js';
34
36
  export { TokenAccumulator } from './runtime/TokenAccumulator.js';
35
37
  export { handoffFilters, removeToolHistory, keepRecentMessages, removeKeys, composeFilters, } from './runtime/handoffFilters.js';
@@ -38,7 +40,10 @@ export { CapabilityHost, TriageCapability, ExtractionCapability, HandoffCapabili
38
40
  export { filterAuditEntries } from './audit/index.js';
39
41
  export { defineAgent, defineFlow, reply, collect, action, decide, confirmGate, } from './authoring/index.js';
40
42
  export { defineTool } from './types/effectTool.js';
41
- export { buildToolSet, toolToAiSdk, ToolApprovalDeniedError, ToolTimeoutError, } from './tools/effect/index.js';
43
+ export { fsErrorCode } from './types/filesystem.js';
44
+ export { createFsTool } from './tools/fs/createFsTool.js';
45
+ export { SkillsCapability, wireAgentSkills, collectRegisteredNames, validateSkillAllowedTools, prepareSkillStore, isSkillStore, InlineSkillStore, } from './skills/index.js';
46
+ export { buildToolSet, toolToAiSdk, wrapAiSdkTool, ToolApprovalDeniedError, ToolTimeoutError, } from './tools/effect/index.js';
42
47
  export { parseConfirmation } from './flow/confirmParse.js';
43
48
  export { harnessToUIMessageStream } from './ai-sdk/uiMessageStream.js';
44
49
  export { createRuntime, Runtime, } from './runtime/Runtime.js';
@@ -1,4 +1,4 @@
1
- import { type PersistentMemoryStore, type PersistentMemoryBlock, type MemoryBlockScope } from './types.js';
1
+ import type { PersistentMemoryStore, PersistentMemoryBlock, MemoryBlockScope } from './types.js';
2
2
  export interface FilePersistentMemoryStoreOptions {
3
3
  /** Root directory. Defaults to `KURALLE_MEMORY_DIR` or `~/.kuralle/memories`. */
4
4
  rootDir?: string;
@@ -9,6 +9,13 @@
9
9
  *
10
10
  * Defaults to `process.env.KURALLE_MEMORY_DIR ?? <homedir>/.kuralle/memories`.
11
11
  *
12
+ * Relationship to `AgentConfig.workspace` (RFC-02): this store persists long-term
13
+ * memory *blocks* on the local Node filesystem via `node:fs`. It is not a
14
+ * portable `FileSystem` backend and is not wired into the agent workspace tool.
15
+ * Use `@kuralle-agents/fs` (`InMemoryFs`, `KnowledgeFs`, …) for model-visible
16
+ * workspace exploration; keep `FilePersistentMemoryStore` for durable USER/AGENT
17
+ * markdown blocks when running on Node.
18
+ *
12
19
  * Atomicity: writes go to a sibling `.tmp` file then `rename` over the
13
20
  * target. `rename` is atomic on POSIX within a single filesystem;
14
21
  * Windows is best-effort.
@@ -21,6 +28,7 @@
21
28
  import { promises as fs } from 'node:fs';
22
29
  import os from 'node:os';
23
30
  import path from 'node:path';
31
+ import { registerNodeDefaultWorkingMemoryStore } from '../../runtime/grounding/defaultStoreRegistry.js';
24
32
  export class FilePersistentMemoryStore {
25
33
  rootDir;
26
34
  constructor(opts = {}) {
@@ -111,3 +119,4 @@ export class FilePersistentMemoryStore {
111
119
  function isNodeError(err) {
112
120
  return typeof err === 'object' && err !== null && 'code' in err;
113
121
  }
122
+ registerNodeDefaultWorkingMemoryStore(() => new FilePersistentMemoryStore());
@@ -0,0 +1,8 @@
1
+ import type { MemoryBlockScope, PersistentMemoryBlock, PersistentMemoryStore } from './types.js';
2
+ export declare class InMemoryPersistentMemoryStore implements PersistentMemoryStore {
3
+ private readonly blocks;
4
+ loadBlock(scope: MemoryBlockScope, owner: string, key: string): Promise<PersistentMemoryBlock | null>;
5
+ saveBlock(block: PersistentMemoryBlock, owner: string): Promise<void>;
6
+ deleteBlock(scope: MemoryBlockScope, owner: string, key: string): Promise<void>;
7
+ listBlocks(scope: MemoryBlockScope, owner: string): Promise<string[]>;
8
+ }
@@ -0,0 +1,25 @@
1
+ function blockKey(scope, owner, key) {
2
+ return `${scope}:${owner}:${key}`;
3
+ }
4
+ export class InMemoryPersistentMemoryStore {
5
+ blocks = new Map();
6
+ async loadBlock(scope, owner, key) {
7
+ return this.blocks.get(blockKey(scope, owner, key)) ?? null;
8
+ }
9
+ async saveBlock(block, owner) {
10
+ this.blocks.set(blockKey(block.scope, owner, block.key), {
11
+ ...block,
12
+ updatedAt: new Date().toISOString(),
13
+ });
14
+ }
15
+ async deleteBlock(scope, owner, key) {
16
+ this.blocks.delete(blockKey(scope, owner, key));
17
+ }
18
+ async listBlocks(scope, owner) {
19
+ const prefix = `${scope}:${owner}:`;
20
+ return [...this.blocks.keys()]
21
+ .filter((k) => k.startsWith(prefix))
22
+ .map((k) => k.slice(prefix.length))
23
+ .sort();
24
+ }
25
+ }
@@ -0,0 +1,18 @@
1
+ import type { MemoryBlockScope, PersistentMemoryBlock, PersistentMemoryStore } from './types.js';
2
+ export type MemoryRouteFn = (scope: MemoryBlockScope, owner: string) => PersistentMemoryStore;
3
+ export interface RoutedPersistentMemoryStoreConfig {
4
+ routes: Partial<Record<MemoryBlockScope, PersistentMemoryStore>>;
5
+ default?: PersistentMemoryStore;
6
+ route?: MemoryRouteFn;
7
+ }
8
+ export declare class RoutedPersistentMemoryStore implements PersistentMemoryStore {
9
+ private readonly routes;
10
+ private readonly defaultStore?;
11
+ private readonly routeFn?;
12
+ constructor(config: RoutedPersistentMemoryStoreConfig);
13
+ private resolve;
14
+ loadBlock(scope: MemoryBlockScope, owner: string, key: string): Promise<PersistentMemoryBlock | null>;
15
+ saveBlock(block: PersistentMemoryBlock, owner: string): Promise<void>;
16
+ deleteBlock(scope: MemoryBlockScope, owner: string, key: string): Promise<void>;
17
+ listBlocks(scope: MemoryBlockScope, owner: string): Promise<string[]>;
18
+ }
@@ -0,0 +1,35 @@
1
+ export class RoutedPersistentMemoryStore {
2
+ routes;
3
+ defaultStore;
4
+ routeFn;
5
+ constructor(config) {
6
+ this.routes = config.routes;
7
+ this.defaultStore = config.default;
8
+ this.routeFn = config.route;
9
+ }
10
+ resolve(scope, owner) {
11
+ if (this.routeFn) {
12
+ return this.routeFn(scope, owner);
13
+ }
14
+ const byScope = this.routes[scope];
15
+ if (byScope) {
16
+ return byScope;
17
+ }
18
+ if (this.defaultStore) {
19
+ return this.defaultStore;
20
+ }
21
+ throw new Error(`No PersistentMemoryStore route for scope=${scope}`);
22
+ }
23
+ async loadBlock(scope, owner, key) {
24
+ return this.resolve(scope, owner).loadBlock(scope, owner, key);
25
+ }
26
+ async saveBlock(block, owner) {
27
+ await this.resolve(block.scope, owner).saveBlock(block, owner);
28
+ }
29
+ async deleteBlock(scope, owner, key) {
30
+ await this.resolve(scope, owner).deleteBlock(scope, owner, key);
31
+ }
32
+ async listBlocks(scope, owner) {
33
+ return this.resolve(scope, owner).listBlocks(scope, owner);
34
+ }
35
+ }
@@ -0,0 +1,10 @@
1
+ import type { MemoryBlockScope, PersistentMemoryBlock, PersistentMemoryStore } from './types.js';
2
+ export declare class TieredPersistentMemoryStore implements PersistentMemoryStore {
3
+ private readonly cache;
4
+ private readonly durable;
5
+ constructor(cache: PersistentMemoryStore, durable: PersistentMemoryStore);
6
+ loadBlock(scope: MemoryBlockScope, owner: string, key: string): Promise<PersistentMemoryBlock | null>;
7
+ saveBlock(block: PersistentMemoryBlock, owner: string): Promise<void>;
8
+ deleteBlock(scope: MemoryBlockScope, owner: string, key: string): Promise<void>;
9
+ listBlocks(scope: MemoryBlockScope, owner: string): Promise<string[]>;
10
+ }
@@ -0,0 +1,30 @@
1
+ export class TieredPersistentMemoryStore {
2
+ cache;
3
+ durable;
4
+ constructor(cache, durable) {
5
+ this.cache = cache;
6
+ this.durable = durable;
7
+ }
8
+ async loadBlock(scope, owner, key) {
9
+ const cached = await this.cache.loadBlock(scope, owner, key);
10
+ if (cached) {
11
+ return cached;
12
+ }
13
+ const fromDurable = await this.durable.loadBlock(scope, owner, key);
14
+ if (fromDurable) {
15
+ await this.cache.saveBlock(fromDurable, owner);
16
+ }
17
+ return fromDurable;
18
+ }
19
+ async saveBlock(block, owner) {
20
+ await this.durable.saveBlock(block, owner);
21
+ await this.cache.saveBlock(block, owner);
22
+ }
23
+ async deleteBlock(scope, owner, key) {
24
+ await this.durable.deleteBlock(scope, owner, key);
25
+ await this.cache.deleteBlock(scope, owner, key);
26
+ }
27
+ async listBlocks(scope, owner) {
28
+ return this.durable.listBlocks(scope, owner);
29
+ }
30
+ }
@@ -21,7 +21,7 @@ export declare function buildMemoryBlockTool(opts: MemoryBlockToolOptions): impo
21
21
  empty: boolean;
22
22
  updatedAt?: undefined;
23
23
  chars?: undefined;
24
- readonly error?: undefined;
24
+ error?: undefined;
25
25
  message?: undefined;
26
26
  ok?: undefined;
27
27
  removed?: undefined;
@@ -38,7 +38,7 @@ export declare function buildMemoryBlockTool(opts: MemoryBlockToolOptions): impo
38
38
  updatedAt: string | undefined;
39
39
  chars: number;
40
40
  empty?: undefined;
41
- readonly error?: undefined;
41
+ error?: undefined;
42
42
  message?: undefined;
43
43
  ok?: undefined;
44
44
  removed?: undefined;
@@ -75,7 +75,7 @@ export declare function buildMemoryBlockTool(opts: MemoryBlockToolOptions): impo
75
75
  empty?: undefined;
76
76
  updatedAt?: undefined;
77
77
  chars?: undefined;
78
- readonly error?: undefined;
78
+ error?: undefined;
79
79
  message?: undefined;
80
80
  remainingChars?: undefined;
81
81
  pattern?: undefined;
@@ -92,7 +92,7 @@ export declare function buildMemoryBlockTool(opts: MemoryBlockToolOptions): impo
92
92
  empty?: undefined;
93
93
  updatedAt?: undefined;
94
94
  chars?: undefined;
95
- readonly error?: undefined;
95
+ error?: undefined;
96
96
  message?: undefined;
97
97
  note?: undefined;
98
98
  pattern?: undefined;
@@ -143,7 +143,7 @@ export declare function buildMemoryBlockTool(opts: MemoryBlockToolOptions): impo
143
143
  content?: undefined;
144
144
  empty?: undefined;
145
145
  updatedAt?: undefined;
146
- readonly error?: undefined;
146
+ error?: undefined;
147
147
  message?: undefined;
148
148
  removed?: undefined;
149
149
  note?: undefined;
@@ -0,0 +1,11 @@
1
+ import type { PersistentMemoryStore } from './types.js';
2
+ export type PersistentMemoryStoreFactory = () => PersistentMemoryStore | Promise<PersistentMemoryStore>;
3
+ export type PersistentMemoryDurabilityFactory = () => {
4
+ storeA: PersistentMemoryStore;
5
+ storeB: PersistentMemoryStore;
6
+ } | Promise<{
7
+ storeA: PersistentMemoryStore;
8
+ storeB: PersistentMemoryStore;
9
+ }>;
10
+ export declare function runPersistentMemoryStoreContract(factory: PersistentMemoryStoreFactory): void;
11
+ export declare function runPersistentMemoryDurabilityContract(factory: PersistentMemoryDurabilityFactory): void;
@@ -0,0 +1,59 @@
1
+ /// <reference types="bun-types" />
2
+ import { describe, test, expect, beforeEach } from 'bun:test';
3
+ const sampleBlock = (overrides = {}) => ({
4
+ key: 'USER',
5
+ scope: 'user',
6
+ content: 'name: Maya\nprefers: vegetarian',
7
+ charLimit: 1000,
8
+ ...overrides,
9
+ });
10
+ export function runPersistentMemoryStoreContract(factory) {
11
+ describe('PersistentMemoryStore contract', () => {
12
+ let store;
13
+ beforeEach(async () => {
14
+ store = await factory();
15
+ });
16
+ test('loadBlock returns null for missing block', async () => {
17
+ expect(await store.loadBlock('user', 'alice', 'USER')).toBeNull();
18
+ });
19
+ test('saveBlock + loadBlock round-trips', async () => {
20
+ const block = sampleBlock();
21
+ await store.saveBlock(block, 'maya@example.com');
22
+ const loaded = await store.loadBlock('user', 'maya@example.com', 'USER');
23
+ expect(loaded).not.toBeNull();
24
+ expect(loaded.content).toBe(block.content);
25
+ expect(loaded.scope).toBe('user');
26
+ expect(loaded.key).toBe('USER');
27
+ expect(typeof loaded.updatedAt).toBe('string');
28
+ });
29
+ test('listBlocks returns keys within scope+owner', async () => {
30
+ await store.saveBlock(sampleBlock({ key: 'USER' }), 'bob');
31
+ await store.saveBlock(sampleBlock({ key: 'preferences', content: 'dark mode' }), 'bob');
32
+ await store.saveBlock(sampleBlock({ key: 'MEMORY', scope: 'agent', content: 'notes' }), 'bob');
33
+ expect((await store.listBlocks('user', 'bob')).sort()).toEqual([
34
+ 'USER',
35
+ 'preferences',
36
+ ]);
37
+ expect(await store.listBlocks('agent', 'bob')).toEqual(['MEMORY']);
38
+ });
39
+ test('listBlocks returns empty array when none exist', async () => {
40
+ expect(await store.listBlocks('user', 'never-existed')).toEqual([]);
41
+ });
42
+ test('deleteBlock removes block; no-op when missing', async () => {
43
+ await store.saveBlock(sampleBlock({ key: 'ephemeral' }), 'dave');
44
+ await store.deleteBlock('user', 'dave', 'ephemeral');
45
+ expect(await store.loadBlock('user', 'dave', 'ephemeral')).toBeNull();
46
+ await store.deleteBlock('user', 'dave', 'ephemeral');
47
+ });
48
+ });
49
+ }
50
+ export function runPersistentMemoryDurabilityContract(factory) {
51
+ describe('PersistentMemoryStore durability', () => {
52
+ test('store B reads block written by store A', async () => {
53
+ const { storeA, storeB } = await factory();
54
+ await storeA.saveBlock(sampleBlock({ content: 'durable payload' }), 'owner-1');
55
+ const loaded = await storeB.loadBlock('user', 'owner-1', 'USER');
56
+ expect(loaded?.content).toBe('durable payload');
57
+ });
58
+ });
59
+ }
@@ -4,7 +4,11 @@ export { InMemoryMemoryService } from './stores/InMemoryMemoryService.js';
4
4
  export { preloadMemoryContext } from './preloadMemory.js';
5
5
  export { extractMemories } from './utils.js';
6
6
  export type { PersistentMemoryStore, PersistentMemoryBlock, PersistentMemoryConfig, MemoryBlockScope, } from './blocks/types.js';
7
+ export type { WorkingMemoryBlockSpec, WorkingMemoryConfig } from '../types/grounding.js';
7
8
  export { DEFAULT_BLOCK_CHAR_LIMIT, DEFAULT_AUTO_LOAD_BLOCKS, } from './blocks/types.js';
9
+ export { InMemoryPersistentMemoryStore } from './blocks/InMemoryPersistentMemoryStore.js';
8
10
  export { FilePersistentMemoryStore, type FilePersistentMemoryStoreOptions, } from './blocks/FilePersistentMemoryStore.js';
11
+ export { RoutedPersistentMemoryStore, type RoutedPersistentMemoryStoreConfig, type MemoryRouteFn, } from './blocks/RoutedPersistentMemoryStore.js';
12
+ export { TieredPersistentMemoryStore } from './blocks/TieredPersistentMemoryStore.js';
9
13
  export { scanMemoryWrite, type SafetyScanResult } from './blocks/safetyScanner.js';
10
14
  export { buildMemoryBlockTool, type MemoryBlockToolOptions } from './blocks/memoryBlockTool.js';
@@ -2,6 +2,9 @@ export { InMemoryMemoryService } from './stores/InMemoryMemoryService.js';
2
2
  export { preloadMemoryContext } from './preloadMemory.js';
3
3
  export { extractMemories } from './utils.js';
4
4
  export { DEFAULT_BLOCK_CHAR_LIMIT, DEFAULT_AUTO_LOAD_BLOCKS, } from './blocks/types.js';
5
+ export { InMemoryPersistentMemoryStore } from './blocks/InMemoryPersistentMemoryStore.js';
5
6
  export { FilePersistentMemoryStore, } from './blocks/FilePersistentMemoryStore.js';
7
+ export { RoutedPersistentMemoryStore, } from './blocks/RoutedPersistentMemoryStore.js';
8
+ export { TieredPersistentMemoryStore } from './blocks/TieredPersistentMemoryStore.js';
6
9
  export { scanMemoryWrite } from './blocks/safetyScanner.js';
7
10
  export { buildMemoryBlockTool } from './blocks/memoryBlockTool.js';
@@ -13,6 +13,7 @@ import type { ConversationOutcome, ConversationOutcomeMarkedBy } from '../outcom
13
13
  import type { selectHostTarget } from './select.js';
14
14
  import type { KnowledgeProviderConfig } from '../types/voice.js';
15
15
  import type { MemoryService as V1MemoryService } from '../memory/MemoryService.js';
16
+ import type { PersistentMemoryStore } from '../memory/blocks/types.js';
16
17
  export interface HarnessConfig {
17
18
  agents: AgentConfig[];
18
19
  defaultAgentId: string;
@@ -26,6 +27,8 @@ export interface HarnessConfig {
26
27
  tools?: Record<string, AnyTool>;
27
28
  knowledge?: KnowledgeProviderConfig;
28
29
  memoryService?: V1MemoryService;
30
+ /** Default store for `agent.memory.workingMemory` when `workingMemory.store` is omitted. */
31
+ defaultWorkingMemoryStore?: PersistentMemoryStore;
29
32
  }
30
33
  export interface RunOptions {
31
34
  sessionId?: string;
@@ -5,6 +5,8 @@ import { TextDriver } from './channels/TextDriver.js';
5
5
  import { createRunContext } from './ctx.js';
6
6
  import { createEventBus, createTurnHandle } from '../events/TurnHandle.js';
7
7
  import { CoreToolExecutor } from '../tools/effect/index.js';
8
+ import { createFsTool } from '../tools/fs/createFsTool.js';
9
+ import { wireAgentSkills } from '../skills/wireAgentSkills.js';
8
10
  import { hostLoop } from './hostLoop.js';
9
11
  import { isDegradableRuntimeError } from '../flow/degradableErrors.js';
10
12
  import { SAFE_DEGRADED_MESSAGE } from '../flow/degrade.js';
@@ -14,7 +16,8 @@ import { SessionRunStore } from './durable/SessionRunStore.js';
14
16
  import { loadRecordedSteps } from './durable/replay.js';
15
17
  import { markSessionOutcome } from './outcomeMarking.js';
16
18
  import { resolveAgentPolicies } from './policies/resolvePolicies.js';
17
- import { buildAutoRetrieveProvider, buildKnowledgeProvider, buildMemoryService, runMemoryIngest, } from './grounding/index.js';
19
+ import { buildAutoRetrieveProvider, buildKnowledgeProvider, buildMemoryService, runMemoryIngest, wireWorkingMemory, } from './grounding/index.js';
20
+ import { resolveAgentWorkspace } from './resolveAgentWorkspace.js';
18
21
  import { SessionMutex } from './SessionMutex.js';
19
22
  export class Runtime {
20
23
  config;
@@ -62,16 +65,38 @@ export class Runtime {
62
65
  sessionStore: this.sessionStore,
63
66
  });
64
67
  const policies = resolveAgentPolicies(opened.agent);
65
- const effectTools = {
68
+ const agentTools = {
66
69
  ...(this.config.tools ?? {}),
67
- ...(opened.agent.effectTools ?? {}),
70
+ ...(opened.agent.tools ?? {}),
68
71
  // Global tools (ADR 0001) are model-visible in speaking turns via the
69
72
  // drivers; register their executors here too so a model call can actually
70
73
  // run them. Visibility stays gated (not exposed during collect extraction).
71
74
  ...(opened.agent.globalTools ?? {}),
72
75
  };
76
+ const resolvedWorkspace = resolveAgentWorkspace(opened.agent.workspace);
77
+ if (resolvedWorkspace) {
78
+ agentTools.workspace = createFsTool({
79
+ fs: resolvedWorkspace.fs,
80
+ readOnly: resolvedWorkspace.readOnly,
81
+ });
82
+ }
83
+ const wiredWorkingMemory = await wireWorkingMemory(opened.agent, opened.session, this.config.defaultWorkingMemoryStore);
84
+ if (wiredWorkingMemory) {
85
+ agentTools.memory_block = wiredWorkingMemory.memoryBlockTool;
86
+ }
87
+ let skillPrompt;
88
+ let skillTools = {};
89
+ if (opened.agent.skills) {
90
+ const wired = await wireAgentSkills(opened.agent);
91
+ if (wired) {
92
+ skillTools = wired.tools;
93
+ Object.assign(agentTools, wired.tools);
94
+ skillPrompt = wired.promptSections.map((s) => s.content).join('\n\n');
95
+ }
96
+ }
97
+ const workspaceTool = agentTools.workspace;
73
98
  const toolExecutor = new CoreToolExecutor({
74
- tools: effectTools,
99
+ tools: agentTools,
75
100
  enforcer: policies.enforcer,
76
101
  agentId: opened.agent.id,
77
102
  onInterim: (message) => {
@@ -111,11 +136,23 @@ export class Runtime {
111
136
  memoryService: this.config.memoryService
112
137
  ? buildMemoryService(this.config.memoryService, opened.agent)
113
138
  : undefined,
139
+ fs: resolvedWorkspace?.fs,
114
140
  });
115
141
  // Agent base layer (ADR 0001): composed into every node turn by the drivers.
116
142
  runCtx.baseInstructions = opened.agent.instructions;
117
- runCtx.globalTools = opened.agent.globalTools;
143
+ runCtx.globalTools = {
144
+ ...(opened.agent.globalTools ?? {}),
145
+ ...(workspaceTool && resolvedWorkspace?.readOnly !== false
146
+ ? { workspace: workspaceTool }
147
+ : {}),
148
+ ...skillTools,
149
+ };
118
150
  runCtx.outOfBandControl = opened.agent.experimental?.outOfBandControl ?? false;
151
+ runCtx.skillPrompt = skillPrompt;
152
+ runCtx.workingMemoryPrompt = wiredWorkingMemory?.promptSection;
153
+ runCtx.workingMemoryTools = wiredWorkingMemory
154
+ ? { memory_block: wiredWorkingMemory.memoryBlockTool }
155
+ : undefined;
119
156
  await this.hooks?.onStart?.(runCtx);
120
157
  const driver = opts.driver ?? new TextDriver();
121
158
  let activeAgent = opened.agent;
@@ -160,6 +197,11 @@ export class Runtime {
160
197
  runCtx.memoryService = this.config.memoryService
161
198
  ? buildMemoryService(this.config.memoryService, target)
162
199
  : undefined;
200
+ const targetWorkingMemory = await wireWorkingMemory(target, opened.session, this.config.defaultWorkingMemoryStore);
201
+ runCtx.workingMemoryPrompt = targetWorkingMemory?.promptSection;
202
+ runCtx.workingMemoryTools = targetWorkingMemory
203
+ ? { memory_block: targetWorkingMemory.memoryBlockTool }
204
+ : undefined;
163
205
  await runCtx.runStore.putRunState(runCtx.runState);
164
206
  continue;
165
207
  }
@@ -1,3 +1,4 @@
1
+ import { buildToolSet } from '../tools/effect/defineTool.js';
1
2
  export function buildAgentReplyNode(agent) {
2
3
  const label = agent.name ?? agent.id;
3
4
  const instructions = agent.instructions ??
@@ -6,7 +7,7 @@ export function buildAgentReplyNode(agent) {
6
7
  kind: 'reply',
7
8
  id: `${agent.id}__host`,
8
9
  instructions,
9
- tools: agent.tools,
10
+ tools: agent.tools ? buildToolSet(agent.tools) : undefined,
10
11
  model: agent.model,
11
12
  };
12
13
  }
@@ -38,7 +38,7 @@ export class TextDriver {
38
38
  const out = { text: '', toolResults: [] };
39
39
  const model = replyNode.model ?? ctx.model;
40
40
  const nodeSystem = node.prompt || buildNodePrompt(replyNode, ctx.runState.state);
41
- const baseSystem = composeSystem(ctx.baseInstructions, nodeSystem, ctx.runState.state);
41
+ const baseSystem = composeSystem(ctx.baseInstructions, nodeSystem, ctx.runState.state, ctx.skillPrompt, ctx.workingMemoryPrompt);
42
42
  const system = appendGatherBlocks(baseSystem, [gather.retrievalBlock, gather.memoryBlock]);
43
43
  const messages = [...ctx.runState.messages];
44
44
  const aiTools = this.resolveTools(node, ctx);
@@ -137,7 +137,7 @@ export class TextDriver {
137
137
  return runSilentExtraction(node, ctx, ctx.controlModel, resolveMaxSteps(ctx.limits, this.maxSteps));
138
138
  }
139
139
  async runStructured(node, ctx) {
140
- const system = composeSystem(ctx.baseInstructions, resolveInstructions(node.instructions, ctx.runState.state), ctx.runState.state);
140
+ const system = composeSystem(ctx.baseInstructions, resolveInstructions(node.instructions, ctx.runState.state), ctx.runState.state, ctx.skillPrompt, ctx.workingMemoryPrompt);
141
141
  return resolveStructuredDecide(node, ctx, system);
142
142
  }
143
143
  async awaitUser(ctx) {
@@ -149,6 +149,7 @@ export class TextDriver {
149
149
  const merged = {
150
150
  ...this.toolDefs,
151
151
  ...(ctx.globalTools ?? {}),
152
+ ...(ctx.workingMemoryTools ?? {}),
152
153
  ...(resolved.localTools ?? {}),
153
154
  };
154
155
  const aiTools = { ...resolved.tools };
@@ -63,9 +63,9 @@ export class VoiceDriver {
63
63
  const gather = await runGatherPhase(ctx, scope);
64
64
  const out = { text: '', toolResults: [] };
65
65
  const nodeSystem = node.prompt || buildNodePrompt(replyNode, ctx.runState.state);
66
- const baseSystem = composeSystem(ctx.baseInstructions, nodeSystem, ctx.runState.state);
66
+ const baseSystem = composeSystem(ctx.baseInstructions, nodeSystem, ctx.runState.state, ctx.skillPrompt, ctx.workingMemoryPrompt);
67
67
  const system = appendGatherBlocks(baseSystem, [gather.retrievalBlock, gather.memoryBlock]);
68
- const geminiTools = resolveVoiceGeminiTools(node, { ...this.toolDefs, ...(ctx.globalTools ?? {}) }, { siloFlowControl: ctx.outOfBandControl && !node.freeConversation });
68
+ const geminiTools = resolveVoiceGeminiTools(node, { ...this.toolDefs, ...(ctx.globalTools ?? {}), ...(ctx.workingMemoryTools ?? {}) }, { siloFlowControl: ctx.outOfBandControl && !node.freeConversation });
69
69
  const toolCallsMade = [];
70
70
  const maxSteps = resolveMaxSteps(ctx.limits, this.maxSteps);
71
71
  const mode = resolveStreamMode(ctx, node);
@@ -139,7 +139,7 @@ export class VoiceDriver {
139
139
  return out;
140
140
  }
141
141
  async runStructured(node, ctx) {
142
- const system = composeSystem(ctx.baseInstructions, resolveInstructions(node.instructions, ctx.runState.state), ctx.runState.state);
142
+ const system = composeSystem(ctx.baseInstructions, resolveInstructions(node.instructions, ctx.runState.state), ctx.runState.state, ctx.skillPrompt, ctx.workingMemoryPrompt);
143
143
  return resolveStructuredDecide(node, ctx, system);
144
144
  }
145
145
  // Non-speaking collect extraction: uses the shared text-model extraction path
@@ -15,7 +15,7 @@ import { buildNodePrompt, composeSystem } from '../../flow/nodeBuilders.js';
15
15
  export async function runSilentExtraction(node, ctx, model, maxSteps) {
16
16
  const replyNode = node.node;
17
17
  const nodeSystem = node.prompt || buildNodePrompt(replyNode, ctx.runState.state);
18
- const system = composeSystem(ctx.baseInstructions, nodeSystem, ctx.runState.state);
18
+ const system = composeSystem(ctx.baseInstructions, nodeSystem, ctx.runState.state, ctx.skillPrompt, ctx.workingMemoryPrompt);
19
19
  const messages = [...ctx.runState.messages];
20
20
  const aiTools = resolveExtractionTools(node);
21
21
  const out = { text: '', toolResults: [] };
@@ -6,6 +6,7 @@ import type { RefinementCapability } from '../capabilities/RefinementCapability.
6
6
  import type { ValidationCapability } from '../capabilities/ValidationCapability.js';
7
7
  import type { InputProcessor, OutputProcessor } from '../types/processors.js';
8
8
  import type { Limits } from '../types/guardrails.js';
9
+ import type { FileSystem } from '../types/filesystem.js';
9
10
  import type { RunState, StepRecord } from './durable/types.js';
10
11
  import type { RunStore } from './durable/RunStore.js';
11
12
  interface EffectClock {
@@ -29,6 +30,7 @@ export interface CtxDeps {
29
30
  limits?: Limits;
30
31
  autoRetrieve?: AutoRetrieveProvider;
31
32
  memoryService?: MemoryService;
33
+ fs?: FileSystem;
32
34
  bargeIn?: AbortSignal;
33
35
  abortSignal?: AbortSignal;
34
36
  clock?: EffectClock;
@@ -108,6 +108,7 @@ function makeCtx(deps) {
108
108
  limits: deps.limits,
109
109
  autoRetrieve: deps.autoRetrieve,
110
110
  memoryService: deps.memoryService,
111
+ fs: deps.fs,
111
112
  bargeIn: deps.bargeIn,
112
113
  abortSignal: deps.abortSignal,
113
114
  turnInputConsumed: false,
@@ -0,0 +1,3 @@
1
+ import type { PersistentMemoryStore } from '../../memory/blocks/types.js';
2
+ export declare function registerNodeDefaultWorkingMemoryStore(factory: () => PersistentMemoryStore): void;
3
+ export declare function getNodeDefaultWorkingMemoryStore(): (() => PersistentMemoryStore) | undefined;