The Pinboard of Proxies
Objects from many apps sit together on one board without merging their databases. Every durable card is a proxy: canvas owns the card; the provider named by the ref owns the object.
The KDCube pinboard (canvas) lets objects from many apps sit together
without merging their databases. A memory, conversation, generated file,
metric, or object from your own provider appears as a card with one durable
identity: its canonical object_ref.
The board owns the card: placement, cached display metadata, comments, and revision history. The provider named by the ref owns the object, its schema, permissions, bytes, lifecycle, and actions.
There is one important refinement. Raw text or an uploaded item has no
provider object yet. Canvas first hosts it as a versioned cnv:
object and then stores a card pointing to that new ref. Therefore every
durable card is a proxy to a canonical object, but the object may be owned by
another app or by canvas itself.
The board never infers what a ref means. It preserves identity and routes back to the owner.
00 Highlights
The concepts the article builds on, in one place:
| Concept | The takeaway |
|---|---|
| Card vs object | Canvas owns the card — placement, cached display metadata, comments, revisions. The provider named by the ref owns the object. |
Canonical object_ref | One durable identity per object; within a board it is the dedup key. The same ref may sit on several boards. |
cnv: hosting | Raw content has no provider object yet: canvas hosts it as a versioned canvas-owned object first, so every durable card points to a canonical ref. |
| Two ingress shapes | payload.object_ref pins an existing ref; payload.content.text hosts then pins. Structure is the discriminator — there is no payload_type. |
| Hints never select behavior | kind, namespace, label, icon, color are presentation/resolver hints only. |
| Display ≠ resolve ≠ open | Cached metadata keeps the board readable; provider truth is authoritative; open routes through the gateway to the provider, which names the target surface. |
| Materialization ownership | Provider bytes → runtime workspace → timeline projection. Not canvas, not the scene, and not ReAct-owned. |
base_revision | Optimistic concurrency on board mutations — one client cannot silently overwrite another. |
| Portable context | Boards and selections move refs plus provenance, never copied bytes. |
01 What a pin stores
Common refs include:
mem:record:<id> memory record conv:conversation:<conversation_id> conversation conv:fi:conv_<conversation_id>.<path> conversation file cnv:<board-name> named board cnv:<board-name>@<revision> immutable board revision cnv:canvas/users/.../objects/.../v000001.md canvas-hosted content
The card does not store a portable copy of provider data. Its title and preview are display caches, not the source of truth.
Within one board, the canonical ref is the identity key. Adding the same ref again is a no-op; if its card was trashed, the operation restores it. The same ref may still appear on several boards because deduplication is per board, not global.
02 One board, many owners
The board can display all of them because the card shape is generic. It
does not need to understand each domain. kind,
namespace, object_kind, label, icon, and color are
presentation/resolver hints. They never select behavior.
03 How a pin reaches the board
The browser flow has one owner at every hop.
Agents do not need a special canvas API for this. An agent harness with the generic named-service tools can call:
named_services.upsert_object( namespace="cnv", object_kind="canvas.card", ... )
The canvas provider enforces the same revisions and object contract regardless of whether the caller is a widget or an agent.
04 The canonical ingress protocol
There are two structural packet shapes. There is no
payload_type.
Pin an existing provider object:
{
"type": "kdcube.canvas.ingress",
"payload": {
"object_ref": "mem:record:mem_fc12861ede7c4944ad5df99541185189",
"mime": "application/json",
"title": "Spreadsheet compatibility note",
"preview": "Use image charts when portability matters.",
"presentation": {
"label": "memory",
"namespace": "mem",
"object_kind": "mem:record"
}
}
}
Host raw text as a canvas-owned object:
{
"type": "kdcube.canvas.ingress",
"payload": {
"title": "Selection",
"content": {
"mime": "text/markdown",
"text": "Selected text to place on the canvas."
},
"presentation": {
"label": "provided text",
"object_kind": "cnv:provided:text"
}
}
}
The rules are exact:
payload.object_refmeans pin an existing canonical ref unchanged.payload.content.textmeans host the content under canvas ownership, obtain a versionedcnv:ref, then pin that ref.- The shape is an intent discriminator, not a domain type or routing key.
presentationis optional and cosmetic.- The scene stamps/normalizes
payload.sourcebecause it knows the mounted surface and runtime. kdcube.canvas.ingress.drag_startandkdcube.canvas.ingress.drag_endare transient browser drag wrappers, not alternate durable payloads.
05 Display, resolve, and open are different steps
The board must remain readable before it contacts every provider, so a card can show cached title/preview metadata. Provider truth remains authoritative.
The pinboard app operation is currently named
scene_object_action because the scene/pinboard boundary can act
on any object present in the scene. Under that gateway, providers still
receive the generic object.resolve or object.action
operation. The canvas never parses mem: to select the memory
viewer.
06 Materializing a pin for an agent
Showing a card in a browser and materializing its object for an agent are different concerns.
The ownership is deliberate:
providerowns bytes, object semantics, and representation policyruntime workspaceowns safe local layout and ref-to-file materializationtimeline projectionowns bounded model-visible blocksagent adapterexposes the model-facing read/pull controls it supportsKDCube exposes this reusable core through
sdk/runtime/harness/events,
sdk/runtime/harness/timeline, and
sdk/runtime/harness/workspace. ReAct is one adapter and exposes
react.pull and react.read; ported agents can use
the same primitives without adopting the ReAct protocol. The materialization
concept itself is not ReAct-owned, and the canvas does not perform it.
07 Who works the board
People and agents use the same durable board through different surfaces.
People:
- drag memories, conversations, files, and provider objects onto a board;
- paste an image from the clipboard — onto the board as a file pin, or into a note at the caret as inline markdown;
- annotate and arrange cards;
- read pins in place: notes render chat-parity markdown (copyable code plates, inline images, link previews for bare URLs), preview text is freely selectable, image pins show thumbnails, and HTML pins render live in a sandboxed frame;
- select several cards and send their refs to chat;
- search pins across boards;
- open a proxy back in its rich app.
The in-place previews keep the neutrality rule intact: the bytes behind
an image thumbnail or an HTML preview arrive through the owning
provider’s download object action — the board
renders what the owner returns and still never parses a ref to decide
behavior.
Agents:
- discover canvas with
named_services.provider_about(namespace="cnv"); - list boards through
named_services.list_objects; - search card snapshots with
named_services.search_objects; - inspect schema and valid mutations through
named_services.object_schema; - add/update objects through
named_services.upsert_object; - read referenced objects through the runtime materialization controls exposed by their harness.
These generic named-service tools are available to any configured agent, not only ReAct.
08 Named boards are portable context
Boards have stable names and immutable revisions:
cnv:research cnv:research@42 cnv:incident-481
A board may mix memory refs, conversation refs, generated files, canvas-hosted notes, metrics, and domain objects. Sending a board or selection to chat moves the refs plus provenance. The receiving agent can then inspect only what the turn needs, through each owning provider. This is portable context without turning canvas into a warehouse.
09 Canvas named-service contract
The current cnv provider exposes provider.about,
object.list, object.search,
object.schema, and object.upsert. The corresponding
model-callable generic tools are:
named_services.provider_aboutdiscovers the canvas provider and what it hostsnamed_services.list_objectsdiscovers boardsnamed_services.search_objectssearches indexed card snapshotsnamed_services.object_schemaexplains object kinds and mutation payloadsnamed_services.upsert_objectapplies card, comment, suggestion, deletion, replacement, and layout operations where allowedMutations against a visible board include base_revision.
That optimistic concurrency boundary prevents one client from silently
overwriting another.
10 Rules that keep canvas neutral
Canvas must:
- preserve the full canonical ref;
- keep provenance visible;
- version board mutations;
- require the expected
base_revisionfor concurrent writes; - route actions through the provider;
- keep a card valid even when its resolver is temporarily unavailable.
Canvas must not:
- infer an action from a prefix,
kind, label, icon, or color; - own another provider's schema or permissions;
- store signed transport URLs as object identity;
- copy every referenced object into its own database;
- treat cached card metadata as provider truth.
11 Integration checklist
Scene configuration
- mount the pinboard widget with its app, runtime, route, and surface identity;
- declare pin/attach/open drop targets;
- configure the
scene_object_actiongateway; - deliver namespace presentation config for consistent labels, icons, and colors.
Canvas provider
- expose the
cnvnamed-service endpoint; - support board listing, card search, schema discovery, and revision-fenced upserts;
- host raw content as versioned canvas-owned objects;
- index card snapshots when semantic search is configured.
Object providers
- issue canonical refs;
- implement
object.resolveandobject.actionfor UI capabilities/actions; - expose object bytes/get behavior if agent materialization is supported;
- optionally implement
block.produceandblock.renderfor owner-defined timeline/model representation.
Agent configuration
- grant only the generic named-service tools needed by that agent;
- publish accurate tool traits and provider schemas;
- expose read/pull controls appropriate to that harness;
- keep authorization at every provider and fenced runtime boundary.
12 Regression path
drop memory/file/conversation on pinboard -> kdcube.canvas.ingress or pin surface command is visible -> cnv object.upsert succeeds at expected base_revision -> card keeps the canonical ref -> pin index updates drop raw text -> canvas hosts a versioned cnv: object -> card points to the hosted ref open a memory pin -> pinboard calls scene_object_action(open) -> memory provider returns sdk.memory.viewer -> scene routes kdcube.surface.command -> viewer acknowledges and opens ask a configured agent about cnv:research -> agent lists boards / searches card snapshots through generic tools -> deeper object reads use runtime workspace/timeline materialization -> provider controls model-visible representation
Bad signs:
card remains "Resolving..." despite a registered provider unsupported action chosen from card kind instead of provider capabilities canvas_revision_conflict during an ordinary single-client write duplicate card for the same ref on one board raw content has no durable cnv: ref agent materializer branches on namespace names
· Closing
The pinboard is a board of proxies, not a second database. It gives many independent objects one shared spatial context while keeping ownership intact. The card belongs to canvas. The referenced object belongs to its provider, including canvas when canvas hosted raw content. The provider decides actions; the runtime workspace and timeline project objects for agents; the scene routes the user back to the rich surface.
That separation is why one neutral board can hold an ecosystem without having to understand every app inside it.