9. Conversations#
Two kinds in one family. The direct conversation is a human's
private chat with an agent they own (§9.1), exactly as before. A group
chat has a title and two to twelve participants, humans and agents of
the project, and is created by a human (§9.2). Both are chat messages
in the event log, read and written through the same endpoints, and the
list (§9.3) shows both — the dashboard's chat list, where a click opens
the chat beside it. The inbox stays the primary surface; conversations
are the channel beside it (context/scope.md, context/chat.md).
{id} in a path is the agent's member id for a direct conversation and
a cnv-… id for a group chat. The hub keeps the two apart; clients read
kind from the conversation, never from the shape of the id (§1.1).
9.1 Direct conversations#
A direct conversation between a human and an agent profile they own,
inside a project. Its id is the agent's member id. Messages are events
of kind message, type chat, with that conversation_id and the two
parties as the audience — nobody else, admins included, except the
bridges the agent's owner connected to it (§2.3, "The bridge").
| Endpoint | Scope | Notes |
|---|---|---|
GET /projects/{owner}/{slug}/conversations |
human / agent | the list of §9.3; its direct items are the caller's owned agents in this project with {agent_id, last_message_at, unread, last_message}; for an agent, its one direct conversation and its group chats |
GET /projects/{owner}/{slug}/conversations/{agent_id} |
the owner of the agent, or the agent (and its bridges with Maity-For, §2.3) |
the conversation as its direct item in the list (§9.3) |
GET /projects/{owner}/{slug}/conversations/{agent_id}/messages |
the owner of the agent, or the agent (and its bridges with Maity-For, §2.3) |
after=<seq> or before=<seq>, limit; oldest first; {items, cursor, earlier}; before pages back as in a group chat (§9.2): the limit messages with a lower seq, after and before together invalid, and it marks nothing read. On every read, before or not, earlier is the lowest seq of the page when older messages exist — the value to pass as the next before — and null when none do or the page is empty. A direct conversation has no window: without before it reads from after as always |
POST /projects/{owner}/{slug}/conversations/{agent_id}/messages |
the owner of the agent, or the agent — not its bridges, which read the direct conversation as observers and do not write in it (with Maity-For: forbidden, details.reason: bridge_observer, as on POST /events, §8.4) |
{body, attachments?, to_instance?} (§8.7); 201 with the message. to_instance (an instance id of the agent, below) is the human's only: from the agent it is invalid (details.fields.to_instance: "human_only"); an id that is no instance the agent ever had, a malformed one included, is invalid ("not_an_instance"). In a group chat (§9.2), and on the project's message route (§8.2) for anything but a direct chat message, a to_instance is invalid ("direct_chat_only"), checked before human_only |
Message shape:
{ "id": "evt-4237", "seq": 4237, "conversation_id": "agt-a", "from": "hum-maintainer", "to": "agt-a",
"body": "How far is the API contract?", "sent_at": "2026-10-21T13:10:00Z", "delivered_at": "2026-10-21T13:10:04Z",
"mentions": [], "attachments": [], "from_instance": null, "to_instance": null, "redacted": false, "redaction": null }
to_instance is the instance id the human named when sending, as
written, or null when none was named; it is not rewritten when that
instance was replaced and the message reached its successor. Group
chats (§9.2) always carry null.
mentions is always []: a @handle in a chat is plain text, never
resolved, never a ping, and never widens the two-party audience (§8.6).
attachments is the metadata of §8.7, readable by the same two.
A redacted message (§8.5) keeps its place with body: null,
mentions: [], attachments: [] (redaction deletes their bytes and
tombstones them, §8.7), redacted: true (as in the list preview, §9.3)
and redaction: {"at": "…", "by": "<member id>", "reason": "…"|null},
reason shown as §8.5 allows the reader. An unredacted message has
redacted: false and redaction: null. Group chats (§9.2) use the same
shape.
The agent party of a conversation is the membership: every instance
reads it, a message from the agent names its instance (from_instance),
and a message from the human wakes the agent by its wake rule (§6.18),
or exactly one instance when POST …/messages carries "to_instance":
"<id>". Wherever a conversation wakes an agent participant, the same
holds: the agent's rule chooses the instance, and an instance address
or a mention @handle#id where mentions resolve chooses that one.
last_message in the list is the conversation's newest message as a
preview, or null when there is none:
{ "agent_id": "agt-a", "last_message_at": "2026-10-21T13:10:00Z", "unread": 1,
"last_message": { "seq": 4237, "from": "hum-maintainer",
"body": "How far is the API contract?", "sent_at": "2026-10-21T13:10:00Z",
"redacted": false } }
body is cut at 200 characters (Unicode code points, never inside
one; a cut body ends there, the full text stays at …/messages). A
redacted last message shows body: null and redacted: true. The
preview has the list's audience — the agent's owner, or the agent for
its own conversation — and is text for the list: mentions in it are not
resolved again, and it carries none.
delivered_at is the first time the message was returned to the
recipient — to a session of the agent through wait, resume or this
endpoint, or to the human through this endpoint — independent of the
cursor; it says the message reached a session, not that it was acted
on. unread for the human is the number of the agent's messages after
the highest seq this endpoint has returned to that human in this
conversation; the agent's side is its event cursor. The agent receives
chat messages through wait like any event and answers by posting here
or to /events with type: chat. The inbox stays the primary surface;
this is the channel beside it.
GET …/conversations/{id} on a direct conversation answers its item
exactly as the list (§9.3) shows it, so a client can open any list item
by its id without knowing its kind first; anyone else gets
not_found. A direct conversation has no title and no participant list
to change: PATCH on it and the participant endpoints of §9.2 answer
invalid
with details.fields.conversation: "direct". It is created by nothing —
it exists while the agent is a member — and the talk answer of a
request (§7) always points here.
9.2 Group chats#
| Endpoint | Caller | Notes |
|---|---|---|
POST /projects/{owner}/{slug}/conversations |
a human member, not a guest (forbidden, details.scope: "events:post", §2.3) |
{title?, participants: [member ids]}; 201 with the conversation; emits conversation_created |
GET /projects/{owner}/{slug}/conversations/{id} |
a participant; the owner of an agent participant | the conversation view below |
PATCH /projects/{owner}/{slug}/conversations/{id} |
the creator while a participant; a human participant with members:admin |
{title} (null clears it); emits conversation_updated |
POST /projects/{owner}/{slug}/conversations/{id}/participants |
as PATCH |
{member_id} (wake not built, below); 200 with the conversation; emits conversation_updated |
PATCH /projects/{owner}/{slug}/conversations/{id}/participants/{member_id} (not built: a participant's wake keeps its default) |
as the chat's PATCH; an agent participant for itself; the owner of an agent participant for it |
{wake}, mentions or all, for an agent participant only (a human: invalid, details.fields.wake: "humans_wake_on_all"); 200 with the conversation; emits conversation_updated with changed.participants_changed; all for an agent of another owner than the caller only by the agent itself or its owner (a manager of the chat: forbidden, details.reason: "owner_sets_wake") |
PUT, DELETE /projects/{owner}/{slug}/conversations/{id}/pin |
a human participant or observer | pins or unpins the conversation for the caller (§9.3); 204; no event |
DELETE /projects/{owner}/{slug}/conversations/{id}/participants/{member_id} |
as the chat's PATCH for anyone; any participant for itself (leaving) |
200 with the participation {conversation_id, member_id, joined_at, left_at, by, reason}; emits conversation_updated |
GET /projects/{owner}/{slug}/conversations/{id}/messages |
a participant; the owner of an agent participant | after=<seq> or before=<seq>, limit; oldest first; {items, cursor, earlier}; the window and the history below; a redacted message keeps its place as in §9.1: body: null, mentions: [], attachments: [] (their bytes deleted and the attachments tombstoned, §8.7), redacted: true and its redaction |
POST /projects/{owner}/{slug}/conversations/{id}/messages |
a participant of an open group chat | {body, attachments?} (§8.7); 201 with the message |
The conversation view:
{
"id": "cnv-0007",
"kind": "group",
"title": "Release 0.2 go/no-go",
"created_by": "hum-maintainer",
"created_at": "2026-10-21T13:00:00Z",
"participants": [
{ "member_id": "hum-maintainer", "kind": "human", "handle": "maintainer", "owner_id": null,
"joined_at": "2026-10-21T13:00:00Z" },
{ "member_id": "agt-a", "kind": "agent", "handle": "agent-a", "name": "maintainer/agent-a",
"owner_id": "hum-maintainer", "joined_at": "2026-10-21T13:00:00Z" },
{ "member_id": "agt-x", "kind": "agent", "handle": "agent-x", "name": "guest/agent-x", "owner_id": "hum-guest",
"joined_at": "2026-10-21T13:05:00Z" }
],
"observers": ["hum-guest"],
"closed_at": null,
"closed_reason": null,
"access": "participant"
}
participantsare the current ones, in the order they joined.observersare the owners of agent participants who are not participants themselves, whether or not they are members of the project (below), and the agents that serve a participant across owners (§2.3, "The bridge"): everyone in the room sees who else can read it.accessis the caller's:participantorobserver.wake. Not built: this bullet is the agreed design, kept for after the launch. Today no participant carries the field, thePOSTtakes onlymember_id, and an agent in a group chat is woken only by a mention. As designed,wake(agent participants only;nullfor a human) says which messages of the others wake the agent and reach its packet:mentions(the default) only those that mention it,allalso every message of a human participant (§8.2, "Wake-relevant"; §5.2). An agent's message wakes another agent only by mentioning it, whatever itswake, so that two agents cannot answer each other in a loop. It is set when the agent is added (wakein thePOST) and changed with the participation'sPATCH, by those who manage the chat, by the agent itself and by its owner;allcosts the agent's owner a model run per human message, so for an agent of another owner only the agent and its owner setall(a manager of the chat may setmentions, and getsforbiddenwithdetails.reason: "owner_sets_wake"forall, also in thePOST), as an agent's settings are its owner's (§6.2, "Another owner's agent"). A chat where the agent is what the humans talk to — two humans and their one agent — is the case forall; a busy chat stays onmentions, so that it cannot push the work out of a bounded packet.- Who creates. A human member, nobody else: an agent's
POSTisforbiddenwithdetails.reason: "humans_only". The one exception is the hub's own thread for a jour fixe round, with its own rules (§20.5). The creator is a participant whether or notparticipantsnames it. - Who takes part. Active members of the project, humans and agents,
agents of any owner. At least two participants including the creator
and at most twelve (§1.7); fewer or more is
invalidwithdetails.fields.participants: "too_few"or"too_many", a non-member"not_a_member", a repeated id counts once. A chat of exactly the creator and one agent the creator owns is"use_direct": that is the direct conversation (§9.1), with its stricter privacy. A project with as many open group chats as §1.7 allows answersconversation_quota. - Who manages. The creator while still a participant, and any human
participant holding
members:admin, rename the chat and add or remove participants; any participant may leave (DELETEon its own id). An admin who is not in the chat cannot see it and manages nothing: the chat is invisible to them like a direct one (not_found). Adding a participant already in the chat answers200with the chat as it is; removing one who is not answers200with the last participation, ornot_foundwhen there was none. A member removed from the project leaves every chat withreason: membership_removed, an isolated agent withreason: membership_isolated(§6.2). - Closing. When the last human leaves or is removed, the chat
closes:
closed_atis set,closed_reason: "no_human", and it emitsconversation_updatedwithchanged.closed. A closed chat takes no message, participant or title change (conversation_closed, 409); its participants keep reading it. Redaction stays possible: its former human participants, and a human withmembers:adminacting on a report, redact in it (§8.5), so a secret said in the room can still be taken out after the last human left. It is not reopened — a human starts a new one. This keeps every group chat a room a human is in: agents of different owners never hold a channel among themselves that no human takes part in. - A closed project (
archived, §6.1) refuses what adds to the room: creating a group chat and adding a participant answerproject_closed. What removes stays allowed — leaving, removing a participant, redacting a message (§8.5) — so that harmful or private content can still be taken out after the project closed. Posting and renaming follow the project's messages, which a closed project does not refuse (§8.4); the chat closes on its own rules above, not with the project. - What a participant reads. Fresh by default, the history on
demand. A participant's window is the chat from its latest join
on: the resume packet (§5.2),
unreadandmentions_unread(below), wake-ups (§8.2) and…/messageswithoutbeforecount only messages in it, so someone added later starts fresh and is not woken or listed as behind by what was said before. When it needs the earlier part, it reads it:GET …/messages?before=<seq>returns thelimitmessages (default 50, at most 200) of the chat with a lowerseq, oldest first, back to the chat's first message, whenever the reader joined;earlierin the response is theseqto pass as the nextbefore,nullat the start of the chat;afterandbeforetogether areinvalid. Every current participant may page back this way, and so may an observer (below). Reading the history marks nothing read and wakes nobody. Reason (the maintainer's decision, 2026-10-22): a newcomer usually needs only what comes next, and a packet or a wake built from a backlog it was not there for would cost model runs on old talk; but when it does need the context, asking someone to repeat it is worse than reading it. Everyone in a group chat therefore writes to whoever is or will later be in it; the dashboard says so when a participant is added. The event log is not rewritten: the earlier messages do not enter the newcomer's readable set ofGET /events, because an event's audience is fixed when it is written and the log is append-only — the history is read through the conversation. A former participant no longer reads the chat through…/messages(not_found) and no longer sees it in the list; the events that named it while it was in the chat stay in its readable set (GET /events). - The agent's owner reads along (the maintainer's decision,
2026-10-22). A human who owns an agent participant reads every message
that agent can read — its window and
the history before it, through
GET …/messagesandGET /events— without being a participant: it is listed underobservers, is never in an audience, is never woken, does not post or manage (forbiddenwithdetails.reason: "observer") except that it sets its own agent'swake(not built, above), does not redact (§8.5) and does not mark anything read. Reason: the owner answers for what their agent does, and a group chat is where a human or an agent of another owner can tell it to do something; without this, instructions could reach the agent through a room its owner cannot see — the prompt-injection path between owners. The privacy cost is stated in the chat itself:observersnames everyone who reads along, and the dashboard shows it in the chat's header. - An owner outside the project (the contract owner's ruling,
2026-10-22). An agent may be a member of a project its human is not
(§6.2); its owner still reads along, as a reader limited to the chats
that agent is in, and is listed under
observerslike any other. For such a humanGET /projects/{owner}/{slug}/conversations?observed=truelists exactly those chats (withoutobserved=truethe list is empty); a human who is not a member and has no agent in the project getsnot_foundon this route,observed=trueor not, so the route does not tell an outsider that the project exists.GET …/conversations/{id}andGET …/conversations/{id}/messagesanswer for those chats as for an observer who is a member, and so doGET …/attachments/{id}and…/contentfor the attachments of their messages (§8.7), the one exception besides the chats themselves. Any write it makes in a chat it reads along, a post (through the chat routes orPOST /events), a change to the chat or a redaction, isforbiddenwithdetails.reason: "observer", except setting its own agent'swake(PATCH …/participants/{member_id}, above, not built), which it may. Every other write and every other read of the project,GET /eventsand the member list included, staysnot_found(§1.3), and so does a chat none of its agents is in; the chat view'sparticipantsis all it learns of the project's members. No event reaches a non-member, so there is no live update and no readable set ofGET /events: the dashboard finds the project in the agent'smemberships(GET /agents, §6.3) and re-reads…/messages?after=<cursor>while it shows the chat. When the agent leaves the chat or the project, this access ends with it. - Wake. A message is addressed to every participant (audience
member:<id>each, §8.2). It wakes the human participants other than its author, and an agent participant when it mentions that agent or, where the agent'swakeisall(not built, above), when a human wrote it.conversation_createdandconversation_updatedwake no agent: being added to a chat is not work; the agent finds the chat in its next resume (§5.2). Reason: under a supervised runner every wake is a model run, and a chat of four agents in which every message wakes the other three costs three runs a message and turns two agents replying to each other into a loop. Rejected: a per-chat switch that wakes every agent on every message — it brings the loop back for the chats that turn it on. When mentioning turned out to be forgotten in practice (2026-10-30), the design iswake: allper agent participation (not built, above), which wakes on human messages only and so cannot loop. The dashboard may prefill the@of a chat's only agent; that is the client's convenience, not a hub rule. - Mentions resolve to current participants only (§8.6); a non-participant's handle is plain text. Each mentioned participant other than the writer counts one ping against the rate (§1.7), which is also what bounds two agents mentioning each other.
- Redaction is §8.5: any human participant, no agent.
- Posting here and
POST /eventswithtype: chatand the conversation id (§8.4) are the same write. A paused agent still posts in its chats (§5.3).
Message shape — as in a direct conversation, with to the conversation
id, the resolved mentions, and no delivered_at (several recipients;
read receipts are not modelled):
{ "id": "evt-5020", "seq": 5020, "conversation_id": "cnv-0007", "from": "hum-maintainer", "to": "cnv-0007",
"body": "@agent-a can we tag today?", "sent_at": "2026-10-21T13:30:00Z",
"mentions": [ { "member_id": "agt-a", "handle": "agent-a", "field": "body", "offset": 0, "length": 8 } ],
"attachments": [], "redacted": false, "redaction": null }
attachments is the metadata of §8.7, readable by whoever reads the
message.
Read and unread are per participant: the hub keeps the highest seq
of this chat that …/messages has returned to the participant, and
moves it to the participant's own message when it posts (whoever speaks
has read up to there). unread counts the others' messages after it,
within the participant's window; mentions_unread those of them that
mention the participant. wait and resume delivering a mention, or a
human's message under wake: all (not built, above), do not move it: the agent has seen
one message, not the chat.
9.3 The conversation list#
GET /projects/{owner}/{slug}/conversations?observed=false&limit=50&cursor=…
Every conversation the caller is in, both kinds, in the four bands of
"Pinned" (below), each newest activity first (last_message_at, or
created_at, which a group chat carries, for a chat without messages), paged
(§1.4). For a human: the direct conversations with the agents it owns in
the project and the group chats it participates in; with
observed=true also the group chats it reads along as an owner (§9.2).
For an agent: its direct conversation and its group chats. Closed group
chats stay in the list for their participants.
{ "items": [
{ "id": "agt-a", "kind": "direct", "agent_id": "agt-a", "title": null,
"participants": [ { "member_id": "hum-maintainer", "kind": "human", "handle": "maintainer" },
{ "member_id": "agt-a", "kind": "agent", "handle": "agent-a", "name": "maintainer/agent-a" } ],
"access": "party", "closed_at": null,
"last_message_at": "2026-10-21T13:10:00Z", "unread": 1, "mentions_unread": 0, "pinned": false,
"last_message": { "seq": 4237, "from": "hum-maintainer", "body": "How far is the API contract?",
"sent_at": "2026-10-21T13:10:00Z", "redacted": false } },
{ "id": "cnv-0007", "kind": "group", "title": "Release 0.2 go/no-go",
"created_at": "2026-10-21T13:00:00Z",
"participants": [ { "member_id": "hum-maintainer", "kind": "human", "handle": "maintainer" },
{ "member_id": "agt-a", "kind": "agent", "handle": "agent-a", "name": "maintainer/agent-a" },
{ "member_id": "agt-x", "kind": "agent", "handle": "agent-x", "name": "guest/agent-x" } ],
"observers": ["hum-guest"], "access": "participant", "closed_at": null,
"last_message_at": "2026-10-21T13:30:00Z", "unread": 0, "mentions_unread": 0, "pinned": false,
"last_message": { "seq": 5020, "from": "hum-maintainer", "body": "@agent-a can we tag today?",
"sent_at": "2026-10-21T13:30:00Z", "redacted": false } }
],
"next_cursor": null }
A direct item keeps the fields of §9.1 (agent_id among them) and gains
id, kind, title (always null; the dashboard shows the agent's
name), participants, access: "party", closed_at (always null)
and mentions_unread (always 0). A group item has no agent_id,
and only a group item carries created_at.
title: null on a group chat means none was given; the dashboard shows
the participants' handles. last_message follows §9.1 (cut at 200
characters, redaction shown as body: null) and lies within the
caller's window. For an observer unread and mentions_unread are
null: it reads along and is never behind.
Pinned. A human pins a conversation it is in or reads along to keep
it at the top of its own list: PUT …/conversations/{id}/pin and
DELETE …/conversations/{id}/pin under the conversation's project
(§9.1, §9.2), human only (forbidden, humans_only), idempotent, 204.
A pin is the caller's own: no one else sees it, it emits no event, and it
holds across the caller's devices because the hub stores it. At most 20
per human across all projects; a 21st is invalid with
details.fields.pin: "too_many". A conversation the caller cannot read
answers not_found, as its GET does; a closed group chat may be
pinned like an open one. A conversation the caller leaves or
loses access to keeps no pin. Every item gains pinned (true or
false; always false for an agent caller), and so does a
conversation's own view (§9.1 GET, the group chat view of §9.2 and
its idempotent replay), so a client shows the pin without reading the
list; no event carries pinned, since a pin is announced to nobody.
Both lists (this one and
GET /me/conversations) are ordered in four bands, each band by the
activity order above: pinned conversations with unread messages, then
unpinned ones with unread messages, then pinned ones without, then the
rest. A pinned chat stays on top while something is new in it, and a
chat with something new rises above a quiet pinned one, so a pin never
hides a new message. unread decides the band (an observer's null
counts as none). The cursor (§1.4) follows this order.
Meat proxy per chat. A human sets the lead part of the meat-proxy
mode (§5.2, "Meat proxy") for one conversation it is a participant of, a
direct one or a group chat, without changing its profile: PUT
…/conversations/{id}/meat-proxy with {"meat_proxy": true | false |
null}, human only (forbidden, humans_only), a conversation it cannot
read not_found, one it reads along without taking part (§9.2)
forbidden with details.reason: not_a_participant, 204, idempotent.
null (the default) follows the profile's meat_proxy_lead; true
switches the lead part on in this chat, false off. The hands part is
not a matter of one chat: it always comes from the profile, so true
over a profile that is off leaves it off and false leaves it on. Every
human entry in a conversation's participants (the view of §9.2 and the
items here) carries the parts that hold in this chat as
meat_proxy_lead and meat_proxy_hands, meat_proxy (true while at
least one of them is, as in §5.2), meat_proxy_chat (true, false or
null, the human's own choice for this chat, read by the human itself
only; others get null) and, while meat_proxy is true, the
meat_proxy_note of §5.2, which names the parts that hold. The resume packet's conversations carries the same per
chat in meat_proxy (§5.2). Where the chat's value and the profile's
lead part differ, the chat's holds in that chat, the direct one with the agent's
own human included. So an agent in the chat reads, next to who it talks
to, whether that person wants to be led step by step here. It is the
same kind of context as the profile's switch: never a permission, set
by nobody but the human for itself. A change emits no event and wakes
nobody — the agents read it on their next resume or read of the chat;
the fields stand in the conversation view wherever it is served,
including the view in a later conversation_created or
conversation_updated (§8.2), where it is the value at that event's
time; what holds now is what the resume packet and a read of the chat
give. The dashboard shows the lead part as a switch next
to "Attach" and "Send" in the conversation's composer, starting from
the profile's value; a human with only the hands part on sees it off.
Across projects. A human's chat overview reads one list for all its projects instead of one list per project:
GET /me/conversations?observed=false&limit=50&cursor=…
Human only; an agent gets forbidden (humans_only), since an agent
lives in one project and reads §9.3 there. The items are those §9.3
gives the caller in every project where it is an active member, under
the same rules (observed, closed chats, windows, observers' null
counts), merged into one list in the four bands of "Pinned" (above),
each band ordered by activity, newest first, then by project.full_name
and then by id, both ascending, paged (§1.4). Activity is
last_message_at; where that is null, a group chat's created_at
and a direct conversation's joined_at of its agent. The project's
name is part of the order because one agent can be a member of several
of the caller's projects, and its direct conversations then share the
same id. Archived projects count like any other, as their
conversations stay open (§9.2) and §18.2 counts their unread messages.
Only memberships count: an owner outside a project reads its agent's
chats through that project's routes (§9.2), never through this list,
whatever observed says. Each item carries one more field, the
project it belongs to:
"project": { "owner": "maintainer", "slug": "maity", "full_name": "maintainer/maity", "name": "Maity" }
A direct item's id is the agent's member id, as in §9.3; together
with project.full_name it names the conversation, and the routes of
§9.1 and §9.2 under that project read and write it. The answer also
carries the totals over all items, not only the page:
"totals": {"unread": 3, "mentions_unread": 1}, the same on every page
whatever limit and cursor say, observers' items counting nothing,
0 and 0 for an empty list. totals.unread is the number §18.2
gives as counts.chat. The list is one read, not one per project: the hub
answers it from one query over the caller's memberships, so its cost
does not grow with the number of projects in the way that reading
§9.3 per project does.
9.4 Agents in group chats#
An agent learns of a group chat in three places: the conversations
list in its resume packet (§5.2), the list endpoint (maity chats), and
a mention — the only thing that wakes it there, unless its wake in
that chat is all, when every human participant's message does (§9.2). The message event that
wait returns carries payload.conversation: {id, kind, title}, so an
agent that lost the chat from its context knows where it is and where to
answer. Before it answers it reads what it missed with maity chat <id>
(§13), which also marks it read, and with maity chat <id> --before
<seq> what was said before it joined when it needs that; it answers with maity say --chat <id>
"…". A message in a group chat that does not mention the agent stays
readable and counts as unread, and never ends a wait — except a human
participant's message where the agent's wake is all (not built, §9.2).
The generated rules (§6.9) say what a chat is to an agent: being in a
group chat is not a task; it answers when mentioned — or, in a chat
where its wake is all (not built, §9.2), when a human writes — and otherwise reads
when it resumes (a change of these rules bumps the rules version,
§6.9); a request in a chat carries the authority of the
writer's trust and role and no more (context/trust.md), so a go that needs
requests:answer still comes as an answer in the inbox, never as a
chat line; and work the chat agrees on becomes a card.