The Scene: A Host for Cooperating App Surfaces
The browser control plane where independent app surfaces become one workspace. The scene knows where a surface is mounted and how to reach it; the owning app knows what its objects mean.
A KDCube scene is the browser control plane where independent app surfaces become one workspace. It mounts widgets, connects each widget to its configured runtime, routes claimed events, brokers context drag/drop, and forwards object actions to the app that owns the object.
The scene is not the apps, their databases, or their object model. Chat, pinboard, memories, news, stats, Connection Hub, and a new surface from your own app can appear together without becoming one frontend monolith.
The scene knows where a surface is mounted and how to reach it. The owning app knows what its objects mean and which actions are allowed.
00 Highlights
The concepts the article builds on, in one place:
| Concept | The takeaway |
|---|---|
| Composition, not absorption | The scene owns mounting and browser routing; provider semantics — refs, schemas, actions — stay with the owning app. |
| Four registries | Runtimes, components, context drop targets, surface-command contracts: most scene behavior is explicit configuration. |
| Runtime-wise routing | Every claim and command carries component → app → runtime alias → tenant → project. Two runtimes are never silently one. |
| Claim-based events | Components declare interests; the broker forwards matches only. Missing delivery is one of four visible conditions — no hidden fallback subscription. |
| Drag moves refs | Context drag carries the canonical ref plus provenance, never domain data; the provider's response decides the open target. |
| Commands move the user | A declared surface command summons/focuses another surface, with readiness waits and acknowledgements. |
| Two ingress shapes | payload.object_ref pins an existing ref; payload.content.text is hosted as a cnv: object first. No payload_type. |
| Three backend contracts | App surfaces, generic named-service tools, and workspace/timeline projection are separate — the scene and canvas are not materializers. |
01 What the scene owns
The scene owns composition and browser routing:
- runtime aliases, origins, tenants, and projects;
- mounted components, iframe routes, and surface identities;
- target-surface ownership and summon/focus behavior;
- docked, floating, and ready/not-ready surface state;
- namespace presentation config used for labels, icons, and colors;
- context drag/drop overlays and delivery;
- claim-based event routing;
- surface-command contracts and acknowledgements;
- an object-action gateway that forwards the complete
object_ref.
The scene does not own provider semantics:
- it does not parse
mem:,conv:conversation:,conv:fi:, orcnv:to choose behavior; - it does not infer actions from a namespace, kind, color, or icon;
- it does not copy provider schemas into the host;
- it does not decide how provider objects are materialized for an agent;
- it does not subscribe for a component that never declared an event claim.
A deployment may declare composition policy such as “this surface
accepts mem:* drops.” That controls which drop targets are
offered. It does not make the scene the owner of mem: semantics.
The actual open/action still goes through the provider with the full
canonical ref.
02 Four registries make the host concrete
Most scene behavior is explicit configuration.
KDCube ships two real scene-host patterns:
- The website reads a browser scene config and mounts widgets from configured runtime origins.
- The workspace app (bundle) receives its component map from
surfaces.as_consumer.ui.scene.componentsthrough itsscene_surface_configoperation.
In both cases, a component remains an iframe served by its owning app. Even the pinboard is a surface owned and served by an app; the scene only mounts it.
03 Runtime-wise routing is explicit
A scene may mount surfaces from more than one KDCube runtime. This is explicit browser composition, not implicit provider federation.
Every event claim and command therefore carries a concrete ownership
route: component → app → runtime alias → tenant
→ project. That route selects the iframe origin, event relay,
API/app operation endpoint, namespace presentation config, and object-action
gateway. The scene never silently treats two runtimes as one.
04 Events: components claim, the scene routes
The scene event broker is claim based and transport agnostic. A component declares the event types it consumes; the host supplies the configured relay and routes matching events.
The core scene broker does not require one particular relay technology. Concrete hosts choose transports according to the events they need:
- The workspace scene keeps an authenticated Socket.IO Data Bus leg for session-routed service events and an SSE leg with
project_events=truefor tenant/project broadcasts. - The website scene opens per-runtime SSE relays for the claims made by its mounted widgets.
Those are deployment integrations, not a universal rule that every scene must open two connections. There is no hidden fallback subscription: missing delivery is diagnosable as one of the four visible conditions in the figure above.
05 Context drag moves refs, not domain data
Cross-surface drag is a browser interaction, not durable Event Bus state. The source offers context, the scene displays eligible targets, and the selected target receives an attach, pin, or provider-owned open.
For example, opening mem:record:<id> can resolve to
sdk.memory.viewer. The scene does not derive that target from
mem:. The memory provider returns it. Candidate target patterns
in scene config are UI hints; the provider response is authoritative.
06 Surface commands move the user
Drag moves context. A surface command asks the scene to summon, focus, or update another mounted surface. The shipped consent flow is a concrete example:
The contract is declared in scene config. A standalone chat can use the same intent by opening the served Connection Hub page with the corresponding deep link; scene composition adds in-page routing without changing provider policy.
07 Canvas ingress has two structural shapes
The canonical ingress message is kdcube.canvas.ingress.
To pin an existing object:
{
"type": "kdcube.canvas.ingress",
"payload": {
"object_ref": "mem:record:mem_fc12861ede7c4944ad5df99541185189",
"title": "Spreadsheet compatibility note",
"preview": "Use image charts when portability matters.",
"presentation": {
"label": "memory",
"namespace": "mem",
"object_kind": "mem:record"
}
}
}
To place raw text:
{
"type": "kdcube.canvas.ingress",
"payload": {
"title": "Selected explanation",
"content": {
"mime": "text/markdown",
"text": "Text selected in the source surface."
}
}
}
The structure is the discriminator:
payload.object_refmeans preserve and pin an existing canonical ref;payload.content.textmeans ask canvas to host the content as a versioned canvas-ownedcnv:object, then pin that ref;- there is no
payload_type; presentationis optional and cosmetic;- the scene normalizes/stamps
payload.source; - browser wrappers
kdcube.canvas.ingress.drag_startandkdcube.canvas.ingress.drag_endexist only for the transient drag lifecycle.
For an existing object, the pinboard stores a proxy card with the unchanged ref, layout, cached display metadata, and annotations. For raw content, canvas first becomes the owner of a durable object; the resulting card still points to an object ref.
08 Backend participation: three separate contracts
A scene surface can participate through only the capabilities it needs. The three contracts below should not be conflated.
1. App surfaces
An app may provide widget routes mounted by the scene; API or app operations used by those widgets; Event Bus claims/producers; Data Bus integrations; scheduled jobs; MCP endpoints; and named-service providers with object resolvers. MCP and API surfaces do not require named services. Named services are the optional generic object/action contract for a domain.
2. Agent-facing named-service tools
Any agent harness can receive the fixed generic tools:
named_services.provider_about named_services.object_schema
named_services.list_objects named_services.object_action
named_services.search_objects named_services.host_file
named_services.get_object named_services.upsert_object
named_services.delete_object
The platform owns this bounded tool grammar and routing. Each provider owns its namespace ontology, canonical ref format, object kinds, schemas, searchable scopes, filters, actions, presentation, authorization, and effects. ReAct is one agent adapter that currently exposes these tools over the shared agent harness. They are not ReAct-only.
3. Runtime workspace and timeline projection
Turning an object ref into bounded readable context is a runtime workspace
and timeline concern. Provider operations such as object.get,
block.produce, and optional block.render supply
bytes and owner-defined representation. The shared implementation boundary
is explicit: sdk/runtime/harness/events/resolver.py resolves a
canonical ref to authorized bytes or an action,
sdk/runtime/harness/workspace/pull.py materializes authorized
refs, sdk/runtime/harness/workspace/layout.py separates runtime
output from user-visible artifacts, and
sdk/runtime/harness/timeline/projection.py validates
provider-owned projections.
providerowns object bytes and representation policyruntime workspace / timelineowns materialization and projectionagent adapterexposes whichever model-facing read/pull tools it supportsReAct is one adapter and exposes react.pull and
react.read. Ported agents can use the same resolver, timeline,
and workspace contracts without importing the ReAct protocol.
Owner-specific rehosters and representation policy remain with their
providers. The scene and canvas are not materializers.
09 What to test
load scene every configured component mounts once each component reports the expected app/runtime/tenant/project drag memory/file/conversation to pinboard scene routes kdcube.canvas.ingress or a pin surface command canvas upserts one card for the canonical ref on that board card resolves through the provider drag a conversation onto chat the existing chat surface opens it no second chat instance appears run a chat turn accounting.usage reaches the usage subscriber kdcube.stats.snapshot reaches the stats subscriber no hidden fallback subscription is required open a memory pin pinboard calls scene_object_action provider returns sdk.memory.viewer scene summons/focuses the memory viewer send a Connection Hub surface command scene queues until the frame is ready sender receives the host acknowledgement target surface applies and acknowledges the payload
Bad signals:
expected event reached the host but had no claimed subscriber card remains "Resolving..." with a registered provider provider open response has no target_surface normal single-user pin produces canvas_revision_conflict drag creates a second chat instance host branches on a namespace prefix to decide behavior
· Why this matters
The scene makes KDCube feel like one product while keeping app ownership intact. A new app (bundle) joins by declaring surfaces, routes, claims, and command contracts. If it exposes domain objects, its provider keeps ownership of those refs and actions. The scene composes; it does not absorb the domain.
new app joins -> serves a widget -> declares a surface -> optionally claims/emits events -> optionally exposes API, MCP, or named services -> scene mounts and routes it
That is the extension point: many independently owned apps, one coherent workspace, and no host that needs to learn every domain.