Self-hosted engineering decision memory. A place to record the trade-offs you weigh, the experiments that test them, and the lessons you'd otherwise re-learn the hard way.
Causelog is a single Rust binary with an embedded SQLite database — no external services, one machine, one backup to care about.
The golden path is: goal → decision → experiment → lesson → timeline & graph.
- Projects & goals — what "done" looks like, per project.
- Decisions — the options you weighed (pros/cons), the choice you made, and the rationale. Every change is kept as an immutable revision.
- Experiments — a falsifiable hypothesis, a lifecycle (planned → running → done/abandoned), and timestamped observations. When you finish, capture the lesson as a note.
- Notes — durable knowledge, extracted from experiments or written directly, with the same revision history.
- Timeline — the story of a project: when things started, ended, and what you measured along the way.
- Graph — how entities connect: what serves a goal, what an experiment
tests, where a note came from, plus explicit typed links
(
supports/rejects/follows/related). - Search — full-text over every entity, kept in sync automatically.
- Export — a canonical, versioned snapshot of any project, derived on demand and downloaded from the project's Export page or the CLI. Formats: JSON (the reference/backup format), a Git-friendly Markdown tree, an offline static HTML site, a single Archive bundle of all three, and an Impress/LibreOffice-compatible ODP slideshow built from the project story.
Prebuilt Linux x86_64 releases are published on the Releases page. Download the archive, unpack, and run — no Rust toolchain required:
tar -xzf causelog-v0.2.0-x86_64-linux.tar.gz
./causelog serve
# → http://127.0.0.1:8080/setup — create the admin accountcargo run -- serve
# → http://127.0.0.1:8080/setup — create the admin accountAfter setup, register additional users at /register. New accounts require
admin approval before they can log in.
Or skip the setup dance with a seeded demo — three projects (two of them unapologetically funny), with goals, decisions, experiments, notes, links, and a searchable history:
cargo run -- seed-demo
# demo / demo-password — admin, owns all three projects
# alice / longenough1 — approved member (Gloria project)
# bob / longenough1 — registered, pending admin approvalUsage: causelog [COMMAND]
Commands:
serve Start the Causelog server (default)
seed-demo Create a first user and a three-project demo, then exit
export Export a project from a local database
export writes a snapshot of any project by id or title to JSON, a Markdown
tree, an offline HTML site, an Archive bundle, or ODP slides:
causelog export "The Coffee Machine Uprising" --format json # → stdout
causelog export <project-id> --format markdown --output ./out # → Markdown tree
causelog export <project-id> --format html # → static site dir
causelog export <project-id> --format archive --output story.zip # → bundle
causelog export <project-id> --format odp --output slides.odp # → slideshowserve flags (all also settable via env):
| Flag | Env | Default | Meaning |
|---|---|---|---|
--database-url |
DATABASE_URL |
sqlite://causelog.db |
DB file or sqlite::memory: |
--addr |
CAUSELOG_ADDR |
127.0.0.1:8080 |
Bind address |
--tls-domain |
CAUSELOG_TLS_DOMAIN |
— | Automatic HTTPS via Let's Encrypt |
--tls-cert / --tls-key |
CAUSELOG_TLS_CERT/KEY |
— | Bring your own cert |
--tls-cache-dir |
CAUSELOG_TLS_CACHE_DIR |
./tls |
ACME cache |
--no-http-redirect |
— | off | Skip the 80→443 redirect |
The Let's Encrypt path is the easiest production setup behind a public IP:
DATABASE_URL=sqlite:///srv/causelog/causelog.db \
CAUSELOG_ADDR=0.0.0.0:8443 \
CAUSELOG_TLS_DOMAIN=causelog.example.com \
causelog serveCertificates are renewed automatically and hot-reloaded. Behind a reverse proxy (Caddy, nginx, Traefik), run plain HTTP on the loopback and let the proxy terminate TLS.
docker compose -f deploy/docker-compose.yml up -d --build
docker compose -f deploy/docker-compose.yml run --rm causelog seed-demo
# → http://localhost:8080
# demo / demo-password (admin), alice / longenough1 (member), bob / longenough1 (pending)Data lives in ./data/causelog.db on the host.
SQLite is a single file — back it up consistently with the included script:
./deploy/backup.sh data/causelog.db data/backups 14 # retention: 14 daily copiesThe script uses sqlite3 .backup (safe against a live writer) when available
and falls back to a plain copy. Schedule it with cron:
0 3 * * * /srv/causelog/deploy/backup.sh /srv/causelog/data/causelog.db /srv/causelog/data/backups 14
Stop the container, replace the database file, start it again:
docker compose -f deploy/docker-compose.yml stop
cp data/backups/causelog-2026-08-15.db data/causelog.db
docker compose -f deploy/docker-compose.yml start- Multi-user with admin approval. Causelog starts un-set-up: the first
account created at
/setupbecomes the admin. Additional users register at/registerand wait for admin approval. Projects can have members (owner and member roles); non-members cannot see a project's contents. - Immutable history. Decisions and notes append a Markdown snapshot to a
revisionstable on every change, so the record of what you decided and why can't be silently rewritten. - Everything is searchable through an FTS5 index kept in sync by database triggers, including content written before the index existed.
- AGPL-3.0-only. This is a tool for thinking out loud about engineering; it ships under the same copyleft as the reference implementation it mirrors.
Three layers plus a browser suite, run with a single cargo test --workspace
(130 Rust tests and 10 Playwright browser tests):
-
Unit — pure functions in the
contentcrate (markdown sanitising, date parsing) and server helpers (options/link parsing, snippet highlighting, password hashing, cookie/CSRF behaviour, registration validation), plus the export crate's pure renderers (JSON, Markdown, HTML, Archive, and ODP — including zip/mimetypeshape). -
Integration (
crates/server/tests/api.rs) — the full HTTP surface against an in-memory SQLite database, from setup to search, including multi-user flows (registration, admin approval, project membership, role-based access control) and the export page/download endpoints. -
E2E (
crates/server/tests/e2e.rs) — boots the realcauselogbinary on a free port with a temporary database, drives the golden path over HTTP with a cookie jar, then restarts the process to prove data survives, plusseed-demoand CLI smoke tests (includingcauselog exportround-trips). -
Browser E2E (
e2e/) — the same journey through a real Chromium via Playwright: it clicks the actual buttons, runs the page's JavaScript (the password toggles, the<details>forms), and shares one owner session. This catches UI regressions the HTTP layer can't see. Requires Node and Playwright's Chromium:cd e2e npm ci npx playwright install chromium npm run test:e2eSet
CAUSELOG_BIN=../target/debug/causelogto reuse a built binary instead of letting the harness runcargo run. CI does this in a dedicated job.
cargo fmt --all --check and cargo clippy --workspace --all-targets -- -D warnings
must stay clean; GitHub Actions enforces all of it on every push and PR.
AGPL-3.0-only. See LICENSE for details.
