Skip to content
docs-bundle okf-loom wiki
GraphIndex

Live studio unavailable. Showing a read-only view. Browse the index.

Explanation 9 min read

Why the live studio exists

The rationale behind the live studio — comment-driven directing, no review gate by default, all-on defaults, SSE plus polling fallback, the separate archive track, and the single-user trust model.

/explanation/live_studio_design.md studiorationalecommentssecurityarchive

Why the live studio exists

This essay explains the reasoning behind the live collaborative studio. It is not the tutorial for using the studio — that is Use the live studio and Direct the agent loop — and it is not the neutral reference for its routes and state model — that is HTTP server routes and Comment lifecycle and archive rules. It is the why behind those surfaces.

The binding design document is the current okf-loom spec; this essay summarises its live-studio posture (§10) and decisions at a readable length and links out for depth.

The posture the studio is built on

The studio rests on a single founding claim, stated as decision D8 and design posture §1.1 of the spec:

The agent is the author and the manager of the OKF files. Writing OKF files is the agent's job. Its mechanical and semantic edits apply directly through the mutators; they appear live and land in the change list with one-click undo. The user's safety net is transparency plus undo, not an approval step.

Everything else in the studio follows from that. If the agent is the author, then the user does not need an editor — they need a directing surface. If the agent edits freely, then the safety net has to be see-and-undo, not review-before-write. If the user is the sole admin, then nothing should gate them, and the only protections that remain are the ones that defend against things that are not the user.

Why comment-driven directing

The studio's primary user interaction is a comment — a short directive the user attaches to a concept, a section, a text selection, or a frontmatter key. The user does not hand-edit markdown in the browser; they point and comment, and the agent authors the files.

The alternative consideredWhy the studio chose comments instead
A direct in-browser markdown editorIntroduces a user↔agent merge problem (concurrent edits to the same file), requires CRDT/OT, and still needs a separate channel to tell the agent what to do. The agent is the sole writer of .md files, so there is no merge problem to solve day-to-day.
A chat sidebar that dispatches commandsCollapses "where is this about" into "what is this about". A comment anchored to a schema row carries both: it names the target and the intent.
A queue of typed actions (button per verb)Forecloses the open-ended asks ("rewrite this section from the spec", "extract these columns into their own concepts") that are the actual daily work. Free-text comments with an anchor cover all of them.

A comment is the steering wheel; the change list plus undo (§12.2 / §12.5 of the spec) is the review surface. The user reads in Rendered / Source / Split — all read-only — and directs; the agent claims a comment, does its scoped work, writes via the ordinary mutators, and resolves it, linking the activity entries it produced. The canonical loop is documented in Direct the agent loop.

The user can still edit files directly on disk in their own editor — that is their machine, and the watcher picks it up. The studio web UI is a directing surface, not a direct editor.

Why there is no review gate by default

This is the most often-questioned choice, so it is worth being precise.

A review gate is the idea that the agent proposes edits and a human approves each one before it lands. The studio ships without one, and the reasoning is that a review gate optimises for the wrong failure mode. The daily failure mode of an agent authoring a knowledge bundle is not "the agent wrote something malicious and we stopped it" — the agent is the user's own, running locally, on the user's own files. The daily failure mode is "the agent wrote the wrong good edit, and the user wants to revert it in one click."

That argues for a fast, visible undo, not a slow, gating approve. So the studio invests in three things instead of a gate:

  1. A live change list — a read-only timeline of everything that happened, with actor, action, affected concepts, and a timestamp.
  2. Per-write undo snapshots — prior bytes captured before every agent write under .okf-loom/session/history/.
  3. Group undo — a single agent pass that writes several files is recorded as one group (a shared group_id), so the user reverts the whole pass with one click, not file-by-file.

A user who genuinely wants to restrain their own agent — propose-only mode, forbidden paths, per-action caps — can opt in via studio.constraints (spec §12.3). The studio ships the seam (the /__apply endpoint and the proposals.jsonl contract) but never the policy. That keeps the default experience frictionless and lets a user build whatever restraint they want as their own module.

Why everything is on by default

scripts/okf-loom serve <bundle> — with no flags — opens the full studio: live SSE updates, source and split views, commenting, directing, agent presence, the change list, and undo. There is no --edit to unlock, no per-save consent, and no permission prompt.

The reasoning is that the studio's value is collaboration that feels alive, and collaboration you have to assemble out of flags is collaboration that does not happen. Every default that is off by default is a feature the user has to discover, and the studio's P0 bar (spec §1.7) is judged on how alive it feels out of the box, not on how many features it has behind toggles.

The defaults that remain are the ones that cost the user zero friction and defend against things that are not the user (see the trust model below). Editing, saving, and watching are never behind a gate; only the loopback boundary, the cross-origin guard, and the active-code gate are — and the first two are invisible to a local user.

Why Server-Sent Events, with a polling fallback

Live push is Server-Sent Events (SSE) over plain HTTP, served from the stdlib ThreadingHTTPServer. The spec records this as decision D0: SSE plus REST, not WebSocket.

The alternative consideredWhy SSE won
WebSocketRicher bidirectional channel, but the studio's traffic is asymmetric (server pushes change/presence/comment events; client sends occasional POSTs). WebSocket would add a dependency surface and a protocol layer for a need SSE already meets with one worker thread per stream under stdlib.
Pure pollingSimpler, but a polling interval short enough to feel live burns cycles doing nothing, and an interval long enough to be cheap feels sluggish. SSE pushes the moment a change lands.

The one real-world failure of raw SSE is that some intermediaries — Cloudflare quick tunnels, corporate proxies, certain CDNs — buffer the text/event-stream response, so EventSource connects but never receives frames. The studio handles this with a starvation watchdog: if no SSE frame arrives within a grace window (~5s), the client arms a polling fallback that fetches /__data/events?since=… on a short interval and dispatches through the same handler path as SSE. If SSE recovers, the poller stops. The no-refresh contract therefore survives behind a proxy that would silently starve SSE alone (spec §7.3).

This matters because the no-refresh contract is a P0 requirement, not polish: a full-page reload after every agent edit would discard the user's scroll position, selection, view mode, and open comment threads. The studio patches the smallest affected region and preserves all of that state (spec §7.3).

Why archive is a separate track

A comment carries two independent pieces of state:

TrackFieldValuesMeaning
Lifecyclestateopen / claimed / resolved / dismissedWhere the work is.
Visibilityarchivedtrue / falseWhether the panel soft-hides the thread.

Archiving a resolved comment keeps it resolved; unarchiving restores the prior visibility without flipping state. Compatibility records with state == "archived" are normalised on read to archived=true, state="open" so old sessions migrate automatically.

The archive track also carries three server-side rules that make it behave predictably:

  • Archive is root-only. Replies cannot be archived independently; the whole thread archives together.
  • Archive is gated on whole-thread-resolved. The server returns 409 with the unresolved ids if any comment in the thread is not yet resolved, so the user cannot accidentally hide unfinished work.
  • Replying to an archived thread auto-unarchives the parent. Adding to a thread reopens it for business; archive is a soft hide, not a closed state.

The full state model is in Comment lifecycle and archive rules; the step-by-step recipe is in Archive and resolve comment threads.

The trust model: gate the port and the code, not the person

The studio is single-user local-admin. There is exactly one human, and they own everything — the bundle and the agent that works for them. The studio never gates, prompts, or flag-guards the user. What it keeps are the protections that defend against things that are not the user, and the spec is explicit (§15) that these must cost the user zero friction.

ProtectionWhat it stopsWhat it does NOT stop
Loopback by default (127.0.0.1)Other machines reaching the studio. Exposing the port is an explicit --public plus an acknowledgement.The user or their agent.
Per-session CSRF token (X-OKF-Token, written 0600)A random web page POSTing to the localhost studio behind the user's back.The studio's own page, which carries the token automatically.
Origin AND Host allow-listCross-site and DNS-rebinding attacks (checking both closes a hole that forging only Origin could exploit).The user's own requests from an allowed host.
Two-layer active-code gateA downloaded bundle trying to run code (JS / full-HTML overrides / viewer plugins) in the user's browser.Editing, saving, and watching — those are explicitly not behind this gate.

The summary the spec arrives at — and that this essay will end on — is:

We gate the port and the bundle's code, never the user or their agent. The user's safety net for agent-authored edits is the live change list plus (group) undo, not an approval step.

Data-safety invariants (validate-before-write, path containment, atomic writes, optimistic per-doc rev) are kept too, but they are framed as correctness guards, not permission guards: they stop corruption and lost work, not access. The single collision case the studio has to handle — the user editing a file directly on disk at the same moment the agent writes it — surfaces non-destructively as a 409 with a diff, never as a silent clobber (spec §9.3 / §9.4).

Why the server stays LLM-free

A final choice worth naming is that the server never runs the agent. The server emits events, runs mechanical operations (index regen, relation mirroring), and serves the studio; all semantic work is done by the external agent (opencode / Claude Code / Codex / a custom harness), which watches the change feed, reacts to comments, reasons, and writes back through the ordinary mutators.

The reasons are the current non-goals: the checkout runtime stays harness-agnostic (no opinion on which agent framework consumes it), keeps the primary runtime dependency-light (no model bundled, no GPU assumed, no network required), and keeps the collaboration working headlessly (scripts/okf-loom watch tails the same change feed with no browser at all). The agent loop — scripts/okf-loom wait blocks in the foreground, the agent claims, writes via mutators, resolves — is the same shape whether the studio is open or not. See Direct the agent loop.

See also

Return to Explanation.

All fields
timestamp2026-06-29T00:00:00Z