lifelog
Uses Google Cloud Speech-to-Text (Gemini/Chirp), Vertex AI embeddings, and LLM services to transcribe recordings, embed transcript windows, and extract temporal knowledge-graph facts.
Uses a Hugging Face token to access pyannote speaker diarization models, enabling speaker-labeled transcripts.
Supports OpenAI speech-to-text as an alternative ASR backend for transcribing recorded audio.
Ingests Zalo chat messages and contacts through a listen-only Zalo sensor and Zalo PC backup importer, making them searchable in the lifelog.
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., "@lifelogsummarize what I did today and who I talked to"
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.
kim-rag: a personal lifelog you can ask questions
kim-rag records your day and turns it into memory an AI agent can search. An iPhone app records conversations all day, a Mac app records online meetings and what you do on the Mac, a Chrome extension keeps the pages you actually read, and a Zalo sensor keeps your chats. A server on your own Mac turns all of it into transcripts with speaker names, a searchable verbatim index, a temporal knowledge graph and daily and weekly journals. Agents such as Claude Code reach it through an MCP server.
It is built for one person, on one Mac, with Vietnamese as the main spoken language (the language is a setting). The design notes are in docs/idea.md.
Privacy. Everything you record stays on your machine: audio, transcripts, journals, the databases and the logs live in
data/and in two Docker volumes, all ignored by git. Text is sent only to the speech-to-text, embedding and LLM services you configure (Google Cloud and/or OpenAI) to be processed. Recording other people may require their consent where you live; check your local law before you turn this on.
Architecture
iPhone app ──┐ audio chunks (POST /chunks)
Mac app ─────┤ meetings, room audio, app/window/clipboard/terminal events, calendar, Wi-Fi, sound
Chrome ext. ─┤ pages read, selections (POST /events)
Zalo sensor ─┤ chat messages, contacts (POST /events)
Claude Code ─┘ session files read from ~/.claude/projects
│
▼
FastAPI server (`lifelog serve`, 127.0.0.1:8000)
│ raw files in data/raw, one row per chunk/event in Postgres
▼
Worker (`lifelog worker`, launchd every 5 min)
VAD (Silero) → episodes → diarization (pyannote, local) → speech-to-text (Gemini / OpenAI / Chirp)
→ speaker identity → verbatim index (pgvector) → Graphiti facts (FalkorDB) → day/week journals
│
▼
MCP server (`lifelog mcp`): search, summary, people, timelinePostgres is the source of truth; the vector index and the graph can always be rebuilt from it
(lifelog reprocess). Each stage is recorded per episode, so a failure is retried from where it stopped.
Related MCP server: loci-mcp
Components
Folder | What | Language |
Server, worker, CLI and MCP server ( | Python 3.12, uv | |
iPhone recorder: all-day recording, background upload, Live Activity | Swift, SwiftUI | |
Mac menu bar app: activity, calendar, meetings, room mic, main window | Swift (SwiftPM) | |
Chrome extension: pages read, selections | JavaScript (MV3) | |
Zalo sensor (listen only) and Zalo PC backup importer | Node.js 18+ | |
LaunchAgent templates and the iPhone app renewal script | plist, zsh | |
Python test suite | pytest |
Requirements
macOS on Apple silicon (the Mac app needs macOS 14.4+ for meeting capture).
Docker for Postgres (pgvector) and FalkorDB.
uv and
ffmpeg(brew install ffmpeg).At least one speech-to-text service: a Google Cloud project (Gemini / Chirp, also used for embeddings and the graph) or an OpenAI API key.
A Hugging Face token for pyannote speaker diarization (optional; without it every turn is
SPEAKER_01).Optional: Xcode for the iPhone app, Node.js 18+ for the Zalo sensor, Chrome for the extension.
Setup
docker compose up -d
uv sync
cp .env.example .envOpen .env and fill in at least:
HF_TOKENfor speaker diarization. Create a read token at huggingface.co/settings/tokens and accept the terms on the model pagepyannote/speaker-diarization-community-1.GOOGLE_CLOUD_PROJECTforgemini-3.5-transcribe,chirp_3, embeddings and the graph. Enable Agent Platform (Vertex AI) and Cloud Speech-to-Text in that project, then rungcloud auth application-default login.OPENAI_API_KEYif you wantgpt-transcribe.API_TOKENbefore the server is reachable from anything other than this Mac (the iPhone app).
Check that everything is in place:
uv run lifelog doctorConfiguration
All server settings are environment variables, read from .env; .env.example documents each
one. The clients keep their own settings:
Client | Settings |
Mac app |
|
Zalo sensor |
|
Chrome extension | The extension's options page (server, token, blocked domains) |
iPhone app | Settings screen in the app; the default server and your Apple team come from |
Daily use
Transcribe one file and wait for the result:
uv run lifelog transcribe ~/Desktop/meeting.m4aOr run the worker and drop files into data/inbox/; finished files move to data/inbox/processed/.
uv run lifelog workerResults are in data/episodes/DAY/TIME_ID.md; the original audio in data/raw/YYYY/MM/DD/.
Command | What it does |
| How many items are at each stage, which are stuck and why |
| Print the transcript of an episode |
| Run the same turns through all three speech-to-text services and write a comparison |
| Switch service and redo only speech-to-text |
| Redo diarization |
| Retry stuck items, e.g. after adding a key to |
| Search both memory stores; prints exactly what the agent gets from |
| Rebuild the whole graph from Postgres |
| Re-embed, e.g. after changing |
| MCP server for agents, over stdio |
| Rewrite the work journal of a day (today when omitted) |
| Print what the agent gets from the |
| Print what the agent gets from the |
| Print what the agent gets from the |
| HTTP server receiving |
Choosing a speech-to-text service
Set ASR_BACKEND in .env to gpt-transcribe, gemini-3.5-transcribe or chirp_3. To choose, run a few real
files and compare:
uv run lifelog compare --since 2026-10-13ASR_KEYWORDS takes names and terms that come up often and helps gpt-transcribe and Gemini hear them right.
GEMINI_MODEL is the model id sent to Google; a new project may only get gemini-3.5-transcribe-preview. If Gemini
answers 403 or 404, try that value. Without HF_TOKEN, set DIARIZER=none to start right away; once you have a
token, set DIARIZER=pyannote and run lifelog reprocess --from diarize --all.
Memory
After speech-to-text, each episode goes into two stores:
Verbatim, in Postgres. The transcript is cut into windows of a few consecutive turns, each embedded with
gemini-embedding-2(1,536 dimensions) and stored in thewindowstable with pgvector. This answers "who said exactly what, and when".Facts, in Graphiti on FalkorDB.
gemini-3.8-flashreads the transcript and extracts people, places, tasks and facts with validity periods. Thegraph_partstable records each part ingested, the tokens spent, and the way back from a fact to the exact turns in Postgres.
The graph can be rebuilt: delete the FalkorDB volume and run lifelog reprocess --from ingest --all. Browse it at
http://127.0.0.1:3001 (graph lifelog). Unnamed speakers appear as e.g. Người nói 1 (bản ghi ab12cd34)
("speaker 1 (recording ab12cd34)"): a SPEAKER_01 label only means something inside one episode, so the episode id
keeps Graphiti from merging two different people. Set GRAPH=none to turn Graphiti off; episodes then stop at
indexed.
Connecting Claude Code
.mcp.json in this folder declares the lifelog server, so Claude Code opened here offers to enable it. To use
it from any folder:
claude mcp add --scope user lifelog -- uv run --directory /path/to/kim-rag lifelog mcpTool | Arguments | Returns |
|
| The closest verbatim passages and Graphiti facts, each with time, episode id and turn numbers |
| optional | The day journal, or the week summary, of that date. Defaults to yesterday |
|
| Everything known about one person: recordings, who else was there, calendar, chats, facts |
|
| Facts and passages about a topic, oldest first, including facts later replaced |
search is semantic, not exact-keyword. Passages more than 0.15 below the best one are dropped and overlapping
passages of one episode are merged. since and until filter by when something was said. If one store cannot be
reached, the other still answers, with a note. The tools are declared in
src/lifelog/mcp_server.py; the work is in src/lifelog/search.py.
Reconnect the server in Claude Code after changing the code.
Work journal (Claude Code sessions)
Claude Code writes every session as JSONL in ~/.claude/projects/. The worker reads those files; no hook or extra
process is needed. List the project folder names it may read in .env:
CLAUDE_CODE_PROJECTS=my-app,my-other-appEmpty means nothing is read; a project not in the list is never opened. From each session only three things are
kept: what you typed, what Claude answered, and what Claude did (files edited, commands run, by description). Tool
results are not kept: they are long, noisy and may contain keys or .env contents. Strings that look like keys or
passwords are masked before storage.
Turns of one session less than an hour apart form one code episode; an open session is grouped after an hour of
silence. Gemini Flash writes a summary of each episode; the full dialogue and the summary go into the verbatim
store, and only the summary goes into Graphiti. The journal of a day (data/journal/DAY.md) is written once the
day is over and all its sessions are summarized, and rewritten if more sessions of that day arrive. Session
content of the selected projects is sent to Google (your Cloud project) to be summarized, embedded and turned into
facts.
Conversations, weeks, people and timelines
The day journal also has a conversations section: one heading per recorded conversation or online meeting (time, where it was recorded, who spoke, episode id) and a few sentences written by Gemini Flash. A recording under 300 characters is quoted instead. Naming a voice clears the summary, so the conversation and its day are written again with the name.
When a week (Monday to Sunday) is over and all its days are written, the worker writes
data/journal/week-MONDAY.md from the day journals: main work, results, people met, open items, then each day's
overview. The summary tool returns the saved journal when it is up to date and writes it on the spot otherwise;
today and the current week are written from the data so far and not saved.
people finds a named voice by exact name or by whole words ("Tuấn" finds "anh Tuấn", not "Tuấnh") and adds
Graphiti's own summary of the person and the newest facts about them. timeline asks Graphiti for 60 facts, keeps
those within 0.15 of the closest, then asks Gemini Flash which facts and passages are really about the topic; if
the LLM cannot be reached, facts within 0.1 are kept and the answer says so.
Other sources can send events with POST /events, an array of {uid, source, type, ts, payload}. Sending the same
uid again does not create a duplicate.
Mac app
KimRag Mac (mac/, Swift) runs in the background from login, with a menu bar icon. It records:
the frontmost app and its window title (needs Accessibility permission),
when you are away: no mouse or keyboard for 5 minutes, screen locked, Mac asleep,
text you copy,
terminal commands, through the zsh hook mac/kim-rag.zsh (commands starting with a space are not recorded),
calendar events, the Wi-Fi network name, sound the Mac plays out loud, and online meetings (below).
It never records keystrokes, never takes screenshots and never reads message contents. All filters run on the Mac
before anything is sent: ignored apps (password managers) record nothing, titles matching hidden patterns (private
tabs, banking) are dropped, copies from password managers are dropped, and keys, passwords and card numbers are
masked. ⌃⌥⌘P pauses and resumes everything. Settings are in ~/Library/Application Support/kim-rag/mac.json.
Events wait in a local queue and are sent in batches to POST /events, so nothing is lost while the server is
down. The worker groups raw events into time spans (mac_spans): same app and same context (project in VS Code,
site in Chrome, connection in Royal TSX) are merged, glances under 30 seconds are dropped, time away is not counted.
App rules are the RULES table in src/lifelog/mac_log.py. After changing them:
uv run lifelog mac --rebuild
uv run lifelog mac 2026-10-14 # one day, today by defaultCommits of the selected projects are read with git log from GIT_SINCE. The day journal gets a Mac activity
section: time per app and context, commits, notable commands (ssh, docker, deploy, failed commands), computed by
code; the LLM only reads it to write the overview.
Install, or reinstall after changing the Swift code:
mac/install.sh
echo 'source "$HOME/Library/Application Support/kim-rag/kim-rag.zsh"' >> ~/.zshrcOn first start macOS asks for Accessibility (System Settings › Privacy & Security › Accessibility). install.sh
signs with your Apple Development certificate when there is one (override with KIMRAG_SIGN_IDENTITY) so
permissions survive rebuilds; an ad-hoc signature resets them on every build.
Main window
Click the Dock icon, the open button in the menu bar panel, or ⌘0. Closing the window keeps the app recording; only
Quit stops it. It reads the server's /app/* routes (src/lifelog/views.py) with the token
from mac.json, so the API server must be running. Sections: Today (counts, time per app, iPhone recording,
calendar, day journal or week summary), Conversations (every episode of the day, turn by turn, with playback), Zalo,
Search (or the timeline of a topic), People (name voices, read a person's page) and System (lifelog doctor, the
queue, failures). The icon is drawn by mac/icon/make-icon.swift.
Calendar
The Mac app reads the macOS calendar (every 15 minutes and on change), the last 3 days and the next 7: title, time,
location, calendar name and attendees; notes and meeting links are never read. ignoreTitles and
ignoreCalendars (birthdays and holidays by default) filter it; "calendar": false turns it off. Attendees of a
meeting during a recording are offered as names on the speakers page and given to the LLM as a hint. Put your own
addresses in MY_EMAILS so invites sent to another of your addresses still count as you.
Sound the Mac plays
A video playing out loud on the Mac reaches the iPhone microphone. The Mac app checks Core Audio every 2 seconds and
sends a sound event naming the apps playing on a device the room can hear (not muted, not at zero volume, not
headphones; apps using the mic are left out, since a call is a meeting). The server then leaves out the iPhone
speech during that sound and keeps the original in the chunk metadata (speech_before_mac_sound). Sounds under 8
seconds are ignored. See src/lifelog/mac_sound.py; headphones, soundIgnoreApps and
"sound": false in mac.json tune it.
Online meetings
When a meeting app uses the microphone, the Mac app records two channels: the mic that app uses (you) and the app's
own sound through a Core Audio process tap (everyone else), so it works with headphones too. A meeting ends 30
seconds after the app stops using the mic. Meeting apps are the bundle ids in meetingApps (Teams, Zoom, Chrome,
Edge, Arc, Slack, FaceTime, Webex, Zalo, Discord by default); for a browser the whole browser's sound is recorded.
"meeting": false turns it off.
Audio is written to ~/Library/Application Support/kim-rag/meetings/, cut every 5 minutes, encoded to stereo AAC
and uploaded to POST /chunks with source mac-meeting; files are deleted once the server has them. One meeting
is one episode. The iPhone's speech during those minutes is cut so the meeting is transcribed once. The voice mostly
on the mic channel is linked to your enrolled voice. See src/lifelog/meeting.py. macOS asks
for Microphone and "System Audio Recording Only" the first time.
Chrome extension
The extension in chrome/ records the pages you actually read: the visible tab of the focused window,
with mouse or keyboard activity in the last 3 minutes. It sends time spent per page (per minute), the main text of
pages read for more than 30 seconds (extracted with Readability 0.6.0,
vendored in chrome/vendor/ under its own license), and text you select and copy.
It never records private windows, form fields, pages with a password field, machines on private networks, or the
blocked domains: mail, banks, password managers and sign-in pages by default. Add your own internal systems
(your company intranet, for example) on the extension's options page. Keys, passwords and card numbers are masked
before sending. Each article becomes a read episode, embedded for search; articles do not go into Graphiti and
are not summarized by an LLM.
Install: open chrome://extensions, turn on Developer mode, click "Load unpacked" and choose the chrome/ folder.
Set the server and token on the options page.
Zalo messages
The sensor in zalo/ listens to your Zalo account with zca-js, an
unofficial library that acts like a Zalo Web session. It only listens: it never sends a message, a reaction or a
"seen". It sends every message of direct chats (yours included), every message of the groups you allow, and for
other groups only that a message happened (type, length, whether you were mentioned), never who wrote it or what it
said. Photos, files and voice messages become short labels such as [file: quote.pdf]; nothing is downloaded.
Secrets are masked with the same rules as the Chrome extension.
Two things to know: Zalo allows one web session per account, so opening Zalo in a browser closes the sensor (use the Zalo desktop app instead). And Zalo may lock accounts that use unofficial clients; the sensor behaves like a normal Zalo Web tab to lower that risk but cannot remove it. Use it at your own risk.
zalo/install.sh # npm install, then start the LaunchAgent
cd zalo && npm run login # scan the QR code with the Zalo app once
uv run lifelog zalo --threads # what came in, per chat, with the ids for zalo.jsongroups in zalo.json lists the groups kept in full and blocked the chats never recorded, by id or exact name.
The login session, queue and state live in ~/Library/Application Support/kim-rag/zalo/, readable only by you.
Restart after editing: launchctl kickstart -k gui/$(id -u)/com.kim.kimrag.zalo. Run zalo/install.sh again after
upgrading Node.
Direct chats become one episode per chat per day, searchable and, from GRAPH_CHAT_SINCE, ingested into Graphiti.
Link a Zalo person to a named voice with lifelog zalo-link so people sees one person.
Loading older messages from a Zalo PC backup
The sensor only hears messages from the moment it logs in. Older messages come from a Zalo PC backup: its message
file ends in _zmessage.zdb, one message per line (docs/giai-ma-backup-zalo.md
explains how to decrypt your own backup). zalo/import.js sends every message through the same rules as the
sensor; running it twice is safe.
cd zalo && node import.js /path/to/<account id>_zmessage.zdb --since 2026-07-15 --dry-run
cd zalo && node import.js /path/to/<account id>_zmessage.zdb --since 2026-07-15Without --since it loads the last 90 days. The backup holds all your messages in plain text: keep it somewhere
safe, outside this repository, or delete it once loaded.
iPhone recorder
The app in ios/ records all day with the iPhone's microphone and uploads to your server.
16 kHz mono AAC in ADTS files, cut every 5 minutes without a gap; ADTS stays readable if iOS kills the app.
Runs in the background next to music and other audio; resumes by itself after calls, Siri or a mic change.
Each chunk carries metadata: why it ended, microphone, battery, rough location, motion, calls, "mark" presses.
Uploads run in a background URLSession to
POST /chunks; a chunk is deleted only after the server accepts it.The lock screen shows a quiet Live Activity with a bookmark button; the orange mic dot is shown by iOS.
The server stays on your Mac. The phone can reach it through Tailscale:
tailscale serve --bg 8000 publishes the API at https://<mac>.<tailnet>.ts.net, reachable only from your tailnet.
Set API_TOKEN in .env and the same token in the app.
Build with a free Apple ID (Personal Team) or a paid account:
cp ios/Config/Local.xcconfig.example ios/Config/Local.xcconfig # then set DEVELOPMENT_TEAM and KIMRAG_SERVER
open ios/KimRagRecorder.xcodeproj # choose your iPhone and RunFrom the command line:
cd ios && xcodebuild -project KimRagRecorder.xcodeproj -scheme KimRagRecorder -destination 'generic/platform=iOS' \
-configuration Release -derivedDataPath build -allowProvisioningUpdates build
xcrun devicectl device install app --device <UDID> ios/build/Build/Products/Release-iphoneos/KimRagRecorder.appTrust the developer the first time in Settings › General › VPN & Device Management. The bundle ids
(com.kim.kimrag.recorder) must be unique to your team; change PRODUCT_BUNDLE_IDENTIFIER in Xcode if signing
complains.
A free profile expires after 7 days, so the Mac can renew it: ops/ios-renew.sh runs every 30
minutes and, once the copy on the phone has under 2 days left, rebuilds with a fresh profile, tells the app to close
its current chunk, reinstalls over Wi-Fi and relaunches it. It reads DEVELOPMENT_TEAM and IPHONE_UDID from
ios/Config/Local.xcconfig. The phone must be unlocked and on the same network; with under a day left the Mac shows
a reminder.
ops/install-agent.sh com.kim.kimrag.ios-renew
ops/ios-renew.sh --force # renew now
lifelog recorder # today: hours recorded, gaps and whyWho is speaking
Diarization only labels voices SPEAKER_01, SPEAKER_02 inside one recording. kim-rag links those voices to people
across recordings. Enrol your own voice once from 30+ seconds of you talking:
lifelog speakers enroll path/to/my-voice.m4a --meEach new recording is then matched against known voices before indexing: a close enough voice (cosine ≥
SPEAKER_MATCH) gets that name, an unmatched one becomes an unknown voice that is recognised again on later days,
and the LLM suggests a name when someone is called by name, plus a role label. Open
http://127.0.0.1:8000/speakers to name voices worth naming (heard on 2+ days, or 1+ minute in one conversation):
listen to a 6-second clip, pick or type a name, or mark it not important. Naming a voice relabels and re-indexes
every recording with it.
Named people keep their voiceprints and a 10-second sample; unnamed voices are deleted after STRANGER_KEEP_DAYS
without being heard.
lifelog speakers # people, voices waiting for a name, share of speech that has a name
lifelog speakers rescan # match recordings indexed before this feature existed
lifelog speakers name 8613 "Tuấn"
lifelog speakers ignore 8613Running in the background
The worker runs from launchd at login and every 5 minutes, does all pending work and exits; a run still going is
never overlapped. The API server runs permanently. The plists in ops/ are templates; ops/install-agent.sh
fills in the paths for your checkout, installs them in ~/Library/LaunchAgents and starts them:
ops/install-agent.sh com.kim.lifelog.worker
ops/install-agent.sh com.kim.lifelog.api
launchctl print gui/$(id -u)/com.kim.lifelog.worker | grep -E "state|last exit"
launchctl kickstart gui/$(id -u)/com.kim.lifelog.worker # run now
launchctl bootout gui/$(id -u)/com.kim.lifelog.worker # removeLogs are in data/logs/. Docker must be running (turn on "Start Docker Desktop when you sign in"); if it is not,
the run logs an error and tries again 5 minutes later. Several workers at once are fine: each job is taken by one
worker, and Graphiti ingestion is always done by one.
Sending audio over HTTP
curl -F audio=@meeting.m4a -F 'metadata={"started_at":"2026-10-13T09:00:00+07:00","client_id":"iphone-0001"}' \
http://127.0.0.1:8000/chunksSending the same file or the same client_id again returns the existing id with "duplicate": true.
Inside the pipeline
received: the file is stored and recorded.vad: Silero removes silence; files without speech stop atno_speech.grouped: files with speech less than 10 minutes apart form one episode.diarized: pyannote separates speakers locally and keeps voice embeddings.transcribed: each turn (up to 50 seconds) goes to the speech-to-text API; markdown is written.indexed: windows are embedded into Postgres.ingested: parts are ingested into Graphiti, each part saved as it completes.
Reprocessing from a stage drops the memory built from the old version: vectors are deleted and old parts are
removed from the graph first. code episodes have no audio and start at transcribed. Known limit: a single file
always stays within one episode, even with more than 10 minutes of silence inside it.
Tests
docker compose up -d # the tests use a separate database, lifelog_test, in the same Postgres
uv run pytest
node --test chrome/scrub.test.js
cd zalo && npm test
cd mac && swift testThe Python tests use fake speech-to-text, embedding, graph and LLM services, generate audio with the macOS say
command, and create Claude Code sessions in a temporary folder. They call no real API, do not touch FalkorDB and
do not read ~/.claude.
License
MIT, Copyright (c) 2026 hatrieukim. chrome/vendor/Readability.js is Mozilla's Readability under the
Apache License 2.0 (chrome/vendor/Readability-LICENSE.md).
Available Tools
4 toolspeopleB
Everything known about one person: how often and when their voice was recorded and who else was there, the latest conversations with them (with a short summary), calendar meetings they were invited to, chats with them on the Mac, their direct chats with the user on Zalo (how much, the groups they write in, the latest days with what each was about), what the memory has gathered about them, and the newest facts that involve them. Their Zalo name also finds them.
When several people match, the answer lists their names: call again with one of them.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | The person's name as the user calls them, e.g. "Tuấn" or "anh Tuấn". A part of a name works when it matches whole words. |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full behavioral burden. It does disclose the broad read scope and the multi-match disambiguation behavior, which is real context, but says nothing about permissions, result size, pagination, or performance characteristics of what is clearly a heavy aggregation call.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The purpose is front-loaded, but the core sentence is an overlong run-on enumerating data sources, several of which the output schema already covers. The trailing disambiguation note earns its place; much of the middle enumeration does not.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a single-parameter tool with 100% schema coverage and an output schema present, the description is largely complete; it even redundantly enumerates return contents. The main gap is the absence of any routing guidance relative to the sibling tools.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With a single parameter and 100% schema description coverage, the schema already documents name matching, partial-name matching, and examples. The description adds only the marginal note that a Zalo name also resolves the person, which is a small increment over the schema's 'name as the user calls them'.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening clause 'Everything known about one person' states a clear resource and scope, and the enumeration of aggregated domains (voice recordings, conversations, calendar, chats, Zalo, memory, facts) makes the tool's remit unambiguous. It does not, however, explicitly differentiate itself from the sibling tools search, summary, and timeline, so it stops short of a 5.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The note 'When several people match, the answer lists their names: call again with one of them' is a genuine follow-up workflow instruction, which is useful. But there is no explicit guidance on when to prefer this tool over the siblings search/summary/timeline, so usage is only implied.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
searchA
Tìm trong bộ nhớ của người dùng: hội thoại đã ghi âm và các phiên làm việc với Claude Code. Trả về hai phần.
Nguyên văn: các đoạn gần nghĩa nhất với câu hỏi, có giờ, người nói, nguồn (ghi âm hay dự án nào), và độ gần
từ 0 tới 1. Độ gần dưới khoảng 0,5 thường là không liên quan. Với phiên Claude Code, đoạn ghi "tóm tắt cả phiên"
là bản tóm tắt do máy viết; các đoạn khác là lời thật của người dùng và của Claude trong phiên đó.
Hỏi việc đã làm ở một dự án thì nêu tên dự án trong `query` và dùng `since`, `until` để khoanh ngày.
Fact: những điều đã được chắt lọc từ hội thoại (ai, việc gì, ở đâu, quan hệ), kèm thời gian fact đó đúng.
Fact ghi "ĐÃ HẾT ĐÚNG" là điều từng đúng nhưng về sau đã đổi. Hội thoại không nhắc tên riêng nào thì
thường không có fact, chỉ có nguyên văn.
Mỗi kết quả ghi mã episode và số lượt nói để dẫn nguồn. Không thấy kết quả phù hợp thì nói là không
tìm thấy, đừng đoán.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Số kết quả tối đa cho mỗi phần. | |
| query | Yes | Điều cần tìm, viết bằng tiếng Việt như một câu nói bình thường. Tìm theo nghĩa, không cần trùng chữ. Đừng nhét ngày tháng hay tên người nói vào đây, dùng các tham số bên dưới. | |
| since | No | Chỉ lấy những gì được nói từ mốc này. Dạng 2026-10-13 hoặc 2026-10-13T09:30, giờ Việt Nam. | |
| until | No | Chỉ lấy những gì được nói trước mốc này. Ghi một ngày trơn như 2026-10-13 thì tính hết ngày đó. | |
| speakers | No | Only passages where these people speak, by name (e.g. "Kim"). Voices are recognised across recordings once the user has named them. Unnamed voices carry a role label (e.g. "nhân viên tổng đài") or SPEAKER_01, which only mean something inside one episode. |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does so well: it explains the two output sections, that similarity under ~0.5 is usually irrelevant, that the 'tóm tắt cả phiên' passage is machine-written while others are verbatim speech, and that facts marked 'ĐÃ HẾT ĐÚNG' were once true but are no longer. It omits operational details like latency, auth, or scope limits.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose is front-loaded in the first sentence, followed by clearly separated sections for the two result types. It is somewhat long and repeats the 'dates belong in since/until, not query' advice already in the schema, but nearly every sentence carries information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so raw return shape need not be restated, yet the description usefully covers interpretation of returned fields (similarity scale, fact staleness, episode/turn identifiers) and the no-match fallback. Nothing essential to calling the tool correctly appears missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents every parameter including query phrasing, date formats, and the unnamed-voice caveat. The description's date/project guidance is consistent with but not additive to the schema, making the baseline 3 appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a concrete verb (tìm/search) and a specific resource (bộ nhớ người dùng: hội thoại đã ghi âm và các phiên Claude Code), and describes the two-part return. It does not explicitly contrast itself with siblings (summary, people, timeline), so an agent must infer the boundary from the corpus description alone.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives practical in-tool guidance (put project name in `query` and bound dates with `since`/`until`; say nothing was found rather than guessing), which is genuine usage context. However it never names an alternative tool or states when another sibling should be preferred, so the routing decision is only implied.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
summaryA
What the user did on one day or in one week, written from all sources: Claude Code sessions by project, recorded conversations and online meetings, the calendar, time spent in apps on the Mac, and pages read.
A day starts with an overview paragraph, then one section per source. A week has a summary by theme (main
work, results, people met, open items) and the overview of each day. Today and the current week are written
from the data so far. Every conversation and session carries its episode id, to look it up with `search`.
| Name | Required | Description | Default |
|---|---|---|---|
| date | No | A day as 2026-10-13. Defaults to yesterday. With period "week", any day of the week (Monday to Sunday). | |
| detail | No | Day only. False lists each Claude Code session by time, title and episode; true adds the full summary of every session, which can be long. | |
| period | No | "day" or "week". | day |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does disclose the output shape (overview paragraph then one section per source; a week adds a theme summary plus each day's overview) and the partial-data caveat for today/current week. It does not state read-only nature, cost/length, or pagination limits, but the return-content disclosure is substantive.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short paragraphs, front-loaded with the purpose, then output structure, then the cross-reference. The sources enumeration is slightly list-heavy but each clause maps to a real data source. No filler or repetition.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be re-explained, and the description sensibly focuses on structure and sources instead. For a zero-required-parameter read tool, the agent has enough to call it correctly; only the sibling-routing relationship to `timeline`/`people` is left implicit.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the schema already documents `date`, `detail`, and `period` incl. defaults and the enum. The description adds no syntax, format, or interaction detail beyond what the schema provides (its mention of period structure maps to the schema's own description), so baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names a specific verb+resource: it generates a written summary of the user's activity for one day or one week, from enumerated sources. An agent can tell it produces a synthesized report. It only partially differentiates from siblings: it references `search` for episode lookup, but never clarifies how it relates to `timeline` or `people`.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied rather than stated: the agent infers that `search` is the follow-up for episode ids, and that today/current week produce partial data. There is no explicit when-to-use versus `timeline` (raw events) or `people`, nor any stated prerequisites or exclusion conditions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
timelineA
How a topic changed over time, oldest first, grouped by day: the facts about it in the order they became true, including those marked ĐÃ HẾT ĐÚNG (true once, replaced later), next to the passages where it was discussed. Use it for "how did we end up deciding X" or "what happened with Y over the last weeks".
Facts are picked by closeness in meaning, so a few may be only loosely related: ignore those. Each entry
carries its episode id and turn numbers for citing.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Maximum number of facts. | |
| since | No | Only from this point: 2026-10-13 or 2026-10-13T09:30. | |
| topic | Yes | The topic, decision or project, in plain words, e.g. "Redis" or "notification bell in the admin page". Do not put dates here; use since and until. | |
| until | No | Only before this point. A plain day counts the whole day. |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full disclosure burden and does well: it states ordering (oldest first), day-based grouping, that superseded facts marked ĐÃ HẾT ĐÚNG are still included, that matching is semantic and may surface loosely related facts, and that each entry exposes episode id and turn numbers for citation. It stops short of describing size/pagination behavior beyond the limit parameter.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The first sentence front-loads the ordering and grouping contract, and the second gives the routing cues. It is slightly dense and the parenthetical Vietnamese marker breaks flow, but no sentence is wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return-value documentation is not required, yet the description still explains the entry structure (episode id + turn numbers) and the noise caveat. Combined with a fully documented schema, an agent has what it needs to call this correctly; only the sibling boundary is unstated.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and all four parameters (topic, limit, since, until) are documented in the schema, including the guidance not to put dates in topic. The description adds no parameter semantics of its own, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names a specific resource (a topic's evolution over time) and a specific shape (facts in chronological order, grouped by day, alongside commentary passages). That is far more than a restatement of the name, but it never distinguishes itself by name from the siblings search/summary/people, so an agent must infer the boundary.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It supplies concrete triggering questions ('how did we end up deciding X', 'what happened with Y over the last weeks') that make the intended usage clear. It does not, however, say when NOT to use it or name search/summary as the alternative for a simple lookup, so routing is left partly to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
4 tool updates
v0.1.0- First observed
people - First observed
search - First observed
summary - First observed
timeline
TDQS
Scored across 4 tools
Each tool targets a distinct query intent: free-text retrieval (search), day/week digest (summary), entity lookup (people), and chronological topic evolution (timeline). search and timeline both surface facts and episodes, which could cause minor misselection, but their framing (query-driven vs. time-ordered) keeps them separable.
All four names are single lowercase words with no mixed conventions (no camelCase/snake_case clashes), so the set reads predictably. They are not a strict verb_noun pattern and mix noun forms (people, timeline, summary) with a verb (search), a minor deviation.
Four tools is on the lean side but each occupies a genuinely different retrieval axis for a personal memory domain. Nothing appears redundant, though a couple more query surfaces might be expected given the breadth of data sources described.
The read-only query surface covers search, aggregation, entity, and timeline views, which matches a passively-ingested lifelog where writes are automatic rather than tool-driven. Minor gaps exist (e.g., no direct fetch-by-episode-id, no explicit listing of sources/projects), but workable via search.
Maintenance
Related MCP Connectors
Personal memory layer: files, notes, messages, tasks and email, semantically indexed.
Long-term memory for AI agents: learns what is true about each person and answers with sources.
Universal persistent memory and knowledge retrieval layer for AI agents and LLMs.
Persistent memory for AI agents. Search and store durable facts, preferences and decisions.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to query your complete local conversation history and knowledge graph over MCP, including full-fidelity transcripts, search, and typed, dated entity relationships.AGPL 3.0
- FlicenseNot gradedqualityAmaintenanceEnables AI agents and MCP hosts to query a local, machine-resident index of scattered notes, project docs, PDFs, Office files, and chat exports using hybrid vector + BM25 retrieval with section-level citations. Exposes tools, resources, and prompts so hosts like Claude Desktop, Cursor, and Cline can search, ask, and explore linked knowledge through natural language.-
- FlicenseNot gradedqualityAmaintenanceEnables AI agents and MCP hosts to search and ask questions across a local hybrid-retrieval index of scattered notes, documents, PDFs, and chat logs, returning section-level citations and agent-usable tools, resources, and prompts.-
- FlicenseNot gradedqualityAmaintenanceEnables AI agents and MCP hosts to query a local hybrid vector and BM25 index over scattered notes, docs, PDFs, web pages, and chat logs, with section-level citations and tools, resources, and prompts.-