notes that don't
corrupt
when you
sync them.
outl is a local-first outliner. Plain markdown is the source of
truth. The CRDT lives in a sidecar, not in your files.
Two devices, both offline — they sync to the
same tree, no data loss, no server. And your LLM reads them as a second brain over MCP.
two devices edit
offline.
they sync to the
same tree.
Roam keeps your notes on their servers — the later write
silently wins. Logseq scatters
id::abc123
UUIDs through your .md so rsync has
something to match on; concurrent moves still lose data.
git gives you conflict markers
across nested bullets every time.
outl uses the Kleppmann et al. 2022 tree CRDT — the algorithm that backs Automerge and Y.js, adapted for trees.
- ✓ Strong eventual consistency. Same ops → same tree, any order.
- ✓ Commutative after reordering. Late arrivals don't break the result.
- ✓ Idempotent. Apply twice = apply once.
- ✓ Tree invariant always holds. No node has two parents, no cycles.
- ✓ No silent loss. Every op stays in the log — even ones turned into no-ops.
your code
your data
your notebook.
Markdown fences aren't just for syntax highlighting. Drop a
```python,
```lisp,
```js,
```lua or
```rust
block. The result lands as a
> result:
subblock right underneath it. Re-runs are idempotent.
Set auto-run::
on a block and it re-runs every time you open the page —
cache-aware by source hash. Adding a new language is an
~80-line adapter.
```python
def fib(n):
return n if n < 2 else fib(n-1) + fib(n-2)
print([fib(i) for i in range(10)])
``` auto-run:: on
the markdown you see
is the markdown you wrote.
No id:: lines.
No UUIDs. No HTML comments smuggling metadata. The IDs the CRDT
needs live in a separate sidecar file
(.foo.outl) — version it, or don't.
Your .md is yours.
git diff is what
you changed — not 30 UUID lines that shifted parents.
outl is your
LLM's second brain.
The same notes you write in the TUI are the notes Claude, Cursor, Zed and ChatGPT read through the Model Context Protocol. One binary. One config block. No upload — your second brain stays on your disk.
- → read any page, search the graph, list backlinks, query by tag.
- → write new blocks, append to today's journal, toggle a TODO — from the chat.
- → pin
outl://daily/todayas a resource so the model has today's context every turn.
{
"mcpServers": {
"outl": {
"command": "outl",
"args": ["--workspace", "~/notes", "mcp", "serve"]
}
}
} MCP is an open standard. Any new host that adopts it picks outl up for free — the binary doesn't change.
we like the ideas.
we don't like the trade-offs.
Roam, Logseq and Obsidian got a lot right — graph thinking, backlinks, daily journals, block addressability. outl picks those up and fixes the sync, the file format, and the runtime.
| feature | outl MIT · rust | Roam closed · cloud | Logseq AGPL · electron | Obsidian closed · electron |
|---|---|---|---|---|
| markdown is the source of truth | ✓ | ✕ | partial | ✓ |
| no UUIDs in your .md | ✓ | n/a | ✕ | ✓ |
| block-level outliner | ✓ | ✓ | ✓ | plugin |
| [[wiki-links]] + backlinks | ✓ | ✓ | ✓ | ✓ |
| daily journal first-class | ✓ | ✓ | ✓ | ✓ |
| code blocks that execute | 5 langs | ✕ | limited | plugin |
| tree-CRDT algorithm (provably correct) | ✓ | ✕ | ✕ | ✕ |
| P2P device sync (offline-safe) | in dev · phase 2 | cloud | paid | paid |
| works offline, no account | ✓ | ✕ | ✓ | ✓ |
| open source | MIT | ✕ | AGPL | ✕ |
| ships as a native binary | ✓ | ✕ | ✕ | ✕ |
| TUI / terminal editor | ✓ | ✕ | ✕ | ✕ |
| vim-style keys built-in | ✓ | ✕ | plugin | setting |
| desktop GUI app | macOS beta | ✓ | ✓ | ✓ |
| mobile app | iOS beta | ✓ | ✓ | ✓ |
| graph view | in dev · phase 5 | ✓ | ✓ | ✓ |
| plugin system | in dev · phase 4 | partial | ✓ | ✓ |
$ outl import logseq ~/graph ~/notes
$ outl import roam ~/backup.json ~/notes strips id::, resolves ((uid)) refs, slugifies filenames, seeds sidecars. unresolved refs stay as ((unresolved:UID)) for triage.
v0.6.0 ships the tree-CRDT algorithm + op-log infrastructure (validated by 10+ property tests) and a daily-driver-ready TUI. The pieces marked in dev above are on a public roadmap with phase numbers: P2P transport via iroh (phase 2), queries (phase 3), plugins via rhai (phase 4), graph view (phase 5). macOS desktop is already shipping in beta via Homebrew cask (install here), iOS is on public TestFlight beta (join here); Android (phase 6) will share the same outl-core via uniffi. See the roadmap.
questions people actually ask.
What is outl?
outl is a local-first outliner — a bullet-point note-taking app where every line is a block you can move, reference and search. It stores plain markdown on disk (no UUIDs, no metadata pollution), has a tree-CRDT under the hood for sync that doesn't lose data, and ships as a single Rust binary with a vim-style TUI.
How is outl different from Logseq, Roam or Obsidian?
Three things. (1) Sync: Roam loses data on offline conflicts, Logseq Sync is rsync-flavored and overwrites, Obsidian Sync is a paid black box. outl uses a Kleppmann tree-CRDT with five formal guarantees. (2) File format: Logseq writes UUIDs into your .md files. outl never does — IDs live in a sidecar. (3) Stack: outl is open source (MIT) and ships as a native binary; the others are Electron or proprietary.
Is outl open source and free?
Yes. outl is licensed under MIT and the entire codebase is on GitHub at github.com/avelino/outl. There is no paid tier, no account, no telemetry. Free forever.
Does outl work offline?
outl is local-first. Your notes live as .md files on your machine. There is no server, no account, no cloud requirement. You can use outl on a plane and lose nothing.
What does "local-first outliner" mean?
Local-first means your data lives on your device as plain files you own. The app reads and writes them. If outl disappears tomorrow, you can open every note in VS Code, cat, or any markdown editor — nothing is trapped. Outliner means the editor is built around nested bullets (blocks) instead of long prose, so you can collapse, reorder and link individual thoughts.
Can I import my notes from Logseq or Roam Research?
Yes. Run `outl import logseq <path-to-graph> <dst>` for Logseq, or `outl import roam <backup.json> <dst>` for Roam. The importer strips id:: lines, resolves ((uid)) block references, slugifies filenames and seeds the sidecars. Unresolved refs are kept as ((unresolved:UID)) for manual triage.
Does outl have backlinks and a graph view?
Backlinks are first-class — every page has a backlinks panel showing what references it. Wiki-style [[page name]] links and #tags work out of the box. A visual graph view is on the roadmap for phase 5 (Tauri desktop).
Can I run code inside my notes?
Yes — this is a core feature, not a plugin. Drop a fenced code block in Python, Lisp, JavaScript, Lua or Rust. The result lands as a markdown blockquote subblock right under the source. Re-runs are idempotent and cache-aware (SHA-256 of source). Set `auto-run:: on` and it re-runs every time you open the page.
Is there a mobile app or desktop GUI?
Yes — both in beta. macOS desktop ships as a Homebrew cask: `brew tap avelino/outl https://github.com/avelino/outl && brew install --cask outl-desktop@beta` (universal dmg, Apple Silicon + Intel). iOS is on public TestFlight. Android (phase 6, Compose) is next. All frontends share the same outl-core and outl-md Rust crates, so the algorithm and file format don't fork.
How do I install outl?
`brew install outl` (Homebrew formula coming soon) or `cargo install outl` (works today). Then `outl init ~/notes && outl --workspace ~/notes` opens the TUI on today's journal. First-run cost is ~30 seconds.
desktop, mobile, terminal —
same notes.
One workspace folder on disk. Three surfaces reading the same plain markdown — no proprietary database, no upload. Open it where the moment finds you. · click any screen to zoom
one workspace folder. every surface reads it. CRDT sync keeps them in lockstep, even when you go offline.
one binary.
your notes.
No electron. No account. No cloud. Press ▷ and you're typing in a journal.
brew tap avelino/outl https://github.com/avelino/outl brew install outl@beta cargo install outl git clone https://github.com/avelino/outl && cd outl && cargo build --release outl init ~/notes && outl --workspace ~/notes
currently shipping outl@beta via the
avelino/outl tap.
stable brew install outl arrives with 1.0.
no brew? grab a binary for macOS, Linux or Windows →