@zenera/neo 1.1.11 → 1.1.14

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 (68) hide show
  1. package/README.md +3 -3
  2. package/dist/agent.d.ts +8 -7
  3. package/dist/agent.js +15 -8
  4. package/dist/architecture.d.ts +7 -5
  5. package/dist/architecture.js +20 -12
  6. package/dist/embeddings/limiter.js +14 -3
  7. package/dist/index.d.ts +12 -2
  8. package/dist/index.js +12 -2
  9. package/dist/inspect/client.d.ts +1 -1
  10. package/dist/inspect/client.js +440 -26
  11. package/dist/inspect/css.d.ts +1 -1
  12. package/dist/inspect/css.js +49 -0
  13. package/dist/inspect/index.d.ts +3 -3
  14. package/dist/inspect/index.js +1 -1
  15. package/dist/inspect/page.js +16 -0
  16. package/dist/inspect/report.d.ts +25 -0
  17. package/dist/inspect/report.js +45 -0
  18. package/dist/kernel.d.ts +1 -1
  19. package/dist/kernel.js +40 -23
  20. package/dist/memory/files.d.ts +24 -0
  21. package/dist/memory/files.js +85 -0
  22. package/dist/memory/graph.d.ts +56 -0
  23. package/dist/memory/graph.js +159 -0
  24. package/dist/memory/index.d.ts +93 -0
  25. package/dist/memory/index.js +357 -0
  26. package/dist/memory/instructions.d.ts +15 -0
  27. package/dist/memory/instructions.js +103 -0
  28. package/dist/memory/page.d.ts +8 -0
  29. package/dist/memory/page.js +797 -0
  30. package/dist/memory/recall.d.ts +35 -0
  31. package/dist/memory/recall.js +161 -0
  32. package/dist/memory/render.d.ts +10 -0
  33. package/dist/memory/render.js +228 -0
  34. package/dist/memory/report.d.ts +69 -0
  35. package/dist/memory/report.js +131 -0
  36. package/dist/memory/store.d.ts +49 -0
  37. package/dist/memory/store.js +226 -0
  38. package/dist/memory/tools.d.ts +11 -0
  39. package/dist/memory/tools.js +337 -0
  40. package/dist/memory/types.d.ts +193 -0
  41. package/dist/memory/types.js +105 -0
  42. package/dist/memory/vectors.d.ts +29 -0
  43. package/dist/memory/vectors.js +152 -0
  44. package/dist/project/config.d.ts +49 -1
  45. package/dist/project/config.js +63 -0
  46. package/dist/project/index.d.ts +2 -2
  47. package/dist/project/index.js +1 -1
  48. package/dist/project/load.d.ts +16 -2
  49. package/dist/project/load.js +67 -1
  50. package/dist/runner.d.ts +4 -2
  51. package/dist/runner.js +29 -30
  52. package/dist/services.d.ts +23 -6
  53. package/dist/services.js +25 -15
  54. package/dist/tools/workspace.js +195 -17
  55. package/dist/trajectory.d.ts +14 -12
  56. package/dist/types.d.ts +5 -5
  57. package/dist/types.js +3 -3
  58. package/package.json +2 -5
  59. package/dist/memory-stores/factory.d.ts +0 -15
  60. package/dist/memory-stores/factory.js +0 -29
  61. package/dist/memory-stores/file.d.ts +0 -37
  62. package/dist/memory-stores/file.js +0 -174
  63. package/dist/memory-stores/in-memory.d.ts +0 -17
  64. package/dist/memory-stores/in-memory.js +0 -80
  65. package/dist/memory-stores/index.d.ts +0 -4
  66. package/dist/memory-stores/index.js +0 -5
  67. package/dist/memory.d.ts +0 -115
  68. package/dist/memory.js +0 -213
@@ -1,7 +1,7 @@
1
1
  import { Agent, AgentRegistry } from '../agent.ts';
2
2
  import type { Embedder } from '../embedding.ts';
3
3
  import { RunStream } from '../events.ts';
4
- import type { MemoryStore } from '../memory.ts';
4
+ import { MemoryIndex } from '../memory/index.ts';
5
5
  import { ModelRegistry, type EmbeddingRef, type ModelRef, type ProviderSpec } from '../models/factory.ts';
6
6
  import type { PayloadStore } from '../payload.ts';
7
7
  import { AgentRunner, type RunOptions, type RunnerOptions } from '../runner.ts';
@@ -54,7 +54,9 @@ export interface ProjectOptions<TCtx = unknown> {
54
54
  * project never reaches the network.
55
55
  */
56
56
  registry?: ModelRegistry;
57
- memory?: MemoryStore[];
57
+ memory?: MemoryIndex;
58
+ /** turns an agent-visible path into a host path, so files can be remembered */
59
+ resolveFile?: (path: string) => string;
58
60
  payloads?: PayloadStore;
59
61
  /** extra providers merged with the ones the project declares */
60
62
  skills?: SkillProvider[];
@@ -99,6 +101,7 @@ interface ProjectParts<TCtx> {
99
101
  skillProviders: SkillProvider[];
100
102
  assets: string | undefined;
101
103
  models: ModelRegistry;
104
+ memory: MemoryIndex | undefined;
102
105
  embedders: (ref: string | undefined, where: string) => Embedder | undefined;
103
106
  options: ProjectOptions<TCtx>;
104
107
  }
@@ -129,7 +132,11 @@ export declare class AgentProject<TCtx = unknown> {
129
132
  readonly assets: string | undefined;
130
133
  /** the providers this project declared, and the clients behind them */
131
134
  readonly models: ModelRegistry;
135
+ /** the one memory graph, when this project has one */
136
+ readonly memory: MemoryIndex | undefined;
132
137
  constructor(parts: ProjectParts<TCtx>);
138
+ /** Releases the memory lock. A project that holds no memory need not be closed. */
139
+ close(): void;
133
140
  get agents(): readonly Agent<TCtx>[];
134
141
  /**
135
142
  * A declared vectoriser, memoized. Named or not: with no argument this is
@@ -153,6 +160,13 @@ export declare class AgentProject<TCtx = unknown> {
153
160
  * for, and only pay for it if an agent actually reaches for it.
154
161
  */
155
162
  export declare function projectRegistry<TCtx>(config: ProjectConfig, opts?: ProjectOptions<TCtx>): ModelRegistry;
163
+ /**
164
+ * Where this project's memory lives, or `undefined` when it has none. Shared
165
+ * with the front end, which has to mount the same directory into the sandbox:
166
+ * two answers to this question would mean `/memory` pointing somewhere the
167
+ * graph is not.
168
+ */
169
+ export declare function memoryDir(root: string, config: ProjectConfig): string | undefined;
156
170
  /**
157
171
  * Where each skill directory appears in the agent's namespace.
158
172
  *
@@ -2,6 +2,8 @@ import { existsSync, readFileSync, statSync } from 'node:fs';
2
2
  import { basename, join } from 'node:path';
3
3
  import { Agent, AgentRegistry } from "../agent.js";
4
4
  import { RunStream } from "../events.js";
5
+ import { MemoryIndex } from "../memory/index.js";
6
+ import { MemoryStore } from "../memory/store.js";
5
7
  import { ModelRegistry, } from "../models/factory.js";
6
8
  import { promptFile } from "../prompt.js";
7
9
  import { AgentRunner } from "../runner.js";
@@ -59,6 +61,7 @@ export async function loadProject(dir, opts = {}) {
59
61
  handoffs: spec.handoffs,
60
62
  skills: bindingFor(spec, providers),
61
63
  fork: forkFor(spec),
64
+ memory: memoryFor(spec),
62
65
  });
63
66
  }
64
67
  validate(config, registry, providers, catalogs, models);
@@ -66,12 +69,14 @@ export async function loadProject(dir, opts = {}) {
66
69
  // The default is resolved here rather than on first use, so a broken
67
70
  // `embedding:` fails at load like a broken `model:` does.
68
71
  embedders(config.embedding, 'embedding');
72
+ const memory = opts.memory ?? (await openMemory(root, config, embedders));
69
73
  return new AgentProject({
70
74
  root,
71
75
  source,
72
76
  config,
73
77
  entry: entrypoint(config),
74
78
  registry,
79
+ memory,
75
80
  skillProviders: providers,
76
81
  assets,
77
82
  models,
@@ -105,6 +110,8 @@ export class AgentProject {
105
110
  assets;
106
111
  /** the providers this project declared, and the clients behind them */
107
112
  models;
113
+ /** the one memory graph, when this project has one */
114
+ memory;
108
115
  #embedders;
109
116
  #options;
110
117
  #runner;
@@ -117,9 +124,14 @@ export class AgentProject {
117
124
  this.skillProviders = parts.skillProviders;
118
125
  this.assets = parts.assets;
119
126
  this.models = parts.models;
127
+ this.memory = parts.memory;
120
128
  this.#embedders = parts.embedders;
121
129
  this.#options = parts.options;
122
130
  }
131
+ /** Releases the memory lock. A project that holds no memory need not be closed. */
132
+ close() {
133
+ this.memory?.store.release();
134
+ }
123
135
  get agents() {
124
136
  return [...this.registry.agents.values()];
125
137
  }
@@ -151,7 +163,8 @@ export class AgentProject {
151
163
  #build(overrides = {}) {
152
164
  const runner = new AgentRunner({
153
165
  payloads: this.#options.payloads,
154
- memory: this.#options.memory,
166
+ memory: this.memory,
167
+ resolveFile: this.#options.resolveFile,
155
168
  skills: [...this.skillProviders],
156
169
  ...overrides,
157
170
  });
@@ -315,6 +328,59 @@ function forkFor(spec) {
315
328
  }
316
329
  return spec.fork === true ? {} : spec.fork;
317
330
  }
331
+ /**
332
+ * `memory: true` is the whole feature working the obvious way: read and write,
333
+ * and recall before a turn that follows new user input. An agent that has to
334
+ * remember to go looking mostly does not, so auto-recall is on by default and
335
+ * `autoRecall: false` is how you turn it off.
336
+ */
337
+ function memoryFor(spec) {
338
+ const b = spec.memory;
339
+ if (!b) {
340
+ return undefined;
341
+ }
342
+ const auto = b.autoRecall ?? true;
343
+ return {
344
+ access: b.access,
345
+ sees: b.sees,
346
+ writes: b.writes,
347
+ autoRecall: auto
348
+ ? { query: 'last_user_input', limit: auto === true ? 5 : auto.limit }
349
+ : undefined,
350
+ };
351
+ }
352
+ /**
353
+ * Where this project's memory lives, or `undefined` when it has none. Shared
354
+ * with the front end, which has to mount the same directory into the sandbox:
355
+ * two answers to this question would mean `/memory` pointing somewhere the
356
+ * graph is not.
357
+ */
358
+ export function memoryDir(root, config) {
359
+ if (!config.memory && !config.agents.some((a) => a.memory)) {
360
+ return undefined;
361
+ }
362
+ return join(root, config.memory?.dir ?? 'memory');
363
+ }
364
+ /**
365
+ * Opened only when something will use it, so an ordinary project neither
366
+ * creates a directory nor takes a lock. A `memory:` block with no agent bound
367
+ * to it still counts: it is how a project declares the graph `zen memory`
368
+ * inspects between runs.
369
+ */
370
+ async function openMemory(root, config, embedders) {
371
+ const dir = memoryDir(root, config);
372
+ if (!dir) {
373
+ return undefined;
374
+ }
375
+ const declared = config.memory;
376
+ const embedder = embedders(declared?.embedding ?? config.embedding, 'memory.embedding');
377
+ return new MemoryIndex({
378
+ store: await MemoryStore.open(dir),
379
+ embedder,
380
+ kinds: declared?.kinds,
381
+ relations: declared?.relations,
382
+ });
383
+ }
318
384
  function skillProviders(root, config, opts) {
319
385
  const declared = skillDirs(root, config);
320
386
  if (!declared.length) {
package/dist/runner.d.ts CHANGED
@@ -3,7 +3,7 @@ import { Agent, AgentRegistry, type AgentOptions } from './agent.ts';
3
3
  import { type Architecture } from './architecture.ts';
4
4
  import { RunStream } from './events.ts';
5
5
  import { type IdClock } from './ids.ts';
6
- import type { MemoryStore } from './memory.ts';
6
+ import type { MemoryIndex } from './memory/index.ts';
7
7
  import type { Model } from './model.ts';
8
8
  import { PayloadResolver, type PayloadStore } from './payload.ts';
9
9
  import { Services } from './services.ts';
@@ -41,7 +41,9 @@ export interface RunnerOptions<TCtx = unknown> {
41
41
  context?: TCtx;
42
42
  services?: Services;
43
43
  payloads?: PayloadStore | PayloadResolver;
44
- memory?: MemoryStore[];
44
+ memory?: MemoryIndex;
45
+ /** turns an agent-visible path into a host path, so files can be remembered */
46
+ resolveFile?: (path: string) => string;
45
47
  skills?: SkillProvider[];
46
48
  handoffPolicy?: HandoffPolicy;
47
49
  /** renders the nodes a policy selected; defaults to a structural note */
package/dist/runner.js CHANGED
@@ -3,7 +3,7 @@ import { describeArchitecture } from "./architecture.js";
3
3
  import { EventQueue, RunStream, } from "./events.js";
4
4
  import { systemClock } from "./ids.js";
5
5
  import * as Kernel from "./kernel.js";
6
- import { renderMemories } from "./memory.js";
6
+ import { renderRecollection } from "./memory/render.js";
7
7
  import { PayloadResolver } from "./payload.js";
8
8
  import { Services } from "./services.js";
9
9
  import { activeSkillNames } from "./skills.js";
@@ -76,19 +76,21 @@ export class AgentRunner {
76
76
  constructor(opts = {}) {
77
77
  this.#model = opts.model;
78
78
  this.#context = opts.context;
79
+ this.#clock = opts.clock ?? systemClock;
79
80
  this.services =
80
81
  opts.services ??
81
82
  new Services({
82
83
  payloads: opts.payloads,
83
84
  memory: opts.memory,
85
+ resolveFile: opts.resolveFile,
84
86
  skills: opts.skills,
87
+ clock: this.#clock,
85
88
  });
86
89
  this.#handoffPolicy = opts.handoffPolicy;
87
90
  this.#summarizer = opts.summarizer ?? structuralSummarizer;
88
91
  this.#joinPolicy = opts.joinPolicy ?? defaultJoinPolicy;
89
92
  this.#stream = opts.stream ?? true;
90
93
  this.#recordRequests = opts.recordRequests ?? false;
91
- this.#clock = opts.clock ?? systemClock;
92
94
  }
93
95
  /** Registers agents; chainable. */
94
96
  add(...agents) {
@@ -387,11 +389,12 @@ export class AgentRunner {
387
389
  * caching for a marginal gain.
388
390
  */
389
391
  async #autoRecall(state, env) {
390
- const agent = this.registry.get(state.agentName);
391
- const bindings = agent
392
- .memoryBindings(state.context)
393
- .filter((b) => b.autoRecall && b.autoRecall.query !== 'none');
394
- if (!bindings.length || !shouldRecall(state)) {
392
+ const memory = this.services.memory;
393
+ const binding = this.registry.get(state.agentName).memoryBinding(state.context);
394
+ if (!memory || !binding?.autoRecall || binding.autoRecall.query === 'none') {
395
+ return state;
396
+ }
397
+ if (!shouldRecall(state)) {
395
398
  return state;
396
399
  }
397
400
  const input = Kernel.lastUserInput(state);
@@ -399,32 +402,28 @@ export class AgentRunner {
399
402
  return state;
400
403
  }
401
404
  const parts = await Promise.all(input.content.map((p) => p.type === 'text' ? this.services.payloads.get(p.text) : Promise.resolve('')));
402
- const query = parts.filter(Boolean).join('\n');
403
- if (!query) {
405
+ const text = parts.filter(Boolean).join('\n');
406
+ if (!text) {
404
407
  return state;
405
408
  }
406
- let next = state;
407
- for (const b of bindings) {
408
- const hits = await this.services
409
- .memoryStore(b.store)
410
- .search(b.scope, { text: query, limit: b.autoRecall?.limit ?? 5 });
411
- if (!hits.length) {
412
- continue;
413
- }
414
- next = await Kernel.applyMemoryEffect(next, {
415
- kind: 'recall',
416
- store: b.store,
417
- scope: b.scope,
418
- query: { text: query, limit: b.autoRecall?.limit ?? 5 },
419
- hits: hits.map((h) => ({
420
- id: h.record.id,
421
- score: h.score,
422
- revision: h.record.revision,
423
- })),
424
- content: renderMemories(hits),
425
- }, env);
409
+ const query = { text, limit: binding.autoRecall.limit };
410
+ const rec = await memory.search(query, binding.sees);
411
+ const content = renderRecollection(rec);
412
+ if (!content) {
413
+ return state;
426
414
  }
427
- return next;
415
+ return Kernel.applyMemoryEffect(state, {
416
+ kind: 'recall',
417
+ query,
418
+ seeds: [...rec.seeds],
419
+ nodes: rec.nodes.map((n) => ({
420
+ id: n.node.id,
421
+ kind: n.node.kind,
422
+ score: n.score,
423
+ })),
424
+ edges: [...rec.edges],
425
+ content,
426
+ }, env);
428
427
  }
429
428
  /**
430
429
  * Failure-tolerant on purpose: an unknown tool, malformed arguments or a
@@ -1,23 +1,40 @@
1
1
  import { PayloadResolver, type PayloadStore } from './payload.ts';
2
- import type { MemoryStore } from './memory.ts';
2
+ import { type IdClock } from './ids.ts';
3
+ import type { MemoryIndex } from './memory/index.ts';
3
4
  import type { SkillProvider } from './skills.ts';
4
5
  /**
5
- * The I/O the kernel is not allowed to do itself: payload resolution, memory
6
- * backends and skill catalogs. Held by the driver (runner or Temporal
6
+ * The I/O the kernel is not allowed to do itself: payload resolution, the
7
+ * memory graph and skill catalogs. Held by the driver (runner or Temporal
7
8
  * activity) and handed to the kernel and to tools explicitly, so nothing reads
8
9
  * ambient state.
10
+ *
11
+ * There is one memory, not a map of them: an agent sees a slice of the single
12
+ * graph through its `sees` mask, which is what makes a memory shareable
13
+ * between agents at all.
9
14
  */
10
15
  export declare class Services {
11
16
  #private;
12
17
  readonly payloads: PayloadResolver;
18
+ /** the one memory graph, when the project has one */
19
+ readonly memory?: MemoryIndex;
20
+ /**
21
+ * Turns a path the agent can see into a host path, for the one tool that
22
+ * needs it: remembering a file. Supplied by whoever built the workspace,
23
+ * because only it knows the mounts. Without it, files cannot be
24
+ * remembered — which is a refusal, not a crash.
25
+ */
26
+ readonly resolveFile?: (path: string) => string;
27
+ /** the driver's clock, so a tool that stamps a node stays replayable */
28
+ readonly clock: IdClock;
13
29
  constructor(opts?: {
14
30
  payloads?: PayloadResolver | PayloadStore;
15
- memory?: MemoryStore[];
31
+ memory?: MemoryIndex;
16
32
  skills?: SkillProvider[];
33
+ clock?: IdClock;
34
+ resolveFile?: (path: string) => string;
17
35
  });
18
- memoryStore(id: string): MemoryStore;
19
36
  skillProvider(id: string): SkillProvider;
20
- hasMemory(id: string): boolean;
37
+ hasMemory(): boolean;
21
38
  hasSkills(id: string): boolean;
22
39
  }
23
40
  //# sourceMappingURL=services.d.ts.map
package/dist/services.js CHANGED
@@ -1,32 +1,42 @@
1
1
  import { PayloadResolver } from "./payload.js";
2
+ // The composition root is the one place in the core allowed to name a backend:
3
+ // it has to fall back to something when the caller supplies no store.
4
+ import { systemClock } from "./ids.js";
2
5
  import { InMemoryPayloadStore } from "./payload-stores/in-memory.js";
3
6
  /**
4
- * The I/O the kernel is not allowed to do itself: payload resolution, memory
5
- * backends and skill catalogs. Held by the driver (runner or Temporal
7
+ * The I/O the kernel is not allowed to do itself: payload resolution, the
8
+ * memory graph and skill catalogs. Held by the driver (runner or Temporal
6
9
  * activity) and handed to the kernel and to tools explicitly, so nothing reads
7
10
  * ambient state.
11
+ *
12
+ * There is one memory, not a map of them: an agent sees a slice of the single
13
+ * graph through its `sees` mask, which is what makes a memory shareable
14
+ * between agents at all.
8
15
  */
9
16
  export class Services {
10
17
  payloads;
11
- #memory = new Map();
18
+ /** the one memory graph, when the project has one */
19
+ memory;
20
+ /**
21
+ * Turns a path the agent can see into a host path, for the one tool that
22
+ * needs it: remembering a file. Supplied by whoever built the workspace,
23
+ * because only it knows the mounts. Without it, files cannot be
24
+ * remembered — which is a refusal, not a crash.
25
+ */
26
+ resolveFile;
27
+ /** the driver's clock, so a tool that stamps a node stays replayable */
28
+ clock;
12
29
  #skills = new Map();
13
30
  constructor(opts = {}) {
14
31
  const p = opts.payloads ?? new InMemoryPayloadStore();
15
32
  this.payloads = p instanceof PayloadResolver ? p : new PayloadResolver(p);
16
- for (const m of opts.memory ?? []) {
17
- this.#memory.set(m.id, m);
18
- }
33
+ this.memory = opts.memory;
34
+ this.resolveFile = opts.resolveFile;
35
+ this.clock = opts.clock ?? systemClock;
19
36
  for (const s of opts.skills ?? []) {
20
37
  this.#skills.set(s.id, s);
21
38
  }
22
39
  }
23
- memoryStore(id) {
24
- const s = this.#memory.get(id);
25
- if (!s) {
26
- throw new Error(`unknown memory store: ${id} (known: ${[...this.#memory.keys()]})`);
27
- }
28
- return s;
29
- }
30
40
  skillProvider(id) {
31
41
  const p = this.#skills.get(id);
32
42
  if (!p) {
@@ -34,8 +44,8 @@ export class Services {
34
44
  }
35
45
  return p;
36
46
  }
37
- hasMemory(id) {
38
- return this.#memory.has(id);
47
+ hasMemory() {
48
+ return this.memory !== undefined;
39
49
  }
40
50
  hasSkills(id) {
41
51
  return this.#skills.has(id);