Skip to content

feat(workspace): @mentioning an agent from a 1:1 starts a group discussion (ent#361) - #2148

Merged
vybe merged 3 commits into
devfrom
feature/ent361-mention-to-group
Aug 13, 2026
Merged

feat(workspace): @mentioning an agent from a 1:1 starts a group discussion (ent#361)#2148
vybe merged 3 commits into
devfrom
feature/ent361-mention-to-group

Conversation

@dolho

@dolho dolho commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Finishes ent#361 — the p1 AC added on the 12th: "@mentioning an agent from ANY existing chat (including a 1:1) creates a group discussion with them."

The issue has been sitting at status-in-dev without it. There was nothing to build on: client_portal/ had zero mention handling, so a 1:1 could only ever stay a 1:1.

Typing @other in a 1:1 now creates a room with both agents, carries the message into it, and moves you there.

Two halves, deliberately in different layers

The in-room half — @mentioning someone not in a room adds them (AC #4) — is engine-side: abilityai/trinity-enterprise#390. Membership is the engine's concern and agent replies flow through it.

This half answers a different question — should this conversation become a room — and answering it in OSS would mean importing the private module. Routing it through the existing POST /api/rooms instead keeps create_room's per-agent ACL as the single enforcement point rather than growing a second one that can drift from it.

An unresolvable @name is plain text, not an error

Reporting "no such agent" would answer, for any string a user cares to type, whether an agent by that name exists on the instance. The rooms engine already behaves this way; the composer now matches it.

The pattern mirrors the engine's _MENTION_RE on purpose, and is pinned by tests. Drift there builds a room around a handle the engine then renders as plain text.

Ordering is not incidental

Create the room → navigate → then post. Posting first leaves the user staring at the old thread while the agents they just summoned reply somewhere they cannot see. A failed post leaves them in a room they can retype into, which is recoverable; a created-but-unreachable room is not. A failed escalation gives the typed text back rather than swallowing it.

Worth flagging

AC #4's premise was wrong, and this is why ent#361 looked done. The issue says "today a chat can only become multi-agent by @mentioning someone mid-conversation; that path is fine and stays", and asks that it "still works". resolve_mentions matched only names already in the room and documented the intent outright — "a mention can never reach outside". So the AC reads like a regression guard and is actually a feature request. Both halves are new work, not preserved behaviour.

Verification

260 frontend tests (18 files) incl. 8 new for the resolver — roster-scoped resolution, the excluded current agent, dedupe, multiple agents in order, hyphen/digit names matching the engine, and the empty/missing-roster cases. Build clean. Enterprise side: 9 new tests, 294 passed vs 285 before (the 29 failures are pre-existing on main, none in shared_sessions).

Gated on the same rooms capability as the picker (#2128) — no rooms, no escalation, @ stays ordinary text.

Submodule pin advanced to the enterprise commit; abilityai/trinity-enterprise#390 should merge first.

Related to abilityai/trinity-enterprise#361

…ssion (ent#361)

The AC Eugene made binding on the 12th — "@mentioning an agent from ANY existing
chat (including a 1:1) creates a group discussion with them" — was the last
piece of ent#361, and the issue has been sitting at status-in-dev without it.
There was nothing to build on: `client_portal/` had zero mention handling, so a
1:1 could only ever stay a 1:1.

Typing `@other` in a 1:1 now creates a room containing both agents, carries the
message into it, and moves the user there.

**Why this half is UI-side and the in-room half is not.** Membership inside a
room is the engine's concern and agent replies flow through it, so
`_join_mentioned_newcomers` lives there (Abilityai/trinity-enterprise#390). The
1:1 escalation is a different question — "should this conversation become a
room" — and answering it in OSS would mean importing the private module. Routing
it through the existing `POST /api/rooms` instead keeps `create_room`'s per-agent
ACL as the single enforcement point rather than growing a second one that can
drift from it.

**An @name that is not a rostered agent stays plain text, and that is not an
error.** Reporting "no such agent" would answer, for any string a user cares to
type, whether an agent by that name exists on the instance. The engine already
behaves this way; the composer now matches it.

The pattern mirrors the engine's `_MENTION_RE` deliberately and is pinned by
tests. If the two drift, a user gets a room built around a handle the engine
then renders as plain text.

Ordering matters and is not incidental: the room is created, then navigated to,
then the message is posted. Posting first leaves the user in the old thread
while the agents they summoned reply somewhere they cannot see; a failed post
leaves them in a room they can retype into, which is recoverable. A failed
escalation gives the typed text back rather than swallowing it.

Gated on the same rooms capability as the picker (#2128) — without it there is
nowhere to escalate to, so an @mention has to keep working as ordinary text.

Related to Abilityai/trinity-enterprise#361

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
trinity-ability and others added 2 commits August 13, 2026 13:11
trinity-enterprise#390 was squash-merged, so the commit this pointer named
(fade3cbb, the feature-branch head) is not reachable from enterprise `main` —
`git compare` reports it diverged, ahead 1 / behind 1, and the branch has since
been deleted. A pointer at an orphaned commit is worse than a stale one: a fresh
`git submodule update` cannot fetch it at all, and CI stays green either way
because both submodules are `update = none` and are never checked out there.

Repointed at dcf0b52, the squash commit now on enterprise main.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four conflicts, all additive — both sides added at the same point, so each
resolves as a union rather than a choice:

  * `core-agent.md` — #2147 (ent#360) and this branch both wrote a `### 5.11`.
    #2147 merged first and keeps the number; ent#361 renumbers to **5.12**.
    No cross-references to either anchor exist elsewhere in docs/ or src/.
  * `portalUtils.js` — #2133's budget constants and ent#361's `mentionedAgents`
    are unrelated exports; both kept.
  * `PortalConversation.vue` — one import line, merged to name all three
    symbols.
  * `portalSidebarIA.spec.js` — two `describe` blocks whose conflict cut through
    both; ent#361's is closed and #2133's follows it.

Verified: 282 frontend tests across 19 files pass, vite build clean, and an
unfiltered marker sweep over the whole tree is empty.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@vybe vybe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validated via /validate-pr. All 4 required checks, all 6 pytest shards and e2e green after the fixes below. No secrets, no new config var, no schema change.

I corrected the submodule pointer before merging — it would have shipped broken. The pin named fade3cbb, the pre-squash head of trinity-enterprise#390. Because #390 was squash-merged (and its branch deleted), that commit is not reachable from enterprise maincompare reports it diverged, ahead 1 / behind 1. A fresh git submodule update could not fetch it at all. Nothing in CI can catch this: both submodules are update = none, so CI never checks them out and stays green either way. Repointed at dcf0b52. I also confirmed dev's prior pin a0ad11c5 is a clean ancestor of enterprise main exactly one commit behind, so after this merge the pointer equals main's tip with nothing skipped.

Conflict resolution against the newly-merged dev — four conflicts, all additive, so each is a union rather than a choice:

  • core-agent.md#2147 (ent#360) and this branch both wrote ### 5.11. #2147 merged first and keeps it; ent#361 renumbered to §5.12. No cross-references to either anchor exist elsewhere in docs/ or src/.
  • portalUtils.js#2133's budget constants and ent#361's mentionedAgents are unrelated exports; both kept.
  • PortalConversation.vue — one import line naming all three symbols.
  • portalSidebarIA.spec.js — the conflict cut through two describe blocks; ent#361's is closed and #2133's follows.

Verified locally on the merge result before pushing: 282 frontend tests across 19 files, vite build clean, and an unfiltered conflict-marker sweep over the whole tree empty.

On the change itself: keeping the 1:1→room decision in OSS and routing it through the existing POST /api/rooms is the right layering — it keeps create_room's per-agent ACL as the single enforcement point instead of growing a second one, and avoids OSS importing the private module. The unresolvable-@name-stays-plain-text rule matches the engine's own behaviour and correctly avoids turning the composer into an existence oracle; mirroring _MENTION_RE with tests pinning it is the right guard against the two sides disagreeing. Create → navigate → post ordering is argued correctly: a failed post is recoverable, a created-but-unreachable room is not.

ent#361 is cross-tracker, so nothing auto-promotes — its enterprise half (#390) is already merged and I'll leave the issue's status to the release close.

@vybe
vybe merged commit b25f598 into dev Aug 13, 2026
25 checks passed
vybe pushed a commit that referenced this pull request Aug 13, 2026
No conflicts. Merged rather than left alone because this branch's CI last ran
against a `dev` from before #2147, #2148, #2150 and #2151 landed — three of
which touch the same portal files (`client_portal/service.py`,
`PortalConversation.vue`, `Portal.vue`). A green run against the older base is
not evidence the combination works, and it also leaves a stale cancelled pytest
shard on the PR.

Frontend suite on the merge result: 282 tests across 19 files.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants