Mnemento
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Mnemento How many companies did I apply to in October, and can you show the records?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Mnemento
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
clarifywith options.A new kind of record over MCP:
recordit with a newentity_typeand the schema you suggest (schema_draft); it is stored and queryable at once under a draft type, and the user approves the type later withapply_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_logtool 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=offturns 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 + evidenceIdentifiers 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.importermakes repeated imports safe — the same source key is the same record, a changed fact becomes acorrectedevent, a changed state (e.g. buy criteria) anupdatedevent 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 daysPeriod,
group_bykey 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):
valuescomputes 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 atsecurity); 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 schemaEvery 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
elapsedcondition, 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: 50from 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.mdindex (first 200 lines loaded) + one markdown file per record, searched with Grep/ReadM Mnemento — interpret → QuerySpec → SQL → template answer
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-mcpTools: 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 toolsapply_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
Scored across 14 tools
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.
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.
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.
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
Related MCP Connectors
Database for your AI agent. Turn its output into data, docs, skills, and apps you can actually use.
Store, query, and update structured data from any AI agent
Private memory for AI agents: save and recall facts across Claude Code, Codex, Cursor and ChatGPT.
Shared memory for your AI agents. Every fact shows its source, and stale facts get replaced.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceProvides Claude Desktop with persistent, structured memory and semantic search via a local SQLite knowledge graph, plus a web UI for visualization and management.MIT
- AlicenseAqualityCmaintenanceEnables 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.5MIT
- AlicenseNot gradedqualityBmaintenanceEnables 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
- AlicenseNot gradedqualityBmaintenanceEnables 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.1MIT