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.
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.
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 | in dev · phase 5 | ✓ | ✓ | ✓ |
| mobile app | in dev · phase 6 | ✓ | ✓ | ✓ |
| 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.1.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), Tauri desktop + graph view (phase 5), mobile via uniffi (phase 6). 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?
Not yet. v0.1.0 is the TUI (terminal interface). The Tauri desktop GUI is roadmap phase 5; mobile (SwiftUI for iOS, Compose for Android) is phase 6. Both will 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 --path ~/notes` opens the TUI on today's journal. First-run cost is ~30 seconds.
one binary.
your notes.
No electron. No account. No cloud. Press ▷ and you're typing in a journal.
brew install outl cargo install outl git clone https://github.com/avelino/outl && cd outl && cargo build --release outl init ~/notes && outl --path ~/notes homebrew formula is in progress — for now, install via cargo. a one-line install script lands with the brew tap.