@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
@@ -0,0 +1,193 @@
1
+ /**
2
+ * The built-in vocabulary. A kind earns its place only when ranking, rendering
3
+ * or loading branches on it — anything else is free text wearing an enum
4
+ * costume, and it costs the model accuracy when it has to choose one.
5
+ *
6
+ * A project may declare its own set (`memory.kinds` in the config), which
7
+ * *replaces* this one; the tool schema is built from the result, so the
8
+ * provider constrains decoding to labels that actually exist.
9
+ *
10
+ * `preference` is the exception, and `MemoryIndex` re-adds it to whatever a
11
+ * project declares: the engine itself reads that kind to build the system
12
+ * prompt, so a project able to define it away would silently lose the feature
13
+ * rather than opt out of it.
14
+ */
15
+ export declare const MEMORY_KINDS: readonly ["task", "plan", "fact", "snippet", "file", "operation", "preference"];
16
+ export type MemoryKind = (typeof MEMORY_KINDS)[number];
17
+ export declare const KIND_HELP: Readonly<Record<MemoryKind, string>>;
18
+ /**
19
+ * Four relations, and only `SUPERSEDES` changes a traversal: recall follows it
20
+ * forward and drops what it points at, so a corrected artifact hides the one it
21
+ * replaced without deleting the history. The other three shape which tier of
22
+ * the budget a node is pulled in on, and read as prose in the rendered graph.
23
+ *
24
+ * `SUPERSEDES` itself never reaches the renderer on the ordinary path — recall
25
+ * drops the node it points at, so the edge has no second endpoint to draw. It
26
+ * appears only under `stale`, which is the audit view.
27
+ */
28
+ export declare const MEMORY_RELATIONS: readonly ["PRODUCED", "INFORMED", "CALLS", "SUPERSEDES"];
29
+ export type Relation = (typeof MEMORY_RELATIONS)[number];
30
+ export declare const RELATION_HELP: Readonly<Record<Relation, string>>;
31
+ /**
32
+ * The relations that carry the work forward, as opposed to the ones that merely
33
+ * supply context. Recall walks these first when spending its budget, and the
34
+ * renderer prefers them when a node has more than one branch it could hang from.
35
+ */
36
+ export declare const SPINE_RELATIONS: ReadonlySet<string>;
37
+ /** An audience label every agent holds implicitly. */
38
+ export declare const ALL_AGENTS = "*";
39
+ /** Read by the engine, not just by the model: it becomes part of the system prompt. */
40
+ export declare const PREFERENCE_KIND = "preference";
41
+ /**
42
+ * `hint` is the recovery step, kept separate from the message because the tool
43
+ * layer returns both to the model as `{error, hint}` rather than throwing.
44
+ */
45
+ export declare class MemoryError extends Error {
46
+ readonly hint?: string;
47
+ constructor(message: string, hint?: string);
48
+ }
49
+ export interface MemoryFile {
50
+ /** what the agent sees, e.g. `/memory/01J8….py` — a mount path, not a host path */
51
+ path: string;
52
+ bytes: number;
53
+ sha256: string;
54
+ /** extension without the dot, '' when there was none */
55
+ format: string;
56
+ }
57
+ export interface MemoryNode {
58
+ id: string;
59
+ kind: string;
60
+ /** the searchable, embeddable summary — the only text that is ranked */
61
+ text: string;
62
+ /** who may see this node; `['*']` is everyone */
63
+ audience: string[];
64
+ createdAt: string;
65
+ updatedAt: string;
66
+ /** last time a `memory_load` returned it; the input to recency decay */
67
+ lastUsedAt: string;
68
+ useCount: number;
69
+ /** optimistic-concurrency token */
70
+ revision: number;
71
+ metadata?: Record<string, unknown>;
72
+ file?: MemoryFile;
73
+ }
74
+ /** Graphology holds source and target itself, so they are not attributes. */
75
+ export interface MemoryEdgeAttrs {
76
+ relation: Relation;
77
+ createdAt: string;
78
+ /** narrower than either endpoint when set; usually absent */
79
+ audience?: string[];
80
+ }
81
+ export interface MemoryEdge {
82
+ source: string;
83
+ target: string;
84
+ relation: Relation;
85
+ }
86
+ export interface MemoryQuery {
87
+ /** semantic query; omitted means a pure filter listing */
88
+ text?: string;
89
+ kinds?: string[];
90
+ /** how many seed nodes the ranker returns before the graph is stitched */
91
+ limit?: number;
92
+ maxHops?: number;
93
+ maxNodes?: number;
94
+ /** ISO timestamp; nodes created before it are excluded */
95
+ newerThan?: string;
96
+ minScore?: number;
97
+ }
98
+ /**
99
+ * The edge a stitched node was reached by. A recollection renders as a tree,
100
+ * and this is the branch each node hangs from; the edges left over once every
101
+ * node has used its own are the ones the tree cannot carry.
102
+ */
103
+ export interface NodeVia {
104
+ from: string;
105
+ relation: Relation;
106
+ /** the edge runs from `from` to this node rather than back the other way */
107
+ outbound: boolean;
108
+ }
109
+ export interface ScoredNode {
110
+ node: MemoryNode;
111
+ score: number;
112
+ /** the ranker found this one; everything else is here to connect it */
113
+ seed: boolean;
114
+ /** absent on a seed: nothing reached it, it was the reason for the walk */
115
+ via?: NodeVia;
116
+ }
117
+ export interface Recollection {
118
+ nodes: ScoredNode[];
119
+ edges: MemoryEdge[];
120
+ /** ids of the seeds, in rank order */
121
+ seeds: string[];
122
+ /** nodes were dropped to stay inside the budget */
123
+ truncated: boolean;
124
+ }
125
+ export type MemoryAccess = 'read' | 'read-write' | 'full';
126
+ /**
127
+ * `sees` and `writes` are the mask, and they are resolved from config before a
128
+ * tool is ever built. Neither is a tool parameter: an agent that could name its
129
+ * own audience could read another agent's slice just by asking.
130
+ */
131
+ export interface MemoryBinding<TCtx = unknown> {
132
+ access: MemoryAccess;
133
+ /** audience labels this agent may read; `'*'` is implicit */
134
+ sees?: string[] | ((ctx: TCtx) => string[]);
135
+ /** labels it may write; defaults to `['*']` */
136
+ writes?: string[];
137
+ autoRecall?: AutoRecall;
138
+ }
139
+ export interface AutoRecall {
140
+ query: 'last_user_input' | 'none';
141
+ limit: number;
142
+ }
143
+ export interface ResolvedMemoryBinding {
144
+ access: MemoryAccess;
145
+ sees: string[];
146
+ writes: string[];
147
+ autoRecall?: AutoRecall;
148
+ }
149
+ export declare const canWrite: (b: ResolvedMemoryBinding) => boolean;
150
+ export declare const canForget: (b: ResolvedMemoryBinding) => boolean;
151
+ /**
152
+ * What a tool did, in the form the trajectory keeps it. Ids and counts only —
153
+ * the prose lives in a payload, so a replay can be reasoned about without
154
+ * rehydrating every body.
155
+ */
156
+ export interface MemoryRecallSpec {
157
+ kind: 'recall';
158
+ query: MemoryQuery;
159
+ /** the matches; the rest of `nodes` is stitched context */
160
+ seeds: string[];
161
+ nodes: {
162
+ id: string;
163
+ kind: string;
164
+ score: number;
165
+ }[];
166
+ edges: MemoryEdge[];
167
+ /** the rendered block the model actually saw */
168
+ content: string;
169
+ }
170
+ export interface MemoryOpSpec {
171
+ kind: 'op';
172
+ /** `load` is here rather than under recall: it hands the model whole bodies, and it writes. */
173
+ op: 'commit' | 'forget' | 'load';
174
+ /** sha256(runId, callId) — deterministic, so a replay is deduplicated */
175
+ opId: string;
176
+ /** nodes this op created, changed, removed or read in full */
177
+ nodes: {
178
+ id: string;
179
+ kind: string;
180
+ revision: number;
181
+ }[];
182
+ edges: MemoryEdge[];
183
+ /** remembered files copied in, or bytes dropped */
184
+ files: number;
185
+ }
186
+ /**
187
+ * Deny by default: a node is visible when it is public, or when the reader
188
+ * holds one of the labels it carries.
189
+ */
190
+ export declare function visible(node: MemoryNode, sees: readonly string[]): boolean;
191
+ /** An edge needs both endpoints visible before its own labels are consulted. */
192
+ export declare function edgeVisible(attrs: MemoryEdgeAttrs, sees: readonly string[]): boolean;
193
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,105 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Memory — knowledge that outlives a run (the trajectory is one run's record)
3
+ //
4
+ // A graph, not a list of records. The reason is the shape of what is worth
5
+ // remembering: a script an agent wrote is useless without the request that
6
+ // asked for it, the plan that shaped it and the endpoint it calls, and those
7
+ // four things are one memory even though they are four pieces of text. A store
8
+ // that can only return a ranked list of rows leaves the joining to the model,
9
+ // every single time.
10
+ //
11
+ // Every node is timed. `createdAt` is provenance, but `lastUsedAt` is what
12
+ // ranking actually leans on: memory that keeps proving useful should surface
13
+ // ahead of memory that was written once and never read, and neither an
14
+ // embedding nor an edge can tell you that.
15
+ //
16
+ // Visibility is per node, by label, denied by default. One graph serves every
17
+ // agent in a project; `audience` decides which of them may see a node at all.
18
+ // A second user is a second graph, not a second label: `audience` is about
19
+ // which agent may read a node, never about whose memory it is.
20
+ // ---------------------------------------------------------------------------
21
+ /**
22
+ * The built-in vocabulary. A kind earns its place only when ranking, rendering
23
+ * or loading branches on it — anything else is free text wearing an enum
24
+ * costume, and it costs the model accuracy when it has to choose one.
25
+ *
26
+ * A project may declare its own set (`memory.kinds` in the config), which
27
+ * *replaces* this one; the tool schema is built from the result, so the
28
+ * provider constrains decoding to labels that actually exist.
29
+ *
30
+ * `preference` is the exception, and `MemoryIndex` re-adds it to whatever a
31
+ * project declares: the engine itself reads that kind to build the system
32
+ * prompt, so a project able to define it away would silently lose the feature
33
+ * rather than opt out of it.
34
+ */
35
+ export const MEMORY_KINDS = [
36
+ 'task',
37
+ 'plan',
38
+ 'fact',
39
+ 'snippet',
40
+ 'file',
41
+ 'operation',
42
+ 'preference',
43
+ ];
44
+ export const KIND_HELP = {
45
+ task: 'a request that started a piece of work',
46
+ plan: 'an approach that worked, worth reusing on a similar request',
47
+ fact: 'a durable truth about the environment or the domain',
48
+ snippet: 'a short piece of code or configuration, too small to be a file',
49
+ file: 'an artifact kept whole under /memory and re-runnable',
50
+ operation: 'an external call that was made — an endpoint, command or query',
51
+ preference: 'a standing instruction from the user that applies to every run',
52
+ };
53
+ /**
54
+ * Four relations, and only `SUPERSEDES` changes a traversal: recall follows it
55
+ * forward and drops what it points at, so a corrected artifact hides the one it
56
+ * replaced without deleting the history. The other three shape which tier of
57
+ * the budget a node is pulled in on, and read as prose in the rendered graph.
58
+ *
59
+ * `SUPERSEDES` itself never reaches the renderer on the ordinary path — recall
60
+ * drops the node it points at, so the edge has no second endpoint to draw. It
61
+ * appears only under `stale`, which is the audit view.
62
+ */
63
+ export const MEMORY_RELATIONS = ['PRODUCED', 'INFORMED', 'CALLS', 'SUPERSEDES'];
64
+ export const RELATION_HELP = {
65
+ PRODUCED: 'the source led to the target being made (task -> artifact)',
66
+ INFORMED: 'the source was context the target was built from',
67
+ CALLS: 'the source invokes the target',
68
+ SUPERSEDES: 'the source replaces the target; the target is stale',
69
+ };
70
+ /**
71
+ * The relations that carry the work forward, as opposed to the ones that merely
72
+ * supply context. Recall walks these first when spending its budget, and the
73
+ * renderer prefers them when a node has more than one branch it could hang from.
74
+ */
75
+ export const SPINE_RELATIONS = new Set(['PRODUCED', 'CALLS', 'SUPERSEDES']);
76
+ /** An audience label every agent holds implicitly. */
77
+ export const ALL_AGENTS = '*';
78
+ /** Read by the engine, not just by the model: it becomes part of the system prompt. */
79
+ export const PREFERENCE_KIND = 'preference';
80
+ /**
81
+ * `hint` is the recovery step, kept separate from the message because the tool
82
+ * layer returns both to the model as `{error, hint}` rather than throwing.
83
+ */
84
+ export class MemoryError extends Error {
85
+ hint;
86
+ constructor(message, hint) {
87
+ super(message);
88
+ this.name = 'MemoryError';
89
+ this.hint = hint;
90
+ }
91
+ }
92
+ export const canWrite = (b) => b.access !== 'read';
93
+ export const canForget = (b) => b.access === 'full';
94
+ /**
95
+ * Deny by default: a node is visible when it is public, or when the reader
96
+ * holds one of the labels it carries.
97
+ */
98
+ export function visible(node, sees) {
99
+ return node.audience.some((a) => a === ALL_AGENTS || sees.includes(a));
100
+ }
101
+ /** An edge needs both endpoints visible before its own labels are consulted. */
102
+ export function edgeVisible(attrs, sees) {
103
+ return (!attrs.audience?.length || attrs.audience.some((a) => a === ALL_AGENTS || sees.includes(a)));
104
+ }
105
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,29 @@
1
+ export interface Scored {
2
+ id: string;
3
+ score: number;
4
+ }
5
+ export declare class VectorBlock {
6
+ #private;
7
+ constructor(dims: number, capacity?: number);
8
+ get dims(): number;
9
+ get rows(): number;
10
+ get ids(): readonly string[];
11
+ has(id: string): boolean;
12
+ set(id: string, vector: ArrayLike<number>): void;
13
+ remove(id: string): boolean;
14
+ /** Exact kNN over the rows `allow` accepts. Ties break toward the earlier row. */
15
+ topK(query: ArrayLike<number>, k: number, allow?: (id: string) => boolean): Scored[];
16
+ /** The raw block, trimmed to the rows in use — what goes to disk. */
17
+ bytes(): Float32Array;
18
+ static load(dims: number, ids: readonly string[], data: Float32Array): VectorBlock;
19
+ }
20
+ export declare function tokenize(v: string): string[];
21
+ export declare function overlap(a: readonly string[], b: readonly string[]): number;
22
+ /** Half-life in days: how fast a memory nobody reads falls behind one that is read. */
23
+ export declare const DECAY_HALF_LIFE_DAYS = 30;
24
+ /**
25
+ * Multiplied into similarity, never added: a stale exact match should still beat
26
+ * a fresh irrelevant one, so decay narrows the field rather than reordering it.
27
+ */
28
+ export declare function recencyDecay(lastUsedAt: string, now: number): number;
29
+ //# sourceMappingURL=vectors.d.ts.map
@@ -0,0 +1,152 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Vectors — a dense block and a linear scan
3
+ //
4
+ // No vector database. At the scale agent memory actually reaches — tens of
5
+ // thousands of nodes at the very outside — an exact scan over one contiguous
6
+ // Float32Array beats an approximate index that costs a native dependency, and
7
+ // it is exact, which an ANN index is not. `@zenera/neo` has two runtime
8
+ // dependencies for a reason, and none of them ship a `.node` binary.
9
+ //
10
+ // `Embedder` guarantees unit vectors (`embeddingResponse` normalizes before it
11
+ // returns), so cosine similarity here is a bare dot product. If that guarantee
12
+ // ever moves, this is the file that silently starts lying.
13
+ //
14
+ // Deletion is a swap-remove: the last row moves into the hole. That keeps the
15
+ // block dense — no tombstones, no compaction pass — at the cost of row indices
16
+ // not being stable, which is why nothing outside this file ever holds one.
17
+ // ---------------------------------------------------------------------------
18
+ const GROWTH = 2;
19
+ const INITIAL_ROWS = 64;
20
+ export class VectorBlock {
21
+ #dims;
22
+ #ids = [];
23
+ #index = new Map();
24
+ #data;
25
+ #rows = 0;
26
+ constructor(dims, capacity = INITIAL_ROWS) {
27
+ this.#dims = dims;
28
+ this.#data = new Float32Array(Math.max(capacity, 1) * dims);
29
+ }
30
+ get dims() {
31
+ return this.#dims;
32
+ }
33
+ get rows() {
34
+ return this.#rows;
35
+ }
36
+ get ids() {
37
+ return this.#ids;
38
+ }
39
+ has(id) {
40
+ return this.#index.has(id);
41
+ }
42
+ set(id, vector) {
43
+ if (vector.length !== this.#dims) {
44
+ throw new Error(`expected a ${this.#dims}-dimension vector, got ${vector.length}`);
45
+ }
46
+ let row = this.#index.get(id);
47
+ if (row === undefined) {
48
+ this.#grow(this.#rows + 1);
49
+ row = this.#rows++;
50
+ this.#ids[row] = id;
51
+ this.#index.set(id, row);
52
+ }
53
+ this.#data.set(vector, row * this.#dims);
54
+ }
55
+ remove(id) {
56
+ const row = this.#index.get(id);
57
+ if (row === undefined) {
58
+ return false;
59
+ }
60
+ const last = this.#rows - 1;
61
+ if (row !== last) {
62
+ const moved = this.#ids[last];
63
+ this.#data.copyWithin(row * this.#dims, last * this.#dims, (last + 1) * this.#dims);
64
+ this.#ids[row] = moved;
65
+ this.#index.set(moved, row);
66
+ }
67
+ this.#ids.length = last;
68
+ this.#index.delete(id);
69
+ this.#rows = last;
70
+ return true;
71
+ }
72
+ /** Exact kNN over the rows `allow` accepts. Ties break toward the earlier row. */
73
+ topK(query, k, allow) {
74
+ if (query.length !== this.#dims) {
75
+ throw new Error(`expected a ${this.#dims}-dimension query, got ${query.length}`);
76
+ }
77
+ const out = [];
78
+ for (let row = 0; row < this.#rows; row++) {
79
+ const id = this.#ids[row];
80
+ if (allow && !allow(id)) {
81
+ continue;
82
+ }
83
+ out.push({ id, score: this.#dot(query, row) });
84
+ }
85
+ out.sort((a, b) => b.score - a.score);
86
+ return out.slice(0, k);
87
+ }
88
+ /** The raw block, trimmed to the rows in use — what goes to disk. */
89
+ bytes() {
90
+ return this.#data.subarray(0, this.#rows * this.#dims);
91
+ }
92
+ static load(dims, ids, data) {
93
+ const block = new VectorBlock(dims, Math.max(ids.length, 1));
94
+ block.#ids = [...ids];
95
+ block.#rows = ids.length;
96
+ for (let row = 0; row < ids.length; row++) {
97
+ block.#index.set(ids[row], row);
98
+ }
99
+ block.#data.set(data.subarray(0, ids.length * dims), 0);
100
+ return block;
101
+ }
102
+ #dot(query, row) {
103
+ const base = row * this.#dims;
104
+ let sum = 0;
105
+ for (let i = 0; i < this.#dims; i++) {
106
+ sum += query[i] * this.#data[base + i];
107
+ }
108
+ return sum;
109
+ }
110
+ #grow(needed) {
111
+ const capacity = this.#data.length / this.#dims;
112
+ if (needed <= capacity) {
113
+ return;
114
+ }
115
+ const next = new Float32Array(Math.max(needed, capacity * GROWTH) * this.#dims);
116
+ next.set(this.#data, 0);
117
+ this.#data = next;
118
+ }
119
+ }
120
+ // ---------------------------------------------------------------------------
121
+ // Lexical fallback
122
+ //
123
+ // Used when a project configures no embedder. It is worse than embeddings and
124
+ // is meant to be: the alternative is memory that silently does nothing, and a
125
+ // term-overlap hit on a remembered task is still a hit.
126
+ // ---------------------------------------------------------------------------
127
+ export function tokenize(v) {
128
+ return v
129
+ .toLowerCase()
130
+ .split(/[^a-z0-9]+/)
131
+ .filter((t) => t.length > 2);
132
+ }
133
+ export function overlap(a, b) {
134
+ const set = new Set(b);
135
+ const shared = a.filter((t) => set.has(t)).length;
136
+ return a.length ? shared / a.length : 0;
137
+ }
138
+ /** Half-life in days: how fast a memory nobody reads falls behind one that is read. */
139
+ export const DECAY_HALF_LIFE_DAYS = 30;
140
+ const DAY_MS = 24 * 60 * 60 * 1000;
141
+ /**
142
+ * Multiplied into similarity, never added: a stale exact match should still beat
143
+ * a fresh irrelevant one, so decay narrows the field rather than reordering it.
144
+ */
145
+ export function recencyDecay(lastUsedAt, now) {
146
+ const age = now - Date.parse(lastUsedAt);
147
+ if (!Number.isFinite(age) || age <= 0) {
148
+ return 1;
149
+ }
150
+ return 2 ** (-age / DAY_MS / DECAY_HALF_LIFE_DAYS);
151
+ }
152
+ //# sourceMappingURL=vectors.js.map
@@ -114,6 +114,34 @@ declare const embeddingSpec: z.ZodObject<{
114
114
  baseURL: z.ZodOptional<z.ZodString>;
115
115
  baseURLEnv: z.ZodOptional<z.ZodString>;
116
116
  }, z.core.$strict>;
117
+ /**
118
+ * What an agent may do with the project's one memory graph, and which slice of
119
+ * it it can see. Both lists are audience labels; `'*'` is the public slice and
120
+ * is always readable, so `sees` can only ever widen from there.
121
+ *
122
+ * `sees` and `writes` are here and not in any tool schema on purpose: an agent
123
+ * that could name its own audience could read another agent's slice just by
124
+ * asking for it.
125
+ */
126
+ declare const memoryBinding: z.ZodObject<{
127
+ access: z.ZodDefault<z.ZodEnum<{
128
+ read: "read";
129
+ "read-write": "read-write";
130
+ full: "full";
131
+ }>>;
132
+ sees: z.ZodOptional<z.ZodArray<z.ZodString>>;
133
+ writes: z.ZodOptional<z.ZodArray<z.ZodString>>;
134
+ autoRecall: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
135
+ limit: z.ZodDefault<z.ZodInt>;
136
+ }, z.core.$strict>]>>;
137
+ }, z.core.$strict>;
138
+ /** Where the project's memory lives, and what vectorises it. */
139
+ declare const memoryConfig: z.ZodObject<{
140
+ dir: z.ZodDefault<z.ZodString>;
141
+ embedding: z.ZodOptional<z.ZodString>;
142
+ kinds: z.ZodOptional<z.ZodArray<z.ZodString>>;
143
+ relations: z.ZodOptional<z.ZodArray<z.ZodString>>;
144
+ }, z.core.$strict>;
117
145
  /**
118
146
  * A Dockerfile to build instead of an image to pull, for the ordinary case the
119
147
  * `image:` key cannot serve: a project that needs two runtimes, or a pinned
@@ -272,6 +300,12 @@ export declare const projectSchema: z.ZodObject<{
272
300
  env: z.ZodOptional<z.ZodArray<z.ZodString>>;
273
301
  keys: z.ZodOptional<z.ZodBoolean>;
274
302
  }, z.core.$strict>>;
303
+ memory: z.ZodOptional<z.ZodObject<{
304
+ dir: z.ZodDefault<z.ZodString>;
305
+ embedding: z.ZodOptional<z.ZodString>;
306
+ kinds: z.ZodOptional<z.ZodArray<z.ZodString>>;
307
+ relations: z.ZodOptional<z.ZodArray<z.ZodString>>;
308
+ }, z.core.$strict>>;
275
309
  agents: z.ZodArray<z.ZodObject<{
276
310
  name: z.ZodString;
277
311
  description: z.ZodOptional<z.ZodString>;
@@ -282,8 +316,8 @@ export declare const projectSchema: z.ZodObject<{
282
316
  skills: z.ZodOptional<z.ZodObject<{
283
317
  provider: z.ZodOptional<z.ZodString>;
284
318
  discovery: z.ZodDefault<z.ZodEnum<{
285
- search: "search";
286
319
  none: "none";
320
+ search: "search";
287
321
  index: "index";
288
322
  }>>;
289
323
  allow: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -294,6 +328,18 @@ export declare const projectSchema: z.ZodObject<{
294
328
  agents: z.ZodOptional<z.ZodArray<z.ZodString>>;
295
329
  maxBranches: z.ZodOptional<z.ZodInt>;
296
330
  }, z.core.$strict>]>>;
331
+ memory: z.ZodOptional<z.ZodPreprocess<z.ZodOptional<z.ZodObject<{
332
+ access: z.ZodDefault<z.ZodEnum<{
333
+ read: "read";
334
+ "read-write": "read-write";
335
+ full: "full";
336
+ }>>;
337
+ sees: z.ZodOptional<z.ZodArray<z.ZodString>>;
338
+ writes: z.ZodOptional<z.ZodArray<z.ZodString>>;
339
+ autoRecall: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
340
+ limit: z.ZodDefault<z.ZodInt>;
341
+ }, z.core.$strict>]>>;
342
+ }, z.core.$strict>>>>;
297
343
  sandbox: z.ZodOptional<z.ZodObject<{
298
344
  image: z.ZodOptional<z.ZodString>;
299
345
  build: z.ZodOptional<z.ZodObject<{
@@ -324,6 +370,8 @@ export type ModelConfig = z.infer<typeof modelSpec>;
324
370
  export type EmbeddingConfig = z.infer<typeof embeddingSpec>;
325
371
  export type SandboxConfig = z.infer<typeof sandbox>;
326
372
  export type SandboxBuildConfig = z.infer<typeof build>;
373
+ export type MemoryConfig = z.infer<typeof memoryConfig>;
374
+ export type MemoryBindingConfig = z.infer<typeof memoryBinding>;
327
375
  /**
328
376
  * Re-renders a zod failure as `agents.yaml: agents[1].skills.discovery — …`.
329
377
  *
@@ -168,6 +168,52 @@ const forkBinding = z
168
168
  maxBranches: z.int().min(2).optional(),
169
169
  })
170
170
  .strict();
171
+ /**
172
+ * What an agent may do with the project's one memory graph, and which slice of
173
+ * it it can see. Both lists are audience labels; `'*'` is the public slice and
174
+ * is always readable, so `sees` can only ever widen from there.
175
+ *
176
+ * `sees` and `writes` are here and not in any tool schema on purpose: an agent
177
+ * that could name its own audience could read another agent's slice just by
178
+ * asking for it.
179
+ */
180
+ const memoryBinding = z
181
+ .object({
182
+ access: z.enum(['read', 'read-write', 'full']).default('read-write'),
183
+ /** audience labels this agent may read, on top of the public slice */
184
+ sees: z.array(name).optional(),
185
+ /** labels it may put on what it writes */
186
+ writes: z.array(name).optional(),
187
+ /**
188
+ * Recall before a turn that follows new user input, rather than before
189
+ * every call: recalling on every turn costs tokens and defeats prompt
190
+ * caching for a marginal gain. `false` leaves the agent the tools and
191
+ * lets it decide when to look.
192
+ */
193
+ autoRecall: z
194
+ .union([z.boolean(), z.object({ limit: z.int().positive().default(5) }).strict()])
195
+ .optional(),
196
+ })
197
+ .strict();
198
+ /** Where the project's memory lives, and what vectorises it. */
199
+ const memoryConfig = z
200
+ .object({
201
+ /** relative to the project root */
202
+ dir: z.string().min(1).default('memory'),
203
+ /**
204
+ * A name from `embeddings:`. Without one, recall ranks by term overlap
205
+ * — which works, but only finds the words the agent happened to reuse.
206
+ */
207
+ embedding: modelRef.optional(),
208
+ /**
209
+ * Closed vocabularies. Absent means the built-in sets, which is almost
210
+ * always right: a kind nothing else understands is a kind no other
211
+ * agent can search for.
212
+ */
213
+ kinds: z.array(name).min(1).optional(),
214
+ relations: z.array(name).min(1).optional(),
215
+ })
216
+ .strict();
171
217
  /**
172
218
  * The container command-line tools run in.
173
219
  *
@@ -275,6 +321,17 @@ const agent = z
275
321
  skills: skillsBinding.optional(),
276
322
  /** parallel sub-agents; `true` is the unrestricted form */
277
323
  fork: z.union([z.boolean(), forkBinding]).optional(),
324
+ /**
325
+ * The shared memory graph. `true` is the whole feature working the
326
+ * obvious way — read and write, with auto-recall on.
327
+ *
328
+ * Normalised before validation rather than unioned with a boolean, so
329
+ * that a misspelled key inside the block is reported as
330
+ * `agents[0].memory.scope` instead of collapsing to "invalid input".
331
+ */
332
+ memory: z
333
+ .preprocess((v) => (typeof v === 'boolean' ? (v ? {} : undefined) : v), memoryBinding.optional())
334
+ .optional(),
278
335
  /**
279
336
  * Overrides on the project's sandbox. An agent that overrides nothing
280
337
  * shares the container with everyone else, which is what a hand-off
@@ -316,6 +373,12 @@ export const projectSchema = z
316
373
  assets: z.string().min(1).optional(),
317
374
  /** the container `run_command` and friends execute in */
318
375
  sandbox: sandbox.optional(),
376
+ /**
377
+ * The one graph every agent's `memory:` binding is a view onto. Declare
378
+ * it to choose where it lives or what embeds it; an agent that opts in
379
+ * without this block gets the defaults.
380
+ */
381
+ memory: memoryConfig.optional(),
319
382
  agents: z.array(agent).min(1),
320
383
  })
321
384
  .strict();
@@ -1,4 +1,4 @@
1
- export { parseConfig, projectSchema, type AgentConfig, type EmbeddingConfig, type ModelConfig, type ProjectConfig, type ProviderConfig, type SandboxBuildConfig, type SandboxConfig, } from './config.ts';
2
- export { AgentProject, assetsDir, loadProject, projectRegistry, readProjectConfig, skillDirs, skillMounts, type ProjectOptions, type ProjectSource, } from './load.ts';
1
+ export { parseConfig, projectSchema, type AgentConfig, type EmbeddingConfig, type MemoryBindingConfig, type MemoryConfig, type ModelConfig, type ProjectConfig, type ProviderConfig, type SandboxBuildConfig, type SandboxConfig, } from './config.ts';
2
+ export { AgentProject, assetsDir, loadProject, memoryDir, projectRegistry, readProjectConfig, skillDirs, skillMounts, type ProjectOptions, type ProjectSource, } from './load.ts';
3
3
  export { projectDir, projectFile, projectPath, projectRoot } from './refs.ts';
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1,4 +1,4 @@
1
1
  export { parseConfig, projectSchema, } from "./config.js";
2
- export { AgentProject, assetsDir, loadProject, projectRegistry, readProjectConfig, skillDirs, skillMounts, } from "./load.js";
2
+ export { AgentProject, assetsDir, loadProject, memoryDir, projectRegistry, readProjectConfig, skillDirs, skillMounts, } from "./load.js";
3
3
  export { projectDir, projectFile, projectPath, projectRoot } from "./refs.js";
4
4
  //# sourceMappingURL=index.js.map