Feature·Interactive Notebook·Browser-based

Interactive Sema, cell by cell.

A Jupyter-inspired notebook built into the Sema toolchain. Write code in cells, evaluate them individually or all at once, and see results inline — including LLM outputs with timing and token usage. All in the browser, all backed by the same runtime.

$sema notebook new my-notebook.sema-nb

Then: sema notebook serve my-notebook.sema-nb → http://localhost:8888

sentiment-analysis.sema-nbsaved
[md]

Sentiment Analysis

Classify text into positive, negative, or neutral using Sema's llm/extract primitive.

[1]
;; Sample texts to classify
(define texts
  ["I love this product!"
   "Terrible experience, would not recommend."
   "It's okay, nothing special."])
3 items · 4ms · $0.0000
[2]
(define classify
  (fn (text)
    (llm/extract
      {:sentiment {:type :string
         :enum ["positive" "negative" "neutral"]}}
      f"Classify the sentiment of: ${text}")))
classify — function defined · 12ms · $0.0000
[3*]
(map classify texts)
Classifying 3 texts via llm/extract…
[{:sentiment "positive"} {:sentiment "negative"} {:sentiment "neutral"}]
847ms · 152 tok · claude-sonnet-4-6

Shared environment

Cells remember each other.

Definitions in earlier cells are visible in later ones. Define a function in cell 1, call it in cell 5. The environment persists across evaluations — no re-imports, no re-definitions, no boilerplate.

  • Persistent scope. Every cell evaluation extends the same environment. Variables, functions, and macros stick around.
  • Incremental workflow. Re-evaluate one cell after editing — downstream cells are marked stale so you know what to re-run.
  • Reset when needed. One click clears everything and starts fresh.
[1]
(define greet
  (fn (name)
    (format "Hello, ~a!" name)))
greet — function defined
[2]
(greet "Sema")
"Hello, Sema!"

Markdown cells

Document as you go.

Markdown cells render formatted text — headings, bold, italic, inline code, code blocks, lists — right alongside your code. Click to edit, press Shift+Enter to re-render. Build a narrative, not just a script.

  • Rich formatting. Headings, lists, inline code, and code blocks for documentation that reads like a page, not a comment.
  • Click to edit. Click rendered markdown to drop back into source. Shift+Enter to re-render.
[md]

Analysis Notes

The classify function uses llm/extract with a typed schema. Results come back as maps, not strings to re-parse.

  • Positive: 1 text
  • Negative: 1 text
  • Neutral: 1 text
[4]
(count results)
3

Undo & rollback

Mistakes don't stick.

After evaluating a cell, click Undo to roll back: the cell's outputs are restored to their previous state, the interpreter environment is rolled back to before the evaluation, and downstream stale markers are reverted. Useful when a cell modifies global state unexpectedly.

  • Environment-level undo. Not just text — the runtime state itself is rewound.
  • Inline undo on errors. Error outputs show an "Undo cell" button right where the failure happened.
  • Stale marker cleanup. Downstream cells that were marked stale by the evaluation are reverted too.
[5]
(define counter 0)
[6*]
(set! counter (inc counter))
1
[5]
; counter is back to 0 after undo

Stale tracking

Know what's out of date.

When an upstream cell is re-evaluated, all downstream code cells with existing outputs are automatically marked stale. A dashed gold border and a [*] in the cell number tell you exactly which outputs might not reflect the current environment — until you re-run them.

  • Visual indicator. Dashed gold left border + [N*] cell number — unmistakable, not annoying.
  • Previous output preserved. Stale cells still show their last output, so you don't lose work — you just know it might be outdated.
  • Automatic propagation. No manual tagging — the notebook tracks dependencies for you.
[1]
(define threshold 0.8)
re-evaluated · 2ms
[2*]
(filter (fn (x) (> x threshold)) scores)
[0.92 0.85 0.81]
stale — re-evaluate cell [1] changed threshold

Headless & export

Run without a browser.

The same notebook you iterate on in the browser runs headless in CI. Evaluate all code cells, print stdout to the terminal, and export to Markdown for reports. No UI required — the notebook format is just JSON.

  • CI validation. sema notebook run evaluates every cell in order. Add it to a pipeline.
  • Selective execution. Run specific cells by index with --cells 1,3,5.
  • Markdown export. sema notebook export produces a clean .md with code, outputs, and errors.
terminal — CI pipeline
$ sema notebook run sentiment.sema-nb
→ evaluating 4 code cells…
→ [1] 3 items (4ms)
→ [2] classify defined (12ms)
→ [3] [{:sentiment "positive"} …] (847ms, 152 tok)
→ [4] 3 (1ms)
✓ all code cells passed
 
$ sema notebook export sentiment.sema-nb -o report.md
→ wrote report.md (2.1 KB)

REST API

Everything is scriptable.

The notebook server exposes a JSON HTTP API on the same port as the UI. Everything the browser does — creating cells, evaluating, reordering, saving — goes through these endpoints. They're stable enough to script against from external tools, CI, or a custom frontend.

  • Full CRUD. Create, read, update, delete, and evaluate cells via /api/cells.
  • Batch evaluation. POST /api/eval-all runs every cell, with optional inline source overrides.
  • VFS included. Read, write, and list files alongside the notebook through /vfs/*.

Full API reference →

curl — evaluate a cell
$ curl -X POST localhost:8888/api/cells/c4a3f2b1/eval
→ 200 OK
{
  "id": "c4a3f2b1",
  "output": {
    "type": "value",
    "display": "3",
    "duration_ms": 12
  },
  "stdout": "",
  "can_undo": true
}

Keyboard-first

Hands stay on the keys.

The notebook is designed for a keyboard-first workflow. No mouse required for the common loop: edit, run, advance.

Shift+EnterRun cell and advance to next
Cmd/Ctrl+EnterRun cell and stay focused
Cmd/Ctrl+SSave notebook
TabInsert 2 spaces
EscDeselect cell

Start a notebook in seconds.

Create one, serve it, open the browser. That's the whole setup.

new$sema notebook new my-notebook.sema-nb
serve$sema notebook serve my-notebook.sema-nb