.NET / SQL / Enterprise Engineering

Hierarchical Meeting Rooms and Agent Routing for Enterprise MATM

Report summary

The best fit for this MATM requirement is a hybrid architecture : a first-class Room Service backed by a relational system of record, a relationship-based authorization graph, an append-only transcript/event stream, and a single-writer room coordinator per durable room or routing subject. That recom

Status
Research archive item
Category
.NET / SQL / Enterprise Engineering
Length
4,625 words
Reading time
22 minutes
Report type
architecture

Key topics

  • .NET / SQL / Enterprise Engineering
  • .NET
  • SQL
  • Enterprise Engineering
  • AI
  • Semantic Systems
  • Research Archive
  • Strategy
  • Audit

Research provenance

Archive status
Research archive item
Content identity
sha256:503fdabd347179db61a791d4742fb0042f2e83dbd8dfb419c83a5c4d49da728b

For citation, use the report title and canonical URL. Archival presence does not establish authorship or promote report statements into portfolio evidence.

Source availability: 31 citation markers in the source export have no recoverable source links. Those markers are omitted from this reader; any supplied bibliography and ordinary links remain. Check the original sources before relying on the cited claims.

This page renders the archived Markdown as safe, formatted HTML. It is background research and does not become a portfolio claim without evidence review.

Full report

On this page

The best fit for this MATM requirement is a hybrid architecture: a first-class Room Service backed by a relational system of record, a relationship-based authorization graph, an append-only transcript/event stream, and a single-writer room coordinator per durable room or routing subject. That recommendation is grounded in several mature patterns: actor systems provide stable, isolated state and one-writer coordination; durable workflow engines provide resumable onboarding and routing; chat protocols provide room history, edits, receipts, and pagination; and collaboration platforms demonstrate that hierarchy, discoverability, and content restrictions must be modeled explicitly rather than improvised in message bodies. Orleans, Dapr, and Akka all emphasize isolated stateful entities and turn-based or single-writer execution; Temporal emphasizes durable messages, updates, and lazy workflow creation; Matrix and XMPP treat rooms, archives, receipts, and corrections as explicit protocol objects; Confluence models hierarchy and restrictions separately from page content.

That leads to a clear subsystem split:

SubsystemResponsibilityWhy it is separate
Room Topology Serviceroom definitions, hierarchy, discoverability, room templates, canonical linkshierarchy and navigation are not transcript concerns
Authorization Servicecompany/workspace/project inheritance, confidential narrowing, service-role exceptionstranscript access must be derived from enterprise auth, not inferred from room membership rows alone
Identity Negotiation Serviceasks who the agent is, why it is present, and what it is working on; assigns canonical agent identitythe display name shown in a room is not the authentication subject
Routing Servicelane selection, coordinator assignment, objective creation, reassignment, conflict resolutionrouting decisions are durable control records, not just chat messages
Transcript Serviceappend-only messages, relations, edits, supersessions, redactions, paginationdurable room conversation must remain independent from task/workflow state
Cursor and Notification Serviceread markers, unread counts, subscriptions, pushes/webhooksread state is per principal per room, not a property of messages
Assignment Servicecoordinator ownership, claims, returns, escalations, pauses, completionswork assignment is a lifecycle of its own
Evidence and Memory Servicedecisions, evidence blobs, provenance, long-term summariesdurable knowledge is company/workspace/project-owned, never user-owned

The core design principle is that a room is an addressable collaboration object, not a memory record and not a free-form field on another entity. Matrix models room hierarchies, room state, room history, receipts, edits, and redactions as distinct concerns. XMPP MUC likewise models rooms, member lists, archives, nicknames, and room controls separately. Microsoft Teams’ retention model also distinguishes chat visibility from compliance storage, which is another sign that rooms and transcripts deserve their own subsystem boundary.

My recommendation is therefore:

  1. Use a single relational database as the transactional source of truth for room metadata, routing decisions, assignments, cursors, and transcript rows.
  2. Use an authorization graph in Zanzibar/OpenFGA style for inheritance and narrowing rules.
  3. Use a single-writer room actor or grain per room for mutable room state, and a single-writer routing actor/workflow per routing subject to prevent conflicting coordinator decisions.
  4. Use an outbox/event bus for search indexing, notifications, analytics, and downstream workflow triggers.
  5. Use a durable workflow for onboarding and complex rerouting, especially when human approval, timeouts, or escalation are involved. Temporal’s Signals, Updates, Queries, and Signal-With-Start are particularly aligned with “always-available welcome room” semantics and resumable routing.

Competing approaches and why the hybrid wins

A pure workflow-engine-centric design is tempting because onboarding and routing are obviously workflows. The problem is that workflow engines are not full collaboration substrates: they do not natively give you room discovery, room-local presence, transcript pagination, read cursors, edits, redactions, or space-like hierarchy. Temporal is excellent for durable message handling, resumable state, child workflows, and escalation control, but it should orchestrate room behavior, not replace room storage. Its own documentation distinguishes asynchronous Signals, synchronous tracked Updates, and child workflow composition, which fits routing and reassignment well, but not room transcript semantics by itself.

A pure chat-protocol-centric design, such as “just use Matrix rooms for everything,” is better than the workflow-only option because Matrix already covers spaces, room history, receipts, edits, relations, redactions, and sync tokens. XMPP MUC plus MAM covers strong room control, archives, read heuristics, and paging. But enterprise MATM has requirements that public chat protocols do not solve end to end: company/workspace/project authorization inheritance, confidential narrowing against tenant objects, machine-routable objectives, coordinator election, structured evidence expectations, and durable assignment/resolution lifecycles require an application-specific domain model on top. In practice, Matrix/XMPP are excellent inspiration and interoperability references, but the room service still needs its own application-level control plane.

A pure actor-system-centric design is the best choice for concurrency control around room state and routing conflicts, but not as the only persistence interface. Akka cluster sharding is strong when you have many durable entities that must run on exactly one node at a time; its persistence model leans on a single-writer principle for a given identity. Orleans provides similar stable identities, persistence, placement, and single-threaded execution defaults; Dapr actors similarly enforce turn-based concurrency and make the timer-versus-reminder distinction explicit. Those are exactly the properties you want for room coordinators and routing subjects. But transcripts, analytics, search, retention reports, and wiki-like navigation still need a relational/query-friendly model.

The memory-record hack should be rejected outright. Mature protocols and platforms separate messages, membership, receipts, archives, edits, and access restrictions because those concerns evolve differently and are governed differently. Teams also demonstrates that compliance retention and end-user visibility cannot be collapsed into “whatever is currently displayed”; that is especially important for temporary goal/task rooms whose decisions and evidence outlive the room’s active phase.

The practical conclusion is:

ApproachStrengthWeaknessVerdict
Workflow-firstgreat for onboarding, retries, escalationpoor room/transcript semanticsuse as orchestrator only
Chat-firstgreat for transcripts, receipts, edits, paginationweak app-specific auth/routing/governanceuse as design reference, not sole platform
Actor-firstexcellent conflict control and room-local statepoor analytics/query surface aloneuse for coordination layer
Hybridcombines durable workflows, coordination, auth graph, and transcript modelmore componentsrecommended

Authorization, tenancy, and room hierarchy

The hierarchy should be represented explicitly as:

account membership -> company -> workspace -> project -> room

with accounts and companies many-to-many, and with knowledge owned only by company, workspace, or project. No durable memory artifact should have a user principal as its owner scope. That is both a practical governance rule and a simplifying rule for tenant isolation.

For authorization, a Zanzibar/OpenFGA-style relationship model is the right match because it handles direct and implied relationships, inheritance over object graphs, and conditions on grants. Zanzibar’s published design highlights a uniform access-control model with external consistency under changing ACLs. OpenFGA models authorization as relationship tuples and also supports conditions on tuples, including time-bounded grants. That is a clean fit for company memberships, workspace/project membership inheritance, welcome-room access, confidential-room narrowing, and temporary coordinator grants.

A minimal authorization model should express object relationships such as:

  • account -> company as direct membership
  • workspace -> parent_company
  • project -> parent_workspace
  • room -> parent_company | parent_workspace | parent_project
  • room -> confidentiality_policy
  • service_principal -> role
  • agent_instance -> authenticated_as principal

Then compute permissions such as:

  • enter_welcome_room(company) if authenticated principal is a company member
  • discover_workspace_rooms(workspace) if principal can view the workspace
  • enter_project_room(room) if principal inherits from the room’s parent project and room policy does not narrow further
  • enter_confidential_room(room) only if principal is explicitly granted or is in an allowed userset
  • read_room(room) and write_room(room) separately
  • route_into(room) as a stronger permission than discover(room)

The important isolation rule is rooms can narrow inherited permissions; rooms cannot broaden them across the tenant graph. Atlassian’s Confluence documentation is a useful analogy here: space permissions and content restrictions are distinct, and content restrictions can further restrict access but cannot grant broader access than the container. That same principle should apply here.

For tenant isolation, enforce the following invariants:

RuleRequired behavior
Tenant scopingevery room, transcript row, assignment, cursor, and evidence item carries company_id and a higher-scope foreign key where applicable
Auth before existence leaksigned-out or unauthorized callers receive uniform not-found/forbidden behavior for room URLs, transcript fetches, search, and membership probes
No cross-tenant backlinksrelationship links may only reference rooms and artifacts in the same company unless a special federated-sharing feature exists
Confidential narrowingconfidential room memberships are computed from parent scope plus explicit allowlists/usersets; never from display names or message mentions
No transcript inheritanceauthorization inheritance applies to room access, not to transcript copying; moving an agent between rooms does not automatically expose prior transcripts
Search isolationindexing pipeline must partition indices by company and apply room ACL filters at query time
Storage isolationencryption keys, object-store prefixes, and export jobs must be company-scoped

The always-available company welcome room should be a pre-provisioned durable room under each company scope. Every authenticated agent with company membership can enter it, but entering that welcome room should not imply access to any workspace or project transcript. The welcome room is there to negotiate identity and routing, not to act as a “catch-all visibility escalation.”

Identity negotiation and agent spoofing prevention

Identity negotiation must separate at least four things:

ConceptMeaning
principal_idthe authenticated subject from your identity provider
agent_typethe software/role class the principal is allowed to run
agent_instance_idthe concrete running session or process
display_namethe human-readable room label, negotiated and conflict-resolved

The system should ask, in the welcome room workflow:

  • Who are you?
  • Why are you present?
  • What are you working on?
  • Which company/workspace/project context do you believe applies?

That answer becomes a claim, not truth. The policy engine validates it against the authenticated principal, company membership, and any allowed agent profiles, then issues a canonical identity record and a route decision.

Display-name conflicts should be resolved exactly once during negotiation. XMPP MUC is instructive here: room nicknames are treated as explicit managed state, some rooms require a reserved nickname, and anonymized rooms still need stable occupant identity to prevent impersonation. XEP-0421 is especially relevant because it introduces stable, anonymous occupant identifiers to prevent impersonation across reconnects and renames. The direct lesson for MATM is that the display label must never be the security identity. The sender identity must be server-minted, stable, and opaque; the display name is merely a presentation field.

So each message should store immutable sender fields such as:

  • sender_principal_id
  • sender_agent_instance_id
  • sender_canonical_identity_id
  • display_name_snapshot
  • sender_auth_context_version

and the API must ignore any client-supplied “from” value. The client can suggest a display name during negotiation; it must never be allowed to choose the stored sender identity.

Data model, lifecycles, and endpoint contracts

The normalized model below keeps rooms, membership grants, routing decisions, messages, cursors, notifications, assignments, and durable memory distinct. That mirrors the way Matrix distinguishes room state, room history, receipts, edits, and relations; XMPP distinguishes room control, archives, and visible markers; and workflow engines distinguish workflow messages from durable business state.

Normalized data model

TableKey columnsPurpose
accountsaccount_idhuman account or service account
companiescompany_idtenant root
account_company_membershipsaccount_id, company_id, role_set, statusmany-to-many account/company membership
workspacesworkspace_id, company_idcompany child scope
projectsproject_id, workspace_id, company_idworkspace child scope
roomsroom_id, company_id, owner_scope_type, owner_scope_id, room_kind, durability_class, visibility_class, canonical_slug, stateroom definition and canonical navigation
room_relationshipsparent_room_id, child_room_id, rel_typeroom-to-room graph such as parent/child, related, supersedes
room_templatesroom_template_id, room_kind, default_policy_idprovision rules
room_membership_grantsgrant_id, room_id, subject_type, subject_id, grant_kind, condition_json, state, authz_provenanceexplicit room-level grants or narrowing grants
canonical_identitiescanonical_identity_id, company_id, principal_id, agent_type, assigned_display_name, statenegotiated room identity
identity_claimsclaim_id, company_id, principal_id, claimed_name, claimed_purpose, claimed_work_item, policy_resultnegotiation transcript
routing_subjectsrouting_subject_id, company_id, scope_type, scope_id, current_route_versionlock point for single-writer routing
routing_decisionsrouting_decision_id, routing_subject_id, route_version, lane, room_id, objective_id, coordinator_identity_id, ack_required, statestructured routing outcome
assignmentsassignment_id, company_id, room_id, objective_id, assignee_kind, assignee_id, state, claimed_at, returned_atwork responsibility
objectivesobjective_id, company_id, scope_type, scope_id, objective_kind, title, expected_evidence_json, stateroute target and business intent
messagesmessage_id, room_id, seq_no, sender_canonical_identity_id, message_type, body_json, created_atappend-only room messages
message_relationssource_message_id, target_message_id, rel_type, metadata_jsonreply, thread, supersedes, cites, evidence-for
message_revisionsrevision_message_id, original_message_id, supersedes_message_idedit/supersession lineage
message_redactionsredaction_id, message_id, reason_code, redacted_by, redacted_atcontent masking while retaining audit shell
read_cursorsroom_id, principal_id, fully_read_seq_no, read_receipt_seq_no, cursor_token, updated_atread progress and unread computation
presence_sessionspresence_session_id, room_id, principal_id, agent_instance_id, presence_state, last_seen_atactive presence
notificationsnotification_id, principal_id, room_id, message_id, delivery_channel, statefan-out tracking
decision_recordsdecision_id, room_id, objective_id, summary, status, supersedes_decision_idroom outcome record
evidence_itemsevidence_id, company_id, owner_scope_type, owner_scope_id, linked_room_id, linked_message_id, prov_jsondurable retained evidence
memory_documentsmemory_id, company_id, owner_scope_type, owner_scope_id, source_decision_id, summary_text, retention_classdurable company/workspace/project memory
audit_eventsaudit_event_id, company_id, interaction_id, event_type, actor_id, subject_ref, payload_jsonsecurity, compliance, and operations trail
outbox_eventsoutbox_event_id, aggregate_type, aggregate_id, event_type, payload_json, published_atreliable event publishing

A few design choices matter a lot:

  • rooms.owner_scope_type is one of company | workspace | project | goal | task, but durable knowledge objects use only company | workspace | project.
  • messages.seq_no is a room-local monotonic sequence for deterministic pagination and read cursors.
  • message_relations and message_revisions are separate so replies/threads do not get tangled with edits/supersessions.
  • routing_decisions.route_version enforces conflict-safe compare-and-swap.
  • audit_events.interaction_id follows OWASP’s guidance that logs should capture the “when, where, who, and what,” and that an interaction identifier should link related events for a user interaction.

Lifecycle state machines

For durable rooms:

stateDiagram-v2
    [*] --> Provisioning
    Provisioning --> Active: created + policy attached
    Active --> Archived: archive requested
    Archived --> Reopening: reopen requested
    Reopening --> Active: policy revalidated
    Archived --> Purged: retention expired + no legal hold

For temporary goal/task rooms:

stateDiagram-v2
    [*] --> Draft
    Draft --> Active: route accepted
    Active --> Resolving: objective met or cancelled
    Resolving --> Archived: decision package stored
    Archived --> Reopened: coordinator reopens
    Reopened --> Active

For assignments:

stateDiagram-v2
    [*] --> Unassigned
    Unassigned --> Claimed
    Claimed --> InProgress
    InProgress --> Paused
    Paused --> InProgress
    Claimed --> Returned
    InProgress --> Returned
    InProgress --> Completed
    Claimed --> Reassigned
    Returned --> Unassigned
    Reassigned --> Claimed

That assignment model is intentionally close to the user-task patterns documented by Camunda: assignment is distinct from work progress; tasks may be claimed, assigned, returned, paused, resumed, and completed; and applications often need custom action semantics on top of a smaller set of engine-level lifecycle events.

Consistency, pagination, corrections, and archival

For transcript access, use cursor-based pagination and since tokens, not offset paging. Slack’s API docs explicitly recommend cursor-based pagination for large conversational collections, Matrix uses next_batch / since sync tokens and room message paging, and XMPP MAM requires Result Set Management for archive paging. XMPP MAM also supports “flipped pages,” which are useful when agents scroll backward through history and want newest-in-page first.

For read state, keep two distinct watermarks:

  • read receipt watermark: “up to which event has this actor acknowledged?”
  • fully read watermark: “up to which event should unread be cleared?”

Matrix explicitly distinguishes receipts from m.fully_read markers, and also separates threaded from unthreaded receipts. That is a strong pattern for MATM because a room can have parallel objectives or thread-like sub-conversations while still needing a main-room unread marker.

For edits and supersession, use append-only revision events rather than destructive updates. Matrix’s replacement-event model is the right precedent: the original message remains intact, later revision events point to it, and clients render the latest valid replacement. Redaction is separate again: it strips visible content while preserving the protocol shell and auditability. That is exactly what an enterprise MATM system needs for message correction, moderation, and legal defensibility.

For retention, distinguish:

  • active transcript visibility
  • archived but discoverable transcript
  • deleted from UI but retained for compliance hold
  • hard-purged after retention expiry

Teams’ first-party retention documentation is particularly useful here because it shows that user-visible messages can disappear while retained copies remain in secured compliance storage, and that conflicting retention rules are resolved in favor of retention and longer retention. That is a good model for temporary goal/task rooms whose active room can close while decisions and evidence remain discoverable.

Endpoint contract

The API should be explicit and machine-friendly. Suggested core endpoints:

EndpointPurpose
POST /v1/companies/{companyId}/welcome-sessionscreate-or-resume identity negotiation and routing session
POST /v1/roomscreate room from template or objective
GET /v1/rooms/{roomId}room descriptor with links, policy summary, and current synopsis
GET /v1/rooms/{roomId}/transcript?cursor=...&limit=...transcript page
POST /v1/rooms/{roomId}/messagesappend message with idempotency
POST /v1/rooms/{roomId}/read-cursorsupdate fully-read and receipt positions
POST /v1/rooms/{roomId}/membership-grantsexplicit grant/narrowing
POST /v1/routing/decisionsroute or reroute a subject
POST /v1/assignmentsclaim, return, reassign, pause, complete
POST /v1/rooms/{roomId}/archivearchive room
POST /v1/rooms/{roomId}/reopenreopen room
GET /v1/notificationsprincipal-specific unread and delivery state

Use an Idempotency-Key header on any write that can be retried. Matrix explicitly uses transaction IDs to ensure idempotent event sending, and Temporal recommends durable idempotency keys for retried activities and messages.

Structured routing response

The routing response should be a first-class contract, not inferred from chat text:

{
  "routingDecisionId": "rd_01J0XQKDA9D9B1V3M2M3R5N7QH",
  "routingSubjectId": "rs_company_9f4d_agentinstance_72c1",
  "routeVersion": 12,
  "lane": "project-coordination",
  "canonicalRoom": {
    "roomId": "room_proj_7x2",
    "slug": "acme/payments/reconciliation",
    "ownerScopeType": "project",
    "ownerScopeId": "proj_7x2",
    "durabilityClass": "durable"
  },
  "specificObjective": {
    "objectiveId": "obj_8fd1",
    "kind": "goal",
    "title": "Stabilize July reconciliation workflow",
    "summary": "Investigate failed imports, agree remediation, produce evidence package."
  },
  "expectedEvidence": [
    {
      "evidenceType": "log-excerpt",
      "required": true,
      "description": "Import failure traces for the last 24 hours"
    },
    {
      "evidenceType": "decision-record",
      "required": true,
      "description": "Coordinator-approved remediation decision"
    }
  ],
  "responsibleCoordinator": {
    "canonicalIdentityId": "cid_coord_001",
    "displayName": "Payments Coordinator",
    "roomId": "room_proj_7x2"
  },
  "nextAction": {
    "type": "enter-room-and-acknowledge",
    "roomId": "room_proj_7x2",
    "messageTemplate": "Introduce yourself briefly and attach current findings."
  },
  "acknowledgementRequired": {
    "required": true,
    "deadlineUtc": "2026-07-12T16:25:00Z",
    "method": "message"
  },
  "supersedesRoutingDecisionId": "rd_01J0XQHWR4H3...",
  "consistency": {
    "authzRevision": "authz_rev_9812231",
    "roomStateVersion": 441,
    "routeIssuedAtUtc": "2026-07-12T16:15:42Z"
  }
}

A synchronous accepted route is preferable when the route can be decided immediately. If coordinator arbitration is slow or requires human approval, return 202 Accepted and surface a resumable workflow handle, using Temporal-style update/query semantics underneath.

Human and agent experience

Humans and agents should see the same underlying rooms, but with different presentation envelopes.

For humans, the experience should be wiki-like and navigable. Confluence is the right reference point here: parent/child hierarchy in a visible page tree, shortcuts, labels, and layered permissions. A room page should therefore have breadcrumbs, related-room links, decision cards, evidence links, assignment state, and transcript tabs. Humans should feel like they are moving through a navigable collaboration graph, not through raw queue entries. Atlassian’s documentation on parent/child pages, sidebar shortcuts, labels, and separate space-versus-content permissions is directly relevant.

For agents, the experience should be compact, machine-readable, and bounded. Large full-history replay is usually the wrong default. Matrix explicitly supports lightweight sync with since tokens and room deltas, and XMPP MAM allows bounded archive queries with paging. LangChain’s multi-agent docs are also useful here: they distinguish handoffs, routers, and subagents, and they call out the token and latency cost of sequential handoffs with growing context. OpenAI’s Agents SDK similarly distinguishes handoffs from agents-as-tools. The architectural lesson is to send agents only the context they need for the next step, plus strong links to fetch more if needed.

A good agent room descriptor response looks like this:

{
  "room": {
    "roomId": "room_proj_7x2",
    "title": "Payments Reconciliation",
    "kind": "project",
    "ownerScope": { "type": "project", "id": "proj_7x2" },
    "links": [
      { "rel": "parent-workspace", "href": "/v1/workspaces/ws_19" },
      { "rel": "parent-company", "href": "/v1/companies/co_acme" },
      { "rel": "related-goal-room", "href": "/v1/rooms/room_goal_23" }
    ]
  },
  "synopsis": {
    "summaryVersion": 44,
    "shortSummary": "Current focus: import failures on vendor file set B.",
    "openDecisions": 2,
    "openAssignments": 1
  },
  "transcriptWindow": {
    "cursor": "seq:441",
    "items": [
      {
        "seqNo": 437,
        "messageId": "msg_437",
        "sender": { "displayName": "Payments Coordinator", "canonicalIdentityId": "cid_coord_001" },
        "type": "decision",
        "body": { "text": "Need import traces and vendor schema diff." }
      }
    ]
  },
  "unread": {
    "fromSeqNo": 438,
    "toSeqNo": 441,
    "count": 4
  },
  "fetchHints": {
    "olderCursor": "seq:436",
    "newerSince": "seq:441",
    "pageSizeDefault": 50
  }
}

Late-arriving agent recovery

Late-arriving agents should not have to reconstruct room state by reading thousands of messages. Recovery should be a three-part bundle:

  1. room synopsis
  2. decision ledger since last acknowledged point
  3. unread transcript delta

This approach is justified by durable-chat precedents. Matrix returns room state deltas and may signal gaps when timelines are limited; XMPP MAM supports archive windows and paging; collaboration APIs such as Slack also use cursor-based history retrieval for large conversations.

So the recovery algorithm is:

  • if the principal has a read_cursor, fetch all unread items after fully_read_seq_no
  • additionally fetch all decision_records with decision_seq_no > decision_cursor
  • if the unread gap exceeds a threshold, replace the first portion with a machine summary and mark gap_recovered=true
  • if the room was archived and reopened, include the most recent archival decision package

Onboarding sequence diagram

sequenceDiagram
    participant A as Authenticated Agent
    participant W as Company Welcome Room
    participant I as Identity Negotiation Service
    participant P as Policy/AuthZ
    participant R as Routing Service
    participant C as Coordinator
    participant T as Target Room

    A->>W: Enter welcome room
    W->>I: Start or resume welcome-session
    I->>A: Ask who are you / why present / what working on
    A->>I: Submit claims
    I->>P: Validate principal, memberships, allowed agent profile
    P-->>I: Allowed scopes + negotiated identity constraints
    I->>R: Request route with claimed objective/context
    R->>C: Obtain coordinator or policy decision
    C-->>R: Route selected + evidence expectations + ack requirement
    R-->>I: Structured routing decision
    I-->>A: Canonical identity + route response
    A->>T: Enter canonical room
    T-->>A: Compact synopsis + unread delta + required acknowledgement

That flow maps cleanly to Temporal’s Signal-With-Start pattern for creating or resuming an onboarding workflow, then issuing durable updates/queries as routing progresses.

Operations, failure handling, and test strategy

Notification integration

Notifications should come from the outbox, not directly from message writes. CloudEvents is a sensible envelope for those domain events because it standardizes common event metadata for interoperable routing across services and platforms. Use event types such as:

  • room.message.appended
  • room.message.redacted
  • routing.decision.issued
  • assignment.claimed
  • room.archived
  • room.reopened

Then have delivery adapters for websocket fan-out, mobile push, email digests, incident channels, or external workflow hooks. CloudEvents exists precisely to reduce the cost of every consumer inventing a different event envelope.

Notification rows should track:

FieldUse
notification_iddedupe and delivery audit
principal_idrecipient
reasonmention, assignment, unread threshold, route issued
room_id / message_idnavigation target
delivery_channelwebsocket, mobile push, email, webhook
delivery_statepending, sent, failed, acknowledged
collapse_keyprevent spammy duplicate pushes

Moderation, redaction, confidentiality, and prompt-injection containment

For moderation and correction, prefer supersession and redaction over silent mutation. Matrix’s edit and redaction model is the most useful ready-made precedent. For confidentiality, copy Confluence’s restriction principle: a room or page may be more restrictive than its container, not less restrictive.

For prompt-injection containment, do not treat transcript content as executable instructions. OWASP’s LLM Prompt Injection Prevention guidance is directly applicable: separate instructions from user data, validate inputs, monitor outputs, use least-privilege tool scopes, validate agent tool calls against session context, and require human approval for destructive actions. Their attacker model explicitly includes remote or indirect prompt injection through documents, emails, issue descriptions, and fetched web content, which is highly relevant for agent meeting rooms that can contain pasted logs or linked artifacts.

So the room service should enforce:

ThreatRequired control
message spoofing another agentsender identity comes only from auth/session context; no client-controlled sender field
pasted prompt tries to reconfigure agenttranscript rendered as data channel; system instructions remain out-of-band
linked evidence contains hidden promptsanitize and classify remote content before injecting into model context
tool misuse from malicious transcriptper-tool auth checks against user/agent permissions and current room scope
room mention leaks confidential roomunauthorized room links render as opaque placeholders or not at all
display-name impersonationcanonical identity ID remains primary; display names are negotiated labels only

Simultaneous coordinators and conflicting routing decisions

This is where the actor/workflow layer matters. Dapr actors enforce turn-based concurrency; Orleans defaults to safe single-activation execution; Akka persistence stresses the single-writer principle for each durable identity. The easiest way to use those lessons is to make routing subject a first-class aggregate with one active coordinator of record and a monotonic route_version.

Recommended conflict rule:

  1. Every route proposal targets a routing_subject_id.
  2. The routing writer uses optimistic concurrency on current_route_version.
  3. If two coordinators propose simultaneously, only one update advances the version.
  4. The loser becomes superseded.
  5. If a split-brain or duplicate-writer event is detected, require fencing tokens and emit an audit event.

If the organization wants collaborative routing rather than strict single-owner routing, keep one effective route and allow multiple advisory proposals. That preserves the transcript/audit trail without creating ambiguous execution state.

Audit records and signed-out behavior

OWASP’s logging guidance is useful here because it names the kinds of events that must be logged: input validation failures, authentication successes and failures, authorization failures, higher-risk admin actions, sensitive-data access, imports/exports, and suspicious flow-bypass behavior. Those map directly to room systems.

A strong audit policy logs at minimum:

  • welcome-room entry and identity negotiation result
  • all room creation, archival, reopen, and deletion attempts
  • all membership-grant writes and authz denials
  • all routing decisions, supersessions, and coordinator overrides
  • all assignment claims, returns, pauses, completions, and reassignments
  • all message posts, edits, redactions, and moderation actions
  • all evidence uploads, exports, and retention deletions
  • all unusual transcript fetches, bulk pagination, or failed row-scope checks
  • all prompt-injection detections or tool-approval denials

Signed-out behavior should be simple: no room discovery, no transcript visibility, no membership introspection, and no room-title leakage. The only public surface should be the sign-in entry point or explicitly public marketing/help content. If you ever choose to support anonymous/public rooms, treat that as an explicit exception policy, not the default. Atlassian’s docs are a good reminder that anonymous access is possible, but it is a conscious configuration, not an accidental outcome.

Operational metrics

Track the subsystem like a collaboration platform and like a workflow engine:

MetricTarget signal
authz check p95 latencyroom enter and transcript fetch responsiveness
room enter success rateonboarding health
routing decision p95 latencycoordinator responsiveness
route conflict ratequality of coordinator arbitration
message append p95 latencycollaboration usability
read-after-write success rateperceived consistency
cursor lagunread correctness
outbox publish lagnotification/search freshness
notification delivery successend-user reliability
redaction SLAmoderation responsiveness
archive/reopen durationoperational efficiency
search indexing lagdiscoverability freshness
cross-tenant denial counttenant-isolation pressure
recovery bundle sizelate-arrival efficiency
retention backlog agecompliance risk

Zanzibar’s paper is a useful benchmark reminder that authorization latency and consistency are first-order product qualities, not implementation details.

Failure scenarios

ScenarioExpected behavior
duplicate POST /messages due to retryidempotency key returns prior result
room actor fails after write but before notifytranscript row is committed; outbox retries
welcome-session times outsession resumes from workflow state
coordinator crash during reassignmentrouting subject remains at last committed version; new coordinator retries
authz graph change while join in progressuse authz revision token; re-check before transcript delivery
tenant index misconfigurationfail closed; do not return partial cross-tenant results
moderation redacts original after edits existoriginal becomes hidden; revisions remain lineage/audit only
temporary room archived before evidence persistedarchive blocked until decision package transaction succeeds
long unread gap after retention purgereturn synopsis + decision ledger + gap flag, not incomplete raw history
split-brain coordinatorsonly one fencing token can commit next route version

Comprehensive integration tests

The integration test suite should be large and deterministic. At minimum, include end-to-end cases for:

CategoryTest
tenant isolationuser in company A cannot discover, name-resolve, fetch, paginate, or search company B rooms
auth inheritancecompany membership enters welcome room; workspace/project room access follows parent scope; confidential room narrows access correctly
identity negotiationconflicting claimed names produce canonical resolution; unauthorized claimed profile is rejected
spoofing preventionmessage body saying “I am Coordinator” does not affect sender identity; client-supplied sender field is ignored
routingroute issuance creates exactly one effective route version under concurrent proposals
onboardingwelcome-session can be resumed after crash or timeout
transcript consistencymessage post is immediately readable to poster and visible in subsequent pagination
paginationconcurrent writes do not duplicate or skip transcript items across cursors
read cursorsfully-read and receipt markers update independently and compute unread correctly
edits and redactionslatest supersession renders; redaction hides content while preserving audit shell
temporary roomsgoal/task room archival preserves decision record and evidence links
reopenarchived durable room reopens without losing transcript or room identity
retentionexpired temporary-room transcript disappears from UI but decision/evidence remain according to policy
prompt injectionmalicious pasted instructions do not alter system policy or unauthorized tool access
notificationswebsocket/email/mobile dedupe correctly with collapse keys
conflict holdslegal hold prevents purge even after room archival
late arrivallate-arriving agent receives synopsis + decisions + unread delta without full replay
assignment lifecycleclaim, pause, resume, return, reassign, and complete transitions enforce auth and comments where required

Done well, this architecture gives you what mature platforms already converged on: rooms as durable collaboration objects, authorization as a separate graph, routing as a governed control plane, transcripts as append-only timelines with revision/redaction semantics, and memory/evidence as retained domain records rather than ad hoc chat residue. The sources above do not hand you this exact MATM system ready-made, but they do converge strongly on the same design boundaries. The most defensible enterprise implementation is to adopt those boundaries deliberately instead of rediscovering them through production failures.