The Scene: A Host For Cooperating App Surfaces
A KDCube scene mounts independent app surfaces — chat, pinboard, memories, tasks, stats — into one usable workspace without making them a monolith. The design rule is one sentence: the scene knows where surfaces are and how to reach runtimes; providers know what objects mean and what actions are allowed. This Deep piece walks the four registries, multi-runtime routing, event claims, cross-surface drag, and canvas ingress.
A KDCube scene is the place where independent apps become one usable workspace. It is not the apps themselves, and it is not a backend. It is a host that mounts surfaces, connects them to runtimes, relays events, brokers context movement, and routes user actions back to the providers that own the objects.
The scene is the user's interaction horizon: chat, pinboard, memories, tasks, usage, stats, news, and future components can all appear on one page without becoming one monolith. Any new app — your realm — joins the same way: it mounts a surface here, claims its events, and routes its actions, without the scene learning its internals.
The design rule is simple: the scene knows where things are mounted and which transport to use. The owning app knows what an object means and what actions it supports.
The "Your Realm" slot in the diagram is the open extension point: your app appears beside Chat, Pinboard, Tasks, and the rest as just another mounted surface — same composition, no special case.
What the scene owns
The scene owns composition:
It does not own provider semantics:
mem:, task:, fi:, or conv: to decide behavior;Host config may contain explicit composition policy, for example "this surface accepts task:*
drops". That is acceptable for a website. It is still policy, not provider behavior. On actual open/action, the
scene must pass the full object_ref to the provider resolver.
The four registries a scene needs
A practical scene is mostly configuration.
scene config
runtimes
default -> origin, tenant, project
dev -> origin, tenant, project
components
chat -> app, runtime, route, surfaceRef, targetSurfaces
pinboard -> app, runtime, route, surfaceRef, targetSurfaces
stats -> app, runtime, route, surfaceRef, targetSurfaces
contextDropTargets
chat -> attach
pinboard -> pin via kdcube.canvas.ingress
task_list -> open via object.action
surfaceCommandContracts
provider target_surface -> mounted component alias + local command
The current docs examples live here:
app/ai-app/docs/sdk/solutions/scene/config/README.mdapp/ai-app/docs/sdk/solutions/scene/config/website-scene.config.example.jsonapp/ai-app/docs/sdk/solutions/scene/config/backend-bundles.yaml.exampleThe live website scene uses website/index.html, website/kdcube.config.json,
website/scene-summon.js, website/scene-context-drag.js, and
website/scene-event-bus.js.
Runtime-wise routing
A scene can connect to more than one KDCube runtime. This matters when one component is served by the demo runtime and another component, such as stats, is served by a dev/runtime collector.
website scene
runtimes:
demo -> https://demo.kdcube.tech / tenant demo / project demo
dev -> https://dev.kdcube.tech / tenant demo / project demo-march
components:
chat runtime demo
pinboard runtime demo
memories runtime demo
stats runtime dev
For each runtime, the scene may need:
The scene must route events and commands with runtime context. A widget claim or surface command is not just "stats" or "chat"; it belongs to a configured app, runtime, tenant, and project.
A component can also belong to a different app bundle than the scene itself: the memories surface
is iframed from the standalone user-memories@2026-06-26 app — the scene mounts it like any
other surface, which is exactly the "host mounts surfaces from many apps" thesis in practice.
Events: components claim, scene routes
The clean event contract is claim-based. A component says what events it wants. The scene provides the transport and routes matching runtime events to that subscriber.
component boots
-> receives scene config
-> claims event interests
type: accounting.usage
type: kdcube.stats.snapshot
type: task_tracker.task.changed
scene host
-> keeps SSE/Event Bus connection per runtime
-> receives runtime event
-> routes to matching component claims
component
-> updates itself or calls its own API
There should be no hidden fallback subscription in the host. If a widget does not claim what it needs, the useful failure mode is visible logging:
component never sent claim
scene rejected claim
runtime alias did not match
event arrived but no subscriber matched
Examples:
accounting.usage and refreshes its budget endpoint.kdcube.stats.snapshot and updates from the pushed snapshot.task_tracker.task.changed and refreshes or patches its task view.Context drag: moving object refs between surfaces
Cross-surface drag is a browser control plane. It is not long-lived Event Bus state. The source emits a context drag message. The scene creates drop overlays. On drop, it either attaches, pins, or asks the provider to open.
source surface
postMessage kdcube-context-drag-start { contexts: [{ ref }] }
|
v
scene drag broker
normalizes context
finds configured targets
draws overlays over mounted iframes
pulses rail buttons for unmounted targets
|
v
drop
attach -> surface command to chat
pin -> kdcube.canvas.ingress to pinboard
open -> object.action(open, object_ref, target_surface)
The provider owns open behavior:
drop task:issue:... onto task editor
scene calls object.action(open, object_ref, target_surface)
|
v
task provider validates object and target
returns ui_event.target_surface = task_tracker.issue_editor
|
v
scene sends local surface command
task editor opens/focuses issue
The scene may use config patterns to show candidate drop targets. That is a UI hint. The provider response is authoritative.
Canvas ingress: pinning without copying
Pinboard uses a canonical ingress packet. This is how a source surface places an object or text on the canvas.
object_ref vs content.text, no type tag; the scene stamps provenance and the provider resolves later.{
"type": "kdcube.canvas.ingress",
"payload": {
"object_ref": "task:issue:ticket_2026_06_23",
"title": "Cancel membership",
"preview": "ticket_2026_06_23 - open",
"presentation": {
"label": "task",
"namespace": "task",
"object_kind": "task:issue"
}
}
}
For raw text:
{
"type": "kdcube.canvas.ingress",
"payload": {
"title": "Selected explanation",
"content": {
"mime": "text/markdown",
"text": "This is the text to place on the board."
}
}
}
Rules:
payload.object_ref pins an existing object, payload.content.text hosts provided text. No separate type tag.presentation is optional and cosmetic.payload.source from mounted component config when it can.object_ref.source widget creates payload it knows
object_ref or content.text
|
v
scene stamps source
surface_ref, component, app, runtime
|
v
pinboard receives ingress
stores card with object_ref, layout, annotations
|
v
provider resolver handles actions later
Backend requirements
A scene only works if the backend apps expose the right surfaces. The backend example is in
app/ai-app/docs/sdk/solutions/scene/config/backend-bundles.yaml.example.
For a component to participate fully, its app should provide the relevant subset:
object.resolve and object.action;react.pull/read;tool_traits so the ReAct harness can classify exploration, neutral, and exploitation tools correctly.For pinboard specifically:
cnv provider
object.search
object.schema
object.upsert
canvas resolvers
mem -> object.resolve, object.action
task -> object.resolve, object.action
ReAct tools
named_services.* with tool_traits
ReAct pull/read
provider object.get
provider block.produce
object.search, object.schema, and object.upsert above are the
provider-capability / config op names. The model-callable forms are named_services.search_objects,
object_schema, upsert_object, get_object, and object_action
— same capabilities, one layer up. Named services also self-describe their namespace through an
intro, so a consumer learns what a namespace holds without the scene hardcoding it.
What to test
Website scene first:
load page
all components mount
no duplicate chat
runtime config visible in component headers
drag memory/task/file/conversation to pinboard
kdcube.canvas.ingress or surface command pin
backend cnv object.upsert
canvas.patch applied
pin index updated
card resolves/open actions work
drag conversation onto chat
existing chat opens that conversation
no second chat instance
run chat turn
accounting.usage reaches usage card
kdcube.stats.snapshot reaches stats widget
no hidden fallback subscriptions
switch board
pin and comment on non-main board
no duplicate main boards
no revision conflict on normal single-user action
Bad signals:
scene event had no subscribers for expected claimed events
Resolving... forever on cards with registered providers
object action response has no target_surface
canvas_revision_conflict on normal single-user writes
401 embedding during pin indexing
second chat instance appears after drop
Why this matters
The scene is what makes KDCube feel like one product while preserving app ownership. It is also the extension point: a new realm — your realm — plugs in by exposing surfaces, events, operations, and named services. The scene mounts it, routes its events, moves its object refs, and calls its provider actions — nothing else needs to learn its domain. The scene does not need to learn the app's database.
That separation is what makes the ecosystem extensible:
new realm appears
provider exposes object refs and actions
widget exposes surface
scene config mounts it
pinboard can hold its refs
chat/ReAct can inspect it through named services
The scene is not a monolith. It is the control plane that lets many apps behave like one workspace.
Documentation on GitHub
The live docs behind this entry:
- Generic scene contract
- Scene composition
- Scene event orchestration
- Scene surface registry
- Cross-surface context drag
- Surface layering
- Scene config — overview
- Website scene config example
- Backend bundles example
- Components ecosystem
- Event subscription & transport
- Named services — overview
- Consumer integration config
- Resolver & policy registration
- Pinboard pin integration