Skip to content

feat(tangent): grant the project's team per message - #2955

Closed
camielvs wants to merge 2 commits into
cvs/team-run-pagefrom
cvs/agent-session-team
Closed

camielvs wants to merge 2 commits into
cvs/team-run-pagefrom
cvs/agent-session-team

Conversation

@camielvs

@camielvs camielvs commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Human Words

Welcome to the top of the stack for the Workspaces x Secrets feature.

The Agentic Tangent-side functionality of this stack is delivered in TangleML/tangent-shell#132.

How it works

This stack builds on top of the Team Impersonation system to allow users to create Workspace-level secrets and execute Pipeline Runs using them.

Workspaces are now associated 1:1 with a Team and through a static config on the backend that Team grants basic roles to users based on their Team membership. Users with Team permissions are then able to create, edit and manage secrets on the entire Workspace, and add them to their pipelines.

When a pipeline is submitted with a Workspace secret the initiating user must have team permissions. That Run will then be Attributed to the team and utilise its secrets. It is not possible to mix user-personal secrets and workspace secrets in one pipeline. Additionally, for runs that do not use secrets the user will have a choice of submitting as themselves or the team. It is also not possible to run a pipeline that uses Workspace secrets from outside of that Workspace.

Tangent agents have access to the same functionality, based on Team membership of the user who initiated the turn, via tangent-shell PRs.

Screenshots

image image image image image image

Testing

  1. Set up a Workspace with a team id you have permissions in
  2. Create a project in the workspace
  3. You should now see a new window for "Workspace Secrets"
  4. Confirm you can add/edit/delete workspace secrets
  5. Make a pipeline inside the Project
  6. Confirm you can add the Workspace secrets to the pipeline and run it

AI Words

Description

Tangent agents submit runs two ways. The tool bridge into a pipeline or run tab acts as the project's team since #2943. The agent's own server-side tools didn't, so their runs went as the user. A run that used a team secret then failed at launch with "User … does not have secret …".

A Tangent session is shared, so the team can't be fixed when it starts: it would act for whoever types next, member or not. It's granted per message instead:

  • getActingTeam: the page passes it to TangentProvider. Tangent reads it as each message is sent and attaches the team to the agent turns that message causes, including work Prime hands to sub-agents.
  • A grant, not a default: on a granted turn, a team-scoped Tangle call (run submit, cancel, annotation writes, secrets list) acts as the team only when its tool asks (asTeam), and goes as the user otherwise. So agents can still list and use the user's own secrets. The tangent-shell server adds the team header itself and strips any copy an agent sends.
  • What it grants: the team id while the signed-in user can act as the project's team, and nothing for any other resolution. It doesn't check that the session belongs to this project, because the opening prompt is sent before the session is attached and such a check would refuse it.
  • Session memory: a project-level brief, the same for everyone in the session. It names the project's workspace and its team id, and calls the team the workspace, as the UI does. It says a turn may act as the workspace only when its sender is in it and grants it, and that even then each call opts in (asTeam). It says to check secrets and submit as the same identity, and to act as the workspace when the user asks or the run needs a secret only the workspace has. It also says never to send the header, where to see the turn's grant, and not to retry as the user unless asked. A project without a known team gets no brief.
  • Session start: a session isn't started while the team is still being checked, so the opening prompt goes out with the team granted. The arrival waits for the team, and New session says to try again in a moment if it's clicked first.

The tangent-shell side is TangleML/tangent-shell#132, not yet merged. This PR pins @tangent/embed-react and @tangent/shared to its head, d229883, so getActingTeam exists and the team it grants is one each agent call opts into.

Related Issue and Pull requests

Type of Change

  • New feature

Line Breakdown

Category Added Removed
Production code 84 4
Tests 196 1
Config / CI / tooling 4 4
Generated / lockfiles 17 17
Total 301 26

Checklist

  • I have tested this does not break current pipelines / runs functionality
  • I have tested the changes on staging

Screenshots (if applicable)

None. There's no UI change beyond the "still being checked" notice.

Test Instructions

pnpm vitest run src/routes/v2/pages/Tangent

Against a local tangent-shell on feat/per-turn-team-impersonation (TangleML/tangent-shell#132), with the tangle 3.6.1 bundle from TangleML/tangent-shell-shopify#15, and a backend that honours the header:

  1. As a member, in a team's project, start a new session and ask the agent to list secrets. It lists yours, and the team's when you ask for them. Ask it to submit a pipeline that uses a team secret: the run's "Created by" is the team's id, and the secret resolves.
  2. Same session, a pipeline that uses only your own secret: it goes as you.
  3. As someone not in the team, send a message in that same session asking for a run. It goes as you, and the agent doesn't act as the team.
  4. In a project without a team, it behaves as on master.

Additional Comments

If the host's __TANGLE_TEAM_IMPERSONATION__.headerName is ever anything other than x-tangle-impersonate-team-id, the tangent-shell server's TANGLE_TEAM_HEADER must be set to match. tangle-ui no longer passes the header name to Tangent.

🤖 Generated with Claude Code

@camielvs
camielvs added this pull request to stack #2930 October 27, 2026 19:16
@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

🎩 Preview

A preview build has been created at: cvs/agent-session-team/04050bb

@camielvs
camielvs force-pushed the cvs/agent-session-team branch from 068de7f to c8e17a0 Compare October 28, 2026 01:12
@camielvs camielvs changed the title feat(tangent): hand an agent session the team its runs act as feat(tangent): grant the project's team per message Oct 28, 2026
@camielvs
camielvs force-pushed the cvs/agent-session-team branch from c8e17a0 to 251adc6 Compare October 28, 2026 19:43
@camielvs camielvs mentioned this pull request Oct 28, 2026
1 of 3 tasks
@camielvs
camielvs force-pushed the cvs/agent-session-team branch 2 times, most recently from fa6511f to 3ba5491 Compare October 28, 2026 23:22
@camielvs
camielvs force-pushed the cvs/agent-session-team branch 2 times, most recently from 61436b5 to c822e4b Compare October 29, 2026 02:42
@camielvs
camielvs force-pushed the cvs/agent-session-team branch from c822e4b to 3ac6349 Compare October 29, 2026 19:45
@camielvs
camielvs force-pushed the cvs/agent-session-team branch 2 times, most recently from b63ab65 to 1d52a6f Compare October 29, 2026 20:42
export function useTangentActingTeam(
projectId: string,
): (sessionId: string) => string | undefined {
const teamId = actingTeamId(useTeamForProjects([projectId]));

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

🤖 This is an AI-generated code review comment.

Tangent still grants read-only workspaces per message

actingTeamId(...) returns the team id for any active resolution, including canWrite: false. That means a user with only read access to the workspace still grants the team id to every Tangent message. The session memory tells the agent it may call tools with asTeam, and tangent-shell will receive a team grant for a user who should not be able to create workspace-owned runs/use workspace secrets for writes.

Please only grant getActingTeam when resolution.status === "active" && resolution.team.canWrite. If read-only users should still inspect workspace-scoped data, keep that as a separate read-only code path rather than using the same acting-team grant.

@camielvs
camielvs force-pushed the cvs/agent-session-team branch from 1d52a6f to c3aaf66 Compare October 29, 2026 23:33
case "not-member":
case "unavailable":
return teamProjectBrief(team.team);
default:

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

🤖 This is an AI-generated code review comment.

Whether a session gets the workspace brief depends on who started it.

The brief describes the project, not the person, and the PR calls it "the same for everyone in the session". But it's written once, from the resolution of whoever starts the session, and an error resolution writes no brief. So if the starter's identity check failed, the session never gets the brief, even when a member who types later is granted the team and their turns would need it. Base the brief on the team of the project's workspace (teamOfWorkspace), not on the starter's resolution.

@camielvs

Copy link
Copy Markdown
Collaborator Author

✅ Manually smoke tested by @camielvs.

@camielvs
camielvs force-pushed the cvs/agent-session-team branch from c3aaf66 to 04050bb Compare October 30, 2026 02:02
Moves the tangent-shell packages from 1998f55 to d229883, which adds
TangentProvider's getActingTeam. The team it returns is a grant that
each agent call opts into. The packages' own dependencies are unchanged,
so the lockfile only moves the commit.

d229883 is the head of TangleML/tangent-shell#132, not yet merged.
Repin to its merge commit before this stack merges.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A Tangent session is shared, so a team fixed when it starts acts for
whoever types next, member or not. The team is now granted per message:
the page passes TangentProvider a getActingTeam, which Tangent reads as
each message is sent and attaches to the agent turns that message
causes. That team is a grant, not a default: a team-scoped call acts as
it only when its tool asks, and goes as the user otherwise. The server
adds the team header itself and strips any copy an agent sends.

The getter returns the team only while the signed-in user can act as
it, and nothing for any other resolution. It doesn't check that the
session belongs to this project: the opening prompt is sent before the
session is attached, so such a check would refuse it.

The session's memory gets a project-level brief instead of one written
for whoever started it. It names the project's workspace and its team
id, calls the team the workspace as the UI does, and says a turn may act
as the workspace only when its sender is in it and grants it, and that
even then each call opts in (asTeam). It also says to check secrets and
submit as the same identity, when to choose the workspace, never to send
the header, where to see the turn's grant, and not to retry as the user
unless asked. A project without a known team gets no brief. The session
no longer carries the team in its environment, since agents would read
it as the session's.

A session still isn't started while the team is being checked, so the
opening prompt goes out with the team granted rather than without it.
The arrival waits for the team, and New session says to try again.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@camielvs
camielvs force-pushed the cvs/agent-session-team branch from 04050bb to 8fab1eb Compare October 30, 2026 18:51
@camielvs

Copy link
Copy Markdown
Collaborator Author

🤖 This is an AI-generated comment.

Closing: per-message team gating is out of scope. The workspace secrets UI ships through #2944, and tangent-shell is expected to handle agents' team submission, so this PR's per-message grant and its pin to TangleML/tangent-shell#132 aren't needed.

Nothing below this PR depends on it. #2927–#2944 don't use its code or the newer @tangent/embed-react, and they keep master's pin. The branch stays in case per-message gating comes back.

@camielvs camielvs closed this Oct 30, 2026
@camielvs
camielvs deleted the cvs/agent-session-team branch October 30, 2026 22:23
An error occurred while trying to automatically change base from cvs/team-run-page to master October 31, 2026 00:25
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.

1 participant