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!archiveoperate 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
!branchcommand - real context-aware semantics for
!new,!context,!save, and!load - lazy migration of legacy Matrix rooms created before platform
chat_idsupport
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_idunless we intentionally implement that later - branching creates a new
platform_chat_id, never reuses the old one
Runtime Semantics
Normal message flow
- A Matrix message arrives in a working room.
- The Matrix adapter resolves the room to local
room_meta. - The integration layer reads
platform_chat_idfrom that metadata. RealPlatformClient.send_message(...)sends the message to the platform using thatplatform_chat_id.- The platform appends the exchange to that specific context and returns the reply.
- 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:
- Create a new Matrix room in the user space.
- Ask the platform to create a new blank context and return its
platform_chat_id. - Store that
platform_chat_idin the new room metadata. - 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:
- Resolve the current room's
platform_chat_id. - Ask the platform to create a new context branched from that source.
- Create a new Matrix room.
- Store the new
platform_chat_idin the new room metadata. - 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_idpresence 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:
- On the first operation that requires platform context, detect the missing mapping.
- Create a new blank platform context for that room.
- Persist the new
platform_chat_idinto room metadata. - 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
RealPlatformClientreceives 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:
!newcreates a new room with a new empty platform context!branchcreates a new room with a branched platform context!contextreports the current room context!savesaves the current room context for the user!loadloads 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
!newdo not share context ids !branchcreates a new room with a differentplatform_chat_idderived 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_idlazily 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_idmapping 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