chronicle
Ingests code repository activity from Forgejo.
Ingests repository and activity data from GitHub.
Ingests emails from Gmail.
Ingests photos and metadata from Immich to build a timeline of visual events.
Ingests issue tracker information from Jira.
Ingests documents and notes from Notion.
Ingests messages from Slack channels.
Ingests messages from Telegram chats and groups them into episodes for retrieval.
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., "@chronicleshow me events from last week"
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.
chronicle
Self-hosted memory for one person's chat archive and activity streams, indexed by conversation instead of by message and served to AI assistants over MCP.
Parked. It works; I am not changing it now and may come back.
flowchart LR
tg[Chat archive] --> ad[Read-only adapters]
st[Coding time, spending, music, photos, documents] --> ad
ad --> ev[Events: one timeline]
ev --> seg[Segments: whole exchanges]
seg --> pg[(PostgreSQL + pgvector)]
pg --> api[HTTP API]
pg --> mcp[MCP server]
mcp --> ai[AI assistant]Why
My Telegram archive holds 681,331 messages over seven and a half years, and 65% of them are under 20 characters:
ок, ага, +1, 😂. The sync service I ran embedded every message, so about 442,000 vectors were filler that
crowded every nearest-neighbour search and buried the 1.5% of messages that say something. I wanted an assistant
to look things up in what I said and did, and that meant indexing whole exchanges, not single lines.
Related MCP server: roxabi-sense
What it does
Indexes conversations, not messages. Each chat is cut into segments (a run of messages that belong together) at a time gap fitted to that chat, with a size cap and replies holding an exchange together.
Reads what I already self-host. 19 adapters over PostgreSQL, MariaDB, SQLite, JSONL files and HTTP APIs map every source onto one event shape. A source declares a density, and density decides how hard it is rolled up first: editor heartbeats become coding sessions, GPS points become stays, scrobbles become listening sessions.
Answers each kind of question with its own tool. Hybrid search for lookups, an earliest match for "when did this first come up", per-period search for how a view changed, read-only SQL for counting, and a timeline. Routing is fixed rules, logged and overridable.
Matches names across scripts. Russian, Ukrainian and Latin spellings of one name (Егор, Єгор, Yehor) resolve to the same keys (ADR-002).
Forgets on request. Every derived row cites the events it came from, so excluding a chat removes everything built from it, and secrets are redacted before a row is stored.
Measures itself against grep on questions you write yourself.
Not here: accounts or authentication, a trained query router, a recency penalty on old content, summaries of summaries. Enrichment (an LLM pass that extracts facts per segment) exists and is off: it lowered the score.
The database holds every message of every chat it reads, other people's words included, and nothing authenticates. Keep ports 8030 and 8031 on loopback and reach them over an SSH tunnel (SECURITY.md).
Quick start
Needs Docker with Compose and a source to read; the quickstart guide walks through one Telegram archive step by step.
git clone https://github.com/Egoushka/chronicle.git && cd chronicle
cp .env.example .env # DB_PASSWORD, the owner, one block per source
cp compose.sources.example.yaml compose.override.yaml # how the worker reaches your sources; trim it
docker compose up -d chronicle-db chronicle-api chronicle-mcp
docker compose --profile batch run --rm chronicle-worker python -m chronicle.worker doctor --tier 1
docker compose --profile batch run --rm chronicle-worker python -m chronicle.worker all --tier 1doctor checks every source read-only before a single row is ingested: reachability, timestamps that are really
timestamps, duplicate ids, import time posing as event time. Every database adapter here was wrong somewhere the
first time it met a real database. all runs ingest, fit-gaps, segment, enrich (off) and embed; every stage is
incremental, so the same command is the nightly job. Point an MCP client at http://127.0.0.1:8031/sse.
How it works
Adapters read each source read-only and write immutable events. The worker fits a time gap per chat and cuts
events into segments, each storing raw_text (what a hit returns) and embed_text (what gets indexed). One
PostgreSQL holds everything: BGE-M3 embeddings as halfvec(1024) with an exact scan instead of an approximate
index, so date filters stay exact (ADR-001), plus a full-text branch
weighted by how rare each word is. The two ranked lists merge by reciprocal rank fusion. A question that names its
own period ("in January 2026") gets that date window.
The invariant: nothing is indexed one message at a time, and no model decides which of two facts is newer (that is
max(version) in code). The api and the MCP server expose the same seven operations, recall, first_mention,
evolution, tally, timeline, open_commitments and ground (reference).
Every database-backed adapter has run against its real application's database on my deployment.
How it works follows the pipeline in detail.
Numbers
All measured on my deployment, one home server with 16 shared CPU cores.
Value | Source | |
Archive | 681,331 messages, 487 chats, 2018-12-30 onward | RESEARCH.md, queried 2026-07-25 |
Messages under 20 / 60 characters | 65.0% / 94.0% | same |
Messages over 200 characters | 1.5% (10,484) | same |
Messages that reply to another | 7.9% | same |
First full ingest | 685,401 events, no loss, no duplicates | CLAUDE.md, 2026-08-11 |
Index reduction | 685,401 events → 51,044 segments (13.4×), 13.4 events and 488 characters each | same |
Retrieval quality by memory unit, SeCom (ICLR 2025) | segment 71.57, turn 65.58, session 63.16, summaries 53.87–56.25 | |
chronicle vs ripgrep, 71 of my questions | 75.4% vs 54.2% | CHANGELOG 0.4.0, 2026-09-29 |
ripgrep allowed the answer's words | 68.4%, still below | CHANGELOG, 2026-09-29 |
First eval, 37 questions | 63.5% vs 62.8%, level | CLAUDE.md, 2026-09-26 |
Segment size cap | 15 beat 20 beat 30 (73.2 / 71.6 / 71.1%) | |
Enrichment on vs off | 71.8% vs 75.4%, so it stays off | CLAUDE.md, 2026-09-29 |
Warm | ~0.4 s | CLAUDE.md, fact 24, 2026-09-14 |
First embed of 51k segments | ~9.8 h on CPU | CLAUDE.md, 2026-08-11 |
Memory | api 2.5 GB, db 2 GB resident; worker 8 GB while it runs, then exits | compose.yaml |
The questions are one person's, and the margin depends on the grep it is compared against, so the rule for grep's keywords (only the question's own words) is written down in CLAUDE.md.
Lessons
The unit matters more than the model. Every weakness of the per-message index traced back to embedding
ок442,000 times; no tuning above that layer would have fixed it.Measure what you think is slow. A 25-second query that I blamed on the CPU was one cold call; the warm cost was SQL, and fixing one materialized CTE took
/recallfrom 7.4 s to 0.4 s.An extraction pass can hurt retrieval. LLM-written facts pulled the wrong segments up, and the A/B said so.
The full list, 52 facts each with its regression test, is in CLAUDE.md.
Docs
Overview: who it is for, what it leaves out, where it stands
Quickstart: from a clone to a first answer from
/recallConnect a source: every adapter, its tier and the variables it needs
Deploy and operate: nightly run, upgrades, erasure, redaction, gated enrichment, rollback
Measure it against grep: the evaluation harness and how to keep it honest
Research · Deploy notes · ADRs 001, 002, 003
Apache-2.0, see LICENSE and NOTICE. Not affiliated with or endorsed by Telegram or any service it reads.
This server cannot be deployed
Maintenance
Related MCP Connectors
Cross-vendor AI memory over MCP. One semantic store, readable and writeable from every MCP client.
Private-by-default, local-first memory/context/task orchestrator for MCP apps and agents.
Tenant-scoped evidence intake and controlled AI context over MCP.
Cross-tool persistent memory and context for AI assistants over MCP.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceAggregates data from email, calendar, tasks, and documents to enable intelligent assistance for AI assistants.MIT

roxabi-senseofficial
AlicenseBqualityBmaintenanceLocal workstation attention journal that tracks focus, idle, and agent sessions, exposing timeline data via MCP for AI agents to query current or past activity.5AGPL 3.0- AlicenseNot gradedqualityCmaintenanceA privacy-first personal context engine that ingests personal photos and message exports, extracts cited facts, and serves a queryable profile over MCP for natural-language questions like 'when did I last see Sarah?'.MIT
- AlicenseNot gradedqualityCmaintenanceRead-only MCP server exposing Android phone timeline data, including events, hourly/daily features, data coverage, and gaps, so Claude can answer questions about observed phone activity.MIT