Skip to main content
Glama

Mnemento

CI

Like Leonard's polaroids in Memento — a shared, structured record book for LLM agents.

LLM agents forget between sessions. Mnemento gives them one place to write facts and ask questions in natural language, answered by exact structured queries — with evidence.

  • Keeper agent — the only writer; validates, deduplicates, records provenance, asks back when unsure

  • JSON documents + schema registry on SQLite — flexible storage, but the LLM always knows which fields exist

  • Event log + current state — every answer can cite the records it counted

  • MCP server — plug into Claude Code / Claude Desktop

Status: v1.3 — storage core, question pipeline, MCP server, measured three times against Claude Code's own memory (benchmarks on v1.1).

Latest result (v3, opus, questions the system was not developed on): the same accuracy as Claude Code's own memory — 27/27 vs 27/27 at 100 records, 24/27 vs 24/27 at 1,000 — and at 1,000 records 6× faster at 1/8 of the cost. Earlier rounds lost on accuracy (v0, v2); they are kept below as history. Design: docs/PLAN.md · decisions: docs/adr/ · demo log: docs/demo/0002-demo-log.md

How a question is answered

"10/2 사람인 지원 몇 곳?"
  ① interpret   rule-based fast path (no LLM) ─┐   or LLM → QuerySpec (sees only the relevant
                                               │      schema dictionary, never the records)
  ② query       QuerySpec ──code──▶ parameterised SQL on generated, indexed columns
  ③ answer      count / list / groups + evidence ids + warnings (small samples, incomplete
                periods, records too recent to have an outcome, resolved dates and names)
  • The model never writes SQL and never sees your records; it gets field names, descriptions and allowed values.

  • Anything outside the schema dictionary is rejected (and the model is asked once more), so it cannot query fields that do not exist.

  • When a question or a write is ambiguous (which company? which record?), the Keeper stores nothing and returns clarify with options.

  • A new kind of record over MCP: record it with a new entity_type and the schema you suggest (schema_draft); it is stored and queryable at once under a draft type, and the user approves the type later with apply_schema_proposal — required fields, enums and indexes apply from then on. A new name that looks like an existing type is asked about first (ADR-0021). Once you organized a type, this repository's later dictionary improvements reach your database only as a proposal you approve item by item (ADR-0023).

  • Each answer carries a trace: per-stage time, LLM calls, input/output tokens and cost.

  • Every question is kept in a local query log (question, interpretation, QuerySpec, SQL and parameters, result count, evidence ids, warnings, timings, cost). The query_log tool shows recent questions, those with warnings or errors, questions whose SQL changed between askings (diverging), and answers that found nothing (empty). An id filter naming no record is read as the import's source key or the record's name, or asked back — never answered with a silent 0 — and plans that found nothing are not cached (ADR-0020). Bounded to 10,000 rows / 50 MB; MNEMENTO_QUERY_LOG=off turns it off (ADR-0015).

Related MCP server: memory-mcp

Beyond job search: investment records (fictional example)

The same record book holds a second domain without new query code — only new schemas (examples/investment: security, trade, decision; task 0008 step 3).

"가상바이오(900001) 몇 번 샀어?"           -> trade, count, security resolved by its code (identifier)  -> 2
"올해 손절한 종목은?"                       -> trade, sell, pnl < 0, this year                            -> list
"왜 그 종목은 안 사기로 했지?"              -> the reason where it was written, quoted as recorded        -> text + evidence
  • Identifiers first: a field marked identifier (a stock code, a business registration number) is matched wherever it appears in the question — "nickname(900001)" finds the security even if the nickname is unknown (ADR-0019). Names alone that only look similar are offered as candidates, never linked.

  • Relation notes: a schema can say how it relates to another ("a trade and the decision behind it may be the same event — count buys from trade"); the interpreter is shown related types and counts each event from one type.

  • Copies of files you keep elsewhere: mnemento.importer makes repeated imports safe — the same source key is the same record, a changed fact becomes a corrected event, a changed state (e.g. buy criteria) an updated event at the source's own date, and records that disappeared from the source are reported, never deleted.

  • Used locally on the author's own investment records (trades, decisions, buy criteria, holdings); none of that data is in this repository. Daily valuations are left for price series (next step, ADR-0016).

Measurements over time (series, fictional example)

Some records are not "things that change state" but values measured again and again — body weight, daily spending, prices. A schema can be kind: "series" (a key such as person, a time field, numeric measures); points are stored one row per key and time, in batches that can be reverted as a whole (ADR-0016).

"가상인 3월 평균 몸무게 얼마였어?"                    -> weight, avg kg, March                       -> 68.7
"샘플인은 6월부터 8월 말까지 몸무게가 얼마나 변했어?"  -> change = last - first in the period        -> +0.9
"8월에 가상인 몸무게가 최근 4주 평균보다 낮았던 날은?" -> 28-day window + compare with the window     -> 20 days
  • Period, group_by key or time bucket (day/week/month/year), avg/min/max/sum, first/last/change/ change_pct, moving windows (unit: points|days, computed over the history before the asked period) and comparisons with a window or a value. SQL is generated by code, as for every other question.

  • Warnings: days without a point in the asked period, small samples, a period still in progress.

  • MCP: record_series (one batch, all or nothing; a point already stored is updated and the batch keeps the old value), revert_series_batch.

  • Evaluation: bench/series_eval.py — fictional questions with answer keys computed from the generator. Dev (10, written with the code): M-haiku 9/10, M-opus 9/10. Unseen (7, written by someone who did not see the code, run once): M-haiku 4/7, M-opus 4/7 (5/7 each when the right value is anywhere in the answer). The misses are honest gaps: a difference of two periods in one number, and counting days without a point, are not expressible yet; a right change listed after first/last is graded on the first measure (dev, unseen).

  • Records joined with a series, as of a date (ADR-0022): values computes per record a series value at a date (asof: the latest point at or before today, a date, or the record's own date such as the trade day) and arithmetic on it (expr), e.g. unrealized P/L = units × last close − invested. Only along a declared reference (holding.security_id and price.security_id both point at security); warnings for records without a price and for stale prices. Unseen price questions (5, run once): strict M-haiku 4/5, M-opus 2/5; 4/5 each counting the right number among the returned values (report). Used locally on real holdings: the P/L of every held position equals a direct calculation from the source data.

From text memory to a ledger (mixed-text ingest, fictional example)

Memory files mix facts that belong in several tables with rules and prose. ingest_preview reads text or a folder of .md files and shows what it would record; nothing is written until the user agrees and ingest_apply writes exactly that preview as one batch, which ingest_revert takes back (ADR-0017, ADR-0025).

- 2026-07-30 가상바이오 1회차 3주 12,340원 매수 — 수출 반등이 근거   -> trade + decision (siblings, one chunk)
- 2026-07-30 가상바이오 1회차 3주 12,340원 매수   (again, elsewhere)  -> the same trade: one record, two spans
- 2026-09-03 샘플전자 … 5,200원 매도 / … 5,300원 매도                  -> conflict: asked, never picked
매수는 세 번에 나눠서 한다.                                            -> not data: kept as a line reference only
| 2026-09-01 | 72.4 |  (a weight table)                              -> new series type, proposed with its schema
  • Every stored value carries a quote from the note, and code checks that the value comes from it (numbers, dates, enum labels, text); values without evidence are dropped and listed. References are linked only on a certain match; an unknown name is asked about. New entity types become draft types (approved later, ADR-0021).

  • Evaluation on fictional, labelled notes (bench/ingest_eval.py). Unseen (3 files, 25 chunks, written by someone who did not see the code, run once): classification 25/25 for both models, field precision/recall 24/24 (M-haiku) and 23/23, 23/24 (M-opus), values not in the source 0, wrong merges 0, siblings 1/1, conflicts 1/1; new-type kind 1/2 for both (a workout log read as a series) (report).

Benchmark (v3, latest)

Measured on the frozen version 2c56c658bc5f10f9 (v1.1). v1.2 (names of referenced records in answers, unknown dates and event-time precision — ADR-0013, a benchmark formatter fix) came from using Mnemento on real records afterwards and is not re-measured; v1.3 (store first, organize later — ADR-0014) was only checked with a regression run on dev questions. Later changes (task 0008) are checked the same way. In its step 2 the benchmark converter for Mnemento's answers was fixed — an average question answered with extra count measures was recorded as the record count (it cost M-haiku two of the D5 misses at 1,000 records below). The fix changes the frozen hash; the scores below stay as measured. Same fictional job-search history rendered three ways (ADR-0011), the same answer-format hint for every system, evaluation set v3 (own seed; reference date Monday 2027-02-01 so weeks and month ends matter), 28 dev questions + 9 unseen questions written by the directing agent before measuring, 3 repetitions, prompts and rules frozen (hash in the report). Claude Code memory = MEMORY.md index + one markdown file per record, searched with Grep/Read.

records

system

dev ¹

unseen

time / question, mean (p95)

input tokens / question

API-equivalent cost / question

100

Mnemento · opus

84/84

27/27

5.9 s (9 s)

6.1k

$0.025

100

Claude Code memory · opus

77/84

27/27

14.8 s (24 s)

36.9k

$0.058

100

Mnemento · haiku

76/84

24/27

26.2 s (75 s)

5.1k

$0.025

100

Claude Code memory · haiku

63/84

19/27

42.4 s (97 s)

61.0k

$0.050

1,000

Mnemento · opus

79/84

24/27

5.8 s (10 s)

6.2k

$0.025

1,000

Claude Code memory · opus

74/84

24/27

35.6 s (127 s)

123.3k

$0.205

1,000

Mnemento · haiku

72/84

24/27

24.8 s (67 s)

5.2k

$0.024

1,000

Claude Code memory · haiku

39/84

10/27

77.0 s (224 s)

454.0k

$0.149

¹ The dev questions were used while fixing Mnemento (v1, v1.1); an advantage on them may be overfitting. Compare on the unseen column.

  • Accuracy on unseen questions: on par with Claude Code's own memory on opus (100 records 27/27 = 27/27; 1,000 records 24/27 = 24/27). On haiku, Mnemento is clearly ahead (24 vs 19 of 27 at 100 records, 24 vs 10 at 1,000), and Claude Code memory on haiku falls apart as records grow (unseen 70% → 37%).

  • Cost and speed at 1,000 records: 6× faster, 1/20 of the input tokens, 1/8 of the cost. Claude Code memory got slower and more expensive as records grew (2.4× the time, 3.6× the cost from 100 to 1,000); Mnemento stayed flat.

  • Every question, every system, with a one-line reason for each miss: results by question · raw data: report · results.

Limitations (current)

  • Elapsed-time filters are not expressible ("viewed more than three days after applying", W5): QuerySpec v1.1 can average the time between two events but cannot filter on it. Haiku said so (clarify); opus found a workaround that happened to hit an accepted reading at 100 records and failed at 1,000.

  • Vague criteria ("quickly viewed", D5) make the haiku interpreter ask back instead of choosing a default.

  • Both are addressed after this measurement (ADR-0018: an elapsed condition, and default readings for vague words shown as warnings). W5 prompted the change, so its score here cannot show the gain; that needs new unseen questions, which have not been measured yet.

  • A harness formatter bug cost Mnemento D3 at 1,000 records: it answered "187 records, showing 50", the benchmark recorded 50 because the plan carried limit: 50 from the format hint. Scored as measured.

  • One fictional domain and generator, 37 questions, 3 repetitions, a format hint that tells every system the expected answer shape; the format hint is not how people ask. API-equivalent cost = list price reported by the CLI (the runs used a Claude subscription, not an API key).

Benchmark history — v2 (haiku vs opus at 100 records)

After v1 (history-aware reached filter, having, ordering by event time), a fairer harness: the same answer-format hint for every system, a new evaluation set (seed and date different from v0; company aliases unique, ~30% of records written under the alias), 20 dev questions + 9 unseen questions written by the directing agent, 3 repetitions, 100 records. Claude Code memory (B1) and Mnemento (M) each on haiku (claude-haiku-4-5) and opus (claude-opus-5-5):

system

dev (20 × 3)

unseen (9 × 3)

total

time / question, mean (p95)

input tokens / question

API-equivalent cost / question

M-haiku

45/60

19/27

64/87 (74%)

22.3 s (63 s)

4.7k

$0.022

M-opus

53/60

22/27

75/87 (86%)

6.2 s (11.5 s)

5.7k

$0.024

B1-haiku

36/60

18/27

54/87 (62%)

44.8 s (124 s)

59.3k

$0.051

B1-opus

56/60

27/27

83/87 (95%)

15.9 s (28 s)

34.6k

$0.059

  • With opus, Claude Code's own memory was more accurate at 100 records: B1-opus 83/87 > M-opus 75/87. Mnemento answered 2.6× faster with 1/6 of the input tokens and ~40% of the cost.

  • With haiku, Mnemento was ahead (64 vs 54 of 87) at half the cost.

  • Mnemento's gap is concentrated in five questions where its interpreter fails the same way on both models (event kinds without Korean labels, schema selection missing the referencing record type, ordering by a date field instead of event time, "last week of a month", and a formatter bug for event counts) — being fixed in task 0005 and re-measured on a fresh evaluation set (v3) with new unseen questions.

Raw data: report · results. API-equivalent cost = list price reported by the CLI; the runs used a Claude subscription, not an API key.

Benchmark history — v0 (first, reduced, haiku only)

Does a structured record book beat an LLM's own memory? Same fictional job-search history, same questions, same model (haiku via the Claude Code CLI), three ways of remembering:

  • B0 full context — every memory file in the prompt

  • B1 Claude Code memory — MEMORY.md index (first 200 lines loaded) + one markdown file per record, searched with Grep/Read

  • M Mnemento — interpret → QuerySpec → SQL → template answer

Accuracy, time and input tokens per question by number of records

records

system

correct (12 questions × 3)

time / question, mean (p95)

input tokens / question

list cost / question

100

B0 full context

23/36 (64%)

35.7 s (106 s)

24.9k

$0.029

100

B1 Claude Code memory

28/36 (78%)

55.2 s (151 s)

84.5k

$0.063

100

M Mnemento

23/36 (64%)

15.2 s (42 s)

3.7k

$0.016

1,000

B0

not measurable — prompt exceeds the 200k context

1,000

B1 Claude Code memory

18/36 (50%)

82.8 s (224 s)

510k

$0.159

1,000

M Mnemento

24/36 (67%)

16.3 s (41 s)

4.0k

$0.017

10,000

B1 Claude Code memory ¹

5/12 (42%)

83.0 s (195 s)

342k

$0.127

10,000

M Mnemento

22/36 (61%)

14.9 s (39 s)

3.9k

$0.016

¹ one repetition only (approved separately for cost). Times include ≈2 s of CLI process start per LLM call for every system; input tokens include the CLI's fixed ≈2.5k (≈6.7k with tools) per call. Values without that overhead, the dev/unseen split and every raw answer: report · notes & limitations · method: ADR-0008, ADR-0011.

Reading it honestly

  • Small data: Claude Code's own memory was more accurate (28 vs 23 of 36 at 100 records). Mnemento was 3.6× faster and used ~1/23 of the input tokens.

  • As records grow, Mnemento's time and tokens stay flat (≈15 s, ≈4k tokens at 100, 1,000 and 10,000) while file search gets slower, more expensive and less accurate (B1: 78% → 50% → 42%).

  • Mnemento's misses are mostly fixable gaps, tracked for v1 (task 0004): it reads "was viewed" as the current status (H2), and QuerySpec v0 cannot filter on group size or sort by event time (H3, H4).

Limitations of this measurement

  • Reduced: 12 of 20 questions (first four of each set: dev D1–D4, unseen U1–U4, independently written H1–H4), 3 repetitions, narration variant only at 100 records; one model (haiku).

  • Harness bias on D4: B0/B1 get an answer-format hint ("value = share viewed"); Mnemento gets only the question and read it as time to view. All three systems missed D4.

  • Generator flaw at 10,000 records: English aliases are drawn from a small pool, so several companies share one (U2). Mnemento asked back instead of guessing (graded wrong); B1 merged them (also wrong).

  • Answer keys come from the generator's ground truth, never from Mnemento; prompts and rules were frozen (hash in the report) before measuring and not changed during it.

Install as an MCP server (Claude Code)

Requires uv and Python 3.11+.

git clone https://github.com/reddol18/mnemento.git
claude mcp add mnemento -e MNEMENTO_DB="$HOME/.mnemento/mnemento.db" -- uv --directory /path/to/mnemento run mnemento-mcp

Tools: query, record, record_series, revert_series_batch, get_entity, history, list_schemas, propose_schema, apply_schema_proposal, query_log, purge_query_log. Natural-language interpretation uses the Claude Code CLI you are already logged into (MNEMENTO_LLM=claude-cli, default model sonnet; set MNEMENTO_MODEL to change, e.g. haiku). With MNEMENTO_LLM=none only the fast path and structured QuerySpecs are answered.

For Claude Desktop, add the same command to claude_desktop_config.json:

{ "mcpServers": { "mnemento": {
    "command": "uv", "args": ["--directory", "/path/to/mnemento", "run", "mnemento-mcp"],
    "env": { "MNEMENTO_DB": "/path/to/mnemento.db" } } } }

Development

uv sync
uv run pytest                              # unit tests (no network, LLM is a scripted fake)
uv run python examples/demo_queries.py     # real LLM calls via the claude CLI, writes docs/demo/

Agents use the MCP server from a separate runtime worktree that is updated only when an issue is finished, so development never breaks their connection (docs/RUNTIME.md).

Layout: src/mnemento/ — storage/ (SQLite, WAL, append-only events), schema/ (dictionary + JSON Schema validation), ledger.py (record/replay API), keeper/ (query pipeline, record tool, identity resolution, schema proposals, LLM adapters), mcp_server.py. Domain schemas live in schemas/. All example data is fictional.


한국어

LLM 에이전트는 세션이 끝나면 잊습니다. Mnemento는 여러 에이전트가 사실을 한 장부에 기록하고, 자연어로 물으면 정확한 조건 질의로, 근거와 함께 답하게 해 주는 기록 관리 에이전트입니다.

  • 질문 처리: ① 해석(정형 질문은 LLM 없이, 나머지는 LLM이 스키마 사전만 보고 QuerySpec 작성) → ② 질의(코드가 SQL 생성, LLM은 SQL을 쓰지 않음) → ③ 답변(근거 id 목록 + 표본 수·미완료 기간·결과 미확정 경고).

  • 기록: 형식이 맞는 입력은 코드로 바로 검증·저장, 자유 서술은 LLM이 구조화한 뒤 같은 검증을 거칩니다. 대상이 여럿이거나 모르는 회사면 저장하지 않고 되묻습니다. 새 종류의 기록은 에이전트가 제안 스키마(schema_draft)와 함께 record하면 draft 종류로 바로 저장·조회되고, 사용자 승인(apply_schema_proposal) 뒤에 필수·enum·색인이 적용됩니다. 기존 종류와 이름이 비슷하면 먼저 묻습니다(ADR-0021). 사용자가 정리한 종류에는 저장소의 사전 개선이 자동으로 들어가지 않고, 항목별로 승인받는 제안으로 옵니다(ADR-0023).

  • 동일 회사 판정: 사업자번호 → 정규화 이름·별칭. 비슷한 이름은 후보로만 제시하고 자동 병합하지 않습니다.

  • 계측: 질문마다 단계별 시간, LLM 호출 수, 입력·출력 토큰, 비용을 기록합니다.

  • 조회 로그: 모든 질문을 로컬 DB에 남깁니다(질문, 해석, QuerySpec, SQL과 파라미터, 결과 수, 근거 id, 경고, 시간, 비용). query_log 도구로 최근 질문, 경고·오류가 붙은 질문, 같은 질문인데 SQL이 달라진 것(diverging), 0건으로 끝난 답(empty)을 볼 수 있습니다. 기록에 없는 id로 거르는 해석은 0건으로 답하지 않고 원본 key·이름으로 읽거나 되묻고, 0건으로 끝난 해석은 캐시하지 않습니다(ADR-0020). 최대 10,000행 또는 50MB, MNEMENTO_QUERY_LOG=off로 끕니다(ADR-0015).

  • 두 번째 도메인(투자 기록, 가상 예시): 질의 코드는 그대로 두고 스키마만 더해 투자 기록(종목·체결·판단)을 담습니다(examples/investment). 질문에 종목 코드·사업자번호 같은 식별자가 있으면 그것으로 먼저 찾습니다(ADR-0019). 스키마 간 관계 설명으로 같은 사건을 두 번 세지 않습니다. mnemento.importer로 다른 곳에 두는 파일을 여러 번 가져와도 안전합니다(바뀐 사실은 정정, 바뀐 상태는 원본 날짜의 갱신, 사라진 기록은 보고만). 작성자의 실제 투자 기록으로 로컬에서 쓰고 있으며, 그 데이터는 저장소에 없습니다.

  • 벤치마크(v3, 최신): opus 기준으로 개발에 쓰지 않은 문항의 정확도는 Claude Code 자체 메모리와 같다(100건 27/27 동률, 1,000건 24/27 동률). 1,000건에서는 6배 빠르고 비용은 1/8이다. 개발 문항에서 앞선 부분은 그 문항으로 고쳤기 때문에 과적합일 수 있다. 남은 약점은 경과 시간 조건 필터를 표현할 수 없다는 점과 모호한 기준이다(측정 이후 ADR-0018로 보완. W5가 계기였으므로 개선 효과는 새 미노출 문항으로 측정해야 하며, 아직 측정하지 않았다). 문항별 결과는 여기.

  • 이력 — 벤치마크(v2, 100건, 같은 형식 힌트, haiku·opus 2×2): opus 기준으로는 Claude Code 자체 메모리가 더 정확했다(B1-opus 83/87 > M-opus 75/87). Mnemento는 2.6배 빠르고 입력 토큰은 1/6이었다. haiku 기준으로는 Mnemento가 앞섰다(64 대 54/87). Mnemento의 약점 5가지는 작업 0005에서 고친 뒤 새 평가셋(v3)으로 다시 잰다.

  • 이력 — 벤치마크(v0, 축소 측정: 12/20문항·3회 반복·haiku 한 모델):

    • 기록 100건에서는 Claude Code 자체 메모리(B1)가 더 정확했다(28 대 23/36). 대신 Mnemento가 3.6배 빠르고 입력 토큰은 약 1/23이었다.

    • 기록이 1,000·10,000건으로 늘어도 Mnemento는 문항당 약 15초·약 4천 토큰으로 일정했다. 반면 파일 검색 방식은 느려지고 비싸지면서 정확도도 떨어졌다(78% → 50% → 42%, 10,000건은 1회 측정).

    • 한계: D4는 형식 힌트가 B0·B1에만 주어진 하네스 편향이 있다. 10,000건에서는 생성기 별칭이 중복된다. 자세한 내용은 위 표와 결과 노트에 있다.

설치(Claude Code): 위 claude mcp add ... 한 줄. 개발: uv sync && uv run pytest. 현재 상태: v1.3(벤치마크 3회는 v1.1 기준). v1.2는 실제 기록으로 써 보며 나온 개선(답에 회사명 표시, 날짜·시각 정밀도)이고, v1.3은 "먼저 저장하고 나중에 정리"(스키마에 없는 필드·값도 저장하고 바로 조회, 정리는 사용자 승인)이다. v1.3은 회귀 측정만 했다. 이후 변경(작업 0008)도 회귀로만 확인하며, 그 ② 단계에서 벤치 답 변환기의 결함(평균을 묻는 질문에 개수 측정값이 함께 있으면 레코드 수를 기록)을 고쳤다. 이 결함 때문에 위 v3 표의 1,000건 M-haiku D5 오답 중 2건이 생겼다. frozen hash는 바뀌었고, 표의 점수는 측정한 그대로 둔다. 기획서는 docs/PLAN.md, 설계 결정은 docs/adr/.

License: MIT

Available Tools

14 tools
apply_schema_proposalA

Organize unregistered (draft) fields and values, register a new record type (proposal id type_...), or take the repository's dictionary improvements into an organized type (dict_...) — ONLY with the user's explicit consent.

    Never call this on your own initiative. First show the user the proposal from propose_schema (its
    `question`), ask whether to apply it, and call this only after they answer yes. Pass who approved
    (approved_by) and the user's own words (user_answer); both are kept in the schema history.
    descriptions: {field: description} for every field being registered (required).
    labels: {field: {value: [natural-language names]}} for every new enum value (required, e.g.
    {"platform": {"remember": ["리멤버"]}}).
    merges: {draft_field: target_field} to fold look-alike names together (values move with history kept).
    index: fields to index. type_description: a new type's description, if its proposal has none.
    items (dict_... only): the item keys the user picked; default: the proposal's additive items. Items left out
    are not offered again for the same file.
    The data itself was already stored when it was written; this only organizes it.
    

Inputs: approved_by (required), proposal_id (required), user_answer (required), index, +5 more. Returns: object

get_entityC

Current state of one record (after corrections/retractions).

Inputs: entity_id (required). Returns: object

historyB

Every event of one record, including corrections and retractions, in recording order.

Inputs: entity_id (required). Returns: object

ingest_applyB

Write exactly one preview as one ingest batch — ONLY after the user agreed to that preview. New entity types become draft types (approve them later with apply_schema_proposal). Returns batch_id.

Inputs: preview_id (required), approved_by (required), user_answer (required). Returns: object

ingest_previewA

Turn notes that mix facts with rules and prose (e.g. a memory folder) into records — PREVIEW ONLY, nothing is written. Give text, or path (a file, or a folder of .md/.txt files; exclude skips file names). The preview lists, per chunk, whether it holds data; the records found (each value checked against a quote from the text — values without evidence are dropped and listed); facts repeated in several places merged; conflicting repeats; facts already recorded; new record types with a suggested schema; and the chunks kept only as notes. Show the user summary, the ambiguous list and the conflicts, then ingest_apply with their answer. Costs LLM calls (about one per 15 chunks per stage).

Inputs: path, text, source, exclude. Returns: object

ingest_revertA

Undo one ingest batch: its records are retracted (history kept) and its series points reverted. Only when the user asks.

Inputs: batch_id (required). Returns: object

list_schemasB

Record types with their fields, descriptions and allowed values, plus drafts (unregistered fields and values already in use) and the history of organize approvals.

Inputs: name. Returns: object

propose_schemaA

Schema proposals (never applied automatically).

    Without samples: how to organize drafts — fields and enum values that records already use but the
    dictionary has not registered (they are stored and queryable; organizing adds descriptions, labels,
    indexes, and merges look-alike names). Ask the user before apply_schema_proposal.
    With samples and a new entity_type: a first schema draft for a new kind of record.
    

Inputs: samples, entity_type. Returns: object

purge_query_logA

Delete query log entries asked before before (ISO 8601), or all of them when omitted. Only when the user asks for it. Recorded facts are not touched.

Inputs: before. Returns: object

queryC

Answer a question from the record book with evidence.

    question: natural-language question (e.g. "10/2 사람인 지원 몇 곳?").
    spec: optional structured QuerySpec; when given, no LLM is used.
    narrate: also write a prose answer with the LLM (it only sees aggregates).
    by: your agent name (optional; kept in the local query log).
    

Inputs: question (required), by, spec, narrate. Returns: object

query_logA

Look back at earlier questions: interpretation, QuerySpec, SQL and parameters, result count, evidence ids, warnings, stage times, tokens and cost.

    view: recent (last n) | warnings (answers that carried warnings) | errors |
          diverging (the same question pattern answered with different SQL — the interpretation moved) |
          path (only questions answered by `path`: fast | cache | llm | structured | entity | none) |
          empty (answers that found nothing — check whether the reading was wrong; with `path`, e.g.
          cache, only those).
    since: only entries asked at or after this time (ISO 8601).
    

Inputs: n, path, view, since. Returns: object

recordA

Record a fact. Either structured or free text.

    Structured: entity_type + kind (created | updated | status_changed | corrected | retracted)
    + payload, and the target by entity_id or match (field values, e.g. {"platform": "saramin",
    "posting_id": "10000001"}). Reference fields may hold a name (e.g. a company name).
    Free text: text="사람인 10000001 가상테크 열람" — the Keeper structures it.
    at: when it happened (ISO 8601 with offset); defaults to now. at_precision: time | date (only the
    day is known) | unknown. by: your agent name.
    evidence: why you believe it (quote, mail subject...).
    Fields or values the dictionary does not know are stored as given (drafts, queryable at once); the
    reply lists them under `drafts` and may ask under `questions` whether a new name means an existing one.
    New kind of record (entity_type not in list_schemas, kind created): it is stored at once under a new draft
    type. Send schema_draft with it — the schema you suggest: {"description", "keywords", "fields": {name:
    {"type", "description", "enum", "labels", "required", "indexed", "format"}}}; you know best what each field
    means now. The reply carries a proposal under `proposals`: ask the user, and on their yes call
    apply_schema_proposal. If the name looks like an existing type you are asked first (new_type=true confirms).
    Returns status:
      recorded — stored (check `drafts` and `questions`);
      clarify  — nothing stored: the target is ambiguous (several matches), a referenced name (e.g. a
                 company) is unknown or only similar, the record looks like a duplicate, or it contradicts
                 the current state; ask the user and call again;
      rejected — nothing stored: a registered field is violated (required field missing, wrong type, bad
                 date) or the target was looked up by an unregistered field;
      error    — free text could not be structured (no LLM configured or the call failed).
    

Inputs: by (required), at, kind, text, +8 more. Returns: object

record_seriesA

Record measurements over time for a series type (list_schemas shows kind "series", its key fields, time field and measures), e.g. [{"person": "A", "measured_on": "2026-07-01", "kg": 70.2}].

    One call = one batch, all or nothing: if any point is invalid nothing is stored and `rejected` says why.
    A point whose key and time are already stored is updated (the batch keeps the previous values).
    source: where the values come from (file, app, "user said"). by: your agent name.
    Returns batch_id and counts (inserted / updated / unchanged); revert_series_batch(batch_id) undoes it.
    

Inputs: by (required), points (required), source (required), entity_type (required). Returns: object

revert_series_batchA

Undo a whole series batch: its new points are removed, updated points get their previous values. Refused when a later batch changed the same points (revert that one first). Only when the user asks.

Inputs: batch_id (required). Returns: object

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

    TDQS

    A3.7/5.0

    Scored across 14 tools

    Disambiguation5/5

    Each tool has a clearly distinct role: recording facts vs. series, querying vs. reading query logs, preview/apply/revert for ingest, and proposal vs. application for schemas. Overlaps are explicitly resolved in descriptions, so an agent can reliably select the intended tool.

    Naming Consistency4/5

    Almost all names use predictable snake_case with verb_noun or verb_noun_noun patterns (record_series, ingest_apply, apply_schema_proposal). A few single-word tools like query, record, and history deviate slightly but remain clear and consistent in style.

    Tool Count5/5

    14 tools are well within the sweet spot for a domain that spans recording, querying, ingestion, history, and schema management. Each tool earns its place with a specific operation, and there is no redundant surface area.

    Completeness4/5

    The surface covers record lifecycle through explicit kinds (created, updated, corrected, retracted), series batches with revert, ingest lifecycle, querying, history, and schema proposals. Minor gaps exist, such as no dedicated list-entities operation, but query and get_entity provide workarounds.

    Maintenance

    ActivityMaintained
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      C
      maintenance
      Enables Claude Code and other MCP-capable agents to turn conversations into structured long-term memories, deciding whether each extracted fact is new, an update, or already known while preserving superseded history. It also lets agents recall the right facts through hybrid keyword and entity-graph search, list what is stored, and permanently forget entries.
      5
      MIT
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables MCP clients such as Claude Desktop, Cursor and Claude Code to add, search, answer from, and audit an agent's long-term memory stored in a single local SQLite file. Memories are backed by verifiable quotes, ranked by task outcomes, access-controlled, and usable fully offline.
      Apache 2.0
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables AI agents and LLMs to persist facts, preferences, and observations in a local SQLite store and to build and traverse subject-predicate-object knowledge graphs across sessions. It provides fuzzy keyword search and graph queries so assistants retain context and recall prior project history without repeating prompts.
      1
      MIT