surfaces/docs/superpowers/specs/2026-04-19-matrix-per-chat-context-design.md

8.7 KiB

Matrix Per-Chat Context Design

Goal

Move the Matrix surface from the current shared-agent-context MVP to true per-chat agent contexts using the new platform chat_id, while preserving the existing Matrix UX model built around rooms, spaces, and local chat labels such as C1, C2, and C3.

Core Decision

The Matrix surface remains the owner of user-facing chat organization.

  • Matrix rooms, spaces, chat names, and archive state remain surface concerns.
  • The platform agent becomes the owner of actual conversation context.
  • The integration layer stores an explicit mapping from each surface chat to one platform context.

This is the selected "Variant A" architecture:

surface_chat -> platform_chat_id

Why This Decision

The current Matrix adapter already has a stable UX model:

  • a user has a space
  • each working room has a local chat id like C1
  • commands such as !new, !chats, !rename, and !archive operate on that model

Replacing that entire model with platform-native chat ids would be a broader refactor than needed. The surface and the platform solve different problems:

  • the surface organizes rooms and commands for users
  • the platform persists and branches real conversation context

Keeping those responsibilities separate lets us add true per-chat context now without rebuilding the Matrix adapter around a new identity model.

Scope

This design covers:

  • true per-chat context for Matrix rooms
  • a new !branch command
  • real context-aware semantics for !new, !context, !save, and !load
  • lazy migration of legacy Matrix rooms created before platform chat_id support

This design does not cover:

  • end-to-end Matrix encryption support
  • Telegram changes
  • platform UI for browsing contexts
  • a future unified cross-surface chat browser

Data Model

Surface chat identity

The Matrix surface keeps its existing identifiers:

  • Matrix room id, for example !room:example.org
  • local chat id, for example C2
  • room name
  • archive status
  • owning space id

These remain the source of truth for Matrix UX.

Platform context identity

Each working Matrix room gets a platform_chat_id stored in its room metadata.

Example room_meta shape:

{
  "chat_id": "C2",
  "space_id": "!space:example.org",
  "name": "Research",
  "platform_chat_id": "chat_8f2c..."
}

Rules:

  • one working Matrix room maps to exactly one current platform context
  • two Matrix rooms must not share the same platform_chat_id unless we intentionally implement that later
  • branching creates a new platform_chat_id, never reuses the old one

Runtime Semantics

Normal message flow

  1. A Matrix message arrives in a working room.
  2. The Matrix adapter resolves the room to local room_meta.
  3. The integration layer reads platform_chat_id from that metadata.
  4. RealPlatformClient.send_message(...) sends the message to the platform using that platform_chat_id.
  5. The platform appends the exchange to that specific context and returns the reply.
  6. The Matrix adapter sends the reply back to the room.

The key change is that the agent no longer treats all Matrix rooms as one shared context.

!new

!new creates a new user-facing chat and a new empty platform context at the same time.

Flow:

  1. Create a new Matrix room in the user space.
  2. Ask the platform to create a new blank context and return its platform_chat_id.
  3. Store that platform_chat_id in the new room metadata.
  4. Invite the user into the room.

Result:

  • the new room is immediately independent
  • sending the first message does not share memory with the previous room

!branch

!branch creates a new room whose starting point is a snapshot of the current room context.

Flow:

  1. Resolve the current room's platform_chat_id.
  2. Ask the platform to create a new context branched from that source.
  3. Create a new Matrix room.
  4. Store the new platform_chat_id in the new room metadata.
  5. Invite the user into the new room.

Result:

  • the new room starts with the current history and state
  • later messages diverge independently

!save

!save [name] saves a snapshot of the current room's platform context under the current user.

Semantics:

  • saves are owned by the user, not by the room
  • the saved snapshot originates from the current platform_chat_id

!load

!load shows the user's saved contexts and loads the chosen snapshot into the current room's platform context.

Semantics:

  • a saved context created in one room can be loaded into any other room owned by the same user
  • loading does not replace the Matrix room identity
  • loading affects only the current room's mapped platform_chat_id

!context

!context reports the state of the current room context, not a global user session.

Minimum expected output:

  • current room name or local chat id
  • current platform_chat_id presence or status
  • what saved context, if any, was last loaded here
  • last token usage if the platform still returns it

Legacy Room Migration

Existing Matrix rooms were created before real platform chat_id support and therefore may not have platform_chat_id in their metadata.

We need a non-destructive migration.

Lazy migration strategy

For a room without platform_chat_id:

  1. On the first operation that requires platform context, detect the missing mapping.
  2. Create a new blank platform context for that room.
  3. Persist the new platform_chat_id into room metadata.
  4. Continue the requested operation normally.

This applies to:

  • first normal message
  • !context
  • !save
  • !load
  • !branch

This avoids forcing users to recreate their rooms manually.

Interface Changes

Matrix metadata

Extend Matrix room_meta helpers to read and write platform_chat_id.

Real platform client

RealPlatformClient must stop treating the current chat_id parameter as a purely local label when talking to the platform. The surface-facing call can still receive the local chat_id, but platform calls must use the resolved platform_chat_id.

Recommended integration direction:

  • Matrix resolves the room mapping before calling the platform
  • RealPlatformClient receives the platform context id it should use

This keeps the platform client simple and avoids giving it Matrix-specific storage responsibilities.

Agent API wrapper

The wrapper must support platform calls that are explicitly context-aware:

  • create new context
  • branch context
  • send message into a specific context
  • save current context
  • load saved context into a specific context

If upstream naming differs, the adapter layer should normalize those operations into stable local methods.

Command Semantics in MVP

The MVP command set should evolve to this:

  • !new creates a new room with a new empty platform context
  • !branch creates a new room with a branched platform context
  • !context reports the current room context
  • !save saves the current room context for the user
  • !load loads one of the user's saved contexts into the current room

Commands that do not have reliable backend support should remain hidden or explicitly marked unavailable.

Error Handling

Missing mapping

If platform_chat_id is missing:

  • try lazy migration first
  • only return an error if migration fails

Platform create or branch failure

If the platform cannot create or branch a context:

  • do not create partially-initialized room metadata
  • return a user-facing error in the source room
  • log enough detail to diagnose the backend failure

Save and load failure

The surface must not claim success before the platform confirms success.

For MVP quality:

  • user-facing text should say "request sent" only when confirmation is not available
  • once platform confirmation exists, switch to real success or failure messages

Testing

Add or update tests for:

  • a new room gets a new platform_chat_id
  • two rooms created with !new do not share context ids
  • !branch creates a new room with a different platform_chat_id derived from the current one
  • sending messages from two rooms uses different platform context ids
  • saved contexts remain user-visible across rooms
  • loading the same saved context into two different rooms affects those rooms independently afterward
  • a legacy room without platform_chat_id lazily receives one on first use
  • failures during create, branch, save, and load do not leave broken metadata behind

Migration Path

This design preserves a clean future direction:

  • Matrix continues to own its UX model
  • Telegram can adopt the same surface_chat -> platform_chat_id mapping later
  • when the platform matures further, more of the save/load/branch logic can move from prompts or local workarounds into real platform APIs

The key long-term boundary stays stable:

  • surfaces own presentation and routing
  • the platform owns context
  • the integration layer owns the mapping