@oortmem/core 0.3.0 → 0.4.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/dist/agents.d.ts +38 -0
- package/dist/agents.js +67 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.js +7 -0
- package/dist/consolidate.d.ts +4 -0
- package/dist/consolidate.js +15 -3
- package/dist/grant.d.ts +58 -11
- package/dist/grant.js +121 -23
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/isolation.d.ts +26 -3
- package/dist/isolation.js +222 -61
- package/dist/levels.d.ts +79 -0
- package/dist/levels.js +140 -0
- package/dist/observe.d.ts +9 -0
- package/dist/observe.js +27 -3
- package/dist/recall.d.ts +27 -1
- package/dist/recall.js +163 -17
- package/dist/store-conformance.js +69 -16
- package/package.json +1 -1
package/dist/agents.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ANONYMOUS_AGENT, type Home } from './grant.js';
|
|
2
|
+
/**
|
|
3
|
+
* Agent identities on one machine (CON-62). Every host Oort is connected to
|
|
4
|
+
* acts as its own agent (`claude-code`, `codex`, `cursor`, …): they share the
|
|
5
|
+
* person's Planet, and each has a Star of its own that no other agent reads.
|
|
6
|
+
* An agent configured as a Star (`oort connect cursor --star`) writes new
|
|
7
|
+
* memories to its Star instead of the Planet. The configuration lives in
|
|
8
|
+
* `$OORT_HOME/agents.json` (0600).
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Claude Code's agent id: what every local memory before agents was written as, and what the
|
|
12
|
+
* Claude Code hooks and transcript import always act as. A caller that names no agent is
|
|
13
|
+
* `ANONYMOUS_AGENT` (Planet only), never this.
|
|
14
|
+
*/
|
|
15
|
+
export declare const DEFAULT_AGENT = "claude-code";
|
|
16
|
+
export { ANONYMOUS_AGENT };
|
|
17
|
+
/** Agents configured as Stars, under `OORT_HOME`. */
|
|
18
|
+
export declare const AGENTS_FILE = "agents.json";
|
|
19
|
+
/** A lowercase id: letters, digits, `.`, `_` and `-`, at most 64 characters, starting alphanumeric. */
|
|
20
|
+
export declare const AGENT_ID: RegExp;
|
|
21
|
+
/** A valid agent id that is not reserved (`anonymous` names a caller that named none). */
|
|
22
|
+
export declare const isAgentId: (s: string) => boolean;
|
|
23
|
+
export interface AgentSettings {
|
|
24
|
+
/** New memories land in this agent's Star (private to it) instead of the person's Planet. */
|
|
25
|
+
star: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The agents configured in `home`. A missing or unreadable file means none,
|
|
29
|
+
* so every agent writes to the Planet (as before agents existed).
|
|
30
|
+
*/
|
|
31
|
+
export declare function readAgents(home: string): Record<string, AgentSettings>;
|
|
32
|
+
/**
|
|
33
|
+
* Records whether agent `id` keeps a Star (0600). Returns true when that changed anything. An agent
|
|
34
|
+
* that stops keeping one stays listed (`star: false`), so its Star is still found (export).
|
|
35
|
+
*/
|
|
36
|
+
export declare function setAgentStar(home: string, id: string, star: boolean): boolean;
|
|
37
|
+
/** Where agent `id`'s new memories land. */
|
|
38
|
+
export declare function agentHome(home: string, id: string): Home;
|
package/dist/agents.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { ANONYMOUS_AGENT } from './grant.js';
|
|
4
|
+
import { writePrivateFile } from './private-fs.js';
|
|
5
|
+
/**
|
|
6
|
+
* Agent identities on one machine (CON-62). Every host Oort is connected to
|
|
7
|
+
* acts as its own agent (`claude-code`, `codex`, `cursor`, …): they share the
|
|
8
|
+
* person's Planet, and each has a Star of its own that no other agent reads.
|
|
9
|
+
* An agent configured as a Star (`oort connect cursor --star`) writes new
|
|
10
|
+
* memories to its Star instead of the Planet. The configuration lives in
|
|
11
|
+
* `$OORT_HOME/agents.json` (0600).
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Claude Code's agent id: what every local memory before agents was written as, and what the
|
|
15
|
+
* Claude Code hooks and transcript import always act as. A caller that names no agent is
|
|
16
|
+
* `ANONYMOUS_AGENT` (Planet only), never this.
|
|
17
|
+
*/
|
|
18
|
+
export const DEFAULT_AGENT = 'claude-code';
|
|
19
|
+
export { ANONYMOUS_AGENT };
|
|
20
|
+
/** Agents configured as Stars, under `OORT_HOME`. */
|
|
21
|
+
export const AGENTS_FILE = 'agents.json';
|
|
22
|
+
/** A lowercase id: letters, digits, `.`, `_` and `-`, at most 64 characters, starting alphanumeric. */
|
|
23
|
+
export const AGENT_ID = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
24
|
+
/** A valid agent id that is not reserved (`anonymous` names a caller that named none). */
|
|
25
|
+
export const isAgentId = (s) => AGENT_ID.test(s) && s !== ANONYMOUS_AGENT;
|
|
26
|
+
/**
|
|
27
|
+
* The agents configured in `home`. A missing or unreadable file means none,
|
|
28
|
+
* so every agent writes to the Planet (as before agents existed).
|
|
29
|
+
*/
|
|
30
|
+
export function readAgents(home) {
|
|
31
|
+
let parsed;
|
|
32
|
+
try {
|
|
33
|
+
parsed = JSON.parse(fs.readFileSync(path.join(home, AGENTS_FILE), 'utf8'));
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
const agents = parsed?.agents;
|
|
39
|
+
if (typeof agents !== 'object' || agents === null || Array.isArray(agents))
|
|
40
|
+
return {};
|
|
41
|
+
const out = {};
|
|
42
|
+
for (const [id, v] of Object.entries(agents)) {
|
|
43
|
+
if (!isAgentId(id) || typeof v !== 'object' || v === null)
|
|
44
|
+
continue;
|
|
45
|
+
out[id] = { star: v.star === true };
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Records whether agent `id` keeps a Star (0600). Returns true when that changed anything. An agent
|
|
51
|
+
* that stops keeping one stays listed (`star: false`), so its Star is still found (export).
|
|
52
|
+
*/
|
|
53
|
+
export function setAgentStar(home, id, star) {
|
|
54
|
+
if (!isAgentId(id))
|
|
55
|
+
throw new RangeError(`not an agent id: ${JSON.stringify(id)}`);
|
|
56
|
+
const agents = readAgents(home);
|
|
57
|
+
if ((agents[id]?.star ?? false) === star)
|
|
58
|
+
return false;
|
|
59
|
+
agents[id] = { star };
|
|
60
|
+
writePrivateFile(path.join(home, AGENTS_FILE), `${JSON.stringify({ version: 1, agents }, null, 2)}\n`);
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
/** Where agent `id`'s new memories land. */
|
|
64
|
+
export function agentHome(home, id) {
|
|
65
|
+
return readAgents(home)[id]?.star === true ? 'star' : 'planet';
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=agents.js.map
|
package/dist/config.d.ts
CHANGED
|
@@ -42,6 +42,7 @@ export declare const OortConfig: z.ZodObject<{
|
|
|
42
42
|
rerankBudgetMs: z.ZodDefault<z.ZodNumber>;
|
|
43
43
|
queryCacheTtlSec: z.ZodDefault<z.ZodNumber>;
|
|
44
44
|
staleAfterDays: z.ZodDefault<z.ZodNumber>;
|
|
45
|
+
specificityPrior: z.ZodDefault<z.ZodNumber>;
|
|
45
46
|
}, z.core.$strict>>;
|
|
46
47
|
project: z.ZodDefault<z.ZodObject<{
|
|
47
48
|
id: z.ZodOptional<z.ZodString>;
|
package/dist/config.js
CHANGED
|
@@ -69,6 +69,12 @@ const recall = z
|
|
|
69
69
|
* marked "may be outdated" when injected, and listed by `oort revalidate`. 0 turns it off.
|
|
70
70
|
*/
|
|
71
71
|
staleAfterDays: z.number().int().min(0).default(30),
|
|
72
|
+
/**
|
|
73
|
+
* The specificity prior ε (spec 7.2): when a recall reads several nodes, each fused score is
|
|
74
|
+
* scaled by 1 + ε × specificity / 3 (an agent's Star 3, the Planet 2, a Constellation 1, a
|
|
75
|
+
* Galaxy 0). 0, the default, turns it off: it is turned on only if the eval shows a gain.
|
|
76
|
+
*/
|
|
77
|
+
specificityPrior: z.number().min(0).max(1).default(0),
|
|
72
78
|
})
|
|
73
79
|
.default({
|
|
74
80
|
lexicalK: 50,
|
|
@@ -84,6 +90,7 @@ const recall = z
|
|
|
84
90
|
rerankBudgetMs: 400,
|
|
85
91
|
queryCacheTtlSec: 600,
|
|
86
92
|
staleAfterDays: 30,
|
|
93
|
+
specificityPrior: 0,
|
|
87
94
|
});
|
|
88
95
|
const project = z
|
|
89
96
|
.strictObject({
|
package/dist/consolidate.d.ts
CHANGED
|
@@ -37,6 +37,10 @@ export declare const COLD_EVICT_BATCH = 500;
|
|
|
37
37
|
* left in its current tier (hot or warm); consolidate never promotes cold to warm/hot, since
|
|
38
38
|
* hydration is recall's job.
|
|
39
39
|
* Idempotent for a fixed `now`.
|
|
40
|
+
*
|
|
41
|
+
* Each node the scope's grant covers (the person's Planet, this agent's Star)
|
|
42
|
+
* is consolidated on its own, so the Planet's tiers do not depend on which of
|
|
43
|
+
* the person's agents ran consolidation.
|
|
40
44
|
*/
|
|
41
45
|
export declare function consolidate(scope: ScopeFilter, deps: {
|
|
42
46
|
store: MemoryStore;
|
package/dist/consolidate.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { activation, tierFor } from './activation.js';
|
|
2
|
-
import {
|
|
2
|
+
import { nodeKey } from './address.js';
|
|
3
|
+
import { localGrant, narrowGrant, readableNodes } from './grant.js';
|
|
3
4
|
import { isCurrent } from './model.js';
|
|
4
5
|
/** Evictions per `coldPutMany` call: one store transaction and one directory sync each. */
|
|
5
6
|
export const COLD_EVICT_BATCH = 500;
|
|
@@ -23,10 +24,14 @@ const RANK = { cold: 0, warm: 1, hot: 2 };
|
|
|
23
24
|
* left in its current tier (hot or warm); consolidate never promotes cold to warm/hot, since
|
|
24
25
|
* hydration is recall's job.
|
|
25
26
|
* Idempotent for a fixed `now`.
|
|
27
|
+
*
|
|
28
|
+
* Each node the scope's grant covers (the person's Planet, this agent's Star)
|
|
29
|
+
* is consolidated on its own, so the Planet's tiers do not depend on which of
|
|
30
|
+
* the person's agents ran consolidation.
|
|
26
31
|
*/
|
|
27
32
|
export async function consolidate(scope, deps) {
|
|
28
33
|
const { store, config } = deps;
|
|
29
|
-
const
|
|
34
|
+
const full = localGrant(scope);
|
|
30
35
|
const now = deps.now ?? new Date();
|
|
31
36
|
const result = {
|
|
32
37
|
promoted: [],
|
|
@@ -35,6 +40,14 @@ export async function consolidate(scope, deps) {
|
|
|
35
40
|
expired: [],
|
|
36
41
|
skipped: 0,
|
|
37
42
|
};
|
|
43
|
+
// Only nodes this grant may change: a Star agent's grant reads the Planet but does not write it.
|
|
44
|
+
const writable = new Set(full.writeNodes.map((p) => nodeKey(p.node)));
|
|
45
|
+
for (const node of readableNodes(full).filter((n) => writable.has(nodeKey(n)))) {
|
|
46
|
+
await consolidateNode(store, narrowGrant(full, [node]), config, now, result);
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
async function consolidateNode(store, grant, config, now, result) {
|
|
38
51
|
// 1. Expire.
|
|
39
52
|
const due = (await store.list(grant, { validOnly: false })).filter((m) => m.validTo !== null && m.expiredAt === null && Date.parse(m.validTo) <= now.getTime());
|
|
40
53
|
if (due.length > 0) {
|
|
@@ -76,7 +89,6 @@ export async function consolidate(scope, deps) {
|
|
|
76
89
|
evictions.push(m);
|
|
77
90
|
}
|
|
78
91
|
await evictAll(store, grant, evictions, result);
|
|
79
|
-
return result;
|
|
80
92
|
}
|
|
81
93
|
async function evictAll(store, grant, evictions, result) {
|
|
82
94
|
for (let i = 0; i < evictions.length; i += COLD_EVICT_BATCH) {
|
package/dist/grant.d.ts
CHANGED
|
@@ -27,15 +27,26 @@ export interface GrantFilter extends Readonly<Principal> {
|
|
|
27
27
|
* One node a grant covers (CON-61): the node, the project bindings inside it,
|
|
28
28
|
* and optionally one author. Matching is structural and exact on every field
|
|
29
29
|
* (never a prefix). `authorAgentId` restricts the pattern to memories that
|
|
30
|
-
* agent wrote
|
|
31
|
-
*
|
|
32
|
-
*
|
|
30
|
+
* agent wrote. The local constructors (`localGrant`, `principalGrant`) name
|
|
31
|
+
* the principal's Planet with no author restriction (a person's agents share
|
|
32
|
+
* their Planet, CON-62) and the principal's agent's own Star.
|
|
33
33
|
*/
|
|
34
34
|
export interface NodePattern {
|
|
35
35
|
readonly node: NodeRef;
|
|
36
36
|
readonly projectIds: GrantProjects;
|
|
37
37
|
readonly authorAgentId?: string;
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Where an agent's new memories land by default (its home node, spec 5.1):
|
|
41
|
+
* the person's Planet, or the agent's own Star when it is configured as one.
|
|
42
|
+
*/
|
|
43
|
+
export type Home = 'planet' | 'star';
|
|
44
|
+
/**
|
|
45
|
+
* The agent id of a caller that named no agent (an MCP entry written before agents existed, a
|
|
46
|
+
* terminal without `--agent`). It reads and writes the person's Planet only: it has no Star, so
|
|
47
|
+
* it can neither see nor create one. No configured agent may use this id.
|
|
48
|
+
*/
|
|
49
|
+
export declare const ANONYMOUS_AGENT = "anonymous";
|
|
39
50
|
/**
|
|
40
51
|
* The permission every `MemoryStore` method takes. Opaque: built only by
|
|
41
52
|
* `localGrant`, `principalGrant` or `nodeGrant`, frozen, and nominal, so a raw
|
|
@@ -58,24 +69,48 @@ export declare class Grant {
|
|
|
58
69
|
readonly write: GrantFilter;
|
|
59
70
|
readonly readNodes: readonly NodePattern[];
|
|
60
71
|
readonly writeNodes: readonly NodePattern[];
|
|
72
|
+
/**
|
|
73
|
+
* The node new memories are written to (the home node): the principal's
|
|
74
|
+
* Planet unless the agent keeps a Star. Engine code that creates a memory
|
|
75
|
+
* (observe, remember) addresses it here; edits of existing memories keep
|
|
76
|
+
* their own address. Covered by `writeNodes` whenever they are not empty.
|
|
77
|
+
*/
|
|
78
|
+
readonly home: NodeRef;
|
|
61
79
|
private constructor();
|
|
62
80
|
/** @internal Use `localGrant` or `principalGrant`. */
|
|
63
|
-
static issue(principal: Principal, read: GrantProjects, write: GrantProjects): Grant;
|
|
64
|
-
/** @internal Use `nodeGrant`. */
|
|
65
|
-
static issueNodes(principal: Principal, read: readonly NodePattern[], write: readonly NodePattern[]): Grant;
|
|
81
|
+
static issue(principal: Principal, read: GrantProjects, write: GrantProjects, home?: Home): Grant;
|
|
82
|
+
/** @internal Use `nodeGrant` or `narrowGrant`. */
|
|
83
|
+
static issueNodes(principal: Principal, read: readonly NodePattern[], write: readonly NodePattern[], home?: NodeRef): Grant;
|
|
66
84
|
/** True for a real grant (not a copy, not a look-alike). */
|
|
67
85
|
static is(x: unknown): x is Grant;
|
|
68
86
|
}
|
|
87
|
+
/** The Star of a principal's agent (owned by the principal's owner). */
|
|
88
|
+
export declare function starNode(p: Pick<Principal, 'orgId' | 'agentId'>): NodeRef;
|
|
69
89
|
/**
|
|
70
|
-
* The grant for
|
|
71
|
-
*
|
|
72
|
-
* `write`
|
|
90
|
+
* The grant for one agent of one person (CON-62): read and write the
|
|
91
|
+
* person's Planet (memories any of their agents wrote) and this agent's own
|
|
92
|
+
* Star, in the projects `read` names; `opts.write` may narrow the write side.
|
|
93
|
+
* Another agent's Star is never covered. `opts.home` picks where new memories
|
|
94
|
+
* land: the Planet (default) or the agent's Star; with `star` the Planet is
|
|
95
|
+
* read-only (the agent writes only its Star). `ANONYMOUS_AGENT` gets the
|
|
96
|
+
* Planet alone. `write` must name the same principal as `read`.
|
|
97
|
+
*
|
|
98
|
+
* Before CON-62 the Planet side was restricted to memories the principal's
|
|
99
|
+
* own agent wrote. Every local memory so far was written as `claude-code`,
|
|
100
|
+
* so a local store sees exactly what it saw; nothing is rewritten.
|
|
73
101
|
*/
|
|
74
102
|
export declare function localGrant(read: ScopeFilter, opts?: {
|
|
75
103
|
write?: ScopeFilter;
|
|
104
|
+
home?: Home;
|
|
105
|
+
}): Grant;
|
|
106
|
+
/**
|
|
107
|
+
* Every project of `p` (and its global memories), for read and write:
|
|
108
|
+
* maintenance such as export. Covers the same nodes as `localGrant`: the
|
|
109
|
+
* person's Planet and this agent's own Star.
|
|
110
|
+
*/
|
|
111
|
+
export declare function principalGrant(p: Principal, opts?: {
|
|
112
|
+
home?: Home;
|
|
76
113
|
}): Grant;
|
|
77
|
-
/** Every project of `p` (and its global memories), for read and write: maintenance such as export. */
|
|
78
|
-
export declare function principalGrant(p: Principal): Grant;
|
|
79
114
|
/**
|
|
80
115
|
* A grant over explicit node patterns, possibly across owners and levels:
|
|
81
116
|
* `read` bounds what is seen, `write` (default: `read`) what may change.
|
|
@@ -88,7 +123,19 @@ export declare function nodeGrant(opts: {
|
|
|
88
123
|
principal: Principal;
|
|
89
124
|
read: readonly NodePattern[];
|
|
90
125
|
write?: readonly NodePattern[];
|
|
126
|
+
/** Where new memories land (default: the principal's Planet if written, else the first node written). */
|
|
127
|
+
home?: NodeRef;
|
|
91
128
|
}): Grant;
|
|
129
|
+
/**
|
|
130
|
+
* `g` narrowed to the nodes `keep` names (by `nodeKey`), on both sides: the
|
|
131
|
+
* grant recall uses for one node's first stage. Never wider than `g`. The
|
|
132
|
+
* home stays when it is kept, else the default for what remains.
|
|
133
|
+
*/
|
|
134
|
+
export declare function narrowGrant(g: Grant, keep: readonly NodeRef[]): Grant;
|
|
135
|
+
/** The distinct nodes `g` reads, in the order its patterns name them. */
|
|
136
|
+
export declare function readableNodes(g: Grant): NodeRef[];
|
|
137
|
+
/** The address a new memory gets under `g`: its home node, bound to `projectId` when given. */
|
|
138
|
+
export declare function homeAddress(g: Grant, projectId?: string): Address;
|
|
92
139
|
/** Rejects anything that is not a real `Grant`; every store calls it first. */
|
|
93
140
|
export declare function assertGrant(g: unknown): asserts g is Grant;
|
|
94
141
|
/**
|
package/dist/grant.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { addressOf, assertNode, authorOf, nodeKey, nodeOf, planetOf, sameAddress, sameAuthor, } from './address.js';
|
|
2
|
+
/**
|
|
3
|
+
* The agent id of a caller that named no agent (an MCP entry written before agents existed, a
|
|
4
|
+
* terminal without `--agent`). It reads and writes the person's Planet only: it has no Star, so
|
|
5
|
+
* it can neither see nor create one. No configured agent may use this id.
|
|
6
|
+
*/
|
|
7
|
+
export const ANONYMOUS_AGENT = 'anonymous';
|
|
2
8
|
/**
|
|
3
9
|
* The permission every `MemoryStore` method takes. Opaque: built only by
|
|
4
10
|
* `localGrant`, `principalGrant` or `nodeGrant`, frozen, and nominal, so a raw
|
|
@@ -21,10 +27,18 @@ export class Grant {
|
|
|
21
27
|
write;
|
|
22
28
|
readNodes;
|
|
23
29
|
writeNodes;
|
|
24
|
-
|
|
30
|
+
/**
|
|
31
|
+
* The node new memories are written to (the home node): the principal's
|
|
32
|
+
* Planet unless the agent keeps a Star. Engine code that creates a memory
|
|
33
|
+
* (observe, remember) addresses it here; edits of existing memories keep
|
|
34
|
+
* their own address. Covered by `writeNodes` whenever they are not empty.
|
|
35
|
+
*/
|
|
36
|
+
home;
|
|
37
|
+
constructor(principal, readNodes, writeNodes, home) {
|
|
25
38
|
this.principal = Object.freeze({ ...principal });
|
|
26
39
|
this.readNodes = Object.freeze(readNodes.map(freezePattern));
|
|
27
40
|
this.writeNodes = Object.freeze(writeNodes.map(freezePattern));
|
|
41
|
+
this.home = Object.freeze(homeNode(principal, this.writeNodes, home));
|
|
28
42
|
this.read = Object.freeze({
|
|
29
43
|
...principal,
|
|
30
44
|
projectIds: legacyProjects(principal, this.readNodes),
|
|
@@ -36,14 +50,27 @@ export class Grant {
|
|
|
36
50
|
Object.freeze(this);
|
|
37
51
|
}
|
|
38
52
|
/** @internal Use `localGrant` or `principalGrant`. */
|
|
39
|
-
static issue(principal, read, write) {
|
|
53
|
+
static issue(principal, read, write, home = 'planet') {
|
|
40
54
|
checkPrincipal(principal);
|
|
41
|
-
|
|
55
|
+
const anonymous = principal.agentId === ANONYMOUS_AGENT;
|
|
56
|
+
if (anonymous && home === 'star')
|
|
57
|
+
throw new RangeError('grant: an anonymous agent has no Star');
|
|
58
|
+
const readSide = anonymous
|
|
59
|
+
? [ownPlanet(principal, read)]
|
|
60
|
+
: [ownPlanet(principal, read), ownStar(principal, read)];
|
|
61
|
+
// An agent whose home is its Star may read the shared Planet but never change it: its writes
|
|
62
|
+
// (new memories, forget, pin, tiers, confirmations) stay in its Star.
|
|
63
|
+
const writeSide = home === 'star'
|
|
64
|
+
? [ownStar(principal, write)]
|
|
65
|
+
: anonymous
|
|
66
|
+
? [ownPlanet(principal, write)]
|
|
67
|
+
: [ownPlanet(principal, write), ownStar(principal, write)];
|
|
68
|
+
return new Grant(principal, readSide, writeSide, home === 'star' ? starNode(principal) : planetNode(principal));
|
|
42
69
|
}
|
|
43
|
-
/** @internal Use `nodeGrant`. */
|
|
44
|
-
static issueNodes(principal, read, write) {
|
|
70
|
+
/** @internal Use `nodeGrant` or `narrowGrant`. */
|
|
71
|
+
static issueNodes(principal, read, write, home) {
|
|
45
72
|
checkPrincipal(principal);
|
|
46
|
-
return new Grant(principal, read, write);
|
|
73
|
+
return new Grant(principal, read, write, home);
|
|
47
74
|
}
|
|
48
75
|
/** True for a real grant (not a copy, not a look-alike). */
|
|
49
76
|
static is(x) {
|
|
@@ -57,13 +84,41 @@ function checkPrincipal(principal) {
|
|
|
57
84
|
}
|
|
58
85
|
}
|
|
59
86
|
}
|
|
60
|
-
/**
|
|
87
|
+
/**
|
|
88
|
+
* The local grant side's Planet: the principal's own Planet, whichever of the
|
|
89
|
+
* person's agents wrote the memory (CON-62; before it, the pattern was
|
|
90
|
+
* restricted to the principal's own agent).
|
|
91
|
+
*/
|
|
61
92
|
function ownPlanet(p, projectIds) {
|
|
62
|
-
return {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
};
|
|
93
|
+
return { node: planetNode(p), projectIds };
|
|
94
|
+
}
|
|
95
|
+
/** The principal's agent's own Star: private to that agent, never another's. */
|
|
96
|
+
function ownStar(p, projectIds) {
|
|
97
|
+
return { node: starNode(p), projectIds };
|
|
98
|
+
}
|
|
99
|
+
/** The Star of a principal's agent (owned by the principal's owner). */
|
|
100
|
+
export function starNode(p) {
|
|
101
|
+
return { level: 'agent', orgId: p.orgId, agentId: p.agentId };
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* `home` checked against the write side, or the default: the principal's
|
|
105
|
+
* Planet when the write side names it, else the first node written, else the
|
|
106
|
+
* Planet (a read-only grant writes nothing anyway).
|
|
107
|
+
*/
|
|
108
|
+
function homeNode(p, write, home) {
|
|
109
|
+
if (home !== undefined) {
|
|
110
|
+
assertNode(home);
|
|
111
|
+
const { level: _l, ...rest } = home;
|
|
112
|
+
const node = { level: home.level, ...rest };
|
|
113
|
+
if (write.length > 0 && !write.some((w) => nodeKey(w.node) === nodeKey(node))) {
|
|
114
|
+
throw new RangeError('grant: home must be a node the grant writes');
|
|
115
|
+
}
|
|
116
|
+
return node;
|
|
117
|
+
}
|
|
118
|
+
const planet = planetNode(p);
|
|
119
|
+
if (write.length === 0 || write.some((w) => nodeKey(w.node) === nodeKey(planet)))
|
|
120
|
+
return planet;
|
|
121
|
+
return write[0].node;
|
|
67
122
|
}
|
|
68
123
|
function freezePattern(p) {
|
|
69
124
|
if (typeof p !== 'object' || p === null)
|
|
@@ -83,12 +138,13 @@ function freezePattern(p) {
|
|
|
83
138
|
}
|
|
84
139
|
/**
|
|
85
140
|
* The projects of `patterns` that a pre-level reader may assume: those of
|
|
86
|
-
* patterns naming exactly the principal's Planet
|
|
87
|
-
* Never wider than the
|
|
141
|
+
* patterns naming exactly the principal's Planet and covering its agent's
|
|
142
|
+
* writing (no author restriction, or that agent). Never wider than the
|
|
143
|
+
* patterns themselves.
|
|
88
144
|
*/
|
|
89
145
|
function legacyProjects(p, patterns) {
|
|
90
146
|
const own = nodeKey({ level: 'user', orgId: p.orgId, customerId: p.customerId });
|
|
91
|
-
const mine = patterns.filter((x) => x.authorAgentId === p.agentId && nodeKey(x.node) === own);
|
|
147
|
+
const mine = patterns.filter((x) => (x.authorAgentId === undefined || x.authorAgentId === p.agentId) && nodeKey(x.node) === own);
|
|
92
148
|
if (mine.some((x) => x.projectIds === 'all'))
|
|
93
149
|
return 'all';
|
|
94
150
|
return Object.freeze([
|
|
@@ -111,9 +167,17 @@ function samePrincipal(a, b) {
|
|
|
111
167
|
return a.orgId === b.orgId && a.customerId === b.customerId && a.agentId === b.agentId;
|
|
112
168
|
}
|
|
113
169
|
/**
|
|
114
|
-
* The grant for
|
|
115
|
-
*
|
|
116
|
-
* `write`
|
|
170
|
+
* The grant for one agent of one person (CON-62): read and write the
|
|
171
|
+
* person's Planet (memories any of their agents wrote) and this agent's own
|
|
172
|
+
* Star, in the projects `read` names; `opts.write` may narrow the write side.
|
|
173
|
+
* Another agent's Star is never covered. `opts.home` picks where new memories
|
|
174
|
+
* land: the Planet (default) or the agent's Star; with `star` the Planet is
|
|
175
|
+
* read-only (the agent writes only its Star). `ANONYMOUS_AGENT` gets the
|
|
176
|
+
* Planet alone. `write` must name the same principal as `read`.
|
|
177
|
+
*
|
|
178
|
+
* Before CON-62 the Planet side was restricted to memories the principal's
|
|
179
|
+
* own agent wrote. Every local memory so far was written as `claude-code`,
|
|
180
|
+
* so a local store sees exactly what it saw; nothing is rewritten.
|
|
117
181
|
*/
|
|
118
182
|
export function localGrant(read, opts = {}) {
|
|
119
183
|
const write = opts.write ?? read;
|
|
@@ -124,11 +188,15 @@ export function localGrant(read, opts = {}) {
|
|
|
124
188
|
if (!samePrincipal(read, write)) {
|
|
125
189
|
throw new RangeError('localGrant: read and write must name the same org, customer and agent');
|
|
126
190
|
}
|
|
127
|
-
return Grant.issue(read, read.projectIds, write.projectIds);
|
|
191
|
+
return Grant.issue({ orgId: read.orgId, customerId: read.customerId, agentId: read.agentId }, read.projectIds, write.projectIds, opts.home);
|
|
128
192
|
}
|
|
129
|
-
/**
|
|
130
|
-
|
|
131
|
-
|
|
193
|
+
/**
|
|
194
|
+
* Every project of `p` (and its global memories), for read and write:
|
|
195
|
+
* maintenance such as export. Covers the same nodes as `localGrant`: the
|
|
196
|
+
* person's Planet and this agent's own Star.
|
|
197
|
+
*/
|
|
198
|
+
export function principalGrant(p, opts = {}) {
|
|
199
|
+
return Grant.issue({ orgId: p.orgId, customerId: p.customerId, agentId: p.agentId }, 'all', 'all', opts.home);
|
|
132
200
|
}
|
|
133
201
|
/**
|
|
134
202
|
* A grant over explicit node patterns, possibly across owners and levels:
|
|
@@ -142,7 +210,37 @@ export function nodeGrant(opts) {
|
|
|
142
210
|
if (!Array.isArray(opts.read) || (opts.write !== undefined && !Array.isArray(opts.write))) {
|
|
143
211
|
throw new TypeError('nodeGrant: read and write must be arrays of node patterns');
|
|
144
212
|
}
|
|
145
|
-
return Grant.issueNodes(opts.principal, opts.read, opts.write ?? opts.read);
|
|
213
|
+
return Grant.issueNodes(opts.principal, opts.read, opts.write ?? opts.read, opts.home);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* `g` narrowed to the nodes `keep` names (by `nodeKey`), on both sides: the
|
|
217
|
+
* grant recall uses for one node's first stage. Never wider than `g`. The
|
|
218
|
+
* home stays when it is kept, else the default for what remains.
|
|
219
|
+
*/
|
|
220
|
+
export function narrowGrant(g, keep) {
|
|
221
|
+
assertGrant(g);
|
|
222
|
+
const keys = new Set(keep.map((n) => nodeKey(n)));
|
|
223
|
+
const read = g.readNodes.filter((p) => keys.has(nodeKey(p.node)));
|
|
224
|
+
const write = g.writeNodes.filter((p) => keys.has(nodeKey(p.node)));
|
|
225
|
+
const home = write.some((p) => nodeKey(p.node) === nodeKey(g.home)) ? g.home : undefined;
|
|
226
|
+
return Grant.issueNodes(g.principal, read, write, home);
|
|
227
|
+
}
|
|
228
|
+
/** The distinct nodes `g` reads, in the order its patterns name them. */
|
|
229
|
+
export function readableNodes(g) {
|
|
230
|
+
assertGrant(g);
|
|
231
|
+
const seen = new Map();
|
|
232
|
+
for (const p of g.readNodes)
|
|
233
|
+
if (!seen.has(nodeKey(p.node)))
|
|
234
|
+
seen.set(nodeKey(p.node), p.node);
|
|
235
|
+
return [...seen.values()];
|
|
236
|
+
}
|
|
237
|
+
/** The address a new memory gets under `g`: its home node, bound to `projectId` when given. */
|
|
238
|
+
export function homeAddress(g, projectId) {
|
|
239
|
+
assertGrant(g);
|
|
240
|
+
return {
|
|
241
|
+
...g.home,
|
|
242
|
+
...(projectId !== undefined && { projectId }),
|
|
243
|
+
};
|
|
146
244
|
}
|
|
147
245
|
/** Rejects anything that is not a real `Grant`; every store calls it first. */
|
|
148
246
|
export function assertGrant(g) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const VERSION = "0.
|
|
1
|
+
export declare const VERSION = "0.4.0";
|
|
2
2
|
export * from './model.js';
|
|
3
3
|
export * from './address.js';
|
|
4
4
|
export * from './export-format.js';
|
|
@@ -8,6 +8,8 @@ export * from './activation.js';
|
|
|
8
8
|
export * from './adapters.js';
|
|
9
9
|
export * from './embed-cache.js';
|
|
10
10
|
export * from './grant.js';
|
|
11
|
+
export * from './levels.js';
|
|
12
|
+
export * from './agents.js';
|
|
11
13
|
export * from './fusion.js';
|
|
12
14
|
export * from './memory-store.js';
|
|
13
15
|
export * from './tokenize.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const VERSION = '0.
|
|
1
|
+
export const VERSION = '0.4.0';
|
|
2
2
|
export * from './model.js';
|
|
3
3
|
export * from './address.js';
|
|
4
4
|
export * from './export-format.js';
|
|
@@ -8,6 +8,8 @@ export * from './activation.js';
|
|
|
8
8
|
export * from './adapters.js';
|
|
9
9
|
export * from './embed-cache.js';
|
|
10
10
|
export * from './grant.js';
|
|
11
|
+
export * from './levels.js';
|
|
12
|
+
export * from './agents.js';
|
|
11
13
|
export * from './fusion.js';
|
|
12
14
|
export * from './memory-store.js';
|
|
13
15
|
export * from './tokenize.js';
|
package/dist/isolation.d.ts
CHANGED
|
@@ -10,8 +10,14 @@ import { type Grant } from './grant.js';
|
|
|
10
10
|
import type { Memory, Scope } from './model.js';
|
|
11
11
|
/** A memory for the isolation checks: text, stub and entities all carry `marker`. */
|
|
12
12
|
export declare function isoMemory(id: string, scope: Scope, marker: string, embedding?: Int8Array): Memory;
|
|
13
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* The narrowest grant for one scope: its Planet, its one project (or global),
|
|
15
|
+
* memories its own agent wrote. (The pre-CON-62 local grant; today's
|
|
16
|
+
* `localGrant` also covers the Planet's other authors and the agent's Star.)
|
|
17
|
+
*/
|
|
14
18
|
export declare function soleGrant(s: Scope): Grant;
|
|
19
|
+
/** The local grant of `ISO_SCOPES.mine`'s agent for its one project: Planet plus its own Star. */
|
|
20
|
+
export declare function defaultGrant(s: Scope): Grant;
|
|
15
21
|
/** The narrowest grant for one memory at any level: its node, its one project, its author. */
|
|
16
22
|
export declare function soleGrantFor(m: Pick<Memory, 'scope' | 'address' | 'author'>): Grant;
|
|
17
23
|
/** `m` moved to `scope`: its address and author dropped, so the scope's Planet is the address. */
|
|
@@ -59,13 +65,20 @@ export declare const ISO_SCOPES: {
|
|
|
59
65
|
/**
|
|
60
66
|
* Victims at the other levels, each next to the caller's own Planet: a Planet
|
|
61
67
|
* memory another of the caller's agents wrote, the caller's agent's own Star,
|
|
62
|
-
*
|
|
63
|
-
*
|
|
68
|
+
* another agent's Star (in the caller's project and global), an Orbit, a
|
|
69
|
+
* Flyby, a Constellation and a Galaxy root of an organisation, and one of the
|
|
70
|
+
* caller's own owner. The narrowest grant sees none of them; the local grant
|
|
71
|
+
* sees only those in `DEFAULT_COVERS`.
|
|
64
72
|
*/
|
|
65
73
|
export declare const ISO_LEVELS: Record<string, {
|
|
66
74
|
address: Address;
|
|
67
75
|
author: Author;
|
|
68
76
|
}>;
|
|
77
|
+
/**
|
|
78
|
+
* The victims the local grant of `ISO_SCOPES.mine` legitimately covers (CON-62): Planet memories
|
|
79
|
+
* the person's other agents wrote, and its agent's own Star. Never another agent's Star.
|
|
80
|
+
*/
|
|
81
|
+
export declare const DEFAULT_COVERS: readonly string[];
|
|
69
82
|
/**
|
|
70
83
|
* Every `MemoryStore` method, called with a grant for `ISO_SCOPES.mine`
|
|
71
84
|
* against memories (warm and cold) in each other scope: nothing is seen,
|
|
@@ -73,6 +86,10 @@ export declare const ISO_LEVELS: Record<string, {
|
|
|
73
86
|
* apart. Also the write-side rules (`GrantError` for an out-of-grant scope
|
|
74
87
|
* and for an id that exists elsewhere, atomic batches, address changes),
|
|
75
88
|
* read/write splits, cursor namespaces and rejection of non-grants.
|
|
89
|
+
*
|
|
90
|
+
* Run twice: as the narrowest grant (every victim out of reach), and as the
|
|
91
|
+
* local grant (`localGrant`, Planet plus own Star), for which every victim
|
|
92
|
+
* but `DEFAULT_COVERS` is out of reach, other agents' Stars included.
|
|
76
93
|
*/
|
|
77
94
|
export declare function isolationViolations(make: () => Promise<MemoryStore>): Promise<string[]>;
|
|
78
95
|
/** A small seeded PRNG (mulberry32), so a failing seed replays exactly. */
|
|
@@ -94,7 +111,13 @@ export declare function seededRng(seed: number): {
|
|
|
94
111
|
* `multiNode`: A's grant spans its Planet, its agent's Star and a
|
|
95
112
|
* Constellation of another owner; B's spans its own Planet and a sibling
|
|
96
113
|
* Constellation. Same property.
|
|
114
|
+
*
|
|
115
|
+
* `peers` (CON-62): A and B are two agents of one person, each with the local
|
|
116
|
+
* grant (the shared Planet plus its own Star). A writes to the Planet and its
|
|
117
|
+
* Star; B writes only to its own Star and aims every method at A's Star ids.
|
|
118
|
+
* Nothing B does through its Star reaches A.
|
|
97
119
|
*/
|
|
98
120
|
export declare function nonInterference(make: () => Promise<MemoryStore>, seed: number, steps: number, opts?: {
|
|
99
121
|
multiNode?: boolean;
|
|
122
|
+
peers?: boolean;
|
|
100
123
|
}): Promise<string | null>;
|