@hasna/mementos
v0.17.10
Published
Universal memory system for AI agents - CLI + MCP server + library API
Maintainers
Readme
@hasna/mementos
Persistent memory for AI agents, available as a CLI, MCP server, REST service, and TypeScript library. Hosted clients use the authenticated Mementos HTTP API; local SQLite is available only through an explicit local-mode opt-in.
Install
Mementos requires Bun 1.0 or newer at runtime.
npm install -g @hasna/mementos
# or
bun add -g @hasna/mementosThe package installs three binaries:
| Binary | Purpose |
| --- | --- |
| mementos | Memory, agent, project, graph, session, and maintenance CLI |
| mementos-mcp | MCP server; Streamable HTTP by default, stdio on request |
| mementos-serve | REST API server |
Quick start
Local mode must be selected explicitly. The first command after the opt-in creates
and migrates ~/.hasna/mementos/mementos.db.
export HASNA_MEMENTOS_LOCAL=1
mementos save project-stack "Bun, TypeScript, SQLite" \
--scope shared --category fact
mementos recall project-stack
mementos search "TypeScript"
mementos list --scope sharedRegister an agent and project when memories need explicit ownership:
mementos projects --add --name my-project --path "$PWD"
mementos register-agent marcus --role coding-agent
mementos inject --project "$PWD" --agent marcus --format compactMemory scopes are global, shared, private, and working. working is
transient session scratch space and defaults to a one-hour lifetime. Categories
are preference, fact, knowledge, history, procedural, and resource.
For CLI context and inject, an explicit --project must resolve to a registered
project and restricts every included scope to that exact project. SDK
getContext({ project_id }) uses the same exact-project rule. Unknown explicit
projects fail before memories are selected or touched; omitting the project keeps
the existing scope selection.
The library and MCP injection strategies retain unassigned agent-private context alongside the selected project's private memories. They exclude private memories assigned to another project. Their existing global/shared policies remain: library strategies include global memories across projects and project-scoped shared memories; direct MCP injection also scopes global memories to the project, while its full smart pipeline uses the library policy.
Injection project references may be a stable project ID, registered name, or registered path. Library, MCP, and HTTP/SDK injection resolve that reference once and use the stable project ID for every downstream profile, search, filter, hook, and touch path.
The list API and SDK expose include_unassigned_project: true with project_id
for this union. The filter applies before pagination and also governs total;
without a project it has no effect. Omitting it keeps an exact project match.
CLI
Optional decision assistance
Decision assistance is off by default. No provider account or key is needed
for ordinary Mementos commands. Configure and enable the features you want from
the CLI; there is no web setup. This adds no service-side configuration and does
not change ordinary search, context injection, or consolidation.
mementos decisions status --json
mementos decisions configure --provider openrouter --model typesafe/jev-1.13
mementos decisions enable --retrieval --relationships
# Supply OPENROUTER_API_KEY through your secret manager's process environment.
mementos --project my-project decisions search "release approval policy" --limit 10 --json
mementos decisions evaluate --input pair.json --json
mementos decisions disableFor prompt hooks, mementos prompt-context --enabled --input - accepts JSON
containing prompt, project and optional explicit scope filters. It returns a
bounded, read-only context receipt. Without --enabled it performs no retrieval;
provider decisions still require their separate opt-in. The Hooks app registers
the native event with hooks install mementos-context --target codex (also
claude or codewith). See the prompt hook setup
for environment enablement, project scope, provider disclosure and native trust.
configure always leaves assistance disabled. enable permits only the selected
features and explicitly permits sending their text to the configured provider.
The key is read from OPENROUTER_API_KEY; it is never saved in decision settings.
status reports key presence without verifying or calling the provider. Settings
are versioned client preferences in the Mementos data root's decisions.json;
updates reject concurrent changes. status, configuration, and evaluate do not
open a memory store or require a hosted Mementos account.
decisions search retrieves a bounded candidate set using the existing project,
agent, session, scope, category, and tag filters, then optionally reranks it. Every
candidate is retained; ties preserve the original order. On a missing key,
timeout, invalid answer, oversized input, or provider failure, the original
ordering is returned with an explicit assessment reason. This command has no
pagination cursor: its receipt names ranking_scope: "returned_candidates" and
reports has_more. Use ordinary search for pagination. JSON includes compact
snippets and source IDs/versions, with a 128 KiB output ceiling.
evaluate accepts a JSON file or stdin (--input -). A relationship input is:
{
"task": "relationship",
"candidates": [
{ "id": "a", "text": "Production deployment requires approval." },
{ "id": "b", "text": "Production deployment does not require approval." }
]
}For relevance, use "task": "relevance", a non-empty "query", and a
"candidates" array of the same {id,text} shape. Relationship suggestions are
equivalent, complementary, contradictory, unrelated, or uncertain.
Evaluation is read-only: suggestions never authorize writes or replace source
authority, human review, or version checks. An unavailable evaluation exits 2;
a disabled evaluation exits 0 with an explicit disabled receipt.
The built-in adapter calls OpenRouter's Decisions API with a pinned model ID,
no automatic retries, and provider fallback disabled. Defaults bound each
assessment to 20 candidates, 16,000 serialized input characters, and five
seconds; configure these with --max-candidates, --max-input-chars, and
--timeout-ms. Input files are limited to 256 KiB and provider responses to
64 KiB. Only query and candidate text go to OpenRouter; arbitrary metadata and
candidate IDs are excluded. Known secret patterns and credential-bearing URLs
are redacted before transmission. Redaction is heuristic: enable this only for
content you are permitted to send to your chosen provider.
Jev returns judgments, not generated memories or embeddings. Its confidence is distribution concentration, not a probability of correctness. Pinning the model and criteria makes requests reproducible but does not promise deterministic provider answers. Keep structural validation and authorization deterministic.
Other providers, including local implementations, can implement the public
DecisionProvider interface; no OpenRouter dependency is required by callers:
import { assessDecisions, rankDecisionCandidates } from "@hasna/mementos/sdk";
import type { DecisionProvider } from "@hasna/mementos/sdk";
// `provider` is your adapter implementing DecisionProvider.
async function rank(provider: DecisionProvider, query: string,
candidates: Array<{ id: string; text: string }>) {
const assessment = await assessDecisions({ task: "relevance", query, candidates }, {
enabled: true, retrieval: true, provider: provider.id, model: provider.model,
}, { provider });
return { assessment, candidates: rankDecisionCandidates(candidates, assessment) };
}Knowledge consumers can reuse this contract to assess already authorized retrieval candidates or flag potentially conflicting passages. Source citations, review state, and guarded publication remain the consumer's responsibility.
mementos --help
mementos <command> --helpHuman-readable list and search commands are compact and paginated by default.
Use --limit with --cursor or --offset, --verbose for wider snippets, and
mementos show <id> for a full record.
JSON collection output is intentionally envelope-based in this minor release.
projects --json, agents --json, list --json, and search --json no longer
return bare arrays: each returns a compact collection plus truthful _meta
continuation fields. --agent-json remains an alias for the list receipt.
--full changes row detail inside the envelope; --all exhausts the query
inside the envelope (up to 100,000 rows / 64 MiB). Neither flag restores a bare
array for these four commands. The sole legacy bare-array escape is
export --all, because export is an explicitly bulk-oriented command.
Agent JSON uses offset pagination. Stable ID tie-breakers prevent overlap for equal timestamps and importance while the result set is unchanged; writes between page requests can shift offsets, so restart traversal when a stable snapshot is required.
mementos list --json # compact 20-row receipt, <=32 KiB
mementos list --json --cursor 20
mementos list --json --full --limit 5 # explicit full detail, still bounded
mementos list --json --all # exhaustive receipt, still an envelope
mementos projects --json # compact continuation receipt
mementos agents --json # compact continuation receipt
mementos search "deploy" --json # compact byte-capped search receipt
mementos export # truthful paginated export receipt
mementos export --all # explicit exhaustive legacy array
mementos storage mode --jsonThe complete command tree and option conventions are in the CLI reference.
MCP
mementos-mcp defaults to a shared, stateless Streamable HTTP server bound to
127.0.0.1:8867:
mementos-mcp
# explicit equivalent
mementos-mcp --http --port 8867Endpoints are GET /health and POST /mcp. Set MCP_HTTP_PORT to change the
port. For an MCP host that launches a child process over stdio, opt in explicitly:
mementos-mcp --stdio
# or: MCP_STDIO=1 mementos-mcpCursor, Codex, Claude, and other command-based MCP host entries should use
command = "mementos-mcp" with args = ["--stdio"].
The default core MCP profile exposes a bounded 23-tool agent surface. Select
additional comma-separated profiles with --mcp-profile,
HASNA_MEMENTOS_MCP_PROFILE, or the compatibility alias
MEMENTOS_MCP_PROFILE: search, graph, automation, admin, storage,
hooks, and full. The explicit full profile preserves all 124 tools and the
legacy unpaged mementos://memories, mementos://agents, and
mementos://projects resources; reduced profiles omit those resources and use
bounded list/get tools instead.
MCP tools/list remains the authoritative schema source. search_tools returns
a bounded names-only page for active-profile tools, and describe_tools
requires one to ten explicit names. memory_context defaults to ten ranked
~240-character previews under a 32 KiB ceiling; use detail="full" explicitly.
memory_inject defaults to lightweight hints and preserves complete injection
behind mode="full". memory_export is paginated and receipt-bearing. See the MCP reference for
profile membership and compatibility details.
REST API
mementos-serve --port 19428The server binds to 127.0.0.1 unless MEMENTOS_HOST is set. /v1 is the
canonical API prefix and /api is a backward-compatible alias. Operational
probes and the generated contract are available without authentication:
GET /health
GET /ready
GET /version
GET /openapi.jsonAPI routes use bearer/API-key authentication when configured. See the REST API reference.
Storage
Client storage selection
The hosted Mementos API is the ordinary client default. The CLI, MCP server, and SDK resolve the hosted credential and authority chain described below; if no hosted credential resolves, client data commands fail closed rather than opening or creating SQLite automatically.
SQLite is available only through an explicit local opt-in:
# Use the standard local data root (~/.hasna/mementos/mementos.db).
export HASNA_MEMENTOS_LOCAL=1
# Or select one exact SQLite file explicitly.
export HASNA_MEMENTOS_DB_PATH=/absolute/path/to/mementos.dbMEMENTOS_LOCAL=1 and MEMENTOS_DB_PATH are compatibility aliases. Legacy
~/.mementos data is considered for migration only after local mode has been
selected explicitly; it is never an automatic client fallback.
Server backend and HTTP clients
There are no deployment modes. The only runtime switch is the server data
backend: sqlite | postgresql, selected by HASNA_MEMENTOS_DATABASE_URL
presence. Raw PostgreSQL credentials are server-only — configure
mementos-serve with HASNA_MEMENTOS_DATABASE_URL; configure CLI and MCP
clients with the HTTPS API endpoint and API key instead:
# mementos-serve environment
HASNA_MEMENTOS_DATABASE_URL=postgres://...
# client environment; do not distribute the database URL to clients
HASNA_MEMENTOS_API_URL=https://mementos.example.com
HASNA_MEMENTOS_API_KEY=...Hosted clients — one credential chain
The CLI, the MCP server and the ./sdk client resolve their credential and
service authority through the ONE resolver in @hasna/contracts
(@hasna/contracts/client), fresh on every call so a key rotation heals a
long-lived shell or MCP server without a restart:
| Tier | Source |
| --- | --- |
| 1 | explicit arguments (--api-key / --profile, or baseUrl / apiKey in the SDK) |
| 2 | deliberate env pointer: HASNA_MEMENTOS_API_KEY_OVERRIDE, HASNA_PROFILE, HASNA_MEMENTOS_API_KEY_REF (a secrets-vault item key, never a value) |
| 3 | macOS Keychain: generic password hasna.credentials.mementos.api-key, account HASNA_STATION → hostname -s → USER |
| 4 | disk: ~/.hasna/mementos/config/credentials, owner-only 0400/0600 (HASNA_HOME / HASNA_CONFIG_HOME move the root) |
| 5 | environment: HASNA_MEMENTOS_API_KEY |
The authority follows HASNA_MEMENTOS_API_URL, the Keychain api-url item,
the credentials file, and finally the fleet gateway
https://api.hasna.com/mementos — a credential ALONE is a complete
configuration. The legacy unprefixed MEMENTOS_API_URL / MEMENTOS_API_KEY
spellings survive only as the resolver's silent alias fallback for one
release. Writing a key to the disk tier:
mkdir -p ~/.hasna/mementos/config
printf 'HASNA_MEMENTOS_API_KEY=%s\n' "$KEY" > ~/.hasna/mementos/config/credentials
chmod 600 ~/.hasna/mementos/config/credentialsFAIL LOUD (owner ruling 2026-09-04): a hosted run with no credential anywhere
exits non-zero naming every tier it consulted; there is no SQLite fallback and
no local-fallback event. The on-box SQLite store is reachable ONLY through the
deliberate opt-ins HASNA_MEMENTOS_DB_PATH / MEMENTOS_DB_PATH (an explicit
file) or HASNA_MEMENTOS_LOCAL=1 (alias MEMENTOS_LOCAL=1, honoured only when
nothing configures an authority) — and every local run prints one line saying
it is local on stderr. Retired locations (~/.hasna/fleet-env/,
~/.hasna/cloud/, ~/.config/hasna/, $XDG_CONFIG_HOME) and retired
*_MODE / *_STORAGE_MODE variables are inputs nowhere.
mementos storage mode reports the chosen transport without opening a
database or making a network request; mementos status prints the resolved
API: line (hasna/apps#1588).
The old storage push, pull, and sync commands remain for compatibility;
they are not the cutover architecture. See Configuration and
storage and the cloud cutover runbook.
TypeScript APIs
The main package exports the synchronous database/domain API from
@hasna/mementos and an authenticated fetch client from @hasna/mementos/sdk
— one package carries every surface (CLI, MCP server, REST server, SDK); there
is no separate -sdk or -mcp package. See Library and SDK APIs.
Shared event webhooks
The CLI includes the events and webhooks command groups supplied by
@hasna/events, allowing memory events to trigger command or HTTP automation.
Inspect their installed-version help before configuring a webhook:
mementos events --help
mementos webhooks --helpEvent command handlers receive the envelope on stdin and in
HASNA_EVENT_JSON. Include working_dir, project_path, or repo_path when a
downstream agent must run in a particular repository.
Development
bun install
bun run typecheck
bun test
bun run buildDevelopment entry points are bun run dev:cli, bun run dev:mcp, and
bun run dev:serve.
Documentation
- CLI reference
- MCP reference
- REST API reference
- Configuration and storage
- Library and SDK APIs
- Cloud cutover runbook
License
Apache-2.0 — see LICENSE.
Claude Code Stop hook
Preview the installed CLI's managed hook configuration:
mementos session setup-hook --claudeApply that exact preview with --apply --expect-settings-sha256 <digest>
(or absent for a new settings file). A recognized legacy hook also requires
--expect-hook-sha256 <digest>. The installer preserves unrelated settings,
retains the original settings as a private backup, and refuses custom or changed
hook files. A symlinked .claude directory is preserved and requires the preview's
--expect-directory-sha256 <digest>; retargeting the alias invalidates the preview
even when its settings have identical bytes. mementos init uses the same installer and does not start a local
server when a hosted API is configured.
When Skills manages Claude's native discovery, installation validates the existing review and updates the Stop hook and its discovery witnesses together. Existing plugin, permission, native skill, or hook drift refuses installation; it is never silently adopted. Installations without a Skills policy retain the standalone behavior. Skills is loaded only while applying the installation. The installer uses the published Skills 0.9.14 SDK and supports explicitly reviewed v2 settings and marketplace witnesses. Their allowed model-effort and marketplace timestamp changes remain valid; the installer does not enable v2 reviews or adopt unrelated changes on its own.
Installation names the owning CLI entrypoint on the installing station — the
entrypoint at ~/.local/bin/mementos first, then ~/.bun/bin/mementos — in the
portable shell form "$HOME/.local/bin/mementos" session stop-hook --claude, so
the Stop hook does not depend on Claude's PATH and a settings copy or restore
keeps working across homes. Neither a versioned package cache path nor a
particular station's home is frozen into Claude's configuration; the entrypoint
symlink owns the runtime and version. When neither entrypoint exists, the
published portable command mementos session stop-hook --claude stays the
installed form and requires the installed CLI on Claude's PATH. It
reads the native transcript_path and final assistant
text, excludes tool payloads, redacts known secret patterns, and submits through
the normal hosted credential resolver. It never ingests the hook envelope as a
conversation, falls back to a local store, or automatically retries an ambiguous
submission. Failures report a fixed, private-data-free phase and reason, such as
[phase=request; reason=timeout], and an actual HTTP error status when available.
Input, transcript, configuration, request, and response failures are distinct;
exception messages, transcript paths, credentials, and server bodies stay out of
the diagnostic. An interrupted request can have reached the server, so its
failure is not proof that no job was created. The hook exits 1 on failure,
which reports it without blocking Claude's Stop; see the
Claude hook exit-code contract.
The hook reads at most 8 MiB of conversation text and 64 KiB of input context.
A transcript within the 128 MiB scan bound is read in full; past it the hook
reads a bounded 64 MiB tail of the most recent records, starting on the first
complete record, and the 8 MiB conversation bound keeps the most recent text of
that window instead of refusing the ingest.
When MEMENTOS_AGENT is configured, the hook preserves that agent attribution.
The installer also recognizes the historical CLI-ingest migration shim by its
exact digest; modified copies remain protected as custom hooks.
