bvandeusen 8a8d6fc9f2 feat(diagnostics): persist crash state to /data so it survives container death
The previous diagnostic instrumentation only wrote to stdout — fine for
'tail the logs while debugging', useless for 'crash happened at 3am
and Docker rotated the logs by morning'. This commit makes the
diagnostic state durable across container restart, OOM-kill, and log
rotation by writing to the mounted /data volume.

Four artifacts in /data/diagnostics/:

- current.json — overwritten atomically every heartbeat. Holds the
  last known good snapshot (rss, asyncio_tasks, db_pool, curator_busy,
  uptime, pid). Post-crash, this file alone tells you what the app
  was doing 0-60 seconds before it died. Atomic write (tmp+rename)
  so a crash mid-write can't leave a half-written file.

- last_shutdown.json — written when SIGTERM/SIGINT is caught OR
  after_serving fires cleanly. If this file's mtime is older than
  current.json's, the previous run died WITHOUT calling shutdown
  (== SIGKILL, OOM-kill, or container hard-stop).

- last_exception.json — written when the asyncio exception hook
  fires. Includes task name, coro name, exception type and message
  alongside the resource snapshot.

- diag.log + diag.log.1..5 — rotating file log (10 MB × 5 backups
  = 50 MB cap) containing every heartbeat, signal, and exception.
  Separate from the app's stdout logger so Docker log rotation
  can't take it out.

- previous_run.json — written at startup IF the post-mortem detects
  the previous run died abruptly. Includes the abrupt-death snapshot
  preserved for retrospection, so a recurring crash pattern can be
  diffed over time.

Post-mortem at startup:
- Reads current.json + last_shutdown.json mtimes.
- If current.json is newer (== no clean shutdown happened after the
  last heartbeat), logs a WARNING: 'PREVIOUS RUN DIED ABRUPTLY. Last
  heartbeat was Xs before this startup. Last-known state: {...}'
- The warning lands in BOTH stdout AND the persistent diag.log, so
  the operator notices it even if they only check one place.
- Stashes the abrupt-death snapshot in previous_run.json for later.

How the operator uses this after a crash:
1. cat /data/diagnostics/current.json    -- last known good state
2. cat /data/diagnostics/last_shutdown.json   -- did it shut down cleanly?
3. cat /data/diagnostics/last_exception.json  -- any unhandled exception?
4. tail -100 /data/diagnostics/diag.log  -- the lead-up

If current is newer than last_shutdown and last_exception doesn't
exist: SIGKILL or OOM (uncatchable). Check docker exit code 137
and host dmesg for oom-killer lines.

If last_exception.json exists: a background task crashed. The
traceback in the file names the coro.

If current.json's rss_mb was climbing across heartbeats: memory
leak / OOM trajectory. Bound the cause to whatever was active.

If current.json's db_pool checked_out was climbing: connection leak.
Look for code paths opening async_session() without exiting
'async with'.

If curator_busy=true across multiple heartbeats: curator hung on
Ollama. Restart Ollama or the Scribe stack to release the lock.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 01:32:56 -04:00

Fabled Scribe

A self-hosted second brain and project management application with integrated LLM capabilities. Write, organise, and act on your notes and tasks with the help of a local AI assistant — all running on your own hardware.

Features

Notes and tasks with a Markdown editor, sub-tasks, milestones, and kanban project workspaces. AI chat with streaming responses, RAG over your notes, and tool use (web search, calendar, weather). A daily briefing that digests your tasks, RSS feeds, and weather on a schedule. Knowledge graph, per-user/group sharing, PWA with push notifications, an MCP server for external AI clients, and an Android companion app.

Quick Start

Prerequisites: Docker and Docker Compose. 8 GB+ RAM recommended for LLM inference.

Download docker-compose.quickstart.yml from this repo, then:

# Optional but recommended — set a secret key
export SECRET_KEY=your-random-secret-here

docker compose -f docker-compose.quickstart.yml up -d

Open http://localhost:5000. The first user to register becomes admin. Go to Settings → General to pull an LLM model — qwen3:8b or llama3.1:8b are good starting points.

GPU: Ollama runs CPU-only by default. See the comments in docker-compose.quickstart.yml to enable NVIDIA GPU passthrough.

Development: To build from source, see Development.

Documentation

Doc Contents
Architecture Stack, design decisions, data models, key services
Configuration Environment variables, Docker Compose, production setup, security
Features Detailed feature breakdown and keyboard shortcuts
Development Dev workflow, CI/CD, migrations, release process
API Keys & MCP API key management and Fable MCP install guide
SSO / OAuth OIDC setup for Authentik, Keycloak, and other providers
API Reference All REST API endpoints
Android App Flutter companion app architecture and feature status

License

This project is privately maintained.

S
Description
No description provided
Readme 14 MiB
v26.06.03 Latest
2026-06-03 12:51:04 -04:00
Languages
Python 53.7%
Vue 38.1%
TypeScript 5.8%
CSS 1.4%
Shell 0.8%