chronicle

chronicle

Aggregates personal events from multiple sources into a unified timeline and exposes them via MCP for AI assistant retrieval.

Category
访问服务器

README

chronicle

Personal event store and retrieval brain. Ingests Telegram, wakapi, dawarich (and whatever comes next) into one timeline, and serves it to agent-runner over MCP so the assistant can actually look things up.

Deploys into homelab-gitops as the chronicle stack.


Why this exists

The Telegram archive is 681,331 messages across 487 chats and 457 senders, 2018-12-30 to now. The existing telegram-sync stack embeds every message into Qdrant. Measured against the real corpus:

messages under 20 chars 65.0% (442,954)
messages under 60 chars 94.0%
messages over 200 chars 1.5%
messages with reply_to_id 7.9%

So ~442,000 of those vectors represent ок, ага, +1, да, 😂. They are not retrieval units — they crowd every neighbourhood they land in and bury the 1.5% of messages that carry a proposition.

Chronicle's core move is aggregation before indexing. Events are grouped into episodes by a per-thread fitted time gap: 681k units become ~50k. The index gets ~11× smaller and retrieval gets better at the same time.

Evidence, from SeCom (ICLR 2025), measuring retrieval quality by memory unit on conversational data — at ~30 tokens/turn, where ours are 5–10:

segment-level   71.57   <- what this builds
turn-level      65.58
session-level   63.16
summaries       53.87-56.25   <- worst. Do not build a summary pyramid.

Full reasoning, benchmarks and citations: docs/RESEARCH.md.

It is not a Telegram tool

Telegram is the richest channel, not the only one. 18 adapters, five storage shapes, one Event shape downstream.

tier sources why
1 · core telegram wakapi dawarich calendar the archive is worth having with only these
2 · behaviour firefly lastfm forgejo jira what you did, as opposed to what you said
3 · artifact immich paperless gmail notion karakeep github linkedin slack things you made, saved, or were sent
4 · ambient miniflux owntracks weak signal; on last, off first if precision drops

Storage shapes, because the homelab is not uniform and assuming it was is how the first version broke: PostgreSQL (telegram, immich, paperless, miniflux, forgejo, dawarich) · MariaDB (firefly) · SQLite (wakapi, karakeep) · flat JSONL files (owntracks) · HTTP/MCP (gmail, calendar, notion, slack, jira, linkedin, lastfm, github).

The policy layer is the point

"Use all possible channels" has a failure mode that looks exactly like the one Chronicle was built to fix. Indexing 681k sub-20-char messages was the wrong unit. Turning on twenty sources without a policy is the wrong source mix — miniflux alone can contribute 100k article rows you never opened, immich has 400 near-identical burst frames per moment, and the archive becomes millions of events that are ~90% chaff. Precision collapses the same way.

So every source declares a density, and density decides how hard the adapter aggregates before anything reaches the episode layer:

  • NARRATIVE — deliberate human text. Segmented into episodes.
  • DISCRETE — one row really is one thing that happened. Passed through.
  • TELEMETRY — meaningful only in aggregate. The adapter rolls it up: wakapi heartbeats → coding sessions, dawarich points → stays, lastfm scrobbles → listening sessions, immich photos → photo sessions.
  • AMBIENT — stored, but kept off the default retrieval surface.

chronicle/sources.py also lists the ~40 homelab stacks that are explicitly not sources, so the boundary is documented rather than rediscovered. All 57 stacks is not the goal; monitoring, qdrant and vaultwarden describe the machine, not the life.

make test enforces this: every adapter must have a policy, densities must match, and dawarich/owntracks are flagged as mutually exclusive (same GPS signal — enabling both double-counts every trip and the duplicate reads as corroboration).

Why more channels actually helps

"What was happening before I got depressed" — your stated goal. Telegram tells you what you said. Location tells you whether you stopped leaving the house, wakapi whether you stopped coding, firefly whether spending changed, lastfm what you played at 3am, immich whether you stopped taking photos. The behavioural signals are more honest than the conversational ones, because you don't curate them.

And cross-source corroboration turns a guess into evidence: a trip mentioned in Telegram, confirmed by dawarich coordinates, photographed in immich, and paid for in firefly is a fact you can trust.

Relationship to Hindsight

They are opposites, which is why they compose.

Hindsight Chronicle
origin you decided it mattered you never chose to save any of it
volume ~5,200 facts 681k events / ~50k episodes
precision high, curated low, exhaustive
evidence trail none nothing but evidence
shape a notebook you write in a recording that ran the whole time

Chronicle does not replace Hindsight and must not flood it. The personal bank is already at 2,726 facts and times out on sync_retain; piping ~50k episodes of extracted facts into it would 40× the bank and make recall useless.

Two narrow flows instead:

  • ground (Chronicle → answer), every recall. Hindsight facts are unsourced assertions. Chronicle attaches the conversations behind them — including ones that contradict. This replaces time-based staleness rules (ticket >14d, finance >30d) with a measurement.
  • Promotion (Chronicle → Hindsight), rare. v_promotable_facts requires support across ≥3 episodes and ≥2 threads at confidence ≥0.7. Target hundreds per year. Watch get_bank_stats after each run.

Architecture

sources ──► adapters ──► event (immutable, partitioned by year)
                            │
                            ▼
                     SEGMENTATION            per-thread fitted time gap
                     681k ──► ~50k           + caps + reply-edge anchors
                            │
                            ▼
                     episode                 raw_text (returned)
                            │                embed_text (indexed)
                            ▼
                     PostgreSQL              halfvec(1024) exact scan
                     one system              tsvector + trgm + B-tree
                            │
                            ▼
                     MCP ──► agent-runner ──► tg-assistant

Everything is in one PostgreSQL. At ~50k episodes for one user, ANN solves a problem that doesn't exist: exact cosine over ~123 MB is single-digit ms, and it keeps every date filter exact — sidestepping the HNSW percolation failure that bites hardest at the ~1%-cardinality date ranges you query most.

Quick start

git clone <this repo> chronicle && cd chronicle
make test                     # 57 unit tests, no DB or models needed
cp .env.example .env          # fill in, then `make encrypt STACK=chronicle` in homelab
make smoke                    # migrations + every SQL function, throwaway DB

make doctor                   # ← ALWAYS. validates sources before you ingest
make ingest                   # sources -> event -> episode -> embedding
make eval-init && make eval   # chronicle vs ripgrep, on your questions

make doctor is not optional

Two adapter assumptions were already wrong on first contact — wakapi is SQLite not Postgres, firefly is MariaDB with amounts on transactions — and each would have surfaced hours into a backfill, after the worker had written wrong rows. Doctor finds that class of problem in ~10 seconds, read-only.

It checks: driver reachability, timestamps that are actually datetimes (SQLite returns TEXT), ascending order, duplicate ids, unaggregated telemetry, empty narrative text, degenerate thread_key, and import-time-masquerading-as- event-time (the immich createdAt vs EXIF dateTimeOriginal trap). Run it again after upgrading any source stack — an upstream migration is exactly what breaks an adapter quietly.

Deploy: see docs/DEPLOY.md. Short version — clone into /opt/stacks/chronicle, resolve the chronicle-db digest into PINS.md, register 10.211.71.0/24 in NETWORKS.md, make validate in the homelab repo, then docker compose up -d.

Requirements

  • PostgreSQL 16 with pgvector ≥ 0.7halfvec does not exist in 0.6. The pinned pgvector/pgvector:pg16 image is fine; a distro postgresql-16-pgvector package may not be.
  • ~2.5 GB resident (api + db). The worker is restart: "no" and needs 8 GB while it runs — the box is 32 GB with 61.4 GB of mem_limit committed and has hit 96% swap, so it is scheduled, not resident.

Status

Working: segmentation, gap fitting, cross-script entity resolution, intent routing, RRF fusion, bi-temporal facts with deterministic conflict resolution, the full schema, the source-policy layer, 18 adapters across 5 storage shapes, MCP tool surface.

The SQL-backed adapters (telegram, wakapi, dawarich, immich, paperless, firefly, karakeep, miniflux, forgejo) carry real queries against real schemas but have only been run against fixtures — verify each one against your data before trusting its output. The API adapters take an injected fetch_page callable and need wiring to the corresponding MCP tool.

Stubbed: only worker.py enrich — the local-LLM pass for summaries, topics and facts. Everything else runs. Enrichment is deliberately last: retrieval works without it, so ship and measure before spending weeks of CPU there.

Tested: 57 unit tests, plus migrations and every SQL function exercised against real PostgreSQL 16 + pgvector 0.8.0 in CI.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选