Skip to main content
Glama
Egoushka

chronicle

by Egoushka

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.

Related MCP server: touch-grass-mcp

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.

F
license - not found
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    -
    quality
    D
    maintenance
    Preference-aware events discovery MCP server that aggregates events, restaurants, and cultural activities across multiple sources and re-ranks them against your personal taste profile to surface things you'd actually want to do.
    Last updated
    1
    MIT
  • F
    license
    -
    quality
    C
    maintenance
    A deterministic MCP server that gives AI assistants a temporal memory layer by extracting events from conversation, normalizing time expressions, and maintaining structured timeline state to enable consistent multi-session conversations.
    Last updated

View all related MCP servers

Related MCP Connectors

  • The personal context layer for AI: your profile and files, read by any MCP client over OAuth.

  • Private-by-default, local-first memory/context/task orchestrator for MCP apps and agents.

  • User-owned memory for AI agents, Copilot, Claude, IDEs, CLIs, and chat apps over remote MCP.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Egoushka/chronicle'

If you have feedback or need assistance with the MCP directory API, please join our Discord server