f451: Documents Without Handcuffs

Documentation often ends up where it is hardest to get back out. In SharePoint or Confluence, in a format that doesn’t survive leaving the platform intact. Anyone who wants to switch exports and patches. Anyone who wants to know who changed an operating procedure and when finds a version history, but without add-ons usually no approval. The change went live immediately.
f451 is my answer to that. A wiki with a page tree, search, editor, and approvals, whose content lives entirely as Markdown in Git repositories. AI agents such as Claude Code contribute through a bundled MCP server, the standard interface for agent tools. They read, search, and write drafts under the account of the person who runs them, with the same permissions and the same review path. The project has been public since September 28, with source code on GitHub and a live demo.
The name is a nod to Ray Bradbury’s Fahrenheit 451, the novel about a society that burns books. Documents their authors control themselves are the opposite.
Git as the Single Source
What is in the repository counts. f451 derives everything else from it. A space is a repo on Forgejo, a self-hosted Git platform, or on GitHub. A page is a folder with an index.md, subpages are subfolders, and images and diagrams sit in _media/ right next to the page.
space-repo/
├── space.yaml
├── _templates/
│ └── meeting-notiz.md
├── betrieb/
│ ├── index.md
│ ├── _media/architektur.drawio.svg
│ └── deployment/index.md
The front matter contains only a stable id, title, tags, optionally the language, and typed relations to other pages.
---
id: 8f3ka2
title: Deployment
tags: [betrieb, kubernetes]
lang: de
relations:
depends_on: [betrieb/monitoring]
---
Relative links work unchanged in the Forgejo web interface, in Obsidian, and in any text editor. Obsidian understands the [[wikilinks]] from the editor directly, Forgejo shows them as text. The documentation stays readable even if f451 stops running one day.
PostgreSQL is only an index in this architecture. On push, f451 fetches the changed files, extracts links, tags, and metadata, and renders each page to HTML once. If the database is lost, reindex --all rebuilds the index from Git. All that is lost then are sessions, locks, and account links, which users restore by signing in again. The content depends on the repository alone.
I chose Postgres over SQLite because of search. Full-text search should reduce German and English words to their stems, and Postgres ships configurations for both languages. SQLite’s full-text search, FTS5, only knows a method for English words. German words would not be reduced to their stems, and a search for “Server” would not find “Servern”.
Draft, Review, Merge
Nobody in the wiki writes directly to the published version. Every change starts as a draft, and the draft is a branch draft/<pageId>. Requesting approval opens a pull request. The approval itself is a merge. Meanwhile, readers see the approved version while authors keep working on the draft.
| State in the wiki | Representation in Git |
|---|---|
| In progress | branch draft/<pageId> exists |
| In review | open pull request on that branch |
| Released | state on main |
The states are not stored anywhere in the database. f451 derives them on every request from whether the branch exists and whether an open pull request can be found. A second status field could drift from the actual state in Git at some point, and then nobody would know which one to believe.
Before approval, a review view shows the change as a visual comparison, block by block and optionally as a word diff on the Markdown source. If main has moved on since the review started, f451 visibly blocks the approval until the draft has been updated. That step is an explicit choice between taking over the new state and keeping one’s own version. Silent overwriting is not part of the design.
Permissions Come from the Repository
f451 has no role model of its own. Whoever may read a repo sees the space. Whoever may write may create drafts. Whoever may merge may approve. The code has no field and no table for this. Whether someone may approve is decided by the Git provider alone. If it rejects the merge with 403, the question is answered.
Sign-in uses OpenID Connect, with Microsoft Entra ID, Forgejo, or another OIDC provider. Each user then links their Forgejo or GitHub account. Commits are made under that personal account and never under a service account. The Git history of a page therefore shows who actually changed it, not that “the wiki” did.
The documents live in your own repositories. Whoever leaves loses access along with their Git account. There is no sync client that spreads copies to every laptop. Local clones exist only where someone deliberately creates one.
An Editor That Changes Nothing Silently
People who don’t work with Git should still be able to write. The editor therefore has a WYSIWYG mode based on Tiptap (ProseMirror) with a toolbar and [[ autocompletion for links to other pages. A slash menu inserts headings, tables, callouts, and images. Next to it is a raw mode with CodeMirror for the complete document including front matter.
The tricky part is the path between the two. A WYSIWYG editor that reads and writes Markdown tends to change formatting unnoticed. f451 therefore checks before every switch into WYSIWYG mode whether the document can be represented without loss. If it contains raw HTML or footnotes, for example, the editor refuses the switch and says why. If only the notation changes, such as * instead of - in lists, it warns. Parsing and serializing run through the same Markdown pipeline as the reading view, and a test corpus of sample pages in consistent notation has to survive the round trip Markdown → editor → Markdown byte for byte.
The rest is protection against everyday disruptions: autosave after 30 seconds, a visible conflict dialog when someone else has saved in the meantime, and a soft lock for two minutes that warns about simultaneous editing without forbidding it. If the connection drops, the editor buffers changes locally in the browser and pushes them once the server is reachable again. The push goes through the same save path with conflict checking, and when a page is reopened, the editor never applies a buffer silently but asks first.
On top of that come templates per space, draw.io and Excalidraw diagrams that stay editable right inside the page, a knowledge graph of links, hierarchy, and relations, a broken-link report, and a German and English interface with light and dark mode.
AI Agents on the Same Review Path
f451’s MCP service is a stateless translator between MCP and the HTTP API and currently provides 16 tools, from search_wiki and read_page through edit_page and update_page_draft to request_review. Because pages are plain Markdown, agents read and write them without conversion.
Agents get no special path. They authenticate with a personal API token and write under the account of the person who runs them. Their changes start as drafts like any other. Technically, an agent may therefore do exactly what its human may do, including approving via release_page if that person has merge rights. The only thing stopping it is the bundled prompt template, which leaves approval to a human.
save_diagram is built specifically for agents. The agent sends no SVG and no Mermaid, but a description made of lanes and steps. The service computes the layout, not the language model, and the agent only inserts the returned Markdown snippet into the page.
Architecture and Origin
f451 is a pnpm monorepo in TypeScript with three applications (API, web interface, MCP service) and shared packages.
| Part | Role |
|---|---|
apps/api (Fastify) | indexing, read and write endpoints, review workflow, permission checks, webhooks |
apps/web (Next.js) | user interface, forwards /api, /auth, and /media to the API |
apps/mcp | access for AI agents |
packages/markdown | one shared Markdown pipeline for reading and writing |
packages/editor | editor core on ProseMirror/Tiptap |
packages/git-provider | adapter for Forgejo and GitHub |
packages/design-tokens | colors, type, and spacing as a token catalog |
The Git provider adapter is the most critical interface. Both implementations, Forgejo and GitHub, have to pass the same contract test suite, a shared set of tests that pins down the behavior of the interface. Everything above it only knows the interface.
The design specification dates from July 9, 2026. After that, f451 was built in phases, from the reading view through the editor core, editor interface, and review workflow to templates and resilience, each phase accepted through end-to-end tests in Playwright. These tests spin up a fresh stack of Postgres and Forgejo containers and check the review flow with a real second identity, not an admin token.
Development ran with Claude Code. The code comprises about 80,000 lines of TypeScript, roughly 35,000 of them tests (own count with wc -l, including blank and comment lines). How the project came about is described in the development chronicle (German) in the repo.
Live Demo and Quick Start
A public instance runs at f451.rotecodefraktion.de. Sign in through Forgejo with one of two demo accounts.
| Account | Password | Permissions |
|---|---|---|
demo | da9d33b2efdb41ed | reads every space |
writer | 43dc099d5da028f4 | also writes in the Playground |
The demo is reset every night. It contains the documentation of f451 itself, split into a User Guide, Developer Guide, and Admin Guide, plus a Playground for trying out drafts and reviews.
Locally, f451 needs Docker or Podman, Node 22, and pnpm 9. The browser and the containers must reach Forgejo and the wiki under the same address. localhost does not work, because inside a container it points to the container itself. With your machine’s LAN address as <IP>, the steps look like this.
git clone https://github.com/rotecodefraktion/f451.git && cd f451
cat > deploy/git/docker-compose.override.yml <<EOF
services:
forgejo:
environment:
FORGEJO__server__ROOT_URL: http://<IP>:3300/
EOF
docker compose -f deploy/git/docker-compose.yml up -d
FORGEJO_URL=http://<IP>:3300 WEB_BASE=http://<IP>:8080 ./scripts/dev-local-setup.sh
cd deploy/wiki && docker compose up -d --build
The setup script creates an admin, an organization, an OAuth app, and a demo space. Then sign in through Forgejo at http://<IP>:8080 and link the Forgejo account under Settings → Connections. Only then do the spaces become visible. This setup is meant for local development only. For production, deploy/BETRIEB.md (German) covers environment variables, backup, restore, and a runbook for incidents.
License
f451 is source-available, not Open Source in the OSI sense. The license builds on the PolyForm Shield License 1.0.0 and adds its own conditions. The full text is in LICENSE.md.
Anyone, companies included, may use, change, and share f451 free of charge. Paid services around f451, meaning installation, customization, and operation for someone who uses it themselves, are free as well. A license is needed only to sell f451 or a changed version, or to offer it as a paid hosted service. Every installation must visibly show the notice “Based on f451 by www.rotecodefraktion.de”.
Excluded from use are the far-right AfD, its affiliated organizations, and the anti-democratic organizations and platforms named in the license.
Sources
- f451 on GitHub: github.com/rotecodefraktion/f451
- Live demo: f451.rotecodefraktion.de
- Development chronicle (German): docs/entwicklungschronik.md
- f451 license: LICENSE.md
- PolyForm Shield License 1.0.0: polyformproject.org
- PostgreSQL, text search dictionaries: postgresql.org
- SQLite FTS5: sqlite.org/fts5.html
- Tiptap: tiptap.dev
- Forgejo: forgejo.org