Problem
Building a real product on top of CAO (harness-control, a multi-agent session manager), we've hit a genuine gap: an agent running in a CAO-managed terminal has no way to discover sibling terminals or message them, outside of the supervisor-dispatched handoff/assign flow.
Concretely: send_message is granted to our agent profile via cao-mcp-server, but per its own documented semantics it only works if the agent was handed a receiver_id (via a blocking [CAO Handoff] message or a non-blocking assign callback) or has a caller_id set (only true for handoff/assign-created terminals). A terminal created directly via POST /sessions (the flow any CAO consumer NOT using the supervisor/worker pattern relies on) has no caller_id and was never handed a receiver_id -- send_message is present but has nothing to target.
There's also no discovery primitive at all -- no MCP tool or API endpoint lets a running agent ask "what other terminals exist, and which of them are relevant to me." TerminalModel itself has no metadata/tags field a consumer could use to organize terminals into meaningful groups (e.g., "these three terminals are working on the same project").
We believe this is a real gap for any CAO consumer building multi-agent products outside the strict supervisor/worker shape, not something specific to our own product.
Proposal
Two additions:
1. group and metadata fields on TerminalModel
group: JSON (nullable) -- an ordered array of strings, general-to-specific (e.g. ["tenant_1", "project_5", "folder_12"]). Consumers decide what the levels mean; CAO just stores and does ordered-prefix matching. Settable at create_session time; updatable later via a new PATCH /terminals/{id}/group-style endpoint (important for consumers whose own grouping can change after a terminal already exists -- e.g. a folder gets reassigned to a different project in our own product).
metadata: JSON (nullable) -- free-form, no fixed schema, purely descriptive ("what is this terminal doing"). Settable at creation and updatable by the running agent itself via a new MCP tool.
2. list_siblings MCP tool (+ matching API endpoint)
- Resolves the calling terminal's own identity via the same mechanism
send_message/handoff already use (terminal's own env-provided identity, never a client-supplied "who am I" claim).
- Looks up the caller's own
group.
- Takes an optional
depth parameter: how many leading elements of group must match to be included in results. Server clamps depth to [1, len(caller_group)] -- can never be 0 (an unscoped, all-terminals-on-the-server query), and can never exceed the caller's own group length.
- Returns
{id, group, metadata} for every OTHER terminal whose group[0:depth] == caller_group[0:depth] AND depth <= len(sibling_group) (a sibling with a shorter group than the requested depth is excluded from that comparison rather than causing an index error or an undefined partial match).
- A terminal with no
group set participates in no discovery (neither finds others nor is found), rather than matching everything or crashing.
Why this shape
- Keeps CAO domain-agnostic -- it never needs to understand what "project" or "folder" or "tenant" means, just ordered-array-prefix matching. Consumers own the semantics.
group[0] naturally becomes a hard multi-tenancy boundary for any consumer that sets it to a tenant/workspace id, since a caller can only ever compare against ITS OWN group[0], never an arbitrarily-requested one -- this falls out of the design rather than requiring CAO to understand tenancy.
- Extensible without a schema migration -- more hierarchy levels, or (later) a real ACL/policy layer layered on top of the depth-prefix match as an additional filter, both fit without breaking existing consumers.
Known open questions / non-goals (flagging rather than hand-waving)
- v1 doesn't include any deny/allow policy beyond group-prefix matching -- a consumer wanting finer-grained visibility (e.g. "visible to my project but not this specific other terminal") would need to build that on top for now.
- No revocation/expiry semantics for
group/metadata -- they're just current-state fields.
- We haven't verified whether CAO's existing
/terminals/{receiver_id}/inbox/messages mechanism is the right delivery path once a sibling is discovered via this tool, or whether list_siblings should return enough for the agent to just call the existing send_message tool directly -- open to whichever is more consistent with CAO's own architecture.
Our own validation plan if this has maintainer appetite
Per how we approached #382/#388: happy to build this against CAO's own dev environment and test suite, validate it end-to-end against our own real downstream product (harness-control), and submit a PR with regression tests -- flagging here first per your own contribution norms before writing code, since this is a real (if small) schema addition, not a bugfix.
Would appreciate a read on whether this fits CAO's own direction before we invest in a PR.
Problem
Building a real product on top of CAO (harness-control, a multi-agent session manager), we've hit a genuine gap: an agent running in a CAO-managed terminal has no way to discover sibling terminals or message them, outside of the supervisor-dispatched
handoff/assignflow.Concretely:
send_messageis granted to our agent profile viacao-mcp-server, but per its own documented semantics it only works if the agent was handed areceiver_id(via a blocking[CAO Handoff]message or a non-blocking assign callback) or has acaller_idset (only true for handoff/assign-created terminals). A terminal created directly viaPOST /sessions(the flow any CAO consumer NOT using the supervisor/worker pattern relies on) has nocaller_idand was never handed areceiver_id--send_messageis present but has nothing to target.There's also no discovery primitive at all -- no MCP tool or API endpoint lets a running agent ask "what other terminals exist, and which of them are relevant to me."
TerminalModelitself has no metadata/tags field a consumer could use to organize terminals into meaningful groups (e.g., "these three terminals are working on the same project").We believe this is a real gap for any CAO consumer building multi-agent products outside the strict supervisor/worker shape, not something specific to our own product.
Proposal
Two additions:
1.
groupandmetadatafields onTerminalModelgroup: JSON(nullable) -- an ordered array of strings, general-to-specific (e.g.["tenant_1", "project_5", "folder_12"]). Consumers decide what the levels mean; CAO just stores and does ordered-prefix matching. Settable atcreate_sessiontime; updatable later via a newPATCH /terminals/{id}/group-style endpoint (important for consumers whose own grouping can change after a terminal already exists -- e.g. a folder gets reassigned to a different project in our own product).metadata: JSON(nullable) -- free-form, no fixed schema, purely descriptive ("what is this terminal doing"). Settable at creation and updatable by the running agent itself via a new MCP tool.2.
list_siblingsMCP tool (+ matching API endpoint)send_message/handoffalready use (terminal's own env-provided identity, never a client-supplied "who am I" claim).group.depthparameter: how many leading elements ofgroupmust match to be included in results. Server clampsdepthto[1, len(caller_group)]-- can never be 0 (an unscoped, all-terminals-on-the-server query), and can never exceed the caller's own group length.{id, group, metadata}for every OTHER terminal whosegroup[0:depth] == caller_group[0:depth]ANDdepth <= len(sibling_group)(a sibling with a shorter group than the requested depth is excluded from that comparison rather than causing an index error or an undefined partial match).groupset participates in no discovery (neither finds others nor is found), rather than matching everything or crashing.Why this shape
group[0]naturally becomes a hard multi-tenancy boundary for any consumer that sets it to a tenant/workspace id, since a caller can only ever compare against ITS OWNgroup[0], never an arbitrarily-requested one -- this falls out of the design rather than requiring CAO to understand tenancy.Known open questions / non-goals (flagging rather than hand-waving)
group/metadata-- they're just current-state fields./terminals/{receiver_id}/inbox/messagesmechanism is the right delivery path once a sibling is discovered via this tool, or whetherlist_siblingsshould return enough for the agent to just call the existingsend_messagetool directly -- open to whichever is more consistent with CAO's own architecture.Our own validation plan if this has maintainer appetite
Per how we approached #382/#388: happy to build this against CAO's own dev environment and test suite, validate it end-to-end against our own real downstream product (harness-control), and submit a PR with regression tests -- flagging here first per your own contribution norms before writing code, since this is a real (if small) schema addition, not a bugfix.
Would appreciate a read on whether this fits CAO's own direction before we invest in a PR.