olai alpha

ஓலை — the palm leaf Tamil was written on for two millennia. Nodes are leaves; your files are the manuscript.

An outliner for people who think the filesystem was right all along.

Self-hosted, AI-native alternative to Workflowy. Your outline is a bunch of #lang olai files in a git repo. You edit them with $EDITOR or point a coding agent at them. A small Racket server renders the tree to your phone. That's it. No cloud, no accounts, no Electron.

github.com/juspay/olai docs/syntax.md docs/cli.md

Why

Workflowy got the data model right (one big tree, mirrors, dates) and the ownership model wrong (their server, their format, their AI). olai inverts it:

Mirrors fall out of the language for free: a node is a binding, referencing it twice is a mirror. Define-before-use kills most cycles before they exist.

Syntax

Quoteless outline, the flagship surface. Nest with 2 spaces; attach notes and dates under a title.

#lang olai

Inbox #capture
  : Quick capture landing zone
  Buy milk — don't quote me
    @date 2026-08-04T18:00
  [x] Already shipped the pitch
  [/] Wiring the third state
  Agent work ^agent
  Wired the CLI
    @done 2026-08-03
  This week
    *agent          ; a mirror, not a copy

Titles and notes are Markdown at render time (web view only); stored strings stay raw. Check off with [x] or @done — one node, one of them; [/] or @doing is the state in between, and done clears it. A title-trailing ^anchor declares a node; a line that is only *anchor is a mirror of it, reaching anywhere in the loaded tree, @included fragments too.

Under the hood every outline becomes s-expressions. Same expander, same error messages:

#lang olai/sexp
(t "Inbox #capture"
   #:description "Quick capture landing zone"
   (t "Buy milk" #:date "2026-08-04T18:00")
   (t "Wiring the third state" #:doing)
   (t "Already shipped the pitch" #:done)
   (t "Wired the CLI" #:done "2026-08-03"))

Agents are the primary CLI users

They are the only ones. Every command replies in JSON — always, with or without --json, which is still accepted and does nothing — and errors are the error object on stderr. Exit codes are contract: 0 ok, 1 usage, 2 the outline said no, 3 file not found. JSON fields are append-only within a version. No ANSI, no terminal tree, no plain mode — the human view is the web app.

$ olai tree examples/Example.rkt
{
  "version": 1,
  "file": ".../Example.rkt",
  "tasks": [
    {
      "title": "Inbox #capture",
      "date": null,
      "description": "Quick capture landing zone",
      "done": null,
      "doing": null,
      "status": "open",
      "id": null,
      "key": "pd076e677",
      "tags": ["capture"],
      "children": [ ... ]
    }
  ],
  "anchors": { "agent": { "title": "Agent work", ... } },
  "task_count": 12, "mirror_count": 1, "anchor_count": 1
}

key is a node's stable identity — its ^anchor, else a hash of its defining file plus the ordinals that reach it. It survives renaming the node or any ancestor. The web view addresses nodes by it.

commandwhat it does
checkvalidate the module(s); the language is the only validator
treethe task forest
add, done, doing, move, dailythe write path — auto-commits when the file's dir is a git work tree
servethe web view

The view

olai serve renders your outline over htmx and pushes changes over SSE: a file moves on disk, every open tab redraws with no refresh. It is installable as a PWA (manifest, icons, theme-color) — live view only, no offline shell, so it still wants the network. No auth; the network is the auth, so bind it to localhost or put it behind Tailscale or Caddy.

The page itself writes nothing yet. Capture and check-off in the browser are next; until then the chat panel — Claude Code driven over the Agent Client Protocol, one turn at a time — is how you change an outline without an editor.

$OLAI_HOME/*.rkt                 <- personal data (#lang olai)
    |                                 ^
    v                                 | edits your files
olai CLI (Racket)                     |   <- validate / query / capture
    |                                 |
    v                                 |
racket web-server --- spawns ---> ACP agent (JSON-RPC on stdio)
    |
    +-- SSE ---> browser (htmx): a file moved, or the agent said something

Run it

With Nix, nothing to install:

$ nix run github:juspay/olai              # the web view, on :8080
$ nix run github:juspay/olai#cli -- check Tasks.rkt   # the CLI

Or from a clone:

$ nix develop        # racket 9.2 + just; or install them yourself
$ just install       # gregor + markdown, then --link olai/
$ just serve         # $OLAI_HOME on http://127.0.0.1:8080
$ just check         # validates $OLAI_HOME/*.rkt
$ just tree examples/Example.rkt

To leave it running, the flake ships a home-manager module — a systemd user unit on Linux, a launchd agent on macOS. It puts the CLI on your PATH too, so the binary you query with cannot skew from the service you read:

# inputs.olai.url = "github:juspay/olai";
{
  imports = [ olai.homeManagerModules.default ];
  services.olai = {
    enable = true;
    dataDir = "${config.home.homeDirectory}/outlines";  # required
    # host = "127.0.0.1";  port = 8080;   the defaults
  };
}

Your data lives outside the repo: point OLAI_HOME at the directory holding your *.rkt outlines. There is no default — unset, the write commands say so, and the repo serves its own examples/ plus Roadmap.rkt instead. Sync that directory however you like: git, Dropbox, rsync. Single user, many devices.

Status

Alpha — the author's daily driver, and nobody else's yet. No release, no version number, no upgrade path promised: this is a repo you clone at a commit. Nothing here is a product.

What works today: outline #lang olai + sexp core + agent CLI. Done status, mirrors and @include composition are first class. The web view reloads on file change, pushes over SSE, installs as a PWA, and carries the chat panel. What does not: the page writes nothing — capture and check-off in the browser are still to come — there is no static HTML export, no auth, and no multi-user story (there will not be one; it is single user by design). Ancestor: srid/Tend.

The project tracks its own plan the way it wants you to track yours: Roadmap.rkt at the repo root is a #lang olai outline, edited and committed like any other file.