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.

User Scene Host mounts surfaces · connects runtimes · relays events · routes actions Chat Pinboard Memories Tasks Stats Your Realm context · events · commands Host roles runtime routing surface registry drag broker event-bus bridge command contracts SSE / Event Bus per runtime API / Data Bus clients object-action gateway · drag overlays KDCube runtime A apps / providers origin · tenant · project KDCube runtime B apps / providers origin · tenant · project
The scene knows where surfaces are and how to reach runtimes; providers own what objects mean.

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:

runtime aliases, origins, tenants, and projects;
mounted components and their iframe routes;
surface refs and target surface names;
summon rails, sizing, ready rules, and close behavior;
object-action gateway configuration;
namespace presentation config delivery;
cross-surface drag/drop overlays;
Event Bus / SSE connections per configured runtime;
Data Bus or API calls needed by the host;
surface command routing.

It does not own provider semantics:

it does not parse mem:, task:, fi:, or conv: to decide behavior;
it does not build provider-specific payloads;
it does not declare component event subscriptions on behalf of widgets;
it does not render provider objects for ReAct;
it does not own object schemas.

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 — four registries runtimes runtime alias to transport context default origin · tenant · project dev origin · tenant · project components alias to mounted surface app · runtime route · surfaceRef targetSurfaces chat · pinboard · stats … contextDropTargets where dragged refs may land chat → attach pinboard → pin (kdcube.canvas.ingress) task_list → open (object.action) surfaceCommandContracts provider target_surface to local command provider target_surface e.g. task_tracker.issue_editor → mounted component alias + command scene dispatches local surface command
A scene is mostly configuration: runtimes, components, contextDropTargets, surfaceCommandContracts.
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.md
app/ai-app/docs/sdk/solutions/scene/config/website-scene.config.example.json
app/ai-app/docs/sdk/solutions/scene/config/backend-bundles.yaml.example

The 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.

Runtime-wise routing a claim or command belongs to an app + runtime + tenant + project components chat pinboard memories stats runtime "demo" demo.kdcube.tech · tenant demo · project demo SSE / Event Bus API ops serves chat · pinboard · memories runtime "dev" dev.kdcube.tech · tenant demo · project demo-march SSE / Event Bus API ops serves stats collector
One scene, many runtimes — each component routes events and commands with its own runtime context.
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:

authenticated iframe/widget routes;
an SSE/Event Bus connection;
API operation URLs;
Data Bus operation calls;
namespace presentation config;
object-action gateway calls.

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.

Events: components claim, scene routes component boots claims its event interests accounting.usage kdcube.stats.snapshot task_tracker.task.changed claims scene event broker one SSE / Event Bus per runtime receives runtime event matches against component claims no hidden fallback subscriptions routes match component updates updates itself from pushed event or pulls its own API Useful failure mode: visible logging component never sent claim scene rejected claim runtime alias mismatch no subscriber matched
Components claim; the scene routes. Missing claims fail visibly, not silently.
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:

Usage card receives accounting.usage and refreshes its budget endpoint.
Stats widget receives kdcube.stats.snapshot and updates from the pushed snapshot.
Task tracker receives 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.

Context drag: moving object refs between surfaces source surface kdcube-context-drag-start { contexts: [{ ref }] } scene drag broker normalizes context · finds targets draws overlays over iframes · pulses rails attach surface command to chat pin kdcube.canvas.ingress to pinboard open object.action(open, object_ref, target_surface) provider owns open validates object + target returns ui_event.target_surface = task_tracker.issue_editor scene sends local surface command · editor focuses issue
Attach, pin, or open — and open is a round-trip the provider authoritatively owns.
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.

Canvas ingress: pinning without copying source widget builds payload payload.object_ref payload.content.text scene stamps payload.source surface_ref component · app runtime pinboard stores proxy card object_ref layout annotations provider resolver handles actions later Rules shape is the discriminator — no type tag object_ref pins an object · content.text hosts text presentation is optional and cosmetic canvas stores proxy, preserves object_ref provider resolver decides open / download / preview / action
The packet shape is the discriminator — 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:

The packet shape is the discriminator: payload.object_ref pins an existing object, payload.content.text hosts provided text. No separate type tag.
The discriminator is intent only, not a domain kind and not a routing key.
presentation is optional and cosmetic.
Scene stamps payload.source from mounted component config when it can.
Canvas stores a proxy card and preserves object_ref.
Provider resolver decides open/download/preview/action.
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:

widget routes for mounted scene surfaces;
API operations used by widgets;
MCP tools where the app is meant to be callable externally;
Event Bus producers/consumers;
Data Bus operations for durable commands/results;
cron jobs for scheduled snapshots/indexes;
named-service providers for realm objects;
object resolvers for object.resolve and object.action;
ReAct event-source policies for react.pull/read;
named-service 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.

KDCube Deep · 24.06.2026