Skip to content

[feat] Channel limits: choose where an agent answers and whom it answers - #7890

Merged
mmabrouk merged 31 commits into
release/v0.127.2from
feat/channel-limits
Oct 29, 2026
Merged

mmabrouk merged 31 commits into
release/v0.127.2from
feat/channel-limits

Conversation

@mmabrouk

@mmabrouk mmabrouk commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

Do not merge. The owner reviews it, and the release conductor picks its release.

Context

An agent owner could not fully choose where a Slack or Telegram agent answers, or whom it answers:

  • "Who can message it" showed only for Telegram. Slack had the same check, but the screen hid it.
  • "Add channel" added a channel to the list but did not limit the agent to it.
  • The people list was stored on the installation. The hosted Slack app has one installation per workspace, so a list set for one agent applied to every agent there.
  • Slack people were member ids. The owner could not pick them by name.
  • A refused person got no reply at all, so a refusal looked like a fault.

This implements the channel limits spec (openspec/changes/channel-limits, spec PR #7836). All seven questions were decided by the owner on 2026-10-26.

Changes

Where it answers. The "Answers in" switches stay. Advanced gets a new row, "Channels it answers in" ("Groups it answers in" on Telegram): "All channels the bot is in" or "Only these channels" with a checklist. It is stored as channel_grants allow rules, never a deny for channels:

All channels, both switches on   -> no rule (as before)
Only these: #support, DMs on     -> allow kind=private, allow space=#support
Only these: #support, DMs off    -> deny kind=private, allow space=#support
Only these, nothing checked      -> allow (or deny) kind=private only

One function writes the switches and the row together. It creates the new rules before it removes the old ones, so a save never passes through "no rule" (which means "answers everywhere"). The screen reads single-channel allow rules back as chosen. An unchecked channel stays in the list, marked "Off". "Add channel" now has a search field and adds several channels at once.

Whom it answers. "Who can message it" (Everyone or Specific people) shows for Slack too. The list moves from the installation to the agent binding:

before: channel_connections.data.allowed_senders = ["U1", ...]
after:  channel_agents.data.audience = {senders: ["U1", ...], labels: {"U1": {name, email}}}

The check compares ids only; labels are for display. Slack people are found by name or email from three sources: people who wrote to the bot (stored messages), the workspace list (Slack users.list, loaded in the background and kept 24 hours in Redis, with Refresh), and a live lookup of a full email. New settings-time routes: POST /channels/connections/{id}/people/query (returns {count, people, directory}) and .../people/refresh (returns {count, directory}). Each person has a source: recent_senders, directory or email_lookup. Which sources a platform offers is declared in its capabilities (people: {directory, email_lookup}; Slack declares both). Telegram keeps numeric ids plus "People who wrote to the bot", and now refuses a typed "@name".

The note. A refused person who addressed the agent (a DM, or a mention or command in a channel) gets one short private note, at most once a day per person, place and reason: an ephemeral message in a Slack channel, a normal reply in a DM, nothing in a Telegram group. The inbox worker sends it like today's fixed notices, keyed by a UUID v5 of person, place, reason (sender_not_listed or space_off) and day. A failed send is logged and never retried. A refusal opens no turn, no session, and charges nothing.

Who may change the limits. Only the owner of a private agent changes its people list and rules, with the binding on or off, and only the owner creates a binding that already carries a people list. The check runs inside the same database transaction as the write (the existing guard_agent_activation lock). It also covers moving the default grant and pointing a binding at another agent.

Other. A refused person's message is not attached to its space, so it never reaches the agent as stored context (also while the binding or the place is off). The channel card in /m says "Only N people" and counts chosen channels. The TypeScript client is regenerated from an EE spec. The Slack guide documents the new settings.

Migration oss000000055 (data only): copies each installation's allowed_senders to every binding of that installation that has no audience. The downgrade removes audience. It is chained after main's newest core head (oss000000053); the release conductor sets the final revision number at merge time. test_channel_people_lists_migration.py checks the copy, a second run, and the downgrade on a scratch Postgres.

Rollout. API pods of the previous release read the list from the installation. Until the next release, every people-list change on a binding (create or edit) is also written to the installation's allowed_senders, in the same database transaction, so an old pod sees the change at once. On an installation with several bindings, the last list written applies to all of them on an old pod, which is how old pods always behaved. New pods read only the binding. Remove this write in the next release. One narrow case is left: a connection rename or credential update at the same moment as a people-list save can put the old list back on the installation; only old pods read it, only during the rollout.

Round trips

Allowed messages cost exactly what they cost before. test_channels_limits_counts_postgres.py counts the SQL statements of resolve for one allowed message against real Postgres. The same file run on main (aaa2701) gave the same numbers:

Case First message Follow-up Same as main
No limits 14 10 yes
People list (sender on it) 14 10 yes
Only these channels 13 9 yes
Both 13 9 yes

No added network calls on the message path: the people check reads the agent row already loaded; the place check reads the rules already read. Extra cost appears only on refusals: an addressed refusal reads one note key a day per person and place, and sends at most one note.

Tests

  • API unit: whole OSS layer 9775 passed, EE layer 10723 passed (rebased on v0.127.1) (new: test_channels_limits.py, test_channels_people.py, slack/test_slack_people_and_ephemeral.py).
  • API integration on the stack DB: integration/channels 118 passed, including test_channels_people_list_rollout_postgres.py, the count test and the note end to end (test_channels_limits_note_postgres.py: Ben refused with one ephemeral note a day and no turn; his message not stored; Ana answered).
  • API acceptance on a local EE stack: acceptance/channels and acceptance/sharing together 169 passed, 5 skipped (live Slack/WhatsApp), including the new test_channel_limits_owner.py (a non-owner cannot change a private agent's limits, on or off, cannot create a binding of it with a people list, and cannot move a binding away, edit it and move it back). The people routes are in the permission matrix.
  • Web: @agenta/settings-ui vitest 247 passed (new channelLimits.test.ts plus panel, Advanced and picker tests); web/mobile tsc passes.
  • Migration: test_channel_people_lists_migration.py (copy, second run, downgrade) passed on a scratch Postgres.

Live QA

On the final head af9daf1f53, on a local EE stack with a production build of /m, a new account and seeded Slack and Telegram connections (no real Slack or Telegram): every Slack and Telegram cell passed, two quick switch clicks never fall back to all channels, the people picker recovers by itself after failed asks, the card and hub count the chosen channels, the saved rules route a mention in an unchecked channel to a refusal and one in a checked channel to a turn, and a self-managed agent on the ChatGPT subscription still answers. Self-managed Claude was not run: the host Claude login has expired. Details in the QA comment below; the recording is kept locally (the GitHub upload was not available).

Review

After the owner's review, the branch was rebased on v0.127.1 and the review loop ran again on the new head: naming, slop and simplify, three rounds with Opus and Codex, all Low at the end. It renamed the refusal reasons, the picker source values and a few fields (listed in the record below) and made the switches and the channel row share one save. Earlier, the review loop ran on this branch: slop and simplify (Opus and Codex), correctness and security (Codex), three rounds plus a delta check of the last fixes. Final ratings are Low. The record is in a comment below. Fixed along the way: a save that could pass through "no rule", the owner check moving inside the write, the default-move partial write, excluded senders stored while a binding or place is off, a retarget bypass of the owner check, and the people picker client.

Left on purpose:

  • A space default-agent rule set through the API counts as a chosen channel (the backend counts it that way), so a later save of "Channels it answers in" can remove it. Only the API writes such rules today. Tell me if a save should keep them.
  • Messages from refused people still reach the agent through the one-time Slack backfill and the channel read tools, as today (the spec keeps "as today").
  • Switching Channels off and on after "Only these channels" with nothing checked comes back as "All channels" (the screen shows it).

Copy for approval

All new user-facing text is a draft:

  • Refusal notes: "This agent only answers chosen people. Ask its owner in Agenta to add you." / "This agent does not answer in this channel." / "This agent does not answer direct messages. Mention it in a channel instead."
  • Advanced: "Channels it answers in" / "Groups it answers in", "Where it answers when mentioned. Direct messages follow the switch above. Channels include group DMs." (Telegram: "Private chats follow the switch above."), "All channels the bot is in" / "Only these channels" (and "groups"), "With no channel checked, the agent answers in no channel.", "A channel the bot joins later stays off until you check it.", "Channels are switched off above, so it answers in none." (Telegram: "Group chats ..."), "The bot is in no channel yet."
  • Who can message it: "Any Slack member can message the bot.", "Add at least one person below.", "Only N person/people. Everyone else is ignored.", "Name or email in Slack", "Search people in , or type a full email.", "Wrote to ", "In ", "Updated N min ago · Refresh", "Refreshing…", "Loading people from Slack…", "Type a name to search.", "No match.", "By email", "No one in uses this email.", "Could not load people from . Reinstall the Slack app, or type a full email.", "Bot · cannot be added", "Guest", "No longer in Slack", "Until you add someone, answers everyone.", "People who wrote to the bot", "Enter the numeric Telegram ID. A username cannot be matched."
  • Add channel: "Pick one or more channels. The app joins public channels itself. For a private channel, run /invite in it first.", "Search channels", "No channel matches “”.", "Run /invite first", "Add N channels".
  • Channel card: "Only N people/accounts", "Answers nowhere".
  • Errors: "Could not save who can message it.", "Could not search people.", "Could not refresh the people list.", "Could not read who can message it."
  • Docs: the new section of the Slack guide.
  • Raised by the naming review, for your call (not changed): "Who can message it" names a send permission, but anyone can still message the bot and the list decides whom it answers ("Who it answers" would match the note). "Only N people. Everyone else is ignored." is the panel's existing line, but people off the list now get a private note. Telegram uses "account", "user ID" and "people" for one person. The guide uses "assistant" and "agent" in one section.

How to review

  1. api/oss/src/core/channels/service.py: resolve_or_refuse (people check after the agent lookup, attach after it), _refusal, _limits_guard and the grant and agent writes.
  2. api/oss/src/tasks/asyncio/channels/inbox.py: _send_refusal_note.
  3. api/oss/src/core/channels/people.py and the Slack adapter's post_ephemeral, list_people, find_person_by_email.
  4. web/packages/agenta-settings-ui/src/channels/actions.ts: behaviorFromGrants, rulesForBehavior, writeBehavior; then ChannelPeopleSection.tsx, ChannelAdvancedSection.tsx, ChannelManagePanel.tsx.
  5. The migration and the regenerated client.

@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

📘 Docs preview

Status ✅ Ready
Preview https://pr-7890-agenta-docs-preview.mahmoud-637.workers.dev/docs/
Inspect Actions run
Commit 63910f1cb341f51dd25b8fb4e5da0bbe71f3b27e

Pages changed in this pull request

This comment updates in place on every push.

@coderabbitai

coderabbitai Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Summary

Summary by CodeRabbit

  • New Features
    • Choose which channels or groups an agent can answer in. Unselected channels—including channels joined later—remain off, and multiple channels can be added at once.
    • Limit an agent’s audience to specific people or allow everyone. Search Slack workspace members by name or email, or add recent senders; Telegram audiences can be set by numeric ID.
    • Refresh the people directory and send private notices to people who aren’t allowed to message an agent. Notices are limited to once per day for each sender and refusal reason.
  • Bug Fixes
    • Private-agent channel settings and access grants are restricted to the agent’s owner.

This change adds per-agent sender audiences, channel-specific reply controls, people-directory search, refusal notifications, ownership checks for private-agent limits, migration support, and corresponding API, settings UI, documentation, and tests.

Changes: Channel controls and people access

Layer / File(s) Summary
Audience data and ownership guards
api/oss/src/core/channels/*, api/oss/src/dbs/postgres/channels/dao.py, api/oss/src/apis/fastapi/channels/router.py, api/oss/databases/postgres/migrations/*
Agent bindings gain audience data. Sender lists remain synchronized with installation data. Agent and grant writes pass ownership and artifact guards. The migration copies existing sender lists into bindings.
People directory and query API
api/oss/src/core/channels/people.py, api/oss/src/core/channels/adapters/*, api/oss/src/apis/fastapi/channels/*
The service combines recent senders with cached Slack directory results and email lookup. Query and refresh endpoints expose people results and directory state.
Refusal routing and notifications
api/oss/src/core/channels/service.py, api/oss/src/tasks/asyncio/channels/inbox.py, api/oss/src/core/channels/render/render.py
Routing returns addressed sender or space refusals separately from silent outcomes. The dispatcher sends eligible notes and applies daily deduplication.
Channel behavior settings
web/packages/agenta-settings-ui/src/channels/*, docs/docs/guides/12-connect-slack.mdx
The settings UI represents channel rules with grants, supports selected-channel behavior, and supports multi-selection when adding discovered channels.
Audience controls in settings
web/packages/agenta-settings-ui/src/channels/ChannelPeopleSection.tsx, web/packages/agenta-settings-ui/src/channels/actions.ts, web/packages/agenta-settings-ui/src/channels/types.ts
The UI adds audience selection and people search for Slack and Telegram. It replaces Telegram-only allowed-user actions with audience and people actions.

Priority: High

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SettingsUI
  participant ChannelsRouter
  participant ChannelsService
  participant ChannelsDAO
  participant PeopleModule
  participant SlackAdapter
  SettingsUI->>ChannelsRouter: Query people with text and IDs
  ChannelsRouter->>ChannelsService: query people
  ChannelsService->>ChannelsDAO: query recent senders
  ChannelsService->>PeopleModule: query people
  PeopleModule->>SlackAdapter: list people or find person by email
  PeopleModule-->>ChannelsService: people and directory state
  ChannelsService-->>ChannelsRouter: people and directory state
  ChannelsRouter-->>SettingsUI: people response
Loading
sequenceDiagram
  participant InboxDispatcher
  participant ChannelsService
  participant SlackAdapter
  InboxDispatcher->>ChannelsService: resolve_or_refuse event
  ChannelsService-->>InboxDispatcher: routing with refusal
  InboxDispatcher->>SlackAdapter: post ephemeral refusal note
  SlackAdapter-->>InboxDispatcher: delivery result
  InboxDispatcher->>InboxDispatcher: record delivery state
Loading
Merge Risk: Moderate · previous review ad0b6

Fix the refusal path before merging so opted-out WhatsApp spaces do not get refusal notes and a refused STOP is still recorded. The oversized sender-list input and the Channels card summary showing destinations the agent does not answer in are small follow-ups.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 21.43% which is insufficient. The required threshold is 60.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documenta… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly summarizes the main change: adding per-agent limits for where an agent answers and which people it answers.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Description check Passed The description is specific and on-topic. It names the channel-limits feature for Slack and Telegram, the migration oss000000055, the owner checks, the refusal note, the rollout write to `allowed_se…
Full details: Description check

Explanation

The description is specific and on-topic. It names the channel-limits feature for Slack and Telegram, the migration oss000000055, the owner checks, the refusal note, the rollout write to allowed_senders, the test coverage, and the review steps. It matches the changeset in detail.

Full details: Docstring Coverage

Explanation

Docstring coverage is 21.43% which is insufficient. The required threshold is 60.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 84 functions across 45 files.

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Website preview

Preview URL: https://pr-7890-agenta-website-preview.mahmoud-637.workers.dev

Built from 63910f1cb341f51dd25b8fb4e5da0bbe71f3b27e. This comment updates in place on every push.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 4

Nitpick comments: 1
api/oss/src/core/channels/people.py: 1

188-200: Trivial · Performance & Scalability · Heavy lift

Avoid loading the whole workspace directory on every picker query.

The cached value is one CachedDirectory that holds every workspace member. Each query_people call runs get_cache(model=CachedDirectory). That call reads the full Redis value and validates every ChannelPerson through pydantic. The picker runs on each search keystroke, so a large Slack workspace pays this cost repeatedly on the API event loop.

Options:

  • Cache the directory in a form that is cheap to filter, such as a lowercase search index or sharded entries.
  • Keep a short-lived in-process copy of the parsed directory keyed by updated_at.
  • Debounce or rate-limit the query route.

Measure with a 50k-member fixture before choosing.


Review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between aaa2701 and 4a2c1ef.

Files ignored due to path filters: 13
  • web/packages/agenta-api-client/src/generated/api/resources/channels/client/Client.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/resources/channels/client/requests/ChannelPeopleQueryRequest.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/resources/channels/client/requests/RefreshChannelPeopleRequest.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/resources/channels/client/requests/index.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/types/ChannelAgentAudience.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/types/ChannelAgentData.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/types/ChannelAgentDataEdit.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/types/ChannelAudienceLabel.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/types/ChannelPeopleDirectory.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/types/ChannelPeopleQuery.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/types/ChannelPeopleResponse.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/types/ChannelPerson.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/types/index.ts is excluded by !**/generated/**
Files selected for processing: 41
  • api/oss/databases/postgres/migrations/core_oss/versions/oss000000055_copy_channel_people_lists_to_bindings.py
  • api/oss/src/apis/fastapi/channels/models.py
  • api/oss/src/apis/fastapi/channels/router.py
  • api/oss/src/core/channels/adapters/interface.py
  • api/oss/src/core/channels/adapters/slack/adapter.py
  • api/oss/src/core/channels/dtos.py
  • api/oss/src/core/channels/interfaces.py
  • api/oss/src/core/channels/people.py
  • api/oss/src/core/channels/render/render.py
  • api/oss/src/core/channels/service.py
  • api/oss/src/dbs/postgres/channels/dao.py
  • api/oss/src/dbs/postgres/sharing/dao.py
  • api/oss/src/tasks/asyncio/channels/inbox.py
  • api/oss/tests/pytest/acceptance/channels/test_channels_permissions.py
  • api/oss/tests/pytest/acceptance/sharing/test_channel_limits_owner.py
  • api/oss/tests/pytest/integration/channels/test_channels_limits_counts_postgres.py
  • api/oss/tests/pytest/integration/channels/test_channels_limits_note_postgres.py
  • api/oss/tests/pytest/unit/channels/slack/fake_slack.py
  • api/oss/tests/pytest/unit/channels/slack/test_slack_people_and_ephemeral.py
  • api/oss/tests/pytest/unit/channels/test_channel_adapter_interface.py
  • api/oss/tests/pytest/unit/channels/test_channels_inbox_dispatcher.py
  • api/oss/tests/pytest/unit/channels/test_channels_limits.py
  • api/oss/tests/pytest/unit/channels/test_channels_people.py
  • api/oss/tests/pytest/unit/channels/test_channels_router.py
  • api/oss/tests/pytest/unit/channels/test_channels_service_routing.py
  • api/oss/tests/pytest/unit/channels/whatsapp/test_whatsapp_dispatcher.py
  • docs/docs/guides/12-connect-slack.mdx
  • web/packages/agenta-settings-ui/src/channels/ChannelAdvancedSection.tsx
  • web/packages/agenta-settings-ui/src/channels/ChannelManagePanel.stories.tsx
  • web/packages/agenta-settings-ui/src/channels/ChannelManagePanel.tsx
  • web/packages/agenta-settings-ui/src/channels/ChannelPeopleSection.tsx
  • web/packages/agenta-settings-ui/src/channels/actions.ts
  • web/packages/agenta-settings-ui/src/channels/helpers.ts
  • web/packages/agenta-settings-ui/src/channels/index.ts
  • web/packages/agenta-settings-ui/src/channels/storyFixtures.tsx
  • web/packages/agenta-settings-ui/src/channels/types.ts
  • web/packages/agenta-settings-ui/tests/unit/channelAdvancedSection.test.tsx
  • web/packages/agenta-settings-ui/tests/unit/channelLimits.test.ts
  • web/packages/agenta-settings-ui/tests/unit/channelManagePanel.test.tsx
  • web/packages/agenta-settings-ui/tests/unit/channelSlackSetup.test.tsx
  • web/packages/agenta-settings-ui/tests/unit/channelWhatsApp.test.tsx

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 0 remain after this review.

Comment thread api/oss/src/core/channels/dtos.py Resolved
Comment thread api/oss/src/core/channels/service.py Resolved
@mmabrouk

Copy link
Copy Markdown
Member Author

🤖 The AI agent says:

Review loop result for head 4a2c1ef38a (base aaa2701a6f), 3 rounds plus one delta round on the commits after round 3.

Lens Final rating
Simplify Low
Slop Low

Round 1 ran Opus and Codex on each lens. Rounds 2 and 3 alternated the models. The delta round ran Codex on both lenses.

Fixed: rule saves now create new rules before removing old ones, so a save never passes through "no rule" (answers everywhere). The owner check runs inside each grant and agent write. The channel picker keeps its selection by key. A person who left Slack is marked "No longer in Slack" through the normal search. A failed "Channels it answers in" save shows the stored choice again. People polling slows down instead of stopping. Grant guard locks are taken in a fixed order. The people client was regenerated.
Left on purpose: post_ephemeral keeps its boolean, for adapters that declare ephemeral messages without sending them. The resolve wrapper stays for its existing callers. Settings-time read counts (the per-connection grants read for the card summary, the grant reads on a default move) stay, since they are not on the message path.

@mmabrouk

Copy link
Copy Markdown
Member Author

🤖 The AI agent says:

Review loop result for head 4a2c1ef38a (base aaa2701a6f), 3 rounds plus two delta rounds, all with Codex.

Lens Final rating
Correctness Low
Security Low

Fixed: a person excluded by the people list is not stored as context while the binding or the place is off. Moving the default grant checks both agents' owners in its first write, so a refusal leaves the old default in place. Pointing a binding at another agent is a limits write for that agent, which closes a move-away, edit, move-back bypass of the owner check. A reference that resolves to no agent fails closed. Saving DMs off with no channel checked no longer reads as "answers everywhere".
Left on purpose: messages from refused people still reach the agent through the one-time Slack backfill and the channel read tools, as today (the spec keeps this). Switching Channels off and on after "Only these channels" with nothing checked comes back as "All channels" (a three-step case that the screen shows). During a rollout, old pods enforce the installation list and new pods the binding list.

@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Railway Preview Environment

Item Value
Status stopped
Commit 63910f1cb341f51dd25b8fb4e5da0bbe71f3b27e
Manual expiry (UTC) Not granted
CI deadline (UTC) Not granted
Workflow https://github.com/Agenta-AI/agenta/actions/runs/40949146601

Dependent tests finished. Post /preview for manual review. Environment absence verified; retained storage requires separate provider accounting.

Post /preview as a new comment to request a one-hour preview. Repeating it while active does not extend the hour.
Preview data is disposable. Expiry is enforced by the next successful cleanup sweep, not an exact timer.
Environment deletion stops its compute. Provider-retained storage and permanent services may still be billed.

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

Major · Attach events only after all refusal checks. · service.py:2280-2287

api/oss/src/core/channels/service.py:2280-2287
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Attach events only after all refusal checks.

attach_event_to_space commits the event’s space_id before the inactive-place, DENY-grant, and restricted-unmatched-grant returns. The dispatcher sends the refusal note and returns without undoing that attachment. Move the call below those returns and immediately before policy = resolve_policy(...).

Suggested fix
-        # the ingress wrote this row before any space existed; attach it before
-        # the remaining refusal paths, so an unanswered message still belongs
-        # to its space
-        await self.channels_dao.attach_event_to_space(
-            project_id=project_id,
-            event_id=event.id,
-            space_id=space.id,
-        )
-
         if not answers_here:
             return ChannelRouting()
@@
             # zero grants anywhere for this agent: unrestricted
 
+        await self.channels_dao.attach_event_to_space(
+            project_id=project_id,
+            event_id=event.id,
+            space_id=space.id,
+        )
+
         channel_defaults = _channel_defaults(capabilities)

Review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 4a2c1ef and b4115dd.

Files selected for processing: 2
  • api/oss/src/core/channels/service.py
  • api/oss/tests/pytest/unit/channels/test_channels_limits.py

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 0 remain after this review.

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

Minor · Publish audience as optional but non-nullable. · dtos.py:733-737

api/oss/src/core/channels/dtos.py:733-737
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Publish audience as optional but non-nullable.

ChannelAgentDataEdit generates audience?: ChannelAgentAudience | null, but the reachable edit_channel_agent route validates that field and rejects explicit null. A generated-client caller can therefore send a type-valid value and receive a validation error. Preserve omission as “keep the stored audience,” and regenerate the client after changing the API definition.

Suggested fix
-    audience: Optional[ChannelAgentAudience] = None
+    audience: ChannelAgentAudience = Field(default=None)

Review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between b4115dd and 24945c0.

Files selected for processing: 2
  • api/oss/src/core/channels/dtos.py
  • api/oss/tests/pytest/unit/channels/test_channels_limits.py
Files skipped from review as they are similar to previous changes: 2
  • api/oss/tests/pytest/unit/channels/test_channels_limits.py
  • api/oss/src/core/channels/dtos.py

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 0 remain after this review.

@mmabrouk

Copy link
Copy Markdown
Member Author

🤖 The AI agent says: Live QA on a local EE stack built from this branch (4a2c1ef38a for the UI journey; later commits are a CI fix, a validator fix and a one-line UI fix). No real Slack or Telegram was used: a Slack connection ("Acme QA") and a Telegram bot were seeded in the local database in place of the platform installs, with two people who "wrote to the bot" on each. The QA agent used a fresh account and a production build of /m.

Cell Result
Slack "Who can message it": Specific people shows "Wrote to @Support-Bot" (Ana, Ben) and "Could not load people from Acme QA" (no real Slack); add Ana; "Only 1 person"; kept after reload; remove Ana returns to Everyone Pass
Advanced "Channels it answers in": Only these channels, check support, Save; random shows Off; kept after reload; All channels clears Off Pass
Add channel: search field shown; the list fails to load without a real Slack and shows an error, no crash Pass
Telegram: "@Tara" refused with the numeric-id message; People who wrote to the bot lists Tara and Omar; add Tara and 12345; "Only 2 accounts" Pass
Agent card: Slack "DMs and 1 channel", Telegram "DMs and 2 groups · Only 2 accounts" Pass
Saved rows: Slack grants allow private + allow space=support; Telegram audience.senders = [8883745180, 12345] Pass
Routing on those saved rows: a mention in #random is refused with reason place_off (note sent); a mention in #support is answered Pass
Self-managed agent (ChatGPT subscription through Pi, no connection slug) answers a turn Pass
Self-managed with a slug: the run says "No subscription connection named 'chatgpt'" (no signed-in connection in the project, as before) Pass
Self-managed Claude Not run: the host Claude login has expired and needs a human sign-in

Found and fixed during QA: the Telegram id error stayed on screen after picking a person from the list (9918bf0811).
Seen and not caused by this change: the Turbopack dev server of /m served broken client modules ("module factory is not available") on the home and agent pages of this stack; a production build has no such error.

The recording (about 3 minutes, the Slack and Telegram cells) is kept locally; the browser upload to GitHub was not available in this session.

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

Minor · Invalidate pending searches when refresh starts. · ChannelPeopleSection.tsx:118-164

web/packages/agenta-settings-ui/src/channels/ChannelPeopleSection.tsx:118-164
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Invalidate pending searches when refresh starts.

refresh awaits actions.refreshPeople, which can return loading. A search that started before the refresh can then resolve afterward because searchSeq is unchanged. Its accepted response calls setDirectory(result.directory), which can replace loading with cached results and stop polling. Stale people can remain visible.

Suggested fix
 const refresh = async () => {
     setRefreshing(true)
+    searchSeq.current += 1
     try {
         setDirectory(await actions.refreshPeople(connectionId))

Review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 24945c0 and 9918bf0.

Files selected for processing: 1
  • web/packages/agenta-settings-ui/src/channels/ChannelPeopleSection.tsx

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 2 remain after this review.

@mmabrouk

Copy link
Copy Markdown
Member Author

🤖 The AI agent says:

Ship record for head 9918bf0811. Tier: Feature.

Gate Result Head Link
Spec Owner decisions of 2026-10-26 (spec PR #7836); copy not yet approved n/a openspec/changes/channel-limits on the spec branch
Design simplify review Not run here: the spec was decided before this build n/a n/a
Slop and simplicity loop Low 4a2c1ef #7890 (comment)
Correctness and security loop Low 4a2c1ef #7890 (comment)
Tests API unit OSS and EE, channels integration and acceptance, sharing acceptance, settings-ui vitest, mobile types 9918bf0 (unit, CI), 4a2c1ef (stack suites) CI checks page
Live QA Passed: Slack and Telegram limits, card summary, routing on saved rules, self-managed (ChatGPT) 4a2c1ef #7890 (comment)
CodeRabbit No open items (1 fixed, 3 answered) 9918bf0 review threads
CI Required checks green, web tests not awaited 9918bf0 https://github.com/Agenta-AI/agenta/pull/7890/checks
Docs in sync Yes 9918bf0 docs/docs/guides/12-connect-slack.mdx, regenerated client

Commits after 4a2c1ef: b4115dd895 passes the limits guard to the agent write only when present (CI fake compatibility, no behavior change), 24945c0c03 validator fix (people list drops nulls and repeats, at most 1000 ids), 9918bf0811 clears a Telegram id error on pick (one line).
Deploy steps: migration oss000000055 (data only); its revision order is fixed at merge time.
Not covered: self-managed Claude (host login expired, needs a human sign-in); the MP4 is kept locally.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1


Review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 9918bf0 and d59b7c7.

Files selected for processing: 5
  • api/oss/src/core/channels/interfaces.py
  • api/oss/src/core/channels/service.py
  • api/oss/src/dbs/postgres/channels/dao.py
  • api/oss/tests/pytest/unit/channels/test_channels_limits.py
  • api/oss/tests/pytest/unit/migrations/test_channel_people_lists_migration.py

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 6 remain after this review.

Comment thread api/oss/src/core/channels/service.py Resolved
@mmabrouk

Copy link
Copy Markdown
Member Author

🤖 The AI agent says:

Review loop result for head 441578ab75, a delta review of the commits after 4a2c1ef38a (the earlier record covers the rest). Two rounds: Codex on both lenses, then Codex on both lenses plus Opus on slop.

Lens Final rating
Simplify Low
Slop Low

Fixed: a binding created switched off with a people list is now an owner-only limits write. During the rollout every people-list change is also written to the installation's allowed_senders, the field the previous release reads, in the same database transaction as the binding (round 1 found that a separate write could leave the two lists apart after a failed save). New tests: the create case (unit and acceptance), the rollout write on Postgres, and a migration test of oss000000055 (copy, second run, downgrade).
Left on purpose: the migration test uses the existing scratch-Postgres pattern (localhost:5444, skipped when none is reachable), like the tracing migration test; it ran on a scratch Postgres for this change. The guard and installation arguments are passed to the DAO only when set, because a test fake of the sharing suite rejects unknown arguments.

@mmabrouk

Copy link
Copy Markdown
Member Author

🤖 The AI agent says:

Review loop result for head 441578ab75, a delta review of the commits after 4a2c1ef38a (the earlier record covers the rest). Codex security: Low, no findings. The correctness items in this range (the two-write rollout gap) were found by the slop and simplify lenses and fixed in the same transaction.

Lens Final rating
Correctness Low
Security Low

Fixed: a non-owner can no longer create a switched-off binding of someone else's private agent with a people list already set.
Left on purpose: nothing new.

@mmabrouk

Copy link
Copy Markdown
Member Author

🤖 The AI agent says:

Review loop result for head aceff694e4. The slop and simplify record for 441578ab75 stands; the commits since then are three small correctness fixes (no limit on stored people lists, connection-first lock order in binding writes and in archiving), each covered by the existing channels tests.

Lens Final rating
Simplify Low
Slop Low

@mmabrouk

Copy link
Copy Markdown
Member Author

🤖 The AI agent says:

Review loop result for head aceff694e4, a delta review of the commits after 4a2c1ef38a. Security: Low (no findings). Correctness: three rounds with Codex, stopped at the round limit.

Lens Final rating
Correctness Medium (one narrow rollout race left, below)
Security Low

Fixed: owner-only people list when creating a binding; the installation list written in the same transaction as the binding; no size limit on stored people lists (a stored list longer than a limit would stop loading); the connection row is locked before the agent's artifact in binding writes and before the bindings when archiving, the order a restore uses.
Left on purpose: a connection edit (a rename or a credential update) that runs at the same moment as a people-list save can write the old allowed_senders back on the installation. Only pods of the previous release read that field, only during the rollout, and the binding list that new pods read stays right. The rollout write goes away in the next release, so I did not add machinery for it.

Agenta Product Agent added 11 commits October 29, 2026 17:33
…or Slack, people picker, multi-add channel picker
…efault and space-deny rules, owner guard fails closed and covers set-default, failed people load waits for Refresh
…luded senders on an off binding, create rules before removing, keyed picker, mark people who left Slack
…n its first write, excluded senders in a switched-off place are not stored, people who left come back with every search
…gent is the owner's limits write, fixed lock order, failed save shows the stored channels, slower people polling
…usal note end to end on Postgres and the fake Slack
@mmabrouk

Copy link
Copy Markdown
Member Author

🤖 The AI agent says:

Review loop result for head ad0b64dd57 (base a77e503782, main at v0.127.1), 3 rounds.

Lens Round 1 Round 2 Round 3 (final)
Naming Opus Medium, Codex Medium Codex Low Opus Low

Fixed: refusal reasons sender_not_listed and space_off (the domain word is space); one name for the owner-check argument (guard_artifact_ids) on agent and grant writes; people-picker support declared in the capabilities (people: {directory, email_lookup}) instead of an adapter attribute; ChannelPerson.source values recent_senders | directory | email_lookup; post_ephemeral(recipient_id); spaceIds and audienceCount on the screen side; the outbox turn_id comment lists the note marker; /people/refresh returns {count, directory}; the story fixture says audience. The client is regenerated.
Left on purpose: guard_agent_activation keeps its name (a shared sharing-module function; its docstring says it also guards limits). installation_senders names a rollout-only write that goes away next release.
For the owner (copy, not changed): "Who can message it" against whom it answers, "Everyone else is ignored" now that refused people get a note, the Telegram words "account"/"user ID"/"people", and "assistant"/"agent" in the guide. Listed in the PR body.

@mmabrouk

Copy link
Copy Markdown
Member Author

🤖 The AI agent says:

Review loop result for head ad0b64dd57 (base a77e503782, main at v0.127.1), 3 rounds.

Lens Round 1 Round 2 Round 3 (final)
Slop Opus Low, Codex Medium Opus Low Codex Low
Simplify Opus Low, Codex High Codex Low Opus Low

Fixed: the switches and the "Channels it answers in" row share one save state, so two saves can no longer overlap and leave every channel open; the picker keeps polling after a failed ask; a repeat refusal note costs one read (the key is checked before the connection); the rule diff computes each stored row's key directly; _limits_guard takes references; an id typed twice in one entry is added once; rename leftovers. Tests added for each.
Left on purpose: one SQL statement for the rollout write (the edit path has no connection id; the subquery keeps the lock order; the write goes away next release); _grant_limits_guard reads the grant again (settings time); the people picker stays on component state like the rest of the channels panel rather than moving to TanStack Query; a rename racing an owner's people-list save on an inactive private binding (every agent edit writes the whole data block it read, a pre-existing pattern).
For the owner: a space default-agent rule set through the API counts as a chosen channel, so a later save of the channel row can remove it. Listed in the PR body.
The last two commits after round 3 are a story fixture rename and the one-line duplicate-id fix with its test.

@mmabrouk

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3


Review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between d59b7c7 and ad0b64d.

Files ignored due to path filters: 16
  • web/packages/agenta-api-client/src/generated/api/resources/channels/client/Client.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/resources/channels/client/requests/ChannelPeopleQueryRequest.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/resources/channels/client/requests/RefreshChannelPeopleRequest.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/resources/channels/client/requests/index.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/types/ChannelAgentAudience.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/types/ChannelAgentData.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/types/ChannelAgentDataEdit.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/types/ChannelAudienceLabel.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/types/ChannelCapabilities.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/types/ChannelPeopleDirectory.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/types/ChannelPeopleDirectoryResponse.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/types/ChannelPeopleQuery.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/types/ChannelPeopleResponse.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/types/ChannelPeopleSupport.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/types/ChannelPerson.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/types/index.ts is excluded by !**/generated/**
Files selected for processing: 30
  • api/oss/databases/postgres/migrations/core_oss/versions/oss000000055_copy_channel_people_lists_to_bindings.py
  • api/oss/src/apis/fastapi/channels/models.py
  • api/oss/src/apis/fastapi/channels/router.py
  • api/oss/src/core/channels/adapters/interface.py
  • api/oss/src/core/channels/adapters/slack/adapter.py
  • api/oss/src/core/channels/adapters/slack/capabilities.py
  • api/oss/src/core/channels/dtos.py
  • api/oss/src/core/channels/interfaces.py
  • api/oss/src/core/channels/people.py
  • api/oss/src/core/channels/service.py
  • api/oss/src/dbs/postgres/channels/dao.py
  • api/oss/src/tasks/asyncio/channels/inbox.py
  • api/oss/tests/pytest/acceptance/sharing/test_channel_limits_owner.py
  • api/oss/tests/pytest/integration/channels/test_channels_people_list_rollout_postgres.py
  • api/oss/tests/pytest/unit/channels/slack/test_slack_capabilities.py
  • api/oss/tests/pytest/unit/channels/slack/test_slack_people_and_ephemeral.py
  • api/oss/tests/pytest/unit/channels/test_channels_grant_references.py
  • api/oss/tests/pytest/unit/channels/test_channels_limits.py
  • api/oss/tests/pytest/unit/channels/test_channels_people.py
  • web/packages/agenta-settings-ui/src/channels/ChannelAdvancedSection.tsx
  • web/packages/agenta-settings-ui/src/channels/ChannelManagePanel.stories.tsx
  • web/packages/agenta-settings-ui/src/channels/ChannelManagePanel.tsx
  • web/packages/agenta-settings-ui/src/channels/ChannelPeopleSection.tsx
  • web/packages/agenta-settings-ui/src/channels/actions.ts
  • web/packages/agenta-settings-ui/src/channels/helpers.ts
  • web/packages/agenta-settings-ui/src/channels/storyFixtures.tsx
  • web/packages/agenta-settings-ui/src/channels/types.ts
  • web/packages/agenta-settings-ui/tests/unit/channelAdvancedSection.test.tsx
  • web/packages/agenta-settings-ui/tests/unit/channelLimits.test.ts
  • web/packages/agenta-settings-ui/tests/unit/channelManagePanel.test.tsx

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

Comment thread api/oss/src/core/channels/dtos.py Resolved
Agenta Product Agent added 2 commits October 29, 2026 19:12
…for STOP/START, a cap of 1000 people on create and edit requests only, both channel summaries count the chosen channels
@mmabrouk

Copy link
Copy Markdown
Member Author

🤖 The AI agent says:

Review loop result for head af9daf1f53. The record for ad0b64dd57 (posted above) stands for these lenses. The two commits since then fix three CodeRabbit items (no refusal note to an opted-out person, or for STOP/START where the platform has consent words; a 1000-person cap on create and edit requests only, stored lists stay readable; both channel summaries count the chosen channels), each with a test. A Codex slop delta review of those commits rated Low after one fix (the consent-word guard now applies only where the platform declares consent words).

@mmabrouk

Copy link
Copy Markdown
Member Author

🤖 The AI agent says:

Review loop result for head af9daf1f53. The record for ad0b64dd57 (posted above) stands for these lenses. The two commits since then fix three CodeRabbit items (no refusal note to an opted-out person, or for STOP/START where the platform has consent words; a 1000-person cap on create and edit requests only, stored lists stay readable; both channel summaries count the chosen channels), each with a test. A Codex slop delta review of those commits rated Low after one fix (the consent-word guard now applies only where the platform declares consent words).

@mmabrouk

Copy link
Copy Markdown
Member Author

🤖 The AI agent says:

Ship record for head af9daf1f53. Tier: Feature. Rebased on main at v0.127.1; mergeable, no conflicts.

Gate Result Head Link
Spec Owner decisions of 2026-10-26 (spec PR #7836); owner review "overall LGTM" on 2026-10-29; draft copy still needs approval n/a openspec/changes/channel-limits on the spec branch
Design simplify review Not run here: the spec was decided before the build n/a n/a
Naming loop Low (3 rounds, Opus and Codex) af9daf1 #7890 (comment) and the update below it
Slop and simplicity loop Low (3 rounds, Opus and Codex, plus a delta round) af9daf1 #7890 (comment) and the update below it
Correctness and security loop Security Low; correctness Medium at the round limit, one narrow rollout race left on purpose aceff69 #7890 (comment)
Tests API unit OSS 9775 and EE 10723 passed; channels integration 118 passed; channels and sharing acceptance 169 passed, 5 skipped; settings-ui vitest 247 passed; mobile types pass; migration test passed on a scratch Postgres af9daf1 CI checks page
Live QA Passed: Slack and Telegram limits, card summary, routing on saved rules, self-managed (ChatGPT) 4a2c1ef #7890 (comment)
CodeRabbit No open items (3 fixed on this head) af9daf1 review threads
CI Required checks green; web test jobs not awaited af9daf1 https://github.com/Agenta-AI/agenta/pull/7890/checks
Docs in sync Yes af9daf1 docs/docs/guides/12-connect-slack.mdx, regenerated client

Changes since the live QA head are renames from the naming review (refusal reasons, picker source values, field names, people support in the capabilities), one shared save state for the switches and the channel row, the owner rule on creating a binding, the rollout write of the installation list, lock order, and the CodeRabbit fixes. Each has unit or integration tests; the screen journeys were not rerun live.
Deploy steps: migration oss000000055 (data only), chained after oss000000053; the release conductor sets the final revision number at merge time.
Not covered: self-managed Claude (the host Claude login has expired; needs a human sign-in); the QA recording is kept locally.

@mmabrouk

Copy link
Copy Markdown
Member Author

🤖 The AI agent says: Live QA on the final head af9daf1f53, on a local EE stack with a production build of /m and a new account. No real Slack or Telegram was used: a Slack connection ("Acme QA") and a Telegram bot were seeded in the local database in place of the platform installs.

Cell Result
Slack "Who can message it": add Ana from "Wrote to @Support-Bot", "Only 1 person", kept after reload, remove Ana returns to Everyone Pass
Advanced "Channels it answers in": Only these channels, support checked; random "Off", support "Mentions only"; kept after reload; card says "DMs and 1 channel" Pass
Two quick switch clicks (both orders): the second click is ignored while the first save runs; the channel choice never falls back to "All channels the bot is in", and random never reads "Mentions only" Pass
People picker recovers after failed asks: with the workspace list loading, the network went offline for 9 seconds, then back; the picker showed the list within 12 seconds with no reload, and "cara" found Cara Diaz Pass
Add channel: search field shown; the list fails without a real Slack and shows an error, no crash Pass
Telegram: "@Tara" refused with the numeric-id message; Tara added from "People who wrote to the bot" (the error clears); "12345, 12345" adds one row; "Only 2 accounts" Pass
Agent card and Publish hub: Slack "DMs and 1 channel", Telegram "DMs and 2 groups · Only 2 accounts" Pass
Saved rows: Slack allow private + allow space=support; Telegram audience.senders = [8883745180, 12345], and the same list written to the installation for the rollout Pass
Routing on those rows: a mention in #random is refused (space_off), a mention in #support is answered Pass
Self-managed agent on the ChatGPT subscription, no connection slug: answers a turn Pass
Self-managed with a slug: "No subscription connection named 'chatgpt'" (no signed-in connection, as before) Pass
Self-managed Claude Not run: the host Claude login has expired

Seen and not changed: the agent card truncates a long line ("Only 2 accounts" is cut off at normal width); the Add channel error is the generic one when Slack discovery fails, and "No new channels." shows under it; the panel's sticky footer can cover the last row's buttons. These come from existing screens. No console errors from this feature.

The recording (about 3 minutes) is kept locally; the browser upload to GitHub was not available in this session.

@mmabrouk

Copy link
Copy Markdown
Member Author

🤖 The AI agent says:

Ship record for head af9daf1f53. Tier: Feature. Rebased on main at v0.127.1; mergeable, no conflicts.

Gate Result Head Link
Spec Owner decisions of 2026-10-26 (spec PR #7836); owner review "overall LGTM" on 2026-10-29; draft copy still needs approval n/a openspec/changes/channel-limits on the spec branch
Design simplify review Not run here: the spec was decided before the build n/a n/a
Naming loop Low (3 rounds, Opus and Codex) af9daf1 #7890 (comment) and the update below it
Slop and simplicity loop Low (3 rounds, Opus and Codex, plus a delta round) af9daf1 #7890 (comment) and the update below it
Correctness and security loop Security Low; correctness Medium at the round limit, one narrow rollout race left on purpose aceff69 #7890 (comment)
Tests API unit OSS 9775 and EE 10723 passed; channels integration 118 passed; channels and sharing acceptance 169 passed, 5 skipped; settings-ui vitest 247 passed; mobile types pass; migration test passed on a scratch Postgres af9daf1 CI checks page
Live QA Passed: Slack and Telegram limits, quick toggles, picker recovery after failed asks, card and hub summaries, routing on saved rules, self-managed (ChatGPT) af9daf1 #7890 (comment)
CodeRabbit No open items (3 fixed on this head) af9daf1 review threads
CI Required checks green; web test jobs not awaited af9daf1 https://github.com/Agenta-AI/agenta/pull/7890/checks
Docs in sync Yes af9daf1 docs/docs/guides/12-connect-slack.mdx, regenerated client

Deploy steps: migration oss000000055 (data only), chained after oss000000053; the release conductor sets the final revision number at merge time.
Not covered: self-managed Claude (the host Claude login has expired; needs a human sign-in); the QA recording is kept locally.

@mmabrouk mmabrouk added lgtm This PR has been approved by a maintainer merge queue In the release merge queue; an agent is landing it and removed needs-review labels Oct 29, 2026
@mmabrouk
mmabrouk changed the base branch from main to release/v0.127.2 October 29, 2026 20:20
Agenta Product Agent added 2 commits October 29, 2026 21:20
A search answer that arrived after Refresh could put the old directory state
back and stop the polling. Refresh now moves the search counter, so that late
answer is dropped.
@mmabrouk

Copy link
Copy Markdown
Member Author

🤖 The AI agent says: Merge queue for v0.127.2. This answers the three CodeRabbit items that sat outside the diff, so they had no thread.

  1. Attach events only after all refusal checks (service.py, Major). Not changed. Only the sender check comes before the attach, because a person who is not on the list must not become stored context. A message in a place that is off, or under a deny rule, still belongs to its space as it does today, so the agent can read it later. This follows the spec.
  2. audience optional but not nullable (dtos.py, Minor). Not changed. references and tools on the same edit model use the same pattern: the type allows null and the validator refuses it with a clear message. Changing only audience would make the three fields differ, and it needs a client regenerated from an EE spec.
  3. Refresh does not stop older searches (ChannelPeopleSection.tsx, Minor). Fixed in 63910f1cb3. Refresh now moves the search counter, so a search answer that arrives after Refresh is dropped. The settings-ui unit tests pass.

I also merged release/v0.127.2 into this branch (c9f7daf1ab, no conflicts). The migration oss000000055 stays the single new head after oss000000053.

@mmabrouk
mmabrouk merged commit 3f69daa into release/v0.127.2 Oct 29, 2026
87 checks passed
@mmabrouk mmabrouk removed the merge queue In the release merge queue; an agent is landing it label Oct 29, 2026
@mmabrouk mmabrouk mentioned this pull request Oct 29, 2026
23 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm This PR has been approved by a maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant