Skip to content

asiniscalchi/froid

Repository files navigation

Froid

Froid is an AI-powered personal journaling backend. It captures your thoughts via a Telegram bot, enriches them with structured analysis and semantic embeddings, and delivers daily and weekly reflections back to you.

How it works

Send a message to your Telegram bot. Froid stores it immediately and returns a confirmation. In the background, workers process each entry:

  • Extraction — an LLM reads the entry and produces a structured document: emotions (with intensity and confidence), behaviors (with valence), psychological needs (with status), and possible patterns. All inference is explicit about uncertainty and never overstates what a single note can support.
  • Embedding — the entry is vectorised for semantic similarity search, so you can query your journal by meaning rather than keywords.

At the end of the day, a review worker synthesises all of that day's raw notes and their structured extractions into a concise reflection delivered via Telegram.

Once a week (Monday by default), a weekly review worker synthesises the previous ISO week's daily reviews and their structured signals into a single reflection covering Monday through Sunday, and delivers it via Telegram. Run /week_review in the chat to request the most recent completed weekly review on demand.

Running with Docker

A pre-built image is published to the GitHub Container Registry on every push to main.

Create an .env file (see Configuration for the full variable reference):

TELEGRAM_BOT_TOKEN=your-token-here
TELEGRAM_ALLOWED_USER_IDS=123456789,987654321    # optional: comma-separated list of allowed Telegram user IDs
OPENAI_API_KEY=your-key-here

FROID_EMBEDDING_WORKER_ENABLED=true
FROID_DAILY_REVIEW_EMBEDDING_WORKER_ENABLED=true
FROID_EXTRACTION_WORKER_ENABLED=true
FROID_DAILY_REVIEW_DELIVERY_ENABLED=true
FROID_WEEK_REVIEW_WORKER_ENABLED=true

Then run:

docker run --env-file .env -v ./data:/app/data ghcr.io/asiniscalchi/froid:latest serve

Exposing tools over MCP

Set FROID_MCP_ENABLED=true to expose the analyzer's read-only tools over the MCP Streamable HTTP transport at http://127.0.0.1:8080/mcp. The MCP server runs alongside the Telegram bot in the same process. Every request must carry an Authorization: Bearer <token> header with a token minted via the Telegram /token command (see Authentication).

FROID_MCP_ENABLED=true cargo run -- serve

Available tools: journal_get, journal_get_recent, journal_search_text, journal_search_semantic, daily_review_get, daily_review_get_range, weekly_review_get, weekly_review_get_range, signals_search.

Authentication

Authentication is always on: every request to /mcp must carry a bearer token minted through the bot:

Authorization: Bearer <your-token>

Users mint their own tokens by sending /token to the Telegram bot. Telegram is already the authenticated channel (the chat id arrives with the message), so the bot can safely issue HTTP credentials for exactly that identity: it generates a 256-bit random token, stores only its SHA-256 hash in the default database, and replies once with the plaintext. Each request is then served from the token owner's isolated journal database, so every user of a shared instance gets their own MCP endpoint. Sending /token again rotates the token; /token revoke disables access. Onboarding a user requires no operator action and no restart.

Requests without a valid token receive 401 Unauthorized; only the /health probe is served without authentication.

Because Telegram access is what mints HTTP credentials, TELEGRAM_ALLOWED_USER_IDS is the gate that decides who can become a user — set it on any instance whose bot a stranger could message.

Health endpoint

Whenever the MCP listener is running, GET /health answers 200 OK with the service name and version. It is intentionally exempt from the bearer-token check so supervisors, load balancers, and container healthchecks can probe it without credentials:

curl http://127.0.0.1:8080/health
# {"name":"froid","status":"ok","version":"..."}

Backups

All persistent state lives in DATA_DIR (default data/): the legacy database file plus one isolated SQLite database per user under journals/user_<chat_id>.sqlite3. To back up:

  • Cold backup — stop the service and copy the whole data directory. SQLite databases are plain files; this is always safe.
  • Hot backup — while the service is running, use SQLite's online backup instead of copying files directly (WAL side-files make raw copies of a live database unreliable): sqlite3 data/journals/user_<chat_id>.sqlite3 ".backup 'backup.sqlite3'" for each database.

Restoring is the reverse: stop the service and put the files back in place.

For per-user data portability, every user can pull their own journal without operator involvement: send /export to the bot to receive a JSON file of all raw messages, and send that file back as a document with /import as the caption to load it (all-or-nothing; a collision with an existing message aborts the import). Telegram bots can download files up to ~20 MB, so an enormous import would need the file-level restore path above instead.

Managing users

Each user's journal lives in its own SQLite database under DATA_DIR/journals/user_<chat_id>.sqlite3. The users subcommands operate directly on those files, so run them while the server is stopped:

froid users list                  # chat id, size, last modified, path
froid users delete <chat_id> --yes

users delete permanently removes the user's entire journal (database plus WAL side-files) and refuses to run without --yes. Combined with the /export command this covers data-portability and right-to-erasure requests.

Configuration

All options can be set via environment variables or the equivalent --flag CLI argument. Copy .env.example as a starting point.

Core

Variable Default Description
TELEGRAM_BOT_TOKEN Required. Telegram bot credentials
TELEGRAM_ALLOWED_USER_IDS (all private chats) Comma-separated list of allowed Telegram user/chat IDs
OPENAI_API_KEY Required when any worker or the MCP semantic search tool is enabled
DATA_DIR data Directory for persistent data
DATABASE_FILE froid.sqlite3 SQLite database filename (resolved relative to DATA_DIR)
RUST_LOG info Log level filter (e.g. debug, froid=trace)

Workers

All workers are disabled by default and require OPENAI_API_KEY.

Variable Default Description
FROID_EMBEDDING_WORKER_ENABLED false Enable journal entry embedding worker
FROID_EMBEDDING_WORKER_BATCH_SIZE 20 Entries processed per cycle
FROID_EMBEDDING_WORKER_INTERVAL_SECONDS 300 Polling interval (seconds)
FROID_DAILY_REVIEW_EMBEDDING_WORKER_ENABLED false Enable daily review embedding worker
FROID_DAILY_REVIEW_EMBEDDING_WORKER_BATCH_SIZE 20 Reviews processed per cycle
FROID_DAILY_REVIEW_EMBEDDING_WORKER_INTERVAL_SECONDS 300 Polling interval (seconds)
FROID_EXTRACTION_WORKER_ENABLED false Enable structured extraction worker
FROID_EXTRACTION_WORKER_BATCH_SIZE 20 Entries processed per cycle
FROID_EXTRACTION_WORKER_INTERVAL_SECONDS 300 Polling interval (seconds)
FROID_DAILY_REVIEW_DELIVERY_ENABLED false Enable daily review generation and delivery
FROID_DAILY_REVIEW_DELIVERY_INTERVAL_SECONDS 300 Polling interval (seconds)
FROID_SIGNAL_WORKER_ENABLED false Enable daily review signal extraction worker
FROID_SIGNAL_WORKER_BATCH_SIZE 20 Reviews processed per cycle
FROID_SIGNAL_WORKER_INTERVAL_SECONDS 300 Polling interval (seconds)
FROID_WEEK_REVIEW_WORKER_ENABLED false Enable weekly review generation and delivery
FROID_WEEK_REVIEW_WORKER_INTERVAL_SECONDS 300 Polling interval (seconds)
FROID_WEEK_REVIEW_KICKOFF_DAY Monday Weekday on which weekly reviews are generated
FROID_WEEK_REVIEW_MIN_DAILY_REVIEWS 1 Minimum completed daily reviews required before generating a weekly review

MCP Server

Variable Default Description
FROID_MCP_ENABLED false Enable the MCP Streamable HTTP server
FROID_MCP_BIND 127.0.0.1:8080 Bind address (e.g. 0.0.0.0:8080 for Docker Compose)

Models

Override the OpenAI model used by each pipeline stage. Accepts any model name recognised by the OpenAI API.

Variable Default Description
FROID_EMBEDDING_MODEL text-embedding-3-small Embedding model for journal entries and daily reviews
FROID_ENTRY_EXTRACTION_MODEL gpt-5-mini Model used for structured entry extraction
FROID_REVIEW_MODEL gpt-5-mini Model used for daily review generation
FROID_SIGNAL_EXTRACTION_MODEL gpt-5-mini Model used for daily review signal extraction
FROID_WEEK_REVIEW_MODEL gpt-5-mini Model used for weekly review generation
OPENAI_BASE_URL https://api.openai.com/v1 Base URL for all LLM and embedding requests. Point it at any OpenAI-compatible endpoint (Ollama, OpenRouter, a self-hosted gateway) to keep journal data off openai.com; pair it with the model variables above

Prompts

Override the prompt file used by each pipeline stage. The version tag recorded in the database is derived automatically from the filename stem (e.g. entry_extraction_v2 from entry_extraction_v2.md).

Variable Default
FROID_ENTRY_EXTRACTION_PROMPT_PATH prompts/entry_extraction_v1.md
FROID_REVIEW_PROMPT_PATH prompts/daily_review_v3.md
FROID_SIGNAL_EXTRACTION_PROMPT_PATH prompts/daily_review_signal_extraction_v1.md
FROID_WEEK_REVIEW_PROMPT_PATH prompts/weekly_review_v1.md

Contributing

See CONTRIBUTING.md for development setup, build instructions, and project conventions.

License

This project is licensed under the GNU Affero General Public License v3.0 or later.

SPDX-License-Identifier: AGPL-3.0-or-later — see LICENSE.

About

The Compass for Navigating the Shadow

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors