@atrib/recall 0.3.2 → 0.6.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/graph.js ADDED
@@ -0,0 +1,217 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * Local graph derivation + BFS for the recall semantic surface.
4
+ *
5
+ * Wires the §3.2.4 derived-graph shape that the recall_walk handler and
6
+ * the rank_by='causal_distance' path need, but scoped to the four
7
+ * load-bearing edge types for "what records causally inform this one":
8
+ *
9
+ * CHAIN_PRECEDES (weight 1): direct chain link.
10
+ * chain_root != genesisChainRoot(context_id) implies a prior record
11
+ * whose canonical hash equals chain_root sans "sha256:" prefix.
12
+ *
13
+ * INFORMED_BY (weight 1): explicit record-to-record reference (D041).
14
+ * Records carry an `informed_by` field whose entries are record_hashes
15
+ * of records consulted during construction.
16
+ *
17
+ * ANNOTATES (weight 2): an annotation record points at a target record
18
+ * (D058). Edge runs annotation -> target.
19
+ *
20
+ * REVISES (weight 2): a revision record supersedes a target record
21
+ * (D059). Edge runs revision -> target.
22
+ *
23
+ * Weights come from the Layer 1 design: direct causality (chain +
24
+ * informed_by) is weight 1; annotation/revision relationships are
25
+ * weight 2 (less direct: the agent's later judgment on a record vs the
26
+ * record's own causal inputs).
27
+ *
28
+ * SESSION_PRECEDES + SESSION_PARALLEL + CONVERGES_ON + CROSS_SESSION
29
+ * are deliberately omitted at Layer 1. They encode temporal/sibling
30
+ * structure, not causal-ancestor structure — useful for the public
31
+ * graph-node /v1/graph view but not for "what should the agent re-read
32
+ * before re-attempting a similar action". The graph-node service
33
+ * (services/graph-node/src/graph-builder.ts) is the spec-faithful
34
+ * full §3.2.4 derivation; this module is its Layer 1 subset.
35
+ *
36
+ * PROVENANCE_OF (D044) is also omitted at Layer 1 — it operates on the
37
+ * cross-session genesis anchor via a 16-byte-truncated token, which
38
+ * doesn't compose naturally with the within-mirror BFS path. A future
39
+ * release that wires cross-session PROVENANCE_OF onto provenance_token
40
+ * inputs would extend this graph.
41
+ *
42
+ * The graph is built once per recall() call from the LoadedRecord array
43
+ * and discarded; no caching. Layer 2 (sqlite-vec sidecar) materializes
44
+ * the graph alongside the embedding store.
45
+ */
46
+ import { genesisChainRoot } from '@atrib/mcp';
47
+ /**
48
+ * Layer 1 BFS edge weights. Direct causal links (CHAIN_PRECEDES,
49
+ * INFORMED_BY) are weight 1; annotation/revision relationships are
50
+ * weight 2. The recall_walk handler accepts an edge_types filter that
51
+ * intersects with these four; weights apply only when an edge type
52
+ * passes the filter.
53
+ */
54
+ export const EDGE_WEIGHTS = {
55
+ CHAIN_PRECEDES: 1,
56
+ INFORMED_BY: 1,
57
+ ANNOTATES: 2,
58
+ REVISES: 2,
59
+ };
60
+ /**
61
+ * Build the local Layer 1 graph from loaded records. Returns adjacency
62
+ * map keyed by record_hash. Records present in the loaded set but with
63
+ * no incident edges still appear as map keys with empty arrays — this
64
+ * keeps the BFS path consistent (graph.has(anchor) is still true even
65
+ * when the anchor has no neighbors).
66
+ *
67
+ * Time complexity: O(N) for the chain index pass + O(E) for edge
68
+ * emission. N = loaded.length, E = sum of informed_by entries + chain
69
+ * links + annotation+revision edges.
70
+ */
71
+ export function buildLocalGraph(loaded) {
72
+ const graph = new Map();
73
+ for (const lr of loaded) {
74
+ if (!graph.has(lr.record_hash))
75
+ graph.set(lr.record_hash, []);
76
+ }
77
+ const addEdge = (from, edge) => {
78
+ const list = graph.get(from);
79
+ if (list)
80
+ list.push(edge);
81
+ else
82
+ graph.set(from, [edge]);
83
+ };
84
+ const addUndirected = (a, b, type) => {
85
+ const weight = EDGE_WEIGHTS[type];
86
+ addEdge(a, { type, target: b, weight });
87
+ addEdge(b, { type, target: a, weight });
88
+ };
89
+ // Index by canonical-hash-hex so CHAIN_PRECEDES lookups (chain_root
90
+ // sans "sha256:") can resolve to the prior record's record_hash.
91
+ const byHashHex = new Map(); // hex -> record_hash form
92
+ for (const lr of loaded) {
93
+ const hex = lr.record_hash.startsWith('sha256:')
94
+ ? lr.record_hash.slice('sha256:'.length)
95
+ : lr.record_hash;
96
+ byHashHex.set(hex, lr.record_hash);
97
+ }
98
+ for (const lr of loaded) {
99
+ // CHAIN_PRECEDES: skip genesis records (chain_root == genesisChainRoot(context_id)).
100
+ const genesis = genesisChainRoot(lr.record.context_id);
101
+ if (lr.record.chain_root !== genesis) {
102
+ const expected = lr.record.chain_root.startsWith('sha256:')
103
+ ? lr.record.chain_root.slice('sha256:'.length)
104
+ : lr.record.chain_root;
105
+ const prior = byHashHex.get(expected);
106
+ if (prior) {
107
+ addUndirected(lr.record_hash, prior, 'CHAIN_PRECEDES');
108
+ }
109
+ }
110
+ // INFORMED_BY: explicit references.
111
+ const informedBy = lr.record.informed_by;
112
+ if (Array.isArray(informedBy)) {
113
+ for (const ref of informedBy) {
114
+ if (typeof ref !== 'string')
115
+ continue;
116
+ // Only emit when the referenced record is present in the mirror.
117
+ // Cross-mirror references will be resolved by Layer 2 (cache or
118
+ // log-side lookup) once that ships.
119
+ if (graph.has(ref) || byHashHex.has(stripPrefix(ref))) {
120
+ addUndirected(lr.record_hash, ref, 'INFORMED_BY');
121
+ }
122
+ }
123
+ }
124
+ // ANNOTATES: annotation record -> target. content.annotates lives in
125
+ // _local.content on a D062 envelope; bare-record annotations have no
126
+ // body to read (the §8.1 posture); skip those.
127
+ if (lr.content && typeof lr.content === 'object') {
128
+ const c = lr.content;
129
+ if (typeof c.annotates === 'string' &&
130
+ lr.record.event_type === 'https://atrib.dev/v1/types/annotation') {
131
+ addUndirected(lr.record_hash, c.annotates, 'ANNOTATES');
132
+ }
133
+ if (typeof c.revises === 'string' &&
134
+ lr.record.event_type === 'https://atrib.dev/v1/types/revision') {
135
+ addUndirected(lr.record_hash, c.revises, 'REVISES');
136
+ }
137
+ }
138
+ }
139
+ return graph;
140
+ }
141
+ function stripPrefix(hash) {
142
+ return hash.startsWith('sha256:') ? hash.slice('sha256:'.length) : hash;
143
+ }
144
+ /**
145
+ * BFS-shortest-path distances from `start` over the local graph.
146
+ * Returns a map record_hash -> distance (weighted). Records with no path
147
+ * to `start` are omitted (rather than mapped to Infinity) so callers
148
+ * iterate only reachable nodes.
149
+ *
150
+ * The traversal honors `edgeTypes` when provided: only edges whose type
151
+ * is in the set are followed. When edgeTypes is undefined or empty,
152
+ * ALL edge types are followed.
153
+ *
154
+ * `maxDepth` caps the traversal at that hop-count (NOT cumulative
155
+ * weight). Set to Infinity (the default) to traverse the full reachable
156
+ * subgraph. Useful for recall_walk's depth parameter.
157
+ *
158
+ * Since edge weights differ (1 or 2), the traversal uses Dijkstra (with
159
+ * a simple O(V^2) min-extraction since the candidate sets are small at
160
+ * Layer 1). This produces correct shortest paths in weighted graphs.
161
+ */
162
+ export function shortestDistances(graph, start, edgeTypes, maxHops = Number.POSITIVE_INFINITY) {
163
+ if (!graph.has(start))
164
+ return new Map();
165
+ const dist = new Map();
166
+ const hops = new Map();
167
+ dist.set(start, 0);
168
+ hops.set(start, 0);
169
+ const visited = new Set();
170
+ while (true) {
171
+ // O(V) min extraction. Layer 1 graphs are small (hundreds of
172
+ // records); a heap is overkill.
173
+ let node;
174
+ let minDist = Number.POSITIVE_INFINITY;
175
+ for (const [k, d] of dist) {
176
+ if (visited.has(k))
177
+ continue;
178
+ if (d < minDist) {
179
+ minDist = d;
180
+ node = k;
181
+ }
182
+ }
183
+ if (node === undefined)
184
+ break;
185
+ visited.add(node);
186
+ const nodeHops = hops.get(node) ?? 0;
187
+ if (nodeHops >= maxHops)
188
+ continue;
189
+ for (const edge of graph.get(node) ?? []) {
190
+ if (edgeTypes && edgeTypes.size > 0 && !edgeTypes.has(edge.type))
191
+ continue;
192
+ const candidate = minDist + edge.weight;
193
+ const existing = dist.get(edge.target);
194
+ if (existing === undefined || candidate < existing) {
195
+ dist.set(edge.target, candidate);
196
+ hops.set(edge.target, nodeHops + 1);
197
+ }
198
+ }
199
+ }
200
+ return dist;
201
+ }
202
+ /**
203
+ * Walk the graph from `start`, returning every reachable record_hash
204
+ * within `maxHops` hops, filtered to the requested edge_types. Result
205
+ * is sorted by ascending distance from start. Used by the recall_walk
206
+ * MCP tool to surface the local causal neighborhood of an anchor.
207
+ */
208
+ export function walkFrom(graph, start, edgeTypes, maxHops = 3) {
209
+ const dist = shortestDistances(graph, start, edgeTypes, maxHops);
210
+ // Drop the start node itself (distance 0) — the agent asked for
211
+ // adjacent records, not a re-echo of the anchor.
212
+ return [...dist.entries()]
213
+ .filter(([h]) => h !== start)
214
+ .map(([record_hash, distance]) => ({ record_hash, distance }))
215
+ .sort((a, b) => a.distance - b.distance);
216
+ }
217
+ //# sourceMappingURL=graph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph.js","sourceRoot":"","sources":["../src/graph.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import type { AtribRecord } from '@atrib/mcp';
3
+ export type ImportanceLabel = 'critical' | 'high' | 'medium' | 'low' | 'noise';
4
+ export declare const IMPORTANCE_NUMERIC: Record<ImportanceLabel, number>;
5
+ export declare const ATRIB_RECALL_ALPHA: number;
6
+ export declare const ATRIB_RECALL_BETA: number;
7
+ export declare const ATRIB_RECALL_GAMMA: number;
8
+ export declare const ATRIB_RECALL_TAU_DAYS: number;
9
+ import type { AnnotationSummary as AggAnnotationSummary } from './aggregations.js';
3
10
  export declare function loadRecords(path: string): AtribRecord[];
4
11
  /**
5
12
  * Load every `*.jsonl` file in `dir` and merge their records. Files that
@@ -11,7 +18,7 @@ export declare function loadRecords(path: string): AtribRecord[];
11
18
  * mirror namespace; every producer running under one identity writes a
12
19
  * file there with the convention `<producer>-<agent>.jsonl`. Scanning the
13
20
  * directory unifies recall across producers without recall having to know
14
- * the naming scheme any producer that follows §5.9 just shows up.
21
+ * the naming scheme - any producer that follows §5.9 just shows up.
15
22
  */
16
23
  export declare function loadRecordsFromDir(dir: string): {
17
24
  records: AtribRecord[];
@@ -19,7 +26,92 @@ export declare function loadRecordsFromDir(dir: string): {
19
26
  };
20
27
  interface RecallArgs {
21
28
  context_id?: string;
22
- event_type?: 'tool_call' | 'transaction';
29
+ event_type?: 'tool_call' | 'transaction' | 'annotation' | 'revision';
30
+ /**
31
+ * Optional exact match on `record.content_id` (`sha256:<64-hex>`). Per spec
32
+ * §1.2.2, content_id is `sha256(serverUrl + ":" + toolName)`. Filtering by
33
+ * content_id groups all records emitted by the same tool on the same MCP
34
+ * server. Useful for "all calls to this tool, ever." Coarser than tool_name
35
+ * because two tools on different servers share no content_id even if their
36
+ * names match.
37
+ */
38
+ content_id?: string;
39
+ /**
40
+ * Optional exact match on the §8.2 disclosed `tool_name`. Records that did
41
+ * NOT opt in to tool-name disclosure (the §8.1 default posture) carry no
42
+ * tool_name field and are excluded from results when this filter is set.
43
+ * Use this to query by human-readable name (e.g. tool_name="Edit") across
44
+ * MCP servers, when the producer disclosed it.
45
+ */
46
+ tool_name?: string;
47
+ /**
48
+ * Optional exact match on `record.args_hash` (`sha256:<64-hex>`). Per spec
49
+ * §8.3, args_hash commits to canonical args bytes. Salted (D045) and plain
50
+ * forms hash identically on the wire; this filter does not distinguish
51
+ * them. Most useful for replay detection (same args, same hash) and for
52
+ * agent-side keyed lookup when the agent computes a probe hash over a
53
+ * normalized {tool, target} dict.
54
+ */
55
+ args_hash?: string;
56
+ /**
57
+ * Layer 1 filter (NEW in 0.5.0): minimum annotation importance. Records
58
+ * are ranked by max(annotation.importance) where annotations are D058
59
+ * records pointing at this record. Records with no annotations at all
60
+ * have importance=0 and are EXCLUDED from results when min_importance is
61
+ * set. Use this to surface only records the agent or its critique loop
62
+ * has marked as worth attention.
63
+ */
64
+ min_importance?: ImportanceLabel;
65
+ /**
66
+ * Layer 1 filter (NEW in 0.5.0): OR-match against annotation topic tags.
67
+ * Records are kept if AT LEAST ONE annotation pointing at them carries
68
+ * AT LEAST ONE of the listed topics. Records with no annotations or no
69
+ * topic-overlap are excluded. Topics come from D058 annotation content.
70
+ */
71
+ topic_tags?: string[];
72
+ /**
73
+ * Layer 1 filter (NEW in 0.5.0): hide records superseded by D059 revision
74
+ * records. Default false (records remain visible even if a later revision
75
+ * supersedes them; the response carries `superseded_by` so the agent can
76
+ * see). Set true to filter superseded records out of the response entirely.
77
+ */
78
+ include_revised?: boolean;
79
+ /**
80
+ * Layer 1 filter (NEW in 0.5.0): minimum count of distinct cross-attesting
81
+ * signers per D052. Useful for transaction records that must carry >= 2
82
+ * signers; also useful as a credibility filter when querying multi-agent
83
+ * substrate. Records below the threshold are excluded.
84
+ */
85
+ min_signers?: number;
86
+ /**
87
+ * Layer 1 ranking (NEW in 0.5.0): how to order results before paging.
88
+ * 'timestamp' (default, backward-compatible): newest first.
89
+ * 'relevance': Park et al. 2023 weighted-sum scoring with annotation-derived
90
+ * importance (NO embedding component until Layer 2 ships; falls back to BM25
91
+ * over summary+topics if rank_anchor query is provided).
92
+ * 'causal_distance': BFS shortest path in the §3.2.4 derived graph from
93
+ * `rank_anchor` (which must be a record_hash). Edge weights per design.
94
+ */
95
+ rank_by?: 'timestamp' | 'relevance' | 'causal_distance';
96
+ /**
97
+ * Layer 1 ranking (NEW in 0.5.0): the anchor for non-timestamp rank_by
98
+ * modes. For rank_by='causal_distance', this MUST be a record_hash
99
+ * (`sha256:<64-hex>`); records are ranked by BFS shortest path from
100
+ * the anchor. For rank_by='relevance' with an optional query string,
101
+ * pass the query here as a free-form text string (Layer 2 will use it
102
+ * for embedding similarity; Layer 1 falls back to BM25-style scoring
103
+ * over summary+topics).
104
+ */
105
+ rank_anchor?: string;
106
+ /**
107
+ * New in 0.5.0: table-of-contents response shape. When true,
108
+ * each record returned is a one-line entry shape (record_hash, tool_name,
109
+ * summary, importance, topic_tags, timestamp, superseded_by). Cheap to
110
+ * scan, ~40-80 tokens per entry; agent expands on demand via
111
+ * `recall(content_id=..., compact=false)` or `recall_walk(...)`. Used at
112
+ * SessionStart for the auto-injected scaffold.
113
+ */
114
+ toc?: boolean;
23
115
  limit?: number;
24
116
  offset?: number;
25
117
  /**
@@ -35,11 +127,23 @@ interface RecallArgs {
35
127
  */
36
128
  include_unverified?: boolean;
37
129
  }
130
+ /**
131
+ * Aggregated annotation summary attached to a record per Layer 1. Same
132
+ * shape as the canonical AnnotationSummary exported from aggregations.ts;
133
+ * aliased here so the response types in this file don't have to drag the
134
+ * aggregations module into their import surface.
135
+ */
136
+ type AnnotationSummary = AggAnnotationSummary;
38
137
  /**
39
138
  * The shape returned to the agent. Each record is annotated with
40
- * signature_verified true if the local Ed25519 signature check passed.
139
+ * signature_verified - true if the local Ed25519 signature check passed.
41
140
  * In compact mode the heavy fields (signature, content_id, chain_root,
42
141
  * spec_version) are dropped; the verified status is preserved.
142
+ *
143
+ * Layer 1 (0.5.0) adds optional `annotations` (max_importance + topics from
144
+ * any D058 annotations pointing at this record) and `superseded_by` (record
145
+ * hashes of any D059 revision records whose `revises` field equals this
146
+ * record's hash).
43
147
  */
44
148
  type RecallRecordCompact = {
45
149
  event_type: AtribRecord['event_type'];
@@ -48,9 +152,37 @@ type RecallRecordCompact = {
48
152
  timestamp: number;
49
153
  signature_verified: boolean;
50
154
  session_token?: string;
155
+ /**
156
+ * §8.2 disclosed tool name. Included in compact mode when present so a
157
+ * caller filtering by tool_name sees the value back in the response (the
158
+ * common pattern: filter by tool_name -> render results, want the name
159
+ * visible). Records without tool_name disclosure (the §8.1 default
160
+ * posture) omit this field as they always do.
161
+ */
162
+ tool_name?: string;
163
+ /** New in 0.5.0: aggregated annotation summary. */
164
+ annotations?: AnnotationSummary;
165
+ /** New in 0.5.0: record hashes of D059 revisions superseding this record. */
166
+ superseded_by?: string[];
167
+ };
168
+ /**
169
+ * TOC entry: the smallest cheap-to-scan shape (~40-80 tokens). Used at
170
+ * SessionStart auto-inject to surface a candidate set the agent can
171
+ * expand on demand via recall(content_id=...) or recall_walk.
172
+ */
173
+ export type RecallRecordToc = {
174
+ record_hash?: string;
175
+ tool_name?: string;
176
+ summary?: string;
177
+ importance?: ImportanceLabel;
178
+ topic_tags?: string[];
179
+ timestamp: number;
180
+ superseded_by?: string[];
51
181
  };
52
182
  type RecallRecordFull = AtribRecord & {
53
183
  signature_verified: boolean;
184
+ annotations?: AnnotationSummary;
185
+ superseded_by?: string[];
54
186
  };
55
187
  export interface RecallResult {
56
188
  total: number;
@@ -73,7 +205,7 @@ export interface RecallResult {
73
205
  record_file: string;
74
206
  log_origin: string;
75
207
  pagination_caveat: string;
76
- records: RecallRecordFull[] | RecallRecordCompact[];
208
+ records: RecallRecordFull[] | RecallRecordCompact[] | RecallRecordToc[];
77
209
  }
78
210
  /**
79
211
  * Discover and load records per the mirror-discovery contract: