GET /v1/chat/{id}/messages now returns entries on two axes instead of a message list, so one renderer can serve chat rooms, agent conversations, and (later) multi-user channels. Requesting the client side of that.
Server: CIRISServer fd18e8d (0.5.197).
The schema
Every entry in messages carries:
| field |
values |
viewer-dependent? |
kind |
message | system | error |
no |
author |
the attester's key_id (empty for system) |
no |
author_kind |
person | agent | node | other | unknown |
no |
author_role |
founder | member | operator-defined; absent if not on the roster |
no |
relation |
self | own_agent | other | none |
yes |
mine |
bool, exactly relation == "self" |
yes |
message_id |
localization key; system/error only |
no |
body |
text, or absent when the row could not be opened |
no |
unopened_reason |
why it could not be opened |
no |
status |
live | superseded | withdrawn | recanted |
no |
Why two axes and not one role
The obvious flat enum — self | other_human | my_agent | other_agent | system | error — was considered and rejected. It fuses two independent questions, and a channel breaks it:
other_human collapses every other participant into one label, and the client still needs author to draw a name and avatar. In a 50-person channel it says nothing.
my_agent vs other_agent is a relationship, so the same row is my_agent to one member and other_agent to another — but these rows are CEG objects replicated byte-identically to every member. A viewer-dependent value cannot be a property of the row.
So kind + author_kind are facts about the entry, relation is the derived convenience, and every combination you asked for is expressible:
| you want |
match on |
| self |
relation == "self" |
| other human |
kind == "message" && author_kind == "person" && relation == "other" |
| my agent |
author_kind == "agent" && relation == "own_agent" |
| other agent |
author_kind == "agent" && relation == "other" |
| system |
kind == "system" |
| error |
kind == "error" |
This is the split Matrix makes (sender + event type, with "is it me" computed client-side). The system|user|assistant triple from LLM APIs is what we deliberately did not copy — it cannot say which user or which agent.
What we are asking for
1. Render kind distinctly. message as a bubble; system as a centered inline note in the transcript (not a toast, not a dialog — it belongs in history, in order); error as a note that reads as a problem.
2. Moderators. author_role == "founder" moderates — in a pair room both members are founders. Please show a mod indicator next to the name. Note the vocabulary is open: persist documents it as "founder / member / operator-defined", so treat unknown values as ordinary members rather than failing, and do not hardcode a two-value enum.
3. Agents must be visually distinct from the people who own them. author_kind == "agent" with relation == "own_agent" is your own agent speaking — same owner, not the same speaker. Rendering it as the user's own message would attribute a machine's words to a human.
4. unknown is a real state. Directory resolution stalls while a key body is in flight. Render neutrally and re-resolve later; do not fall back to "person".
5. Localized system notes. New ids, all currently rendering the server's English (see CIRISClient#36 — please fold these in with those):
| id |
English |
chat.state.ready |
End-to-end encrypted. |
chat.state.awaiting_peer |
Waiting for them to join this chat. They will see your invitation when their device next syncs, and your messages will send once they do. |
chat.state.join_requested |
Request to join chat sent. Waiting for them to let you in — this completes on its own once their device answers. |
chat.state.no_author_signer |
This device cannot act as you yet, so it cannot join the chat's key exchange. Create or unlock your federation ID to continue. |
Each entry carries both message_id and the English in body, so render the lookup and fall back to body — never a blank line.
6. Do not offer a retry button when converges_on_its_own is true. The response carries it at the top level alongside ready. Both waiting states resolve when the peer's row replicates; a human pressing retry changes nothing, and offering the button says otherwise.
7. total counts what people said — system notes are excluded, so an unstarted conversation reports total: 0 with one entry in messages. Don't drive unread badges off messages.length.
Replaces
503 chat.room_not_keyed_yet is gone. A room that has not finished its key exchange is a 200 with a system entry. If the client has a special case for that 503, it can be deleted.
GET /v1/chat/{id}/messagesnow returns entries on two axes instead of a message list, so one renderer can serve chat rooms, agent conversations, and (later) multi-user channels. Requesting the client side of that.Server: CIRISServer
fd18e8d(0.5.197).The schema
Every entry in
messagescarries:kindmessage|system|errorauthorsystem)author_kindperson|agent|node|other|unknownauthor_rolefounder|member| operator-defined; absent if not on the rosterrelationself|own_agent|other|noneminerelation == "self"message_idsystem/erroronlybodyunopened_reasonstatuslive|superseded|withdrawn|recantedWhy two axes and not one
roleThe obvious flat enum —
self | other_human | my_agent | other_agent | system | error— was considered and rejected. It fuses two independent questions, and a channel breaks it:other_humancollapses every other participant into one label, and the client still needsauthorto draw a name and avatar. In a 50-person channel it says nothing.my_agentvsother_agentis a relationship, so the same row ismy_agentto one member andother_agentto another — but these rows are CEG objects replicated byte-identically to every member. A viewer-dependent value cannot be a property of the row.So
kind+author_kindare facts about the entry,relationis the derived convenience, and every combination you asked for is expressible:relation == "self"kind == "message" && author_kind == "person" && relation == "other"author_kind == "agent" && relation == "own_agent"author_kind == "agent" && relation == "other"kind == "system"kind == "error"This is the split Matrix makes (
sender+ event type, with "is it me" computed client-side). Thesystem|user|assistanttriple from LLM APIs is what we deliberately did not copy — it cannot say which user or which agent.What we are asking for
1. Render
kinddistinctly.messageas a bubble;systemas a centered inline note in the transcript (not a toast, not a dialog — it belongs in history, in order);erroras a note that reads as a problem.2. Moderators.
author_role == "founder"moderates — in a pair room both members are founders. Please show a mod indicator next to the name. Note the vocabulary is open: persist documents it as "founder/member/ operator-defined", so treat unknown values as ordinary members rather than failing, and do not hardcode a two-value enum.3. Agents must be visually distinct from the people who own them.
author_kind == "agent"withrelation == "own_agent"is your own agent speaking — same owner, not the same speaker. Rendering it as the user's own message would attribute a machine's words to a human.4.
unknownis a real state. Directory resolution stalls while a key body is in flight. Render neutrally and re-resolve later; do not fall back to "person".5. Localized system notes. New ids, all currently rendering the server's English (see CIRISClient#36 — please fold these in with those):
chat.state.readychat.state.awaiting_peerchat.state.join_requestedchat.state.no_author_signerEach entry carries both
message_idand the English inbody, so render the lookup and fall back tobody— never a blank line.6. Do not offer a retry button when
converges_on_its_ownis true. The response carries it at the top level alongsideready. Both waiting states resolve when the peer's row replicates; a human pressing retry changes nothing, and offering the button says otherwise.7.
totalcounts what people said — system notes are excluded, so an unstarted conversation reportstotal: 0with one entry inmessages. Don't drive unread badges offmessages.length.Replaces
503 chat.room_not_keyed_yetis gone. A room that has not finished its key exchange is a200with asystementry. If the client has a special case for that 503, it can be deleted.