Skip to content

feat(platform): add multi-team agent access control#1399

Merged
larryro merged 2 commits into
mainfrom
feat/issue-1343-agent-access
Apr 11, 2026
Merged

feat(platform): add multi-team agent access control#1399
larryro merged 2 commits into
mainfrom
feat/issue-1343-agent-access

Conversation

@yannickmonney
Copy link
Copy Markdown
Contributor

@yannickmonney yannickmonney commented Apr 11, 2026

Summary

  • Add sharedWithTeamIds field to agentBindings schema enabling agents to be shared with multiple teams (backward-compatible: agents with no teams remain org-wide)
  • Add access.ts helper module with checkAgentAccess and getAgentTeamIds for centralized access control logic
  • Add updateAgentSharing mutation (admin-only) and listBindingsByOrg query for managing and reading team assignments
  • Update document scoping (getAgentScopedFileIds, listIndexedDocumentsForAgent) to use full team set instead of single team ID
  • Thread agentTeamIds through the agent chat/response pipeline, budget checks, and all agent tools (RAG search, approval, human input, location)
  • Add useUpdateAgentSharing frontend mutation hook
  • Add comprehensive unit tests for access control logic (team merging, admin overrides, role restrictions, edge cases)

Test plan

  • Lint passes (bun run --filter @tale/platform lint)
  • Typecheck passes (bun run --filter @tale/platform typecheck)
  • Server tests pass (157 files, 1978 tests)
  • Client tests pass (402 files, 3830 tests)
  • Verify existing agents with no sharedWithTeamIds continue to work as org-wide
  • Verify updateAgentSharing mutation rejects non-admin callers
  • Verify multi-team document scoping returns documents from all assigned teams

Closes #1343

Summary by CodeRabbit

Release Notes

  • New Features
    • Agents can now be shared with multiple teams simultaneously, expanding from single-team assignment.
    • Enhanced agent access control with improved authorization checks and team-based permissions.
    • Knowledge files now support multi-team scoping for agents, allowing better resource sharing across team boundaries.

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@yannickmonney
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 11, 2026

✅ Actions performed

Review triggered.

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
Copy link
Copy Markdown

coderabbitai Bot commented Apr 11, 2026

📝 Walkthrough

Walkthrough

This PR implements multi-team agent sharing and access control. It adds a new Convex mutation updateAgentSharing to persist team assignments for agents, introduces access control helper functions (checkAgentAccess and getAgentTeamIds) that evaluate user permissions based on organization role and team membership, extends the agent bindings schema with sharedWithTeamIds, and systematically updates team-scoped logic throughout the codebase to prefer multiple agent team IDs (agentTeamIds) over the legacy single-team field with backwards-compatible fallback behavior.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(platform): add multi-team agent access control' accurately summarizes the main change of introducing team-scoped access control for agents via the new sharedWithTeamIds field and related infrastructure.
Linked Issues check ✅ Passed The PR fully addresses issue #1343 requirements: implements team-scoped agent access control via sharedWithTeamIds, adds checkAgentAccess for permission validation, restricts knowledge documents to assigned teams, and provides backend APIs for managing team assignments.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing multi-team agent access control and knowledge scoping as specified in issue #1343; no unrelated refactoring, unplanned features, or scope creep detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/issue-1343-agent-access

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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@services/platform/convex/agents/mutations.ts`:
- Around line 111-146: The updateAgentSharing flow currently writes
client-supplied args.teamIds directly into agentBindings; instead, validate each
team id belongs to args.organizationId before persisting. For function logic
around getOrganizationMember / existing / ctx.db.insert / ctx.db.patch, load
each team record (e.g., findOne/get on "team" by _id) and confirm
team.organizationId === args.organizationId, collect any invalid ids and throw a
descriptive error if any are found, then use the validated list (and
primaryTeamId derived from it) when patching or inserting into agentBindings.

In `@services/platform/convex/documents/list_indexed_documents_for_agent.ts`:
- Around line 85-91: The current logic building agentTeamIdSet treats
args.agentTeamIds as truthy which makes an empty array disable the fallback to
args.agentTeamId; change the checks to test for a non-empty array (e.g.,
args.agentTeamIds && args.agentTeamIds.length > 0) before iterating so that when
agentTeamIds is [] the code falls back to using agentTeamId; update the same
pattern wherever agentTeamIds is checked (the other occurrences noted) to ensure
legacy team fallback works and ensure you still add args.agentTeamId to
agentTeamIdSet when agentTeamIds is absent or empty.

In `@services/platform/convex/lib/agent_chat/internal_actions.ts`:
- Around line 424-428: The teamIds assignment incorrectly treats an empty array
(agentConfig.agentTeamIds === []) as truthy and prevents falling back to
agentConfig.agentTeamId; update the logic in internal_actions.ts where teamIds
is set to first check that agentConfig.agentTeamIds is a non-empty array (e.g.,
agentConfig.agentTeamIds && agentConfig.agentTeamIds.length > 0) and only use it
when non-empty, otherwise fall back to agentConfig.agentTeamId (wrapping it into
an array) or undefined; ensure you modify the expression that currently reads
agentConfig.agentTeamIds ?? (agentConfig.agentTeamId ? [agentConfig.agentTeamId]
: undefined) to the non-empty-array check using the unique symbols
agentConfig.agentTeamIds, agentConfig.agentTeamId and teamIds.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f0eccfa4-88cd-46b6-8688-7d254889535b

📥 Commits

Reviewing files that changed from the base of the PR and between 1d6fbf9 and 6f46b77.

📒 Files selected for processing (22)
  • services/platform/app/features/agents/hooks/mutations.ts
  • services/platform/convex/agent_tools/approval_shared.ts
  • services/platform/convex/agent_tools/human_input/mutations.ts
  • services/platform/convex/agent_tools/location/mutations.ts
  • services/platform/convex/agent_tools/rag/helpers/list_indexed_documents.ts
  • services/platform/convex/agent_tools/rag/rag_search_tool.ts
  • services/platform/convex/agents/__tests__/access.test.ts
  • services/platform/convex/agents/access.ts
  • services/platform/convex/agents/config.ts
  • services/platform/convex/agents/file_actions.ts
  • services/platform/convex/agents/internal_queries.ts
  • services/platform/convex/agents/mutations.ts
  • services/platform/convex/agents/queries.ts
  • services/platform/convex/agents/schema.ts
  • services/platform/convex/documents/get_agent_scoped_file_ids.ts
  • services/platform/convex/documents/internal_queries.ts
  • services/platform/convex/documents/list_indexed_documents_for_agent.ts
  • services/platform/convex/lib/agent_chat/internal_actions.ts
  • services/platform/convex/lib/agent_chat/start_agent_chat.ts
  • services/platform/convex/lib/agent_chat/types.ts
  • services/platform/convex/lib/agent_response/generate_response.ts
  • services/platform/convex/lib/agent_response/types.ts

Comment on lines +111 to +146
const member = await getOrganizationMember(ctx, args.organizationId, {
userId: String(authUser._id),
email: authUser.email,
name: authUser.name,
});

const role = member.role ?? 'member';
if (role !== 'owner' && role !== 'admin') {
throw new Error('Only admins can update agent sharing');
}

const existing = await ctx.db
.query('agentBindings')
.withIndex('by_org_agent', (q) =>
q
.eq('organizationId', args.organizationId)
.eq('agentSlug', args.agentSlug),
)
.first();

const primaryTeamId = args.teamIds[0] ?? undefined;
const sharedWithTeamIds =
args.teamIds.length > 0 ? args.teamIds : undefined;

if (existing) {
await ctx.db.patch(existing._id, {
teamId: primaryTeamId,
sharedWithTeamIds,
});
} else {
await ctx.db.insert('agentBindings', {
organizationId: args.organizationId,
agentSlug: args.agentSlug,
teamId: primaryTeamId,
sharedWithTeamIds,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Validate each shared team against the organization before writing.

updateAgentSharing writes client-supplied teamIds straight into agentBindings without checking that those teams actually belong to args.organizationId. That allows stale or cross-org IDs to be persisted and makes the sharing state invalid. Please load each team first and reject any ID whose owning org does not match.

Based on learnings, addMember validates the target team belongs to the provided organization (findOne on team by _id, compare organizationId) and keeps this org-ownership validation for team-member mutations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/convex/agents/mutations.ts` around lines 111 - 146, The
updateAgentSharing flow currently writes client-supplied args.teamIds directly
into agentBindings; instead, validate each team id belongs to
args.organizationId before persisting. For function logic around
getOrganizationMember / existing / ctx.db.insert / ctx.db.patch, load each team
record (e.g., findOne/get on "team" by _id) and confirm team.organizationId ===
args.organizationId, collect any invalid ids and throw a descriptive error if
any are found, then use the validated list (and primaryTeamId derived from it)
when patching or inserting into agentBindings.

Comment on lines +85 to +91
// Build effective team set: prefer agentTeamIds, fall back to single agentTeamId
const agentTeamIdSet = new Set<string>();
if (args.agentTeamIds) {
for (const id of args.agentTeamIds) agentTeamIdSet.add(id);
} else if (args.agentTeamId) {
agentTeamIdSet.add(args.agentTeamId);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Empty agentTeamIds currently disables legacy team fallback.

if (args.agentTeamIds) treats [] as truthy, so agentTeamId is ignored and team-scoped docs can be dropped unexpectedly.

Proposed fix
-  if (args.agentTeamIds) {
+  if (args.agentTeamIds && args.agentTeamIds.length > 0) {
     for (const id of args.agentTeamIds) agentTeamIdSet.add(id);
   } else if (args.agentTeamId) {
     agentTeamIdSet.add(args.agentTeamId);
   }

Also applies to: 94-94, 131-131

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/convex/documents/list_indexed_documents_for_agent.ts`
around lines 85 - 91, The current logic building agentTeamIdSet treats
args.agentTeamIds as truthy which makes an empty array disable the fallback to
args.agentTeamId; change the checks to test for a non-empty array (e.g.,
args.agentTeamIds && args.agentTeamIds.length > 0) before iterating so that when
agentTeamIds is [] the code falls back to using agentTeamId; update the same
pattern wherever agentTeamIds is checked (the other occurrences noted) to ensure
legacy team fallback works and ensure you still add args.agentTeamId to
agentTeamIdSet when agentTeamIds is absent or empty.

Comment on lines +424 to +428
teamIds:
agentConfig.agentTeamIds ??
(agentConfig.agentTeamId
? [agentConfig.agentTeamId]
: undefined),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle empty agentTeamIds before legacy fallback.

If agentTeamIds is [], the current expression still picks it and suppresses fallback to agentTeamId, which can unexpectedly drop team-scoped access.

Proposed fix
-              teamIds:
-                agentConfig.agentTeamIds ??
-                (agentConfig.agentTeamId
-                  ? [agentConfig.agentTeamId]
-                  : undefined),
+              teamIds:
+                agentConfig.agentTeamIds && agentConfig.agentTeamIds.length > 0
+                  ? agentConfig.agentTeamIds
+                  : agentConfig.agentTeamId
+                    ? [agentConfig.agentTeamId]
+                    : undefined,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/convex/lib/agent_chat/internal_actions.ts` around lines 424
- 428, The teamIds assignment incorrectly treats an empty array
(agentConfig.agentTeamIds === []) as truthy and prevents falling back to
agentConfig.agentTeamId; update the logic in internal_actions.ts where teamIds
is set to first check that agentConfig.agentTeamIds is a non-empty array (e.g.,
agentConfig.agentTeamIds && agentConfig.agentTeamIds.length > 0) and only use it
when non-empty, otherwise fall back to agentConfig.agentTeamId (wrapping it into
an array) or undefined; ensure you modify the expression that currently reads
agentConfig.agentTeamIds ?? (agentConfig.agentTeamId ? [agentConfig.agentTeamId]
: undefined) to the non-empty-array check using the unique symbols
agentConfig.agentTeamIds, agentConfig.agentTeamId and teamIds.

Add sharedWithTeamIds field to agentBindings schema for multi-team
agent scoping. Agents can now be shared with multiple teams instead
of just one. The access model: org-wide (no teams) or team-scoped
(visible only to members of assigned teams). Admins always have
full access.

- Add access.ts helper with checkAgentAccess and getAgentTeamIds
- Add updateAgentSharing mutation with admin-only enforcement
- Add listBindingsByOrg query and internal listBindingsForOrg
- Update document scoping (getAgentScopedFileIds, listIndexedDocumentsForAgent)
  to use agentTeamIds array instead of single agentTeamId
- Thread agentTeamIds through agent chat/response pipeline and all
  agent tools (RAG, approval, human input, location)
- Add useUpdateAgentSharing frontend mutation hook
- Add comprehensive tests for access control logic
@larryro larryro force-pushed the feat/issue-1343-agent-access branch from 6f46b77 to f54f078 Compare April 11, 2026 11:07
…d permissions

Owning team members get canUse + canEdit, shared team members get canUse only.
updateAgentSharing no longer overwrites teamId. Agent settings UI shows create-team
guidance when no teams exist and a multi-select for sharing when an owning team is set.
@larryro larryro merged commit 9e978e1 into main Apr 11, 2026
24 checks passed
@larryro larryro deleted the feat/issue-1343-agent-access branch April 11, 2026 11:26
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.

Agent and Knowledge Base access control missing. No team-scoped sharing or permissions

2 participants