pi-chalin 0.1.0 → 0.2.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.
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <a href="package.json"><img alt="version" src="https://img.shields.io/badge/version-0.1.0-111111?style=for-the-badge"></a>
2
+ <a href="package.json"><img alt="version" src="https://img.shields.io/badge/version-0.2.0-111111?style=for-the-badge"></a>
3
3
  <a href="package.json"><img alt="license" src="https://img.shields.io/badge/license-MIT-0f766e?style=for-the-badge"></a>
4
4
  <a href="package.json"><img alt="bun" src="https://img.shields.io/badge/bun-%3E%3D1.3.14-111111?style=for-the-badge&logo=bun&logoColor=white"></a>
5
5
  <a href="package.json"><img alt="typescript" src="https://img.shields.io/badge/typescript-6.0-3178c6?style=for-the-badge&logo=typescript&logoColor=white"></a>
@@ -31,7 +31,7 @@ Use it when a task benefits from:
31
31
  - deeper repository discovery before implementation;
32
32
  - isolated planning, execution, and review roles;
33
33
  - resumable long-running work;
34
- - local memory with explicit review;
34
+ - first-class memory through either pi-chalin local review or native Engram;
35
35
  - visible routing, safety, and runtime state in the Pi TUI;
36
36
  - audited web context for current external information.
37
37
 
@@ -42,7 +42,7 @@ Use it when a task benefits from:
42
42
  | Routing | Direct execution for bounded work, chalin workflows for broad or risky work. |
43
43
  | Subagents | Built-in `scout`, `planner`, `worker`, `reviewer`, `researcher`, `delegate`, `oracle`, `context-builder`, and `conflict-resolver` agents. |
44
44
  | Topologies | `single`, `chain`, `parallel`, `dag`, and `memory-only` workflow shapes. |
45
- | Memory | Local records, candidates, approval/rejection, deduplication, revisions, and FTS-backed search. |
45
+ | Memory | First-class `pi-chalin local`, `engram`, or `auto` backends with search, write, revise, review, and cloud-aware Engram sync. |
46
46
  | Artifacts | Resumable checkpoints, validation contracts, interviews, and handoffs. |
47
47
  | Safety | Approval thresholds, autonomy modes, recursion guards, single-writer isolation, stale-run recovery, and mutation checks. |
48
48
  | TUI | Smart Panel, agent manager, activity monitor, memory review, artifact panel, and web fetch audit. |
@@ -99,11 +99,12 @@ Once Pi loads the package, use `/chalin` inside a Pi session.
99
99
  | `/chalin off` | Disable autonomous routing for the current project. |
100
100
  | `/chalin agents` | Open the agent manager. |
101
101
  | `/chalin memory` | Review memory records and pending candidates. |
102
- | `/chalin memory <query>` | Search local memory. |
102
+ | `/chalin memory <query>` | Search the configured memory backend. |
103
103
  | `/chalin artifacts` | Open artifact and resumable task context. |
104
104
  | `/chalin artifacts <feature>` | Resume context for a specific feature artifact. |
105
105
  | `/chalin activity` | Inspect the active or latest run. |
106
106
  | `/chalin web` | Open the web fetch audit panel. |
107
+ | `/chalin settings` | Choose the memory provider: `auto`, `engram`, or `pi-chalin` local. |
107
108
  | `/chalin status` | Print routing, autonomy, safety, agent, memory, and guard status. |
108
109
 
109
110
  ## Tools
@@ -150,6 +151,7 @@ src/runner.ts mock and SDK-backed worker execution
150
151
  src/agents.ts built-in, project, and user agent catalog
151
152
  src/config.ts config, autonomy, safety, and overrides
152
153
  src/memory.ts memory records, candidates, and search
154
+ src/memory-provider.ts configurable pi-chalin local and Engram memory backends
153
155
  src/artifacts.ts resumable task state and handoffs
154
156
  src/webfetch.ts audited external context
155
157
  src/worktrees.ts isolated writer worktrees
@@ -207,6 +209,42 @@ Project-local runtime files live under `.pi-chalin/`:
207
209
  .pi-chalin/runs/
208
210
  ```
209
211
 
212
+ Memory can run against pi-chalin's local SQLite store or Engram. Configure it in `.pi-chalin/config.json` or through `/chalin settings`:
213
+
214
+ ```json
215
+ {
216
+ "memory": {
217
+ "provider": "auto",
218
+ "engram": {
219
+ "baseUrl": "http://127.0.0.1:7437",
220
+ "command": "engram",
221
+ "autoStart": false,
222
+ "autoSync": true,
223
+ "syncThrottleMs": 30000,
224
+ "timeoutMs": 800
225
+ }
226
+ }
227
+ }
228
+ ```
229
+
230
+ `auto` uses Engram when the HTTP service is reachable and falls back to local memory. `engram` uses Engram as the memory source for `/chalin memory`; the `baseUrl` may point at any Engram local-runtime-compatible endpoint, including a remote `engram serve` instance. Engram Cloud is supported through Engram's local-first sync model: run `engram serve` against a cloud-enrolled/synced Engram store, then point pi-chalin at that runtime. When `autoSync` is enabled and the configured Engram runtime is local, pi-chalin checks `/sync/status` and runs the official `engram sync --cloud --import --project <project>` before reads, then `engram sync --cloud --project <project>` after writes. This uses `ENGRAM_CLOUD_TOKEN` from the runtime environment and never persists the token.
231
+
232
+ If the user already has `gentle-pi`/`gentle-engram` working, pi-chalin reuses that Engram runtime. No MCP bootstrap command is required from pi-chalin: choose `engram` in `/chalin settings`, and pi-chalin will use `ENGRAM_URL`, `ENGRAM_PORT`, `ENGRAM_BIN`, the default `http://127.0.0.1:7437` runtime, and Engram's own project detection. If Engram Cloud is enrolled and the Pi process has `ENGRAM_CLOUD_TOKEN`, reads automatically import pending cloud chunks before listing/searching memory.
233
+
234
+ When Engram is the active/preferred memory provider, `/chalin memory` lists Engram observations only, including project and personal scopes returned by Engram. pi-chalin does not expose its local `approve`/`reject` review flow in that mode. If the user selects `pi-chalin local`, the local SQLite memory store keeps its existing pending-review, approve, reject, delete, search, and revise behavior.
235
+
236
+ ### Engram Memory
237
+
238
+ Engram is supported as a native memory backend, not as an external afterthought. When `/chalin settings` is set to `engram`, every memory-facing surface uses Engram directly:
239
+
240
+ - `/chalin memory` lists and searches Engram observations.
241
+ - `chalin_memory_search`, `chalin_memory_write`, and `chalin_memory_revise` operate against Engram.
242
+ - Routed, chained, parallel, DAG, and `memory-only` workflows receive memory from the configured Engram backend.
243
+ - Engram-backed memory does not use pi-chalin's local pending `approve`/`reject` queue.
244
+ - Engram Cloud works through Engram's official local-first sync path when the runtime is enrolled and the Pi process has `ENGRAM_CLOUD_TOKEN`.
245
+
246
+ Use `auto` when you want Engram if it is reachable with a local fallback, `engram` when Engram must be the source of truth, and `pi-chalin` when you want the local SQLite review workflow.
247
+
210
248
  User-level configuration and agents currently live under `~/.pi/chalin/`. That path is part of the Pi chalin workflow namespace, not the package name.
211
249
 
212
250
  ## Environment Variables
@@ -221,6 +259,11 @@ PI_CHALIN_MOCK_STEP_DELAY_MS
221
259
  PI_CHALIN_WORKFLOW_MODEL
222
260
  PI_CHALIN_WORKFLOW_THINKING
223
261
  PI_CHALIN_WORKFLOW_GATES
262
+ PI_CHALIN_MEMORY_PROVIDER
263
+ ENGRAM_URL
264
+ ENGRAM_PORT
265
+ ENGRAM_BIN
266
+ ENGRAM_CLOUD_TOKEN
224
267
  ```
225
268
 
226
269
  See `package.json` and the evaluator files under `evals/` for the full set used by development scripts.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-chalin",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Pi Coding Agent extension for routed, memory-aware subagent workflows",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/autoroute.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { AgentCatalog } from "./agents.ts";
3
3
  import { loadEffectiveConfig } from "./config.ts";
4
- import { MemoryStore } from "./memory.ts";
4
+ import { createConfiguredMemoryStore } from "./memory-provider.ts";
5
5
  import { buildCompactChalinCriticalSystemPrompt, buildCompactChalinOrchestratorSystemPrompt, buildCompactChalinResumeSystemPrompt, buildChalinOrchestratorSystemPrompt } from "./orchestration.ts";
6
6
  import { isUsableStepHandoff, loadResumableRunState } from "./runner-state.ts";
7
7
  import { beginChalinTurn, recordDirectToolCompletion } from "./runtime-state.ts";
@@ -159,7 +159,7 @@ async function globalMemoryContextForPrompt(cwd: string, prompt: string): Promis
159
159
  const query = prompt.trim();
160
160
  if (query.length < 8) return undefined;
161
161
  try {
162
- const bundle = await new MemoryStore({ cwd }).retrieve({
162
+ const bundle = await createConfiguredMemoryStore({ cwd }).retrieve({
163
163
  query,
164
164
  sourceAgent: "primary-pi-global",
165
165
  limit: 5,
@@ -15,7 +15,8 @@ import { Type } from "typebox";
15
15
  import { ArtifactStore, type ArtifactFeatureStatus } from "./artifacts.ts";
16
16
  import type { BudgetPolicy } from "./budget.ts";
17
17
  import { buildProjectDiscoveryIndex, formatProjectDiscoveryIndex } from "./discovery.ts";
18
- import { createMemoryCandidate, MemoryStore } from "./memory.ts";
18
+ import { createMemoryCandidate } from "./memory.ts";
19
+ import { createConfiguredMemoryStore } from "./memory-provider.ts";
19
20
  import { buildProjectSnapshot, formatProjectSnapshot } from "./snapshot.ts";
20
21
  import { fetchWebUrls, formatWebBundle, searchWeb } from "./webfetch.ts";
21
22
 
@@ -475,7 +476,7 @@ export function createChalinMemorySearchTool(policy: ChildToolPolicy): ToolDefin
475
476
  const input = isRecord(params) ? params : {};
476
477
  const gate = policy.beforeTool("chalin_memory_search", input);
477
478
  if (!gate.allowed) return blockedToolResult(gate.reason);
478
- const store = new MemoryStore({ cwd: policy.cwd });
479
+ const store = createConfiguredMemoryStore({ cwd: policy.cwd });
479
480
  const bundle = await store.retrieve({
480
481
  query: String(params.query ?? ""),
481
482
  sourceAgent: policy.agentName,
@@ -510,7 +511,7 @@ export function createChalinMemoryWriteTool(policy: ChildToolPolicy): ToolDefini
510
511
  if (!gate.allowed) return blockedToolResult(gate.reason);
511
512
  const validation = validateMemoryWriteParams(params);
512
513
  if (!validation.allowed) return blockedToolResult(validation.reason);
513
- const store = new MemoryStore({ cwd: policy.cwd });
514
+ const store = createConfiguredMemoryStore({ cwd: policy.cwd });
514
515
  const [record] = await store.submitCandidates([createMemoryCandidate({
515
516
  category: params.category,
516
517
  content: params.content,
@@ -546,7 +547,7 @@ export function createChalinMemoryReviseTool(policy: ChildToolPolicy): ToolDefin
546
547
  if (!gate.allowed) return blockedToolResult(gate.reason);
547
548
  const validation = validateMemoryRevisionParams(params);
548
549
  if (!validation.allowed) return blockedToolResult(validation.reason);
549
- const store = new MemoryStore({ cwd: policy.cwd });
550
+ const store = createConfiguredMemoryStore({ cwd: policy.cwd });
550
551
  const record = await store.revise(params.id, {
551
552
  category: params.category,
552
553
  content: params.content,
package/src/commands.ts CHANGED
@@ -1,9 +1,9 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
1
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import { sessionModelOverrides, sessionThinkingOverrides } from "./agent-overrides.ts";
3
3
  import { AgentCatalog } from "./agents.ts";
4
4
  import { ArtifactStore } from "./artifacts.ts";
5
- import { loadEffectiveConfig, writeProjectConfig } from "./config.ts";
6
- import { MemoryStore } from "./memory.ts";
5
+ import { loadEffectiveConfig, writeProjectConfig, type ChalinConfig, type MemoryProvider } from "./config.ts";
6
+ import { createConfiguredMemoryStore, resolveMemoryBackendStatus, type MemoryBackendStatus } from "./memory-provider.ts";
7
7
  import { getActiveRun, getLatestRun } from "./runtime-state.ts";
8
8
  import { openAgentManager } from "./ui-agents.ts";
9
9
  import {
@@ -20,7 +20,7 @@ export function registerChalinCommands(pi: ExtensionAPI): void {
20
20
  pi.registerCommand("chalin", {
21
21
  description: "Open pi-chalin Smart Panel or toggle autonomous routing with: /chalin on|off",
22
22
  getArgumentCompletions: (prefix) => {
23
- const values = ["on", "off", "agents", "memory", "artifacts", "activity", "web", "status"];
23
+ const values = ["on", "off", "agents", "memory", "artifacts", "activity", "web", "settings", "status"];
24
24
  const filtered = values.filter((value) => value.startsWith(prefix.trim()));
25
25
  return filtered.length > 0 ? filtered.map((value) => ({ value, label: value })) : null;
26
26
  },
@@ -39,11 +39,12 @@ export function registerChalinCommands(pi: ExtensionAPI): void {
39
39
 
40
40
  const loaded = loadEffectiveConfig({ cwd: ctx.cwd });
41
41
  const catalog = AgentCatalog.load({ cwd: ctx.cwd });
42
- const memory = new MemoryStore({ cwd: ctx.cwd });
42
+ const memory = createConfiguredMemoryStore({ cwd: ctx.cwd }, loaded.config);
43
43
  const artifacts = new ArtifactStore({ cwd: ctx.cwd });
44
44
  const agents = catalog.list();
45
45
  const diagnostics = [...loaded.diagnostics, ...catalog.diagnostics.warnings, ...catalog.diagnostics.errors];
46
46
  const pendingMemories = await memory.list("pending");
47
+ const memoryStatus = await resolveMemoryBackendStatus({ cwd: ctx.cwd }, loaded.config);
47
48
  const activeRun = getActiveRun();
48
49
  const lastRun = getLatestRun();
49
50
 
@@ -62,11 +63,15 @@ export function registerChalinCommands(pi: ExtensionAPI): void {
62
63
  approve: (id) => void memory.approve(id),
63
64
  reject: (id) => void memory.reject(id),
64
65
  delete: (id) => void memory.delete(id),
65
- });
66
+ }, memoryReviewOptions(memoryStatus));
66
67
  }
67
68
  return;
68
69
  }
69
70
 
71
+ if (command === "settings") {
72
+ await openChalinSettings(ctx, loaded.config);
73
+ return;
74
+ }
70
75
 
71
76
  if (command === "artifacts") {
72
77
  const featureId = rest.join(" ").trim();
@@ -94,6 +99,8 @@ export function registerChalinCommands(pi: ExtensionAPI): void {
94
99
  `routing: ${loaded.config.enabled ? "on" : "off"}`,
95
100
  `autonomy: ${loaded.config.autonomy}`,
96
101
  `approval threshold: ${loaded.config.safety.approvalRiskThreshold}`,
102
+ `memory: ${memoryStatus.summary}`,
103
+ ...(memoryStatus.detail ? [`memory detail: ${memoryStatus.detail}`] : []),
97
104
  `agents: ${agents.length}`,
98
105
  `pending memory: ${pendingMemories.length}`,
99
106
  `last activity: ${lastRun?.id ?? "none"}`,
@@ -120,6 +127,7 @@ export function registerChalinCommands(pi: ExtensionAPI): void {
120
127
  pendingApprovals: 0,
121
128
  activeRuns: activeRun ? 1 : 0,
122
129
  pendingMemoryCandidates: pendingMemories.length,
130
+ memoryBackend: memoryStatus.summary,
123
131
  lastRun,
124
132
  },
125
133
  agents,
@@ -131,10 +139,69 @@ export function registerChalinCommands(pi: ExtensionAPI): void {
131
139
  approve: (id) => void memory.approve(id),
132
140
  reject: (id) => void memory.reject(id),
133
141
  delete: (id) => void memory.delete(id),
134
- }),
142
+ }, memoryReviewOptions(memoryStatus)),
135
143
  onSelectArtifacts: () => openArtifactPanel(ctx, artifacts),
136
144
  onSelectWebFetch: async () => openWebFetchAuditPanel(ctx, await listWebFetchAudit({ cwd: ctx.cwd })),
145
+ onSelectSettings: () => openChalinSettings(ctx, loaded.config),
137
146
  });
138
147
  },
139
148
  });
140
149
  }
150
+
151
+ async function openChalinSettings(ctx: ExtensionContext, config: ChalinConfig): Promise<void> {
152
+ const current = config.memory.provider;
153
+ if (!ctx.hasUI) {
154
+ ctx.ui.notify(`memory provider: ${current}`, "info");
155
+ return;
156
+ }
157
+
158
+ const selected = await ctx.ui.select("pi-chalin Settings", [`Memory provider · ${labelForMemoryProvider(current)}`, "Close"]);
159
+ if (!selected?.startsWith("Memory provider")) return;
160
+
161
+ const choice = await ctx.ui.select("Memory Provider", [
162
+ "Auto · Engram when available",
163
+ "Engram · native Engram memory",
164
+ "pi-chalin local",
165
+ "Close",
166
+ ]);
167
+ const provider = providerFromSettingsChoice(choice);
168
+ if (!provider) return;
169
+
170
+ const loaded = writeProjectConfig({ cwd: ctx.cwd }, { memory: { provider } } as Partial<ChalinConfig>);
171
+ const status = await resolveMemoryBackendStatus({ cwd: ctx.cwd }, loaded.config);
172
+ ctx.ui.notify(`Memory provider set to ${labelForMemoryProvider(provider)}.\nActive: ${status.summary}`, "info");
173
+ }
174
+
175
+ function providerFromSettingsChoice(choice: string | undefined): MemoryProvider | undefined {
176
+ if (choice?.startsWith("Auto")) return "auto";
177
+ if (choice?.startsWith("Engram")) return "engram";
178
+ if (choice?.startsWith("pi-chalin")) return "pi-chalin";
179
+ return undefined;
180
+ }
181
+
182
+ function labelForMemoryProvider(provider: MemoryProvider): string {
183
+ if (provider === "auto") return "auto";
184
+ if (provider === "engram") return "engram";
185
+ return "pi-chalin local";
186
+ }
187
+
188
+ function memoryReviewOptions(status: MemoryBackendStatus): { title: string; emptyMessage: string } {
189
+ if (status.configuredProvider === "engram") {
190
+ return {
191
+ title: "Engram Memory",
192
+ emptyMessage: status.engramAvailable
193
+ ? status.detail ?? "No Engram memory records found."
194
+ : "Engram memory is selected, but Engram is unavailable. Start Engram or update /chalin settings.",
195
+ };
196
+ }
197
+ if (status.activeProvider === "engram") {
198
+ return {
199
+ title: "Engram Memory",
200
+ emptyMessage: status.detail ?? "No Engram memory records found.",
201
+ };
202
+ }
203
+ return {
204
+ title: "pi-chalin Memory",
205
+ emptyMessage: "No pi-chalin memory records found.",
206
+ };
207
+ }
package/src/config.ts CHANGED
@@ -6,6 +6,7 @@ import { isAgentThinkingLevel, riskRank, type AgentScope, type AgentThinkingLeve
6
6
  export type AutonomyLevel = "low" | "balanced" | "high";
7
7
  export type ApprovalRiskThreshold = RouteRisk;
8
8
  export type ModelPersistenceTarget = "session" | "project" | "user";
9
+ export type MemoryProvider = "auto" | "engram" | "pi-chalin";
9
10
 
10
11
  export interface ChalinConfig {
11
12
  enabled: boolean;
@@ -22,6 +23,18 @@ export interface ChalinConfig {
22
23
  thinkingOverrides: Record<string, AgentThinkingLevel>;
23
24
  modelPersistenceDefaults: Record<AgentScope, ModelPersistenceTarget>;
24
25
  };
26
+ memory: {
27
+ provider: MemoryProvider;
28
+ engram: {
29
+ baseUrl: string;
30
+ command: string;
31
+ autoStart: boolean;
32
+ autoSync: boolean;
33
+ syncThrottleMs: number;
34
+ timeoutMs: number;
35
+ project?: string;
36
+ };
37
+ };
25
38
  }
26
39
 
27
40
  export interface LoadedChalinConfig {
@@ -49,6 +62,17 @@ export const DEFAULT_CONFIG: ChalinConfig = {
49
62
  project: "project",
50
63
  },
51
64
  },
65
+ memory: {
66
+ provider: "auto",
67
+ engram: {
68
+ baseUrl: "http://127.0.0.1:7437",
69
+ command: "engram",
70
+ autoStart: false,
71
+ autoSync: true,
72
+ syncThrottleMs: 30_000,
73
+ timeoutMs: 800,
74
+ },
75
+ },
52
76
  };
53
77
 
54
78
  function isObject(value: unknown): value is Record<string, unknown> {
@@ -118,6 +142,40 @@ function coerceConfig(input: ChalinConfig, diagnostics: string[]): ChalinConfig
118
142
  delete config.agents.thinkingOverrides[agentRef];
119
143
  }
120
144
  }
145
+ if (!isObject(config.memory)) config.memory = structuredClone(DEFAULT_CONFIG.memory);
146
+ if (!["auto", "engram", "pi-chalin"].includes(config.memory.provider)) {
147
+ diagnostics.push(`Invalid memory.provider '${String(config.memory.provider)}'; using '${DEFAULT_CONFIG.memory.provider}'.`);
148
+ config.memory.provider = DEFAULT_CONFIG.memory.provider;
149
+ }
150
+ if (!isObject(config.memory.engram)) config.memory.engram = structuredClone(DEFAULT_CONFIG.memory.engram);
151
+ if (typeof config.memory.engram.baseUrl !== "string" || !config.memory.engram.baseUrl.trim()) {
152
+ diagnostics.push(`Invalid memory.engram.baseUrl '${String(config.memory.engram.baseUrl)}'; using '${DEFAULT_CONFIG.memory.engram.baseUrl}'.`);
153
+ config.memory.engram.baseUrl = DEFAULT_CONFIG.memory.engram.baseUrl;
154
+ }
155
+ if (typeof config.memory.engram.command !== "string" || !config.memory.engram.command.trim()) {
156
+ diagnostics.push(`Invalid memory.engram.command '${String(config.memory.engram.command)}'; using '${DEFAULT_CONFIG.memory.engram.command}'.`);
157
+ config.memory.engram.command = DEFAULT_CONFIG.memory.engram.command;
158
+ }
159
+ if (typeof config.memory.engram.autoStart !== "boolean") {
160
+ diagnostics.push(`Invalid memory.engram.autoStart '${String(config.memory.engram.autoStart)}'; using '${DEFAULT_CONFIG.memory.engram.autoStart}'.`);
161
+ config.memory.engram.autoStart = DEFAULT_CONFIG.memory.engram.autoStart;
162
+ }
163
+ if (typeof config.memory.engram.autoSync !== "boolean") {
164
+ diagnostics.push(`Invalid memory.engram.autoSync '${String(config.memory.engram.autoSync)}'; using '${DEFAULT_CONFIG.memory.engram.autoSync}'.`);
165
+ config.memory.engram.autoSync = DEFAULT_CONFIG.memory.engram.autoSync;
166
+ }
167
+ if (!Number.isFinite(config.memory.engram.syncThrottleMs) || config.memory.engram.syncThrottleMs < 0 || config.memory.engram.syncThrottleMs > 300_000) {
168
+ diagnostics.push(`Invalid memory.engram.syncThrottleMs '${String(config.memory.engram.syncThrottleMs)}'; using '${DEFAULT_CONFIG.memory.engram.syncThrottleMs}'.`);
169
+ config.memory.engram.syncThrottleMs = DEFAULT_CONFIG.memory.engram.syncThrottleMs;
170
+ }
171
+ if (!Number.isFinite(config.memory.engram.timeoutMs) || config.memory.engram.timeoutMs < 100 || config.memory.engram.timeoutMs > 10_000) {
172
+ diagnostics.push(`Invalid memory.engram.timeoutMs '${String(config.memory.engram.timeoutMs)}'; using '${DEFAULT_CONFIG.memory.engram.timeoutMs}'.`);
173
+ config.memory.engram.timeoutMs = DEFAULT_CONFIG.memory.engram.timeoutMs;
174
+ }
175
+ if (config.memory.engram.project !== undefined && typeof config.memory.engram.project !== "string") {
176
+ diagnostics.push(`Invalid memory.engram.project '${String(config.memory.engram.project)}'; removing override.`);
177
+ delete config.memory.engram.project;
178
+ }
121
179
  return config;
122
180
  }
123
181
 
package/src/index.ts CHANGED
@@ -35,6 +35,8 @@ export { ArtifactStore } from "./artifacts.ts";
35
35
  export { DEFAULT_CONFIG, approvalDecision, loadEffectiveConfig, setAgentModelOverride, setAgentThinkingOverride, writeProjectConfig, writeUserConfig } from "./config.ts";
36
36
  export { ChalinKernel } from "./kernel.ts";
37
37
  export { MemoryStore, createMemoryCandidate } from "./memory.ts";
38
+ export { EngramMemoryStore, createConfiguredMemoryStore, resolveMemoryBackendStatus } from "./memory-provider.ts";
38
39
  export { MockWorkerRunner, SdkWorkerRunner, parseAgentOutput } from "./runner.ts";
39
40
  export { resolveChalinPaths } from "./paths.ts";
40
41
  export type { AgentDefinition, AgentMemoryPolicy, AgentThinkingLevel, RouteDecision, RoutePlan, RunState, MemoryCandidate, MemoryRecord } from "./schemas.ts";
42
+ export type { MemoryProvider } from "./config.ts";
package/src/kernel.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import { AgentCatalog } from "./agents.ts";
2
2
  import { ArtifactStore } from "./artifacts.ts";
3
- import { approvalDecision, type ChalinConfig } from "./config.ts";
4
- import { MemoryStore } from "./memory.ts";
3
+ import { DEFAULT_CONFIG, approvalDecision, type ChalinConfig } from "./config.ts";
4
+ import type { MemoryStoreLike } from "./memory.ts";
5
+ import { createConfiguredMemoryStore } from "./memory-provider.ts";
5
6
  import { MockWorkerRunner, SdkWorkerRunner, type WorkerRunner, type WorkerRunnerContext } from "./runner.ts";
6
7
  import type { AgentDefinition, AgentStage, AgentStep, AgentThinkingLevel, ApprovalDecision, MemoryRecord, RouteDecision, RoutePlan, RunState } from "./schemas.ts";
7
8
 
@@ -9,7 +10,7 @@ export interface ChalinKernelOptions {
9
10
  cwd?: string;
10
11
  config?: ChalinConfig;
11
12
  catalog?: AgentCatalog;
12
- memory?: MemoryStore;
13
+ memory?: MemoryStoreLike;
13
14
  artifacts?: ArtifactStore;
14
15
  runner?: WorkerRunner;
15
16
  sdkRunner?: WorkerRunner;
@@ -29,7 +30,7 @@ export class ChalinKernel {
29
30
  private readonly cwd: string;
30
31
  private readonly config: ChalinConfig;
31
32
  private readonly catalog: AgentCatalog;
32
- private readonly memory: MemoryStore;
33
+ private readonly memory: MemoryStoreLike;
33
34
  private readonly artifacts: ArtifactStore;
34
35
  private readonly runner: WorkerRunner;
35
36
  private readonly sdkRunner: WorkerRunner;
@@ -38,14 +39,9 @@ export class ChalinKernel {
38
39
 
39
40
  constructor(options?: ChalinKernelOptions) {
40
41
  this.cwd = options?.cwd ?? process.cwd();
41
- this.config = options?.config ?? {
42
- enabled: true,
43
- autonomy: "balanced",
44
- safety: { approvalRiskThreshold: "medium", recursionGuard: true, singleWriterGuard: true, mutationExpectationGuard: true, blockCritical: true },
45
- agents: { modelOverrides: {}, thinkingOverrides: {}, modelPersistenceDefaults: { "built-in": "user", user: "user", project: "project" } },
46
- };
42
+ this.config = options?.config ?? DEFAULT_CONFIG;
47
43
  this.catalog = options?.catalog ?? AgentCatalog.load({ cwd: this.cwd });
48
- this.memory = options?.memory ?? new MemoryStore({ cwd: this.cwd });
44
+ this.memory = options?.memory ?? createConfiguredMemoryStore({ cwd: this.cwd }, this.config);
49
45
  this.artifacts = options?.artifacts ?? new ArtifactStore({ cwd: this.cwd });
50
46
  this.runner = options?.runner ?? new MockWorkerRunner();
51
47
  this.sdkRunner = options?.sdkRunner ?? new SdkWorkerRunner();