Privacy
Everything stays on your machine until you publish. What each command sends and to where, the exact body of the publish request, what the server keeps, how to take a page down, and what madelog export contains.
Local first
The log lives in .madelog/log.db inside the repository, and git is told to ignore .madelog/ through .git/info/exclude, so it never enters your history. There is no account, no login and no email anywhere on the publish path. The server knows a device by its public key and nothing else.
What leaves the machine, per command
| Command | Connects to | What is sent |
|---|---|---|
init, onboard, note, log, verify, export, hide, config, analyze, summary edit, hook post-commit | nothing | nothing |
summarize, with llm.provider auto or ollama | Ollama at OLLAMA_HOST or localhost:11434, on your machine unless you pointed it elsewhere | GET /api/tags, then per week POST /api/generate with the model name, a fixed system prompt, and the week's commit subjects with dates and line counts, public decisions and pattern sentences. Never a diff, never a prompt from an AI session. Summaries shows the exact body |
summarize, with llm.provider none | nothing | nothing |
publish --local | nothing | nothing; it still runs summarize, so the line above applies |
publish | server.url or --server | POST /v1/devices with your public key when server.device_id is empty; then POST /v1/pages, twice, with the body below |
unpublish | server.url or --server | POST /v1/devices when server.device_id is empty; DELETE /v1/pages/<slug> with {device_id, signature} |
The install scripts fetch two files from GitHub Releases and send nothing. Nothing in madelog phones home, checks for updates or reports errors.
The publish request
POST /v1/pages, JSON, these fields and no others (the server rejects unknown fields):
{
"device_id": "5ea56900-b0d3-4683-aa32-fe1e3eca821c",
"slug": "north-fable-4xt3",
"html": "<!doctype html>...",
"root": "6cc1406d82499fc6f69483a603f1f5efe04ac8767edd31674a4f1afaf3b18535",
"first_ts": "2026-09-18T07:53:48.617Z",
"last_ts": "2026-09-18T07:53:49.507Z",
"published_ts": "2026-09-18T07:53:50Z",
"signature": "<128 hex characters>"
}| Field | Content |
|---|---|
device_id | The id the server gave your public key. A UUID, public, not a secret |
slug | Omitted on a first publish; the page's slug on a re-publish |
html | The rendered page, at most 2 MiB. Exactly the file at .madelog/site/index.html |
root | The chain root, 64 hex characters |
first_ts, last_ts | The timestamps of the first and last event in the log |
published_ts | The time of publishing, RFC 3339 UTC to the second |
signature | ed25519 over root + "\n" + published_ts + "\n" + slug, hex |
POST /v1/devices sends {"public_key": "<64 hex characters>"}. The private key never leaves ~/.config/madelog/device.key. The log itself is not uploaded: the page is the only content, and everything in it went through the preview first. The second upload carries the same root and timestamps with the slug filled in and a new signature.
The page carries, in plain HTML: your intent answers, project.name, project.delivered_url and project.screenshot if set, every commit subject with its short sha, every diff excerpt the hook kept, every public decision, every AI session's tool, model-derived tool name and prompt unless hidden, the summaries, the moments, the root and the timestamps. It carries the commit author's name only inside diff excerpts if the diff itself contains it; the author field of commit events is not rendered.
What the server stores
The HTML, the root with its history, the four timestamps, the device id and the public key. It sets no cookies and has no sessions. A published page is public to anyone with the link; links are minted as <adjective>-<noun>-<4 characters> and are listed nowhere. The server never sees the log, private notes, hidden sessions, your redact patterns, your email or your private key. Concepts lists the tables.
Never recorded
- Keystrokes, editor state, screenshots.
- Files git ignores, and untracked files. A diff excerpt is taken only for a tracked, text, non-ignored path.
- Paths that match a
privacy.redactpattern: they still appear in the commit's file list with line counts and status, but contribute no excerpt. - Prompts that match a
privacy.redactpattern. - Model output. A session event never holds the AI's reply;
response_excerptis always null. - Anything outside the repository, with one exception: the first user message of a Claude Code transcript under
~/.claude/projects/, read for sessions Git AI attributed to Claude Code. AI sessions has the rule.
A diff excerpt is at most 20 lines per file, starting at the first hunk header. Binary files never contribute one. A commit that adds a token file under secrets/ with ^secrets/ in the redact list records this and nothing more:
{ "added": 1, "path": "secrets/api.txt", "removed": 0, "status": "added" }Before the page goes out
madelog publish opens a preview of the page in the terminal: every timeline item, the summaries, the moments. From there you can hide any AI session (h) and rewrite any summary (e) before confirming with Enter; q aborts and uploads nothing. --yes skips the preview, for scripts. madelog publish --local renders the same page to .madelog/site/index.html without a connection, so you can read it in a browser first.
Two switches keep things out of the page without leaving the chain:
madelog note --privaterecords a decision that is never rendered, previewed or summarised.madelog hide <event-id>withholds an AI session; the page shows only that one session is hidden.
Redact patterns
privacy.redact is a list of regular expressions in .madelog/config.toml. A path that matches gets no diff excerpt; a prompt that matches is dropped whole. Set it as a comma-separated list:
$ madelog config set privacy.redact "^secrets/, [.]env$, credentials"
privacy.redact = ^secrets/, [.]env$, credentialsA pattern that does not compile is rejected at config set, so a typo never silently stops redacting. Patterns are read by the hook at commit time; a commit recorded before you added one keeps its excerpt in the chain, and the chain cannot be edited. To keep such an excerpt off the page there is no switch in this version; a hidden session hides a prompt, but a commit's excerpt is either recorded or not.
.gitignore
madelog respects git's own ignore rules through libgit2. An ignored path never contributes an excerpt, even if it was force-added. .madelog/ itself is excluded through .git/info/exclude, so it never appears in your .gitignore and never in a commit.
Getting your data out
madelog export writes everything madelog holds about the repository as one JSON document:
$ madelog export ledger-export.json
wrote 16 events to /Users/ana/projects/ledger/ledger-export.jsonThe shape, with the events elided:
{
"version": 1,
"exported_at": "2026-09-18T07:53:49.519Z",
"intent": {
"what": "A CLI that reconciles bank CSV exports against invoices",
"for_whom": "A small accounting firm that closes its books by hand",
"tools": ["claude-code", "cursor"],
"record_ai_sessions": true
},
"intent_md": "# Intent\n\n## What I am building\n\n...",
"config": {
"llm": { "provider": "auto", "model": "qwen3:4b", "remote": false },
"server": { "url": "https://madelog-server-production.up.railway.app" },
"privacy": { "redact": ["^secrets/", "[.]env$", "credentials"] },
"project": { "name": "ledger", "delivered_url": "https://ledger.example" }
},
"root": "6cc1406d82499fc6f69483a603f1f5efe04ac8767edd31674a4f1afaf3b18535",
"events": [ ... ]
}intent is the first intent event's payload; intent_md is the file as text; config is the parsed config, so server.device_id and server.slug appear once a page was published; root is the chain root; events are the rows of the log exactly as madelog log --json prints them, private notes and hidden sessions included, with their hashes. Without a path the document goes to standard output. The device key is not part of the export.
Taking a page down
madelog unpublish sends a signed delete for the page this repository owns. The server clears the HTML and marks the page deleted; /p/<slug> and /v1/verify/<slug> answer 404 from then on.
$ madelog unpublish
north-fable-4xt3 is gone from https://madelog-server-production.up.railway.app. The chain is untouched, and `madelog publish` can put it back.
$ curl -sS https://madelog-server-production.up.railway.app/v1/verify/north-fable-4xt3
{"error":"not_found","message":"no such page"}The server keeps the root history and the slug, so the slug is never handed to someone else and a later madelog publish from the same device brings the page back at the same address. Deleting the whole record from your own machine is rm -r .madelog, plus the two hook lines and the device key if you want nothing left; see Uninstall.
Training
Nothing you record is used to train a model. The only model that ever sees your log is the one you run yourself through Ollama, and it sees commit subjects, decisions and pattern sentences, not diffs or prompts.