rwa

A Rewritable File Should Know What It Is

Rewritable started with a single idea about editing, and it was a good one. You open a self-contained HTML file, describe the change you want in a sentence, and a model rewrites the document's own source in place. We called that surface the lens: one verb — modify — pointed at whatever the file happens to be. The lens is type-agnostic by construction, because under it every container is just text to be rewritten, and models are very good at rewriting text. One runtime, one edit contract, any content. That was the whole pitch. It was also, it turns out, the tell.

Push the lens at a spreadsheet and it stops feeling natural. A spreadsheet is not prose. Its essence is direct manipulation and propagation — you type a value into a cell, a dependent cell recomputes, and the loop is tight, local, and immediate. "Describe the change and wait for a full rewrite" is the wrong shape for that interaction by an order of magnitude. The obvious patch is to special-case it: build a datatable harness beside the lens. But that fragments the one property that matters — one portable file, one runtime, one extension model — and puts us back on the path to Office, where every type earns its own application. The real question is how to get type-specific richness without a fragmented runtime.

The lens was the prose affordance

The fix starts by naming what the lens really is. It is not the base unit of editing. It is the prose affordance — the edit behaviour that fits documents made of text — and it looked universal only because prose was the first type we built. Editing was never one verb. It is type-relative: what it means to edit depends on what the thing is. A document wants surgical rewrites of spans. A table wants values typed into cells and changes that propagate. A deck wants structural moves across slides. The lens is one of these, not the parent of all of them.

A type is not a schema. It is a bundle of affordances

In rewritable, a type is not a file format and not a static schema. It is a registered bundle of affordances — the set of things the runtime knows how to do with this particular container. And once you define type that way, something falls out that we did not plan for: the type system and the skill system stop being two systems. They become one kernel. A built-in type is a bundle of affordances that ships with the file and is trusted. A third-party skill is a bundle of affordances that arrives with provenance, permissions, and isolation. Same mechanism; different trust.

An affordance is a runtime contribution — a behaviour the kernel invokes at a defined moment. There are five kinds:

  • view — render another face of the same file (a present-mode, a grid, an app UI).
  • edit-surface — let a human manipulate structured content directly, with no model in the loop.
  • tool — expose an operation the agent can call ("add a column computing X," "flag the rows where Y").
  • compute — derive a value from declared inputs, recomputed when they change.
  • hook — observe or take part in a lifecycle event.

A prose document carries one affordance: the lens itself, which is a tool — the agent operation that rewrites the text. A datatable carries four: a grid view, a direct edit-surface for cell values, a tool for agent-native table operations, and a compute affordance that recalculates derived cells. A presentation adds a present-mode view. An application adds interactive regions. Document is not a privileged base with the others bolted on; it is simply the thinnest bundle.

This is why the spreadsheet is the useful case, not the awkward one. It does not break the model. It reveals that the model always had two edit surfaces and we had built only one: direct manipulation for the human, the lens for the agent. The same human-versus-agent split rewritable already records in its own edit history, surfacing now inside a single file. Privileging the lens was always a default, never a law.

Types are first-party skills

The only thing that separates "the datatable type's grid editor" from "a charting extension someone downloaded" is trust. A datatable is not a special object the runtime knows about in advance. It is a bundle of providers that happen to ship with the seed and receive first-party grants. The downloaded extension is the same shape: it registers providers, declares the capabilities they need, and is invoked by the same kernel at the same moments through the same registration API. The difference is not mechanism. The difference is provenance, permission, and isolation.

By kernel I mean something specific and small: the runtime layer that accepts registered providers, checks each provider's declared capabilities, invokes it at the right lifecycle moment, and records its effects back into the file. That layer does not care whether a provider came from the seed or from a download. It cares what kind of provider it is and what it is allowed to touch.

This does not mean every skill gets every affordance. Provider kind is gated by trust. A first-party datatable may register a main-thread grid editor; a low-trust third-party skill may register only Worker-safe tools, computes, and hooks. The mechanism is shared; the permitted provider kinds differ by tier. That boundary is forced rather than chosen — the next section is why.

That is what makes this a layer shift rather than a feature. We had been treating the type system and the skill layer as sequential work: first decide what the four types are, then later design how plugins extend them. They are one design problem over one kernel — provider registration plus declared capability. Build them apart and you specify the same mechanism twice, badly. Build the kernel once and the types fall out of it as first-party skills.

What runtime.on grows into

The runtime already carries the seed of this kernel — it just does not extend yet. Today runtime.on is a notification bus: code can subscribe to three lifecycle events — commit, modify, status — but only after the fact. A listener learns that a commit happened; it cannot register a view, contribute an edit surface, intercept the commit, expose an agent tool, or take part in recomputation. It observes the runtime; it does not extend it.

The kernel is what that bus becomes when it grows up: from observe-only over three events to provider registration over a typed set of contributions, capability-gated and provenance-aware. Capability attaches per provider kind, reusing the permission grammar the actions spec already defines — a view needs nothing, a pure compute needs nothing, a tool that reaches the network declares network:, a tool that reads a stored secret declares vault:. The skill spec as written covers the gate — install, permissions, signing, Worker isolation — and is silent on the engine. The provider kernel is that missing engine, and it sits under both the types and the governed skill layer.

Two findings the taxonomy forces

Enumerating the provider kinds is the hard part, because the taxonomy is not cosmetic. Two structural results fall straight out of it, and both constrain the design rather than decorate it.

Thread affinity is the trust boundary. A view and an edit-surface need the DOM. They can run only on the main thread. They therefore cannot be Worker-sandboxed — and Worker isolation is the entire basis of the security story for untrusted code. So a third-party skill that wants to paint its own view or own an edit surface is, by construction, not sandboxable, and must sit in a higher trust tier than a skill that contributes only a tool, a compute, or a hook — all of which confine cleanly to a Worker. Which provider kinds a skill may register is not a UX preference; it is fixed by where the contribution has to run. Trust boundary equals thread boundary equals DOM access.

Compute providers must be pure. The compute provider is what makes "recalc as an affordance" real — a derived cell recomputes when its inputs change — and it is precisely not an imported formula engine. It is constrained to be pure: no I/O, deterministic, orchestrated by the runtime, round-tripping its results back into the text model that stays the source of truth. That purity is exactly why it needs zero capability, and exactly why it must not grow into Excel. The constraint is the feature. The day compute starts reaching for .xlsx compatibility is the day we have drifted back toward the calculator we decided not to build.

Open issues and future work

This design puts the kernel on the critical path. We can prototype presentation and datatable templates without it, but we cannot ship them as real types — because without the kernel a "type" is still what it is today: about six substituted regions and a system-prompt entry, a starter template rather than an affordance system. Getting the provider taxonomy right is critical, since every type and every future skill binds to it.

And there is a second face of "type". A container eventually needs to declare how other containers can query it, wire it into a graph, dispatch work to it, or depend on it. That is a different problem from the one here. The provider kernel answers how one file knows how to edit itself. It does not answer how many files become a system — and that layer is deferred, not solved.

The lens was the right first metaphor but the wrong base unit. It gave every type one verb and made the file forget what it was. The kernel is the third path: one runtime, one portable file, and exactly the behaviours the type demands. Word knew it was a document. Excel knew it was a grid. A file that edits itself should know what it is. That is the part we are building next.

Unlock the Future of Business with AI

Dive into our immersive workshops and equip your team with the tools and knowledge to lead in the AI era.

Scroll to top