Skip to main content

zettel

Zettel is a zettelkasten knowledge base for code repositories. It keeps atomic notes as markdown files in .zettel/. Notes link to each other through YAML frontmatter and inline [[id]] references. Git tracks the notes next to the code, and no external service holds them.

During research an agent reads code, traces bugs, and compares options. That work is usually lost at the end of the session. Zettel writes it down as notes labeled with their provenance: who produced each piece of text, and what kind of claim it makes. A human reviews the agent content later and approves what they stand behind.

Install

The command is zettel, whichever package you install.

Archives of precompiled binaries are available for macOS and Linux. The Linux binaries are static executables. Each archive holds the binary, its man pages, the README and the licence. There is no Windows build.

Package manager Package Command
Homebrew cjohnhanson/tap/zettel brew install cjohnhanson/tap/zettel
Cargo zttl cargo install --locked zttl
uv zttl uv tool install zttl
npm @cjohnhanson/zttl npm install -g @cjohnhanson/zttl

On Debian or Ubuntu, download the .deb from the releases page and install it:

wget https://github.com/cjohnhanson/zettel/releases/download/v0.3.0/zttl_0.3.0-1_amd64.deb
sudo dpkg -i zttl_0.3.0-1_amd64.deb

To run it once without installing anything:

uvx zttl read
npx @cjohnhanson/zttl read

Building

zettel is written in Rust, so you need a Rust installation to compile it. zettel compiles with Rust 1.88 or newer. A C compiler is needed as well, which aws-lc-sys uses for its cryptography.

To build zettel:

git clone https://github.com/cjohnhanson/zettel
cd zettel
cargo build --release
./target/release/zettel --version

Usage

zettel init                                                  # run inside a git repo
zettel note create "Connection pooling causes stale reads" \
  --tag bug,postgres --body "Pool reuses sockets after failover..."
zettel note list --tag postgres
zettel search "stale read"
zettel context a3f2 --depth 2

Every command:

zettel init                                  # make .zettel/ in a git repo
zettel note create "Title" -t a,b -p agent:summary   # make a note; set tags and provenance
zettel note list [--tag t] [--provenance p]  # list the notes
zettel note list --unreviewed                # list the notes with unreviewed agent content
zettel note show <id>                        # show the full content of one note
zettel note edit <id> --add-link b7c1        # link one note to another
zettel note review <id> [--approve all]      # list provenance spans; approve agent content
zettel note delete <id>                      # remove a note
zettel read [--tag t] [--provenance p]       # show the content of the matching notes/spans
zettel search <pattern>                      # search all notes with a regex
zettel backlinks <id>                        # show the notes that link to this note
zettel context <id> --depth N                # show the notes within N hops
zettel orphans                               # show the notes with no links
zettel store list                            # show this store and the stores it declares
zettel store sync                            # fetch the declared remote stores into the cache
zettel store root [<path>]                   # show or set the store that reads fall back to
zettel check                                 # check for broken links and invalid provenance
zettel migrate                               # convert pre-provenance notes (status keys)
zettel stats                                 # show counts, tag distribution, and connectivity
zettel serve [--bind ADDR] [--access MODE]   # serve this knowledge base over MCP
zettel docs [topic]                          # show the bundled documentation
zettel prime                                 # print what zettel is, for an agent's context

How it works

Each note is one markdown file with YAML frontmatter:

---
title: Connection pooling causes stale reads
tags: [bug, postgres]
links: [b7c1]
provenance: agent:summary
---

After failover, the pool reuses sockets bound to the old primary.
See [[b7c1]] for the workaround.

<!-- prov agent:inference -->
The 2026-08-02 retry storm probably started here.
<!-- /prov -->

<!-- prov human:cody -->
Confirmed with the infra team.
<!-- /prov -->

The note ID is the filename stem, so the frontmatter does not repeat it. Zettel keeps the notes in one flat .zettel/ directory:

.zettel/
  a3f2-connection-pooling-causes-stale-reads.md
  b7c1-workaround-force-new-connection.md

Notes connect through the frontmatter links field and inline [[id]] references. Zettel walks the note graph. It computes backlinks, finds the orphan notes, shows the neighborhood of a note, and checks for broken links.

Every piece of text has a provenance. The frontmatter provenance: key sets the note default; a <!-- prov ... --> marker overrides it for one section. The origins:

  • human[:name] — a person wrote it.
  • agent[:kind] — an agent wrote it: a summary of sources, an index, or an inference (a new claim not present in the sources).
  • citation[:source] — quoted verbatim; the source is a note ID or a src=<url> attribute. A citation of a note is a link-graph edge.
  • No provenance means unknown. Unknown is never upgraded to human, so an agent that forgets the label cannot mint human-authored text.

A human approves agent content with zettel note review <id> --approve, which writes a reviewed= stamp. A later reader filters by all of this: zettel read --provenance human,citation,reviewed returns only the text a human wrote, quoted, or vouched for. Nothing verifies a label against what actually wrote the span. Reading the files directly gives the same labels and the same caveat.

Composed stores

A knowledge base can link into others. Declare them in stores.yml:

format: 2
stores:
  - alias: project
    path: ../project                            # this machine
  - alias: handbook
    git: https://example.com/org/handbook       # a git repository
  - alias: archive
    blob: https://example.com/notes             # an https prefix with an index.txt

zettel store sync fetches the remote stores into a local cache. It is the only command that reaches the network.

A reference then names the store. Write [[project:a3f2]] in a body, project:a3f2 in links:, or citation:project:a3f2 in a provenance marker. A reference with no alias stays local.

The declarations set the direction. A personal knowledge base declares the repositories that it annotates. A repository does not declare the personal knowledge base. It cannot: the target does not exist for the other users who clone the repository. Two repositories can declare each other, because each one is equally reachable.

Each command runs from one store. It reads that store and the stores that the store declares. Thus one note has different backlinks in different stores. Dependency stores are read-only.

Serving over MCP

zettel serve offers the same knowledge base to a Model Context Protocol client. The library answers both interfaces, so the server returns what the CLI returns.

zettel serve                        # speak MCP on stdin and stdout
zettel serve --bind 127.0.0.1:7431  # serve over HTTP for a client that connects
zettel serve --access read-write    # also allow note creation

The server is read-only by default. With --access read-write it stamps every note it writes as agent-written. It refuses any other provenance, and it never exposes approval. A served store has no authentication: bind it to 127.0.0.1, or put a proxy that authenticates in front of it.

Documentation

Run zettel docs to read the same documentation from the binary.

  • tisket — issue tracker. Markdown issues with YAML frontmatter, in the repository
  • almanac — agent skill index, over pluggable sources
  • gaff — context-lifecycle handler for coding agents
  • missouri — end-to-end tests as directed graphs of filesystem states
  • mdstore — the frontmattered markdown library zettel stores notes with

License

MIT.

Metadata

Release files for zttl 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for zttl 0.3.0
File Size Uploaded
zttl-0.3.0.tar.gz 112.2 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for zttl 0.3.0
File
zttl-0.3.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.musllinux_1_1_x86_64.whl Python 3 none Linux musl 1.1+ x86-64, Linux glibc 2.17+ x86-64 Details
zttl-0.3.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl Python 3 none Linux glibc 2.17+ ARM64, Linux musl 1.1+ ARM64 Details
zttl-0.3.0-py3-none-macosx_11_0_arm64.whl Python 3 none macOS 11.0+ ARM64 Details
zttl-0.3.0-py3-none-macosx_10_12_x86_64.whl Python 3 none macOS 10.12+ x86-64 Details

Total release size: 32.3 MB

Release files / zttl-0.3.0.tar.gz

Download URL zttl-0.3.0.tar.gz
Size 112.2 kB
Tags Source
SHA-256 checksum
How to use checksums
a9bd5ede81a6eda0a466c5f59e6600b5ce2b67148b58223639dbeff6c5521bc7
BLAKE2b-256 checksum
How to use checksums
bf79c00a7ab344c3fb03abc5ff4de7501ace9a3e610986af0861f36fe50f2537
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / zttl-0.3.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.musllinux_1_1_x86_64.whl

Download URL zttl-0.3.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.musllinux_1_1_x86_64.whl
Size 8.5 MB
Tags Linux glibc 2.17+ x86-64 Linux musl 1.1+ x86-64 Python 3
SHA-256 checksum
How to use checksums
d67113ce33f049bdbc39ac0947948557eeb88180ea07407949f9b7f7e2bd5d56
BLAKE2b-256 checksum
How to use checksums
5baf64615828e53456dc68c77488b8189b599fd0d8be2e6f83469927087c8aa4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / zttl-0.3.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl

Download URL zttl-0.3.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl
Size 7.9 MB
Tags Linux glibc 2.17+ ARM64 Linux musl 1.1+ ARM64 Python 3
SHA-256 checksum
How to use checksums
e59e4efc0f9399f009d36170a27eaeaf4548f816cb2d74a4955bd9380973a56d
BLAKE2b-256 checksum
How to use checksums
611de8acdb2a5bbe96f28be00d14d720bdc3171bd8c51c0d0a6c827669f88d42
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / zttl-0.3.0-py3-none-macosx_11_0_arm64.whl

Download URL zttl-0.3.0-py3-none-macosx_11_0_arm64.whl
Size 7.8 MB
Tags Python 3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
b9ec924bd25e4f78bf6d3fbb35e4a45281f62ef989626e9649cd324a498e1905
BLAKE2b-256 checksum
How to use checksums
ad3fbb87038037af6c31c924b891edffd9cff6f549ce4ff83b2356024feb95b5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / zttl-0.3.0-py3-none-macosx_10_12_x86_64.whl

Download URL zttl-0.3.0-py3-none-macosx_10_12_x86_64.whl
Size 8.0 MB
Tags Python 3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
598ca09b6e93eb66f10e33a4d50573c8971ef7a3b71354e2e922fdc85538c657
BLAKE2b-256 checksum
How to use checksums
043c86d2d05577f71d08bd3c548e372f277e81cafe0d9ecce0c014d4158326df
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.3.0 This release

5 release files

0.2.5

5 release files

0.2.4

5 release files

0.2.3

5 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page