nums = [1, 1, 2, 3, 5, 8, 13] print(sum(nums) / len(nums))
the idea is
embarrassingly small.
You're outlining a problem. You want to verify a calculation. Today: tab to a REPL, paste, run, tab back, paste the result. Maybe annotate. Two days later you can't tell which number was hand-typed and which one was computed.
outl removes the tabbing. The code lives in your note. The result lives in your note. The note is the notebook.
A fenced code block isn't syntax highlighting. It's an invitation.
press ▷ on this.
```python
from datetime import date
commits = [42, 17, 31, 9, 28, 14, 22]
total = sum(commits)
avg = total / len(commits)
print(f"{total} commits · avg {avg:.1f}/day")
``` a91f2c3 auto-run:: on
The button on the right is the same key as gx in the TUI. Or set auto-run:: on on the block and it runs every time the page opens — cache-aware, so unchanged source returns the cached result in <1ms.
five languages,
one trick.
Each language is an outl-exec adapter — about 80 lines of Rust. The runtime registry is plugin-shaped: a new language ships as a new crate.
(define (sq x) (* x x)) (map sq '(1 2 3 4 5))
const days = (a, b) =>
(b - a) / 864e5;
days(new Date('2026-01-01'), new Date()) local t = {1, 2, 3, 4, 5}
local sum = 0
for _, v in ipairs(t) do sum = sum + v end
return sum fn main() {
let v: Vec<i32> = (1..=10).collect();
println!("{}", v.iter().sum::<i32>());
} auto-run
and the hash cache.
Set auto-run:: on on a block. Every time the page opens, the block evaluates — but only if its source changed.
a91f2c3a91f2c3 → "194 commits · avg 27.7/day"a91f2c3
The hash is stamped into the result subblock as source-hash::. If you edit the source, the hash changes, the cache misses, the block re-runs. Deterministic and observable.
the sandbox
is paranoid.
Every block runs in wasmtime with WASI cut down to the bone. If a random gist on the internet runs in your outl, it can't reach anything that matters.
the runtime can't see your filesystem. The .md file calling it doesn't even exist from inside the sandbox.
no environment variables, no $HOME, no shell. The block runs in a vacuum.
no network. No http requests, no DNS, no databases. If you want net, lift the source out and run it in a real shell.
the runtime instruction counter is capped. An infinite loop bails out cleanly instead of pegging your CPU.
wall-clock timeout via wasmtime epoch interruption. A frozen runtime gets aborted.
stdin/stdout/stderr are captured in memory. Whatever you print is the result block; no log files, no side effects.
Pasting a stranger's snippet into a regular REPL is a leap of faith. Pasting it into outl is a contained experiment.
vs the usual
suspects.
| feature | outl | Jupyter | Notion | Dataview | Logseq | Observable |
|---|---|---|---|---|---|---|
| markdown is the source of truth | ✓ | .ipynb (JSON) | ✕ cloud | via .md | via .md (with id::) | .ocnb (JSON) |
| multi-language native | 5 langs | via kernels | ✕ | JS only | limited | JS only |
| result lands as markdown | ✓ > result: | output cell | cell | rendered | limited | cell |
| source-hash cache | ✓ SHA-256 | manual | ✕ | memoized | limited | live |
| WASM sandbox by default | ✓ | ✕ host | ✕ cloud | host JS | host | ✕ host |
| works offline, no account | ✓ | ✓ | ✕ | ✓ | ✓ | limited |
| open source | MIT | BSD | ✕ | MIT plugin | AGPL | ✕ partial |
- +markdown is yours forever — the source format your editor already speaks
- +five languages, no kernel install
- +sandboxed by default, not as opt-in
- +native binary, no Electron tax
- −no rich output yet — matplotlib plots, dataframes, images are stdout-only
- −no shared kernel state across blocks — each block is its own ephemeral runtime
- −no third-party Python packages (RustPython subset; no numpy yet)
- −no interactive widgets — the result is text
run code,
keep markdown.
One binary. Five runtimes. Sandbox by default.