@atrib/recall
v5.1.4
Published
MCP server for atrib's verifiable action layer. Lets agents query their own provable past from the local mirror.
Maintainers
Readme
@atrib/recall
MCP server for atrib's verifiable action layer. Lets agents query their own provable past from the local signed-record mirror with per-record signature verification.
The consumer-side counterpart to @atrib/attest: attest produces signed records, recall reads them back and exposes them to the agent. Each returned record carries a signature_verified boolean so a poorly-written agent treats tampered records as such.
The recall verb and the absorb (attest/recall rename, D164)
@atrib/recall now exposes a recall tool that absorbs the eight legacy
recall_* tools under a shape argument, absorbs trace/trace_forward
as shape: "walk" with a direction, and absorbs atrib-verify as a
verification parameter. Results are JSON-identical to the legacy tools:
this is the read-equivalence conformance family in
spec/conformance/attest-recall/.
| shape | Legacy tool | Notes |
| ------------------------------- | ------------------------------- | -------------------------------------------------------------- |
| history | recall_my_attribution_history | base filter-rank-page query |
| walk | recall_walk | when direction omitted or "backward"; also absorbs trace |
| walk + direction: "forward" | trace_forward | forward walk over the same graph |
| content | recall_by_content | BM25 free-form retrieval |
| chain | recall_session_chain | ordered chronological session walk |
| annotations | recall_annotations | aggregated annotation summary for a target |
| revisions | recall_revisions | forward revision chain for a target |
| orphans | recall_orphans | records not cited by any other record |
| by_signer | recall_by_signer | per-creator aggregation |
| state | none | policy-bound current heads for revision lineages |
The verification parameter absorbs atrib-verify: pass the same
packet/records/claims evidence shapes as @atrib/verify and the
recall result carries a verification block instead of a separate tool
call. @atrib/verify (the verifier library) is an OPTIONAL peer
dependency, lazily imported. When it is absent, verification returns a
typed { status: "verifier_unavailable" } block and the read still
succeeds; the degradation is explicit and typed, not a silent drop.
createAtribRecallServer now mounts the full twelve-tool read union:
recall plus the eight recall_* tools plus trace, trace_forward,
and atrib-verify. All twelve tool names stay mounted as permanent
aliases during the alias window.
state is intentionally available only through the recall verb. It is a
new read shape, not a new primitive or legacy alias.
Install
pnpm add @atrib/recallVerify a local build with pnpm --filter @atrib/recall test.
Tool surface
The eight legacy recall_* tools below cover the cognitive surface of the
local mirror. They stay mounted as permanent aliases; see the absorb
section above for the recall verb that unifies them under one tool.
recall_my_attribution_history
The base filter-rank-page tool over the local mirror.
mcp__atrib-recall__recall_my_attribution_history({
// All optional
context_id?: string, // 32-hex. Filter to records signed under this trace.
context_scope?: 'all' | 'env', // Default 'all'. Set 'env' to apply the D078/D083
// env-derived current context when context_id is omitted.
creator_key?: string, // Ed25519 public key, base64url. Filter to records signed by this
// specific creator. The tool's name says "my history" but the local
// mirror may hold records from other signers (multi-agent flows,
// transactions with counterparty signatures, etc.); use this filter
// to scope strictly to your own past.
event_type?: 'tool_call' | 'transaction' | 'observation' | 'annotation' | 'revision' | 'directory_anchor' | string,
// Filter to a single event kind. Short-form names are normalized
// to the URI form. Full event_type URIs are also accepted.
content_id?: string, // sha256:... exact match on §1.2.2 content_id.
tool_name?: string, // §8.2 disclosed tool name; records without disclosure excluded.
args_hash?: string, // sha256:... §8.3 args_hash exact match.
limit?: number, // Default 10, max 200. (D085: matches field convention.)
offset?: number, // For pagination. Note pagination_caveat in the response.
compact?: boolean, // Default true - omits signature/content_id/chain_root/spec_version
// fields. `record_hash` is always included (so callers can chain
// recall_walk / recall_annotations / recall_revisions / trace
// from any result). Set false for full record bytes
// (re-verification).
include_unverified?: boolean, // Default false - drops records whose signature didn't verify.
// Set true ONLY when consuming the verbose mode AND explicitly
// checking signature_verified per record.
// Annotation- and revision-driven filters. Records with no incident
// annotation are excluded when min_importance or topic_tags is set;
// records that have a revision pointing at them surface superseded_by
// by default and are hidden when include_revised=true.
min_importance?: 'critical' | 'high' | 'medium' | 'low' | 'noise',
topic_tags?: string[], // OR-match against annotation topic_tags.
include_revised?: boolean, // True hides records superseded by a D059 revision.
min_signers?: number, // Distinct-signer threshold; 1 for non-transaction records.
// Ranking.
rank_by?: 'timestamp' | 'relevance' | 'causal_distance',
// 'timestamp' (default): newest first.
// 'relevance': Park et al. weighted-sum scoring (recency +
// annotation-derived importance + BM25 against rank_anchor).
// 'causal_distance': BFS shortest-path from rank_anchor over
// the local derived graph (CHAIN_PRECEDES, INFORMED_BY,
// ANNOTATES, REVISES).
rank_anchor?: string, // record_hash for causal_distance, free-form query for relevance.
// Response shape.
toc?: boolean, // Default false. True returns the ~40-80-token-per-entry
// table-of-contents shape (record_hash, tool_name, summary,
// importance, topic_tags, timestamp, superseded_by) suitable
// for SessionStart auto-injected scaffolds.
})Omitting context_id searches cross-context history by default. This keeps topic, importance, creator, event type, and tool-name recall useful as memory lookups across sessions. Harnesses that need the old D078 / D083 env-derived current-context behavior should pass context_scope: 'env'. An explicit context_id always wins over context_scope.
Returns { total, returned, filtered_out_by_verification, record_files, record_file, log_origin, pagination_caveat, records }. Each record carries record_hash (always, per D084, so the result is chainable into other primitives without a verbose-mode round-trip), annotations (when annotation records point at it), and superseded_by (when revision records point at it).
Every call to this tool (and every sibling tool below) writes a per-call jsonl entry to ~/.atrib/state/read-primitives/calls.jsonl for the unified loop-closure analyzer per D084. Silent-failure per §5.8; the tool response is unaffected by instrumentation failures. The ATRIB_READ_PRIMITIVES_LOG env var overrides the default path for tests.
Current state
await recall({
shape: 'state',
trusted_creator_keys: ['<base64url Ed25519 key>'],
allowed_context_ids: ['<32-hex context id>'],
root_record_hashes: ['sha256:<64-hex>'],
include_content: true,
limit: 100,
head_limit: 100,
})The state projection verifies candidate signatures and applies the caller's
creator and context policy. Each cell returns its lineage root and every
active revision head. A single head is resolved; multiple heads are
conflict. The projector never picks a winner from timestamp or signer
identity. Excluded revisions remain visible with typed reasons.
Cells, heads within each cell, and exclusion details are bounded separately.
The response reports complete counts and truncation, so a large fork cannot
inflate one nominally bounded result without detection. Repeated mirror
records are deduplicated by canonical record hash.
Omitting trusted_creator_keys accepts every locally verified signer and
returns a warning. The response also reports log_inclusion_verified: false:
local signature verification does not prove public-log inclusion. Verify
inclusion separately when the state view crosses an operator boundary.
The current revision format names one revises target. A fork cannot be
merged by pretending that informed_by means supersession, so every active
head remains visible until an application policy resolves it. See
D174
and
P053.
The open explorer session view renders the public revision commitments
available from graph-node. That browser projection shows conflicts and partial
external roots, but it is not the caller-policy state view above. It does not
apply trusted_creator_keys or independently verify inclusion proofs.
Sibling tools
mcp__atrib-recall__recall_walk({ from_record_hash, edge_types?, depth? })- walks the local derived graph fromfrom_record_hashup todepthhops (default 3), returning each reachable record_hash + weighted distance. Edge types: CHAIN_PRECEDES (weight 1), INFORMED_BY (weight 1), ANNOTATES (weight 2), REVISES (weight 2). SESSION_PRECEDES, SESSION_PARALLEL, CONVERGES_ON, CROSS_SESSION, and PROVENANCE_OF are deferred to subsequent releases.mcp__atrib-recall__recall_annotations({ record_hash })- returns the aggregated annotation summary (max_importance, union of topics, latest summary) for the target record. Returnsannotations: nullwhen no annotation points at the record.mcp__atrib-recall__recall_revisions({ record_hash })- returns the forward revision chain for the target record. Each chain entry carriesrecord_hash,timestamp, and the D086-normative content fields (new_position,reason,importance) when present, so the agent can read the chain inline without follow-uprecallcalls per revision. The chain follows the first-by-timestamp revision at each step; when more than one revision targets the same record (sibling fan-out, common in multi-agent flows), the other branch heads are listed on that step's entry assibling_hashes, so the agent can recursively callrecall_revisionson a sibling to traverse a parallel branch instead of having to manually enumerate revisions viarecall_my_attribution_history.mcp__atrib-recall__recall_by_content({ query, k?, max_records?, evidence_mode? })- BM25 free-form retrieval over the newestmax_recordsrecords' indexable text + annotation summary + topic_tags when present, then reranked by Park et al. weighted-sum scoring (recency + importance + relevance). Default k=10, max 50. Defaultmax_recordsisATRIB_RECALL_CONTENT_MAX_RECORDSor 5000. The defaultevidence_mode: "bounded"keeps casual searches fast by tail-loading that newest-first window instead of loading the whole mirror. The response includesruntime,evidence_mode,evidence_status,fallback_required,total_records,searched_records,candidate_records,truncated_corpus, andcoverage;total_recordsisnullwhen recall served a partial tail-loaded snapshot instead of a full mirror snapshot.runtimenames the loaded@atrib/recallpackage version plus the coverage and content-index contract versions, so a stale MCP process is detectable from the result.coveragecarries a version, strategy, local-mirror high-water mark, mirror file count, searched record count, andcoverage.indexstatus so callers can tell whether a result came from a bounded newest-first window, a complete scan that rebuilt the durable sidecar, a durable-index hit, or an explicit disabled/write-failed fallback. Per D086 and D118, "indexable text" is per-event_type record content from the D062 sidecar (observation:what + why_noted + intent + rationale + topics; tool_call:tool_name + args excerpt + result excerpt; annotation:summary + topics; revision:prior_position + new_position + reason + topics; transaction: counterparty + memo + protocol fields; directory_anchor:tree_root + epoch_id). Extension URIs fall back to a generic recursive string-walk (depth <= 4, field cap 2KB). OpenInference local sidecars add recall tokens for span kind/name, tool/agent/model, prompt identifiers and templates, inputs/outputs, usage, cost, score, and metadata when those fields are mirrored locally per D108. BM25 contribution is clamped to [0, 1] in the parkScore site so the documented Park-component bound is honored. A future embedding sidecar can add semantic similarity over the same indexed text.
For critical-path audits, use evidence_mode: "require_complete". That mode loads the full mirror and searches every loaded record. If a caller also sets max_records below total_records, the tool returns no results with evidence_status: "incomplete", fallback_required: true, truncated_corpus: true, and the search_cap / total_records mismatch. Do not treat that as an empty search result. The deterministic fallback is to rerun without max_records for full loaded-mirror coverage, or to run a caller-owned partition plan and treat each partition as its own explicit coverage claim.
Complete-mode recall uses a durable content-token sidecar when it can. The sidecar is keyed to the current local mirror fingerprint and stores the BM25 token corpus plus display metadata for recall_by_content. A sidecar is accepted only when its stored mirror signature and high-water mark match the current mirror stats. If the sidecar is absent or stale, require_complete rebuilds it from the full local mirror and still returns complete evidence. If the sidecar is disabled with ATRIB_RECALL_CONTENT_INDEX=0, or if writing the sidecar fails, recall falls back to the loaded-mirror path and reports that status in coverage.index.
In wrapped MCP hosts, the recall tool call and its JSON response are signed as a tool_call record. That means an incomplete critical-path recall is not a quiet warning in transcript prose; the signed result carries fallback_required: true. Agents should emit an observation naming the incomplete recall status and the fallback they chose before continuing.
mcp__atrib-recall__recall_session_chain({ context_id?, limit?, include_content? })- returns all records in a context_id, ordered chronologically (oldest-first). The natural traversal of the CHAIN_PRECEDES topology for a single session/trace. Each entry carriesrecord_hash,event_type,timestamp,display_summary,display_producer,age, plus signed causal/tool fields when present (informed_by,tool_name,args_hash,result_hash). Wheninclude_contentis true, each entry also includes the D062 local mirror body aslocal_contentand the local producer label aslocal_producer. Defaults false to keep the session chain cheap. Whencontext_idis omitted, falls back toresolveEnvContextId(the same precedence as the other tools:ATRIB_CONTEXT_IDenv, then a D083-registered harness env likeCLAUDE_CODE_SESSION_ID).mcp__atrib-recall__recall_orphans({ context_id?, event_type?, creator_key?, limit? })- returns records that are NOT cited by any other record viainformed_by(loose ends: decisions or observations the agent made but never followed up on). Optionally scoped to one context_id, one event_type, or one creator_key. Newest-first ordering. Useful for the agent to discover dropped balls (e.g. "I noted X but never built on it").mcp__atrib-recall__recall_by_signer({ min_records? })- aggregates the local mirror bycreator_key. Returns distinct creators present + per-creator record count + earliest/latest timestamp. Pure aggregation; no records returned directly. Userecall_my_attribution_historywith thecreator_keyfilter to drill into one creator's records. Useful when the mirror is multi-signer.
Tunable weights
The Park et al. ranking weights and recency time constant are environment-tunable for per-axis sensitivity studies:
| Env var | Default | Role |
| ---------------------------------- | ---------------- | ----------------------------------------------------------------- |
| ATRIB_RECALL_ALPHA | 0.3 | Recency component weight |
| ATRIB_RECALL_BETA | 0.3 | Importance component weight |
| ATRIB_RECALL_GAMMA | 0.4 | Relevance (BM25) component weight |
| ATRIB_RECALL_TAU_DAYS | 7 | Exponential-decay time constant for recency |
| ATRIB_RECALL_NOISE_FLOOR | 0.6 | Anti-noise threshold for rank_by=relevance (see below) |
| ATRIB_RECALL_CONTENT_MAX_RECORDS | 5000 | Newest-first corpus size for bounded recall_by_content searches |
| ATRIB_RECALL_CONTENT_INDEX | enabled | Set to 0 to disable the durable content-token sidecar |
| ATRIB_RECALL_CONTENT_INDEX_DIR | ~/.atrib/cache | Directory for mirror-keyed content index files |
| ATRIB_RECALL_CONTENT_INDEX_FILE | unset | Exact content index file path, mainly for tests |
The implementation does not enforce that alpha + beta + gamma sum to 1.0; the operator-facing defaults do. See D085 for the survey-grounded rationale: ALPHA=0.3 matches CrewAI's recency_weight=0.3 (the only normalized-weights peer in a 2026-05-23 OSS survey); TAU_DAYS=7 produces a ~4.85-day half-life inside the field range and close to Park et al.'s ~5.75-day empirical anchor.
Legibility fields (added in 0.8.0)
Compact recall responses carry three derived fields per record so the agent can scan results without dereferencing opaque hashes:
| Field | Source |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| display_summary | Annotation summary if present, else per-event_type synthesis from record fields + _local.content (tool_call: call <tool_name>(<args>); observation: first 80 chars of what; transaction: <amount> to <merchant> via <protocol>; annotation: annotates <hash>: [<importance>] <summary>; revision: revises <hash>: <new_position>; directory_anchor: directory anchor <root>; extension URI: tail). Capped at 120 chars. |
| display_producer | _local.producer sidecar label (e.g. atrib-emit-cli, claude-hooks-builtin-2b). Falls back to key:<8hex> of the creator key. Answers which local code signed the record, not which human or organization. The complementary display_signer field (AKD-backed identity claim) is planned as a separate field; repurposing display_producer for AKD lookups would conflate two distinct trust signals. |
| age | Relative time string (just now, 5m ago, 3h ago, 3d ago, ISO date for older than 30 days). Returns "unknown" for non-finite timestamps. |
recall_by_content and recall_walk carry the same fields starting in the 0.8.0 audit-pass-1 follow-up.
Local content normalization
Recall treats _local.content as the preferred semantic payload. If older mirror entries only have wrapper fields (toolName, args, result) or OpenInference callback fields (source, spanKind, spanName, input, output, agentName, model_name), recall derives the same content shape through @atrib/mcp before scoring or formatting. That keeps legacy mirrors readable without promoting prompts, outputs, usage, cost, or scores into signed protocol fields.
Anti-noise threshold for rank_by=relevance
When rank_by=relevance produces a top Park score below ATRIB_RECALL_NOISE_FLOOR (default 0.6), recall returns empty records plus a quality: "below_threshold" signal and the observed top_score. The default sits above the recent-plus-annotated baseline from the D086 calibration and below the observed real-query minimum. Below that, results are effectively noise. Set the env var to 0 to disable. Threshold applies only to relevance ranking; timestamp + causal_distance modes are unaffected.
Novel in field. The 2026-05-23 survey of comparable systems (Park et al., MemGPT/Letta, A-MEM, MemoryBank, Mem0, LangChain, LlamaIndex, CrewAI, Haystack, AutoGen) found no published or OSS implementation that returns "empty + quality:below_threshold" rather than top-K. The field convention is "always return something, let the agent decide it's noise." atrib's inversion is a deliberate protocol choice (lower hallucination risk from low-confidence context). See D085 for the full survey and D086 for the 0.6 recalibration.
Trust scope
Signature verification is local-only. A passing signature_verified proves the record was signed by the named creator_key; it does NOT prove the record was committed to log.atrib.dev. To confirm log inclusion, fetch the inclusion proof from the log API.
Configuration
| Env var | Required | Purpose |
| ------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ATRIB_RECORD_FILE | optional | Path to a single signed-record jsonl mirror to read. When set, overrides directory scanning. Back-compat with pre-0.4.0 callers that pinned a specific producer's mirror. No default. |
| ATRIB_MIRROR_DIR | optional | Directory to scan; recall reads every *.jsonl inside. Default: ~/.atrib/records/ (the spec §5.9 well-known per-agent mirror namespace). When unset, this is the path used. |
| ATRIB_LOG_ORIGIN | optional | Origin used in human-readable response messages. Default: log.atrib.dev |
Mirror discovery priority (per spec §5.9): if ATRIB_RECORD_FILE is set, recall reads that single file. Otherwise recall scans ATRIB_MIRROR_DIR and merges every *.jsonl inside. The directory-scan default unifies recall across producers without recall having to know per-producer naming conventions; any producer that follows the spec convention just shows up.
Installation in an MCP host
{
"mcpServers": {
"atrib-recall": {
"command": "node",
"args": ["/abs/path/to/atrib/services/atrib-recall/dist/index.js"],
"env": {},
},
},
}Or run via npx:
{
"mcpServers": {
"atrib-recall": {
"command": "npx",
"args": ["-y", "@atrib/recall"],
},
},
}What this does NOT do
- No log-inclusion verification. Local signature verification ≠ log commitment proof. Use the log API for inclusion proofs.
- No graph derivation. Returns records, not the §3.2.4 graph. For declared-relationship walks, use
recallwithshape: "walk"(the legacytrace/trace_forwardtool names still work); for graph projections, query graph-node directly. - No write surface. Read-only. Use
@atrib/attestto sign new records.
Part of atrib
atrib is an open protocol for verifiable agent actions. Every action becomes a signed, chain-linked record that anyone can verify against a public Merkle log, with no operator to trust. This package is one entrypoint. See the full package family and the protocol spec.
