la-machina-engine 0.4.0 → 0.5.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
@@ -495,7 +495,7 @@ engine.run({ runId, nodeId, task })
495
495
 
496
496
  ### Storage Adapter
497
497
 
498
- Two backends, same interface:
498
+ Three backends, same interface and the same relative layout on all of them:
499
499
 
500
500
  | Adapter | Backend | Use |
501
501
  |---------|---------|-----|
@@ -503,6 +503,33 @@ Two backends, same interface:
503
503
  | `R2StorageAdapter` | Cloudflare R2 via S3 protocol | Node / anywhere with S3 creds |
504
504
  | `R2BindingStorageAdapter` | Cloudflare R2 native binding (`env.BUCKET`) | Cloudflare Workers (`provider: 'r2-binding'`) |
505
505
 
506
+ **Path layout (identical across all three backends):**
507
+
508
+ ```
509
+ {rootPath}/workspaces/{workspaceId}/.claude/ ← tenant root
510
+ ├── memory/ ← tenant-shared, survives across runs
511
+ ├── skills/ ← (if config.skills.autoload)
512
+ └── projects/{runId}/nodes/{nodeId}/
513
+ ├── state.json, snapshot.json, 000000.jsonl, meta.json
514
+ └── subagents/{agentId}/… ← recursive, same shape
515
+ ```
516
+
517
+ `workspaces/` is a namespace guard (keeps engine data separate from
518
+ anything else in a shared bucket/filesystem); `.claude/` marks
519
+ engine-owned content. Both cost one directory level each.
520
+
521
+ **The workspace IS the tenant boundary.** One `workspaceId` per
522
+ tenant; nothing is shared across workspaces. The previous
523
+ `global` storage scope was removed in v0.5.0 — see migration note
524
+ below.
525
+
526
+ > **Migration from pre-0.5.0**: if you had data at `{rootPath}/.claude/`
527
+ > (the old global scope), move it under your workspace root:
528
+ > `mv {rootPath}/.claude {rootPath}/workspaces/{workspaceId}/.claude`.
529
+ > `config.memory.scope: 'global'` still parses but emits a
530
+ > deprecation warning and is rewritten to `'workspace'`; it'll be
531
+ > rejected outright in v1.0.0.
532
+
506
533
  ### Smart Memory
507
534
 
508
535
  Per-workspace learning across runs:
package/dist/index.cjs CHANGED
@@ -943,7 +943,8 @@ var ModelProviderEnum = import_zod.z.enum([
943
943
  ]);
944
944
  var StorageProviderEnum = import_zod.z.enum(["local", "r2", "r2-binding"]);
945
945
  var MemoryModeEnum = import_zod.z.enum(["off", "read-only", "read-write"]);
946
- var MemoryScopeEnum = import_zod.z.enum(["workspace", "global"]);
946
+ var MemoryScopeUserEnum = import_zod.z.enum(["workspace", "global"]);
947
+ var MemoryScopeResolvedEnum = import_zod.z.enum(["workspace"]);
947
948
  var FlushPolicyEnum = import_zod.z.enum(["turn-end", "entry", "manual"]);
948
949
  var LogLevelEnum = import_zod.z.enum(["silent", "error", "warn", "info", "debug"]);
949
950
  var R2ConfigResolved = import_zod.z.object({
@@ -982,7 +983,7 @@ var StorageConfigResolved = import_zod.z.object({
982
983
  });
983
984
  var MemoryConfigResolved = import_zod.z.object({
984
985
  mode: MemoryModeEnum,
985
- scope: MemoryScopeEnum
986
+ scope: MemoryScopeResolvedEnum
986
987
  }).strict();
987
988
  var ToolsConfigResolved = import_zod.z.object({
988
989
  enabled: import_zod.z.array(import_zod.z.string()),
@@ -1177,7 +1178,10 @@ var StorageConfigUser = import_zod.z.object({
1177
1178
  r2: R2ConfigUser.optional(),
1178
1179
  r2Binding: R2BucketBindingShape.optional()
1179
1180
  }).strict();
1180
- var MemoryConfigUser = MemoryConfigResolved.partial();
1181
+ var MemoryConfigUser = import_zod.z.object({
1182
+ mode: MemoryModeEnum.optional(),
1183
+ scope: MemoryScopeUserEnum.optional()
1184
+ }).strict();
1181
1185
  var ToolsConfigUser = ToolsConfigResolved.partial();
1182
1186
  var AgentsConfigUser = AgentsConfigResolved.partial();
1183
1187
  var SkillsConfigUser = SkillsConfigResolved.partial();
@@ -1290,8 +1294,21 @@ function splitApiRuntime(user) {
1290
1294
  const clone = { ...user, api: schemaSafe };
1291
1295
  return { stripped: clone, runtime };
1292
1296
  }
1297
+ function coerceDeprecatedMemoryScope(user) {
1298
+ const scope = user.memory?.scope;
1299
+ if (scope !== "global") return user;
1300
+ console.warn(
1301
+ '[la-machina] config.memory.scope: "global" is deprecated and has been rewritten to "workspace". Cross-tenant memory sharing was removed in Plan 022 \u2014 the workspace is the tenant root. This field value will be rejected outright in a future major release.'
1302
+ );
1303
+ const memory = user.memory ?? {};
1304
+ return {
1305
+ ...user,
1306
+ memory: { ...memory, scope: "workspace" }
1307
+ };
1308
+ }
1293
1309
  function mergeConfig(user) {
1294
- const { stripped, runtime } = splitApiRuntime(user);
1310
+ const withCoercedScope = coerceDeprecatedMemoryScope(user);
1311
+ const { stripped, runtime } = splitApiRuntime(withCoercedScope);
1295
1312
  const validatedUser = UserConfigSchema.parse(stripped);
1296
1313
  const merged = deepMerge(DEFAULTS, validatedUser);
1297
1314
  const resolved = ResolvedConfigSchema.parse(merged);
@@ -7165,7 +7182,7 @@ ${entries.map((e) => `- ${e}`).join("\n")}
7165
7182
  // src/memory/memoryConfig.ts
7166
7183
  function createSmartMemory(options) {
7167
7184
  const { storage, config } = options;
7168
- const adapter = config.scope === "global" ? storage.global : storage.workspace;
7185
+ const adapter = storage.workspace;
7169
7186
  const hippocampus = new Hippocampus(adapter, "smart-memory");
7170
7187
  const writesEnabled = config.mode === "read-write";
7171
7188
  const readsEnabled = config.mode !== "off";
@@ -7536,18 +7553,7 @@ ${lessons}`);
7536
7553
  }
7537
7554
  async function collectSkills(storage, skillsDir) {
7538
7555
  const workspace = await loadSkills(storage.workspace, skillsDir);
7539
- const global = await loadSkills(storage.global, skillsDir);
7540
- const seen = /* @__PURE__ */ new Set();
7541
- const out = [];
7542
- for (const s of workspace) {
7543
- seen.add(s.name);
7544
- out.push({ name: s.name, description: s.description });
7545
- }
7546
- for (const s of global) {
7547
- if (seen.has(s.name)) continue;
7548
- out.push({ name: s.name, description: s.description });
7549
- }
7550
- return out;
7556
+ return workspace.map((s) => ({ name: s.name, description: s.description }));
7551
7557
  }
7552
7558
 
7553
7559
  // src/engine/jsonOutput.ts
@@ -7881,14 +7887,8 @@ var StorageSkillSource = class {
7881
7887
  this.baseDir = options.baseDir ?? "skills";
7882
7888
  }
7883
7889
  async list() {
7884
- const [workspace, global] = await Promise.all([
7885
- loadSkills(this.storage.workspace, this.baseDir),
7886
- loadSkills(this.storage.global, this.baseDir)
7887
- ]);
7888
- const byName = /* @__PURE__ */ new Map();
7889
- for (const s of global) byName.set(s.name, s);
7890
- for (const s of workspace) byName.set(s.name, s);
7891
- return Array.from(byName.values()).map((s) => ({
7890
+ const loaded = await loadSkills(this.storage.workspace, this.baseDir);
7891
+ return loaded.map((s) => ({
7892
7892
  name: s.name,
7893
7893
  description: s.description,
7894
7894
  hasPages: s.hasPages
@@ -7896,8 +7896,7 @@ var StorageSkillSource = class {
7896
7896
  }
7897
7897
  async getSkillFile(skill) {
7898
7898
  if (!SAFE_NAME4.test(skill)) return null;
7899
- const found = await this.findFile(skill, "SKILL.md");
7900
- return found;
7899
+ return this.findFile(skill, "SKILL.md");
7901
7900
  }
7902
7901
  async getPage(skill, page) {
7903
7902
  if (!SAFE_NAME4.test(skill)) return null;
@@ -7906,9 +7905,7 @@ var StorageSkillSource = class {
7906
7905
  }
7907
7906
  async listPages(skill) {
7908
7907
  if (!SAFE_NAME4.test(skill)) return [];
7909
- const wsPages = await this.listPagesIn(this.storage.workspace, skill);
7910
- if (wsPages.length > 0) return wsPages;
7911
- return this.listPagesIn(this.storage.global, skill);
7908
+ return this.listPagesIn(this.storage.workspace, skill);
7912
7909
  }
7913
7910
  async listPagesIn(adapter, skill) {
7914
7911
  const pagesDir = `${this.baseDir}/${skill}/pages`;
@@ -7921,9 +7918,7 @@ var StorageSkillSource = class {
7921
7918
  }
7922
7919
  async findFile(skill, relative) {
7923
7920
  const path = `${this.baseDir}/${skill}/${relative}`;
7924
- const fromWorkspace = await this.readIfExists(this.storage.workspace, path);
7925
- if (fromWorkspace !== null) return fromWorkspace;
7926
- return this.readIfExists(this.storage.global, path);
7921
+ return this.readIfExists(this.storage.workspace, path);
7927
7922
  }
7928
7923
  async readIfExists(adapter, path) {
7929
7924
  try {
@@ -8506,7 +8501,6 @@ async function createEngineStorage(config) {
8506
8501
  }
8507
8502
  async function createLocalStorage(config) {
8508
8503
  const path = await import("path");
8509
- const globalRoot = path.join(config.rootPath, ENGINE_DATA_FOLDER);
8510
8504
  const workspaceRoot = path.join(
8511
8505
  config.rootPath,
8512
8506
  WORKSPACES_FOLDER,
@@ -8514,7 +8508,6 @@ async function createLocalStorage(config) {
8514
8508
  ENGINE_DATA_FOLDER
8515
8509
  );
8516
8510
  return {
8517
- global: new LocalStorageAdapter(globalRoot),
8518
8511
  workspace: new LocalStorageAdapter(workspaceRoot)
8519
8512
  };
8520
8513
  }
@@ -8523,10 +8516,8 @@ function createR2Storage(config) {
8523
8516
  throw new StorageError('storage.r2 is required when storage.provider === "r2"');
8524
8517
  }
8525
8518
  const rootPrefix = config.rootPath.replace(/^\/+|\/+$/g, "");
8526
- const globalPrefix = `${rootPrefix}/${ENGINE_DATA_FOLDER}`;
8527
8519
  const workspacePrefix = `${rootPrefix}/${WORKSPACES_FOLDER}/${config.workspaceId}/${ENGINE_DATA_FOLDER}`;
8528
8520
  return {
8529
- global: new R2StorageAdapter(config.r2, globalPrefix),
8530
8521
  workspace: new R2StorageAdapter(config.r2, workspacePrefix)
8531
8522
  };
8532
8523
  }
@@ -8535,10 +8526,8 @@ function createR2BindingStorage(config) {
8535
8526
  throw new StorageError('storage.r2Binding is required when storage.provider === "r2-binding"');
8536
8527
  }
8537
8528
  const rootPrefix = config.rootPath.replace(/^\/+|\/+$/g, "");
8538
- const globalPrefix = `${rootPrefix}/${ENGINE_DATA_FOLDER}`;
8539
8529
  const workspacePrefix = `${rootPrefix}/${WORKSPACES_FOLDER}/${config.workspaceId}/${ENGINE_DATA_FOLDER}`;
8540
8530
  return {
8541
- global: new R2BindingStorageAdapter(config.r2Binding, globalPrefix),
8542
8531
  workspace: new R2BindingStorageAdapter(config.r2Binding, workspacePrefix)
8543
8532
  };
8544
8533
  }