Skip to content

feat(agent-org): [9/10] add multi-target Group ingress and linked Member Inbox #764

Description

@ShiboSheng

Effort

Delivery boundary P50 P90 Substantive files
Group multi-ingress, per-target retry idempotency, Linked Inbox cascade, and recovery 3,100 review lines 5,500 review lines 22–35

Delivery contract

Dependency and single goal

Strictly stack on PR 8. Add GroupMention and MemberInbox source adapters to the same UserDirectedWork dispatcher and deliver multi-target ingress, per-target retry idempotency, communication-link authority, and bounded recovery. PR 10 still owns safe default rollout of the Group projection.

State after merge

With the rollout gate enabled in internal/test environments, one Group message may mention one or more Members. Every target receives an independent user message, Turn, FIFO sequence, runtime, and outcome; network retry cannot duplicate delivery. During UserDirectedWork, a Member may use PR 2 links to send another Member one bounded side quest, and the Coordinator is always reachable. Working/Idle/Paused all accept this work. A busy target only queues in its own FIFO: no hard interruption, no clearing direct intervention, and no automatic Resume. The backend control plane is complete, but the default product gate remains closed until PR 10 removes the old N-channel Group feed.

Primary files

src-tauri/crates/agent-core/src/state/commands/session/org_tasks/group_chat.rs
src-tauri/crates/agent-core/src/core/coordination/agent_inbox/*
src-tauri/crates/agent-core/src/core/tools/impls/orchestration/agent_org/send_message*
src-tauri/crates/agent-core/src/core/tools/impls/orchestration/inbox_wake.rs
src/engines/ChatPanel/hooks/useAgentOrgGroupChatController.ts
src/api/tauri/agent/orgTasks.ts

Group ingress implementation

  1. The composer reads targets from structured member://<canonical_member_id> pills, deduplicates by ID, and canonicalizes the body after removing pills. Display names never resolve authority.
  2. Zero Member targets or Coordinator-only mentions use the existing Root send. This version typed-rejects a mixed @Coordinator + @Member message to avoid silently introducing another atomic protocol that creates Root plus N Member Turns in one click. deliveries contains non-Coordinator Members only.
  3. The frontend generates one stable and distinct turnIntentId per target and retains immutable deliveries:[{targetMemberId,turnIntentId}]. When transport outcome is unknown, Retry reuses the exact composer snapshot. Changed body or targets generate new IDs.
  4. One bounded backend transaction fully preflights Team, targets, target cap, queue cap, IDs, and digest before writing. Only three outcomes exist: all-new writes all; all-existing with exact matches returns existing receipts; mixed existing/new or any binding conflict returns idempotency_conflict with zero new rows.
  5. Each target separately receives source message/Inbox, PR 3 context, source=GroupMention, dispatch Member, FIFO sequence, root authority/budget, and independent receipt. Kick each target only after commit; pending recovery handles kick failure.
  6. Siblings do not reference each other. There is no group_message_id, batch row, shared progress, group-wide Stop/Retry, or hidden collaboration relationship.

Linked Inbox and scheduler implementation

  1. org_send_message resolves real sender, Turn kind, root authority, parent, and depth from persisted CallContext/context. The model cannot supply is_user_directed, delivery class, link override, or budget.
  2. The UserDirectedWork branch permits one plain side-quest target. Member-to-Member send checks the frozen undirected link. The Coordinator is always reachable. Self, unknown, and removed targets are rejected. Working/Idle/Paused all permit the write.
  3. PR 7 call_id receipt, root delivery/depth budget, child Inbox/context, and recipient FIFO allocation commit in one transaction. Kick is event-driven after commit.
  4. The formal branch remains Working-only and Task-bound. TaskAssigned can be generated only from a committed Task/assignment outbox; it does not read peer links and cannot carry a Member-defined side quest.
  5. Split generic drain into exact typed claims: formal drain claims only TaskAssigned/formal coordination; UserDirectedWork claims only the context's exact source_inbox_id. Remove group_chat.rs auto-Resume/intervention-clear and blanket drain.
  6. Startup-owned pending UserDirectedWork recovery uses keyset pagination, single-flight, and at most 100 rows or 50 ms per batch before yielding. Recover only an exact delivery that has never started. After start, a crash writes failed/abandoned/unknown and never automatically replays file or external side effects. Normal Idle/Paused adds no periodic scan.

Scope boundaries

A single org_send_message does not fan out. Do not allow self-send, unbounded cascades, automatic side-quest Task registration, Group-as-direct interruption, automatic Idle/Paused Resume, or model-forged authority. Multi-target submit atomicity covers initial acceptance only; after commit, targets are fully independent.

Failure scenarios that must be rejected or remain no-ops

  • Duplicate, unknown, removed, ambiguous, or N+1 targets, or mixed Coordinator plus Member.
  • Two targets reuse the same Turn ID.
  • Exact retry after response loss creates new Inbox, sequence, or Wake.
  • Mixed existing/new or same ID with different target/body is “filled in.”
  • Group/peer work clears direct intervention, auto-Resumes, or hard-interrupts a busy Member.
  • Blanket drain consumes TaskAssigned.
  • The model forges link, delivery class, root/parent, or budget.
  • Claim after Archive, or unbounded cascade/Provider amplification.
  • Pending delivery is lost on restart, or a started side quest is replayed automatically.

Acceptance and handoff

Verify concurrent duplicate/all-new/all-existing/mixed matrices; crash after commit before kick; pending-versus-started recovery; link on/off/bidirectional/Coordinator-reachable; TaskAssigned independent of links; call_id Inbox replay; N+1, queue, delivery, depth, and Provider backpressure; a 10-target burst with per-Member runtime ≤ 1; Working/Idle/Paused matrix; and ordinary SDE with zero additional context/Inbox/Wake/listener. PR 9 must freeze per-target independent context sequence, exact source_inbox_id, exact assistant reply_to, Member/source/outcome, and unified run-changed invalidation. PR 10 reads only these facts and cannot infer causality.

Suggested review commits — still one PR

A. Inbox authority and schema. B. Transactional Group ingress. C. Linked Inbox and recovery. D. Composer/RPC and focused tests. These aid review and are not four features that may merge out of order.

Plain-language contract

Dependency

PR 8.

How it works

The composer generates one stable, distinct turnIntentId for each canonical Member target. After one complete preflight, the backend accepts only three outcomes: write all-new together, return prior results for all-existing, or reject mixed state with zero new rows. After commit, kick targets independently. org_send_message derives sender/root/depth from real CallContext and writes peer Inbox according to communication links and budgets. Formal and user-directed Inbox use different exact claims. Recover only pending deliveries that never started.

State after merge

One message can mention multiple Members, and each receives its own message, Turn, FIFO, and result. Network retry does not duplicate work. In Working/Idle/Paused, a Member can use a link to ask another Member to do a side quest. A busy target queues only: no interruption, no clearing intervention, and no automatic Resume. The backend is complete, but the default Group feed still waits for PR 10.

Boundaries

Zero Member or Coordinator-only goes to Root. Mixed @Coordinator + @Member must be split into two sends. Do not create group_message_id, shared progress, or group-wide Stop/Retry. Do not self-send, cascade without bound, register a side quest as a formal Task automatically, or replay already-started side effects.

How to test

Test unknown/over-limit/mixed-target zero-write, all-new/all-existing/mixed idempotency, crash after commit before kick, pending versus started recovery, bidirectional links and Coordinator always reachable, TaskAssigned unaffected by links, call_id Inbox deduplication, N+1/backpressure, all three Team states, and zero extra ordinary-SDE branches.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Effort: HighUXImprovements to user experience, workflow smoothnessenhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions