Skip to main content
Glama

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, timeline

Postgres 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

src/lifelog/

Server, worker, CLI and MCP server (lifelog command)

Python 3.12, uv

ios/

iPhone recorder: all-day recording, background upload, Live Activity

Swift, SwiftUI

mac/

Mac menu bar app: activity, calendar, meetings, room mic, main window

Swift (SwiftPM)

chrome/

Chrome extension: pages read, selections

JavaScript (MV3)

zalo/

Zalo sensor (listen only) and Zalo PC backup importer

Node.js 18+

ops/

LaunchAgent templates and the iPhone app renewal script

plist, zsh

tests/

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 .env

Open .env and fill in at least:

  1. HF_TOKEN for speaker diarization. Create a read token at huggingface.co/settings/tokens and accept the terms on the model page pyannote/speaker-diarization-community-1.

  2. GOOGLE_CLOUD_PROJECT for gemini-3.5-transcribe, chirp_3, embeddings and the graph. Enable Agent Platform (Vertex AI) and Cloud Speech-to-Text in that project, then run gcloud auth application-default login.

  3. OPENAI_API_KEY if you want gpt-transcribe.

  4. API_TOKEN before the server is reachable from anything other than this Mac (the iPhone app).

Check that everything is in place:

uv run lifelog doctor

Configuration

All server settings are environment variables, read from .env; .env.example documents each one. The clients keep their own settings:

Client

Settings

Mac app

~/Library/Application Support/kim-rag/mac.json (server, token, ignore lists, features), opened from the menu

Zalo sensor

~/Library/Application Support/kim-rag/zalo.json (groups kept, blocked chats); server and token come from mac.json

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 ios/Config/Local.xcconfig

Daily use

Transcribe one file and wait for the result:

uv run lifelog transcribe ~/Desktop/meeting.m4a

Or run the worker and drop files into data/inbox/; finished files move to data/inbox/processed/.

uv run lifelog worker

Results are in data/episodes/DAY/TIME_ID.md; the original audio in data/raw/YYYY/MM/DD/.

Command

What it does

lifelog status

How many items are at each stage, which are stuck and why

lifelog show ID

Print the transcript of an episode

lifelog compare ID

Run the same turns through all three speech-to-text services and write a comparison

lifelog reprocess --from transcribed --since 2026-10-01 --backend chirp_3

Switch service and redo only speech-to-text

lifelog reprocess --from diarize --episode ID

Redo diarization

lifelog retry

Retry stuck items, e.g. after adding a key to .env

lifelog search "question" --since 2026-10-01

Search both memory stores; prints exactly what the agent gets from search

lifelog reprocess --from ingest --all

Rebuild the whole graph from Postgres

lifelog reprocess --from index --all

Re-embed, e.g. after changing EMBED_MODEL

lifelog mcp

MCP server for agents, over stdio

lifelog journal 2026-10-13

Rewrite the work journal of a day (today when omitted)

lifelog summary 2026-10-13 [--week] [--detail]

Print what the agent gets from the summary tool. Defaults to yesterday

lifelog people "Tuấn"

Print what the agent gets from the people tool

lifelog timeline "Redis" --since 2026-10-01

Print what the agent gets from the timeline tool

lifelog serve

HTTP server receiving POST /chunks and POST /events

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-13

ASR_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 the windows table with pgvector. This answers "who said exactly what, and when".

  • Facts, in Graphiti on FalkorDB. gemini-3.8-flash reads the transcript and extracts people, places, tasks and facts with validity periods. The graph_parts table 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 mcp

Tool

Arguments

Returns

search

query, optional since, until, speakers, limit

The closest verbatim passages and Graphiti facts, each with time, episode id and turn numbers

summary

optional date, period (day or week), detail

The day journal, or the week summary, of that date. Defaults to yesterday

people

name

Everything known about one person: recordings, who else was there, calendar, chats, facts

timeline

topic, optional since, until, limit

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-app

Empty 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 default

Commits 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"' >> ~/.zshrc

On 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.json

groups 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-15

Without --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 Run

From 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.app

Trust 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 why

Who 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 --me

Each 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 8613

Running 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        # remove

Logs 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/chunks

Sending the same file or the same client_id again returns the existing id with "duplicate": true.

Inside the pipeline

  1. received: the file is stored and recorded.

  2. vad: Silero removes silence; files without speech stop at no_speech.

  3. grouped: files with speech less than 10 minutes apart form one episode.

  4. diarized: pyannote separates speakers locally and keeps voice embeddings.

  5. transcribed: each turn (up to 50 seconds) goes to the speech-to-text API; markdown is written.

  6. indexed: windows are embedded into Postgres.

  7. 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 test

The 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 tools
peopleB

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.
ParametersJSON Schema
NameRequiredDescriptionDefault
nameYesThe 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

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

B3.4/5.0
Behavior3/5

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.

Conciseness3/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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.

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`.
ParametersJSON Schema
NameRequiredDescriptionDefault
dateNoA day as 2026-10-13. Defaults to yesterday. With period "week", any day of the week (Monday to Sunday).
detailNoDay only. False lists each Claude Code session by time, title and episode; true adds the full summary of every session, which can be long.
periodNo"day" or "week".day

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.7/5.0
Behavior4/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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.
ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMaximum number of facts.
sinceNoOnly from this point: 2026-10-13 or 2026-10-13T09:30.
topicYesThe 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.
untilNoOnly before this point. A plain day counts the whole day.

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.9/5.0
Behavior4/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines4/5

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.

  1. 4 tool updatesv0.1.0
    • First observedpeople
    • First observedsearch
    • First observedsummary
    • First observedtimeline

TDQS

A3.7/5.0

Scored across 4 tools

Disambiguation4/5

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.

Naming Consistency4/5

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.

Tool Count4/5

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.

Completeness4/5

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

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables 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
  • F
    license
    Not graded
    quality
    A
    maintenance
    Enables 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.
    -
  • F
    license
    Not graded
    quality
    A
    maintenance
    Enables 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.
    -
  • F
    license
    Not graded
    quality
    A
    maintenance
    Enables 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.
    -