Building a local-first Deepnote in one autonomous run — a test of roadmap-level resolution in an agentic dev harness #5
Replies: 1 comment
m3/s1 — a headless runtime server and a one-command local launchThis sprint landed the first slice of running a Deepnote project locally: a standalone runtime server plus The shape: a new package behind a one-way arrowThe server is a new Opening a project without starting a kernel
One kernel, many callers: serialize at the serverA local UI can fire runs concurrently, but there is exactly one kernel behind it. ADR-005 is explicit that a single shared kernel must be serialized, so rather than expose that hazard the server fronts execution with a single-concurrency FIFO run queue. Runs stream over Save that can't silently lose work
Integration parity without duplicating logic or inverting the arrowA SQL/integration block has to resolve the same credentials and environment that Verified against a real kernel — and what that turned upThe parity claim — "the server runs your project the way That suite surfaced a real bug, and a notable one: it lives in existing shared code, not the new package. A hard kernel crash mid-run makes the Jupyter server auto-restart the kernel — the status goes How it's packaged for reviewThe whole slice is |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Building a local-first Deepnote in one autonomous run — a test of roadmap-level resolution in an agentic dev harness
Deepnote's open-source toolchain already runs a
.deepnoteproject end-to-end against a localPython kernel — but only headlessly.
deepnote run notebook.deepnoteexecutes the wholeproject and prints outputs to your terminal. The one way to get the interactive Deepnote
experience — render the notebook, edit a cell, hit run, watch a plot appear, change an input and
have dependents re-run — is
deepnote open, which uploads your project to Deepnote Cloud.There is no local, interactive, in-browser way to read and run a
.deepnoteproject against theopen runtime. That's the feature this milestone builds. But the feature is also the payload for
an experiment — see "The experiment" below.
A browser opens. You see your notebooks in a left rail and the active one rendered top to
bottom — markdown as prose, code with its last-saved plot or dataframe, a SQL block with its
result table, a big-number tile, an input at its current value. Nothing has been uploaded.
Nothing left
localhost.What it is, in three layers
@deepnote/runtime-server) —runtime-core's existingexecution engine exposed over a documented HTTP + WebSocket API: open a project, list
notebooks/blocks, run block(s), stream
block-start/output/block-doneevents live,and save back to disk. No UI required to use it — it's scriptable on its own.
deepnote serveCLI command, a natural sibling ofdeepnote run, that boots that server over a local project and (optionally) opens a browser.type the format supports, the browser counterpart to the terminal output renderer.
Why it's useful even though Deepnote builds this closed-source
The hosted Cloud UI is excellent — and closed. This is the complement, not a competitor:
disabled; zero outbound requests beyond
localhost. That's the whole point for anyone whocan't upload — regulated industries, client-confidential data, air-gapped environments — or
who just wants a fast local loop.
no account and no upload path.
deepnote serveare a natural extension of theexisting CLI, and they're the piece offered back to the project. (More on the boundary below.)
Why this is genuinely hard
The ambition isn't "wrap an engine in a web server." The real surface is parity with a closed
product plus the concurrency and durability problems a live, stateful, single-kernel session
forces. Among the load-bearing pieces:
deepnote run. The API must stream byte-identical JupyterIOutputs to what the headless CLI produces — across 100% of executable block types — andpreserve the CLI's typed failure categories (
missing-kernel/kernel-launch/kernel-died/in-block) rather than flattening them to strings.engine, but a UI is inherently concurrent (mash Run, drag a slider, fan out a reactive chain).
Issue two runs naively and you interleave two output streams onto one handler and corrupt
per-block attribution. The fix is a single-concurrency FIFO run queue with a structurally
guaranteed terminal event for every run — including the common case where a block fails and
the engine stops early — so no consumer ever hangs waiting on blocks that will never run.
the bar is precise: semantic round-trip + idempotence, not byte-equality (the serializer
canonicalizes YAML, so the first save reformats and is stable forever after). Atomic
write-temp-then-rename, plus external-change detection that refuses to clobber a file edited out
from under you. This gate ships before any editing UI exists.
render a notebook's persisted state with no kernel running at all, and a missing kernel surfaces
as a typed capability flag on run, not as an opaque boot crash.
zero frontend footprint, sliceable by directory off upstream
main— which drives the packagelayout (a publishable server package; the SPA in a fork-only
apps/tier importing only aNode-free types entry) and introduces the first frontend toolchain this monorepo has ever
had, isolated so it can't red the backend's gate.
The honest upstream boundary
Deepnote's epic deepnote/deepnote#162
explicitly assigns the interactive notebook UI to the VS Code extension repo, and the Cloud UI is
closed-source. So the split is deliberate: the server +
deepnote servewedge sits squarelyinside deepnote#162's stated CLI/runtime ownership and is the upstream-contributable piece; the SPA
overlaps an assigned surface and stays a fork-only showcase of what the open backend makes
possible. Backend-first, so the shareable part can be offered early — never pushed unsolicited.
The experiment
Here's the actual claim being tested, and it's a bigger one than "I used AI to build a feature."
The hypothesis: a single autonomous execution of the gitban development harness can take a
milestone from a one-line roadmap entry all the way to a reviewed PR — producing every artifact
along the way — driving itself through the whole lifecycle, with a human stepping in only for the
tasks that genuinely require one.
That means one run generates and executes the whole lifecycle:
card by card by an autonomous dispatcher,
The planning is not a human deliverable that an agent then implements. The planning, the
architecture decisions, the test strategy, the UI, and the code are all outputs of the same run.
My role is to unblock the things an agent can't self-serve — credentials, external approvals, the
occasional human-required gate — not to write the artifacts. The point I'm trying to demonstrate
is roadmap-level resolution: that the harness can hold and resolve a unit of work as large and
as deep as an entire milestone — not a function, not a file, not a ticket, but five sequenced
sprints of interdependent backend and frontend — and drive it coherently to completion on its own.
The full M3 roadmap
The milestone decomposes into five sequenced sprints → nine projects → thirty-two features. Each sprint depends on the one before it. Sprint 1 — the server +
deepnote servewedge — is the only upstream-contributable tier; Sprints 2–5 are the fork-only browser SPA showcase (per the deepnote#162 boundary above). Everything below was generated by the harness from the one-line milestone entry.The master spec is PRD-003 · Local Deepnote UI. Each sprint links its own design doc / ADRs below; all of it lives on the
milestone/m3-local-uibranch underdocs/.Sprint 1 — Headless runtime server + one-command launch · the upstream wedge · no dependencies
📄 Design: m3-s1 · server API and
serve· 📐 ADR-007 · server/SPA package layoutExpose
runtime-core's existing headless execution over a stable, documented HTTP + WebSocket API — open a project, list notebooks/blocks, run block(s), stream events, save — with no UI required, plus adeepnote servecommand that boots it over a local.deepnoteproject. ReusesExecutionEngine+ interpreter/kernel resolution exactly asrun.tscomposes them. This is the piece offered upstream on deepnote#162.runtime-core— a new server package wrappingExecutionEngine; carries the save-safety gate (ships before any editing UI exists).packages/*server package, located so it slices clean of the SPA; HTTP for request/response, WebSocket for the execution event stream, zero frontend dependency.GET /api/projectreturns project metadata + the notebook/block tree, reusingdeserializeDeepnoteFileand kernel resolution asrun.tsdoes.ExecutionEngine; deliver everyblock-start/block-done/outputevent over the WS in order, preserving therun.tsfailure-category discriminants (missing-kernel/kernel-launch/kernel-died/in-block).POST /api/project/saveviaserializeDeepnoteFile; deep-equal round-trip + idempotent no-op save — byte-equality is explicitly not the bar.deepnote run, and save with semantic round-trip fidelity; the documented launch criteria for the wedge.deepnote serve/deepnote uicommand — the CLI that boots the server over a local project, a natural sibling ofdeepnote run.deepnote servecommand — canonical command; port selection with fall-back to the next free port and reported URL,--port/--no-open, clean startup/ready/stop logging, Ctrl-C shutdown.deepnote uibrowser-opening alias — thin alias that defaults--open.run— local SQL/integration support to the extentrun.tsalready has it; any network-backed feature off by default to preserve the local-first guarantee.contrib/*diff offupstream/main— code only, no.gitban/.claude/ docs; the exact diff we'd open againstdeepnote/deepnoteonce invited.Sprint 2 — Open & view a notebook locally · read-only viewer SPA · depends on S1
📄 Design: m3-s2 · viewer · 📐 ADR-006 · SPA framework & bundler
The read-only viewer. Introduces the first-ever UI framework + browser bundler to the monorepo, an app shell that loads a project over the S1 API, and read-only DOM renderers for every block type plus Jupyter output — the browser counterpart to the terminal output renderer. No execution, no editing yet: it renders the project's persisted state in a Cloud-like view.
GET /api/projectand load the notebook/block tree into application state, ready to render.stream/display_data/execute_result/error; the browser counterpart to the terminal output renderer, shared by all output-bearing blocks.Sprint 3 — Run blocks with live streamed output · live execution UI · depends on S1, S2
📐 ADR-005 · browser↔kernel transport (proxy vs direct)
Run-this-block / run-all wired to the S1 WebSocket server; kernel outputs stream live into the S2 renderers; execution state and counts tracked; kernel-failure banners surfaced.
run.tsfailure categories (e.g. "deepnote-toolkit not installed —pip install deepnote-toolkit[server]") rather than a blank cell; block exceptions render their traceback in place and mark the block failed.Sprint 4 — Edit blocks & save safely · editing + persistence · depends on S3
Per-block editors for the editable types plus add/delete/reorder, and safe save back to
.deepnote. Save is the highest-stakes operation in the product; faithful means semantic round-trip + idempotence, not byte-equality..deepnoteserializeDeepnoteFileso re-deserializing is deep-equal to the saved project, with an idempotent no-op save; reuses the S1 save-API gate.Sprint 5 — Reactive re-execution · reactivity · depends on S4
Auto re-run of downstream blocks on edit/input change via
getDownstreamBlocksForBlocksIds, in dependency order — mirroring Cloud's signature reactive mode against local compute.run.tsdoes validation-only analysis today); a branch whose block raised an exception is halted.run.tsdoes (in-order, no dependency analysis) with a visible "reactivity disabled" notice rather than silently mis-ordering.How to follow
Everything — roadmap, PRD, ADRs, design docs, the board — lives on the
milestone/m3-local-uibranch.This is a dry run on my own fork; nothing goes to
deepnote/deepnoteuntil the diff is somethingI'd be proud to put in front of a maintainer. Feedback genuinely welcome.
All reactions