← Ambient Intelligent Systems

The Ambient architecture

Part III — The Paradigm · Chapter 6 of 9

Everything before this chapter was approach. Seer learned to rank what one person cares about; partitions gave ideas an identity; the mesh taught me where the assistant should live; Totem gave memory a body a user can point at; the two-lane turn gave voice and hands separate authorities. All of it converges on a question none of it answers: when the assistant lives on the Mac, in the middle of a person's actual day — where does what it notices go?

The package that answers is called BonnieAmbient, and the architecture documentation labels it without modesty in the layering diagram:

BonnieAmbient — THE PARADIGM — evidence, worlds, passages, containers, selection handoff, workspace focus, reference resolution, attention routing. No inference. No plugins.

This chapter is about why that label is earned — and, in keeping with how this whole system got built, it starts with the failure that forced the design.

The failure that named the layer

For a while, the ambient layer's vocabulary of worlds — the places a fact can belong to — was exactly three cases: Xcode, Pages, TextEdit. The three applications Bonnie had live observers for. The doc line on the enum read "the worlds Bonnie has eyes for," and it seemed harmless, even tidy.

It was neither. The header of AmbientWorld.swift now carries the postmortem permanently, and I'll quote it rather than soften it:

THE FAILURE THIS FILE CAUSED, AND NOW PREVENTS: this enum used to be exactly the three WATCHED apps, with the doc line "the worlds Bonnie has eyes for". That silently made "ambient context" a synonym for "things with eyes" and structurally excluded the whole ecosystem — registerRead guards on from(pluginOwner:), which answered nil for "calendar", so a calendar read was not deposited to Totem AND not storable here: it reached NOBODY and was gone by the next turn, while a Pages read persisted as a fact. That asymmetry IS the user's report, "she can pull documents but not calendars".

Walk through what actually happened. Bonnie read the calendar — the read itself succeeded. But reads are never deposited to durable memory (that's by design; Totem's default is no durable user content), so the only place the fact could live was the ambient store. The ambient store keys every fact on a world. Calendar had no world. The fact reached nobody and evaporated before the next turn — while an identical read of a Pages document, which did have a world, persisted and could be talked about.

From the outside, none of this machinery is visible. What the user experiences is an assistant with inexplicable moods: she can pull documents but not calendars. The bug wasn't in perception, retrieval, or reasoning. It was in the taxonomy — the system's vocabulary for where evidence lands was narrower than the set of things that could produce evidence.

The doctrine that replaced it is the most important sentence in this book:

Eyes are an UPGRADE for workspace apps, never a precondition for acting. Workspace focus decides which document Bonnie perceives and EDITS — never which Skills she may USE. "Add a reminder" behaves identically whether Pages, Xcode, or nothing is in front of the user.

An ambient system watches some things. It must never confuse what it watches with what it can act on. The moment "ambient context" collapses into "things with eyes," everything eyeless — calendars, reminders, mail, the entire data layer of a person's life — silently falls out of the system's world.

Worlds: the closed vocabulary

The fix was not "add a calendar case." The fix was a rule: one case per recognized owner — the enum now enumerates every compiled plugin owner, held in bijection with the plugin catalog, twenty-three cases at present: the workspace apps (Xcode, Pages, TextEdit, Keynote), the data sources (Calendar, Reminders, Contacts, Notes, Mail, Messages, Safari, Music, Files, System, Shortcuts, Maps, Photos), the services and hands (window management, the Mac itself, Claude Code, the typer, the shader), and one deliberately strange case, otherApps, which we'll get to.

Two properties cut across the cases, and keeping them separate is the design:

  • worldClass names the taxonomy — what kind of place a world is. Four classes: workspace (an application the user works inside, where document focus matters), dataSource (nothing to look at — EventKit, SQLite, AppleScript answer directly, and nothing needs to be open or visible), service (in the code's own words: "hands and services rather than a place with contents: they act, they are not queried for a picture of their state"), and perceptionOnly (sight with no hands — a live watcher publishes facts, but there are no Skills behind it).
  • hasEyes names the narrower, executable truth: does Bonnie currently ship a live observer for this world? Three worlds qualify — Xcode, Pages, TextEdit. Keynote is the deliberate counterexample: workspace-class, recognized, focusable, and eyeless — because "claiming sight from taxonomy would turn that unavailable state into a false execution fact."

That last distinction is worth sitting with. A taxonomy claim ("this is a workspace") and an observation claim ("something is watching it right now") are different kinds of truth, and the earlier design's collapse of the two is exactly what produced the calendar bug. The class says what a thing is; eyes say what is happening. An ambient system needs both axes, separately.

And the vocabulary is closed on purpose. Every switch over AmbientWorld covers all cases with no default: — so, as AmbientRealm.swift puts it, "the compiler still refuses to build a world that forgot to say what it is, which is the check that would have caught the original calendar bug at compile time rather than in a user's report."

Realms: the open identity

A closed vocabulary immediately raises the obvious objection: users teach Bonnie new applications. Scrivener. Sketch. A browser. There is no bound on how many there will be, and growing a compiled enum for every application anyone ever teaches her is precisely the growth the architecture refuses.

The resolution is the two-case type the whole ambient layer routes by — AmbientRealm:

Swift
public enum AmbientRealm {
    /// A built-in world answering for itself.
    case native(AmbientWorld)
    /// A registered application's LOGICAL id ("sketch"),
    /// riding the .otherApps host lane.
    case dynamic(String)
}

The file's header states the split as doctrine:

NATIVE IS THE CLOSED VOCABULARY. […] DYNAMIC IS THE OPEN IDENTITY. The set of applications a user may teach Bonnie is not closed, so a registered application is a case carrying its LOGICAL id — and nothing else.

A native realm is its world — it renders byte-identically to the bare world it always was, because "naming it twice would be a second spelling of one fact." A dynamic realm carries a logical id like "scrivener" or "sketch" and rides otherApps as a host lane — a shared perception channel, not an identity. Every taxonomy question a realm gets asked (what class is this? does it have eyes? which craft is it for? what's it called?) resolves through the application's registration when one owns the lane, and falls back to the host when none does — "so an unregistered application degrades rather than crashes."

Figure

Three consequences of this shape, each of which I initially got wrong somewhere and the architecture corrected:

Scrivener has no world case — and its absence is the invariant working. Scrivener used to have a native, compiled plugin. When that adapter was removed and Scrivener became a portable .bonnie package — pure data, no Swift anywhere in the tree — it lost its enum case. The comment marking the spot says it plainly: keeping the case "would have meant a user could not install that package into a Bonnie that had never heard of Scrivener." The manuscript world now lives at AmbientRealm.dynamic("scrivener"), resolved entirely through its registration. The vocabulary stays closed; the identity became data.

The browser is one workspace, no matter how many packages act in it. Bonnie ships browsing and chrome as Ability packages — but there is no "browsing realm" and no "chrome realm." There is a single browser workspace realm, and Safari and Chrome bundles both resolve to it. A package that registers against the browser grants verbs; it never re-homes the workspace. The alternative — every capability package minting its own place — would shatter the user's one mental "browser" into per-package shards that can't see each other's evidence.

Eyeless is an axis, not an identity. Quoting the header once more: "Whether anything is actually looking at a realm cuts across both cases — a native service world and a workspace-classed registration with no perception contract are both eyeless — so it is a property here, never a third case." The type system holds where a fact lives and whether anything is watching apart, permanently.

The architecture README generalizes the whole section into one engineering principle, and it deserves its emphasis:

A layer that routes evidence by actor must enumerate its actors completely, or reads land nowhere — so enumerate the kinds closedly and the instances openly, and never let the two share a type.

The family

Worlds and realms are the address system. Around them, the ambient layer is a small family of mechanisms, each carrying one piece of the paradigm. A tour, one paragraph each:

The context store. AmbientContextStore is short-term, in-memory awareness for the current machine and conversation — "live perception and reads stay here; Totem owns durable retrieval." The split matters: ambient context is now, cheap to hold and cheap to lose; memory is durable and crosses a consent boundary to get written.

Facts, slots, keys. One processed sensory detail is an AmbientFact, keyed by (realm, slot) — and the slot is the unit of supersession: a fresh fact about the same slot of the same realm replaces the old one, so the store converges instead of accumulating. Every fact carries its provenance (was this a live accessibility read, a cached body, a recipe read, or derived?) and its registration — whether it was perceived ambiently or asked for. Those two axes are the store's honesty: the system always knows how it knows.

The reference gate. When the user says "that paragraph" or "the sketch file," something has to map a spoken referent onto the ambient world. AmbientElementIndex holds per-scope vectorized records of what's around; AmbientReferenceGate is, in its own words, "THE FRONT DOOR for mapping a spoken referent onto the ambient world." One door, so resolution has one behavior.

The engine. AmbientEngine "resolves a turn once, before prompts or executable Skills are assembled" — producing one AmbientRoute (intent, attention, writing target, memory plan) that the rest of the turn treats as settled. Routing is not something each downstream consumer re-derives; it happens once, up front, from evidence.

The voice. AmbientVoice is Bonnie speaking unprompted — the most invasive thing an ambient system can do, and correspondingly the most governed. Its mode is a three-state enum, off / observe / speak, and the doctrine on the first state is exact: "off is not observe with the volume down. off does not think: no tick, no diffing, no trace." And when it does decide a moment deserves a remark, the remark deliberately carries no sentence — because composing one at the detection site would mean a template, "which is precisely the random this feature exists to avoid. 'With intention' means the sentence is written by the thing that writes her other sentences."

The affordances. An AmbientAffordance is "SOMETHING THE SCREEN IS OFFERING RIGHT NOW" — a button, in the control's own words: "Skip Ads", "Full screen (f)", "Sign in". The labels matter because "the embedding matches 'skip the ad' against these words, not against a table Bonnie wrote." An observer walks the front window's accessibility tree — one window, the front one, only while it leads, because "a stale affordance is a confidently wrong press waiting for a phrase." And the affordance probe is consulted at exactly one moment: when a turn that plainly wanted something done has run its rounds and executed nothing — "THE LAST RUNG BEFORE THE HONEST FAILURE." The rule that admits it into routing at all is the one every route-shaped signal here must satisfy: "IT ADDS WORK AND NEVER REMOVES REACH."

That last rule is the calendar doctrine again, wearing different clothes. Every signal in the ambient layer is allowed to expand what Bonnie can reach and forbidden to gate it.

No inference, no plugins

The strangest fact about the paradigm layer is what it doesn't contain. BonnieAmbient depends on BonnieSchema alone — no inference engine, no plugins, no EventKit, no model. Everything it once reached upward for is now an injected protocol: the ability roster, the calendar digest, the roster of registered applications.

That purity is not aesthetic. The ambient layer is the part of the system that decides what is true right now and where it belongs — and a layer with that job cannot be entangled with any particular way of acting on the truth. Because it is pure evidence-and-address, everything above it is swappable: a different inference engine, a different plugin roster, a different memory backend, and the paradigm holds.

That is what I mean when I call the Ambient architecture the paradigm rather than a feature. It's the layer that makes the rest coherent — the answer to "where does what the assistant notices go," designed by the failure that happened when the answer was incomplete.

Enumerate the kinds closedly and the instances openly — and never let the two share a type.