docs: add matrix per-chat context design
This commit is contained in:
parent
430c82dba1
commit
9bb93fbbda
1 changed files with 278 additions and 0 deletions
|
|
@ -0,0 +1,278 @@
|
||||||
|
# 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:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue