Skip to content

feat(trust): multi-agent workspaces — provenance chips, per-agent grant scoping, rate budgets (#59)#74

Merged
100yenadmin merged 2 commits into
mainfrom
act4/multi-agent
Jul 10, 2026
Merged

feat(trust): multi-agent workspaces — provenance chips, per-agent grant scoping, rate budgets (#59)#74
100yenadmin merged 2 commits into
mainfrom
act4/multi-agent

Conversation

@100yenadmin

Copy link
Copy Markdown
Owner

Closes #59

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 5 minutes

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5b3d3f27-e30b-4b5e-a86e-9d0909d9c4b2

📥 Commits

Reviewing files that changed from the base of the PR and between e1b2c92 and 65358c1.

📒 Files selected for processing (34)
  • .changeset/multi-agent-workspaces.md
  • packages/core/src/distribution.test.ts
  • packages/core/src/distribution.ts
  • packages/core/src/queries.ts
  • packages/core/src/store.test.ts
  • packages/core/src/store.ts
  • packages/core/src/transforms/approvals.ts
  • packages/core/src/transforms/transforms.test.ts
  • packages/core/src/types.ts
  • packages/lit/src/agent-provenance.test.ts
  • packages/lit/src/agent-provenance.ts
  • packages/lit/src/boardstate-view.test.ts
  • packages/lit/src/boardstate-view.ts
  • packages/lit/src/boardstate-widget-cell.ts
  • packages/lit/src/locales/de.ts
  • packages/lit/src/locales/es.ts
  • packages/lit/src/locales/fr.ts
  • packages/lit/src/locales/ja-JP.ts
  • packages/lit/src/locales/zh-CN.ts
  • packages/lit/src/renderers/approvals.ts
  • packages/lit/src/renderers/renderers.test.ts
  • packages/lit/src/strings.ts
  • packages/lit/src/styles/boardstate.css
  • packages/schema/SPEC.md
  • packages/schema/src/schema.test.ts
  • packages/schema/src/schema.ts
  • packages/server/src/broker-actions.test.ts
  • packages/server/src/broker-actions.ts
  • packages/server/src/broker-agent-tools.test.ts
  • packages/server/src/broker-agent-tools.ts
  • packages/server/src/broker-workspace.ts
  • packages/server/src/host.ts
  • packages/server/src/rpc.ts
  • packages/server/src/ws-transport.test.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch act4/multi-agent

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

@evaos-code-review-bot

evaos-code-review-bot Bot commented Jul 10, 2026

Copy link
Copy Markdown

evaOS review status: closed or merged before review

PR: #74 - feat(trust): multi-agent workspaces — provenance chips, per-agent grant scoping, rate budgets (#59)
Head: 98f4f228e272850e8adf274ceb70bf0f6bbab688
Updated: 2026-07-10T20:15:10.122Z

evaOS review stopped because the PR closed or merged before this queued head could be reviewed.

Automation note: agents should wait for this comment to reach completed, stale_head, closed_or_merged_before_review, skipped, or failed before treating evaOS review as settled for this head. provider_deferred means evaOS still intends to retry.

PR URL: #74

Details: state=closed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 98f4f228e2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// this verb is the single source of truth for the operator-only fields.
autoConfirm: autoConfirmSubset,
expiresAt,
agents: agentsScope,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve agent scopes on approval saves

When an operator uses the approvals widget to save changes on an already scoped grant, the UI displays item.agents for granted rows but only forwards tools, autoConfirm, and expiresAt through approveCapability; no agents option is ever sent. Because this assignment treats an omitted agents param as undefined, any such save silently clears the scope and widens the grant to all agents, defeating the new per-agent gating.

Useful? React with 👍 / 👎.

Comment on lines +291 to +294
grant.agents !== undefined &&
(boundActor === undefined || !grant.agents.includes(boundActor))
) {
continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Allow scoped-out agents to request existing tools

When a connector grant already contains a tool but is scoped to another agent, this new filter hides it from the current agent, yet boardstate_tool_search.request still treats requested ids that are already in existing.tools as a no-op and returns before re-pending anything. In practice, if officecli:read_mail is granted only to agent:alice, agent:bob cannot get an approval card by requesting that same tool, so scoped grants can permanently block later agents from requesting access unless an operator edits the grant out-of-band.

Useful? React with 👍 / 👎.

Comment on lines +371 to +373
admit(rateWindows, connector, rateMax, `too many invocations for connector "${connector}"`);
if (perAgentRateMax !== undefined && actingAgent) {
admit(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid charging connector limits for per-agent denials

With perAgentInvokeRateMax configured below the connector limit, this admits the connector window before checking the agent window. Once Alice has exhausted her per-agent budget, each refused Alice call still appends to rateWindows, so enough denied attempts can make Bob hit rate_limited at the connector level even though only Alice exceeded her budget. That breaks the advertised independent per-agent ceiling and lets one over-limit agent starve others.

Useful? React with 👍 / 👎.

Adversarial verify (narrow refute): sanitizeImportedWorkspace stripped
grantedBy/autoConfirm/expiresAt but not agents — the one re-pend site missed
when the others gained the scope strip. Currently inert (gateCall checks
status before scope; approve overwrites), but a foreign doc's scope claims
are as untrustworthy as a granted status: strip at the same seam. The exact
toEqual in the import test makes the fixture's new agents field delta-sensitive.
@100yenadmin

Copy link
Copy Markdown
Owner Author

Adversarial verify — verdict

  • Actor binding server-side everywhere: SAFE — params-actor rejected at the whitelist; WS threads no identity → scoped grants fail CLOSED networked (proven over a real socket); the adapter assembly filter blocks unbound/cross-agent tool leakage; confirm re-gates on the park-time server-bound requester. Residual params.actor uses are audit/provenance only.
  • Wipe completeness: NARROW REFUTE → fixed (65358c1) — the IMPORT re-pend (sanitizeImportedWorkspace) stripped grantedBy/autoConfirm/expiresAt but not agents. Inert today (status gates before scope; approve overwrites), but foreign scope claims are as untrustworthy as a foreign granted status — stripped at the same seam, fixture now delta-sensitive.
  • Byte-identical unset: SAFE — no rate windows allocated, no new wire fields, discriminating pins in place.
  • Chips inert: SAFE — numeric hue only; actor strings through escaped bindings; presentation-only filter.

Merging.

@100yenadmin 100yenadmin merged commit ddc2710 into main Jul 10, 2026
5 checks passed
@100yenadmin 100yenadmin deleted the act4/multi-agent branch July 10, 2026 20:14
@github-actions github-actions Bot mentioned this pull request Jul 10, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 65358c1bd9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const { connector, tool, args } = readInvokeParams(ctx.params);
checkRate(connector);
const { id, readOnly } = await gateCall(connector, tool);
const actingAgent = boundAgentActor(ctx);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Thread an agent identity into scoped MCP binding reads

When a scoped grant backs a live source:"mcp" widget, the normal binding path calls transport.request("dashboard.connector.read", params) without a request context (see packages/host/src/store.ts), so boundAgentActor(ctx) is always undefined here. That makes gateCall(..., actingAgent) reject every scoped read binding as capability_pending, even when the widget was authored by an allowed agent; only unscoped grants can render live data. The binding resolver needs to pass an authenticated/widget-author agent identity, or scoped grants should not be enforced on board-owned reads.

Useful? React with 👍 / 👎.

!sameStringSet(grant.autoConfirm ?? [], currentGrant.autoConfirm ?? []) ||
(grant.expiresAt ?? "") !== (currentGrant.expiresAt ?? ""));
(grant.expiresAt ?? "") !== (currentGrant.expiresAt ?? "") ||
!sameStringSet(grant.agents ?? [], currentGrant.agents ?? []));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Strip agent scopes from untrusted requested grants

This scope comparison only runs when the incoming grant is still status:"granted", so an untrusted dashboard.workspace.replace can write agents onto a requested or revoked capability and have it persist. That operator-only scope is then rendered in the approvals row as if it were operator-authored, violating the new single-writer invariant for agents; strip agents from non-granted/re-pended grants on the replace path as well.

Useful? React with 👍 / 👎.

Comment on lines +480 to +485
const invokeCtx = options.agentId !== undefined ? { agentId: options.agentId } : undefined;
const adapter = createBrokerAgentTools({
broker: options.broker,
store: options.store,
invokeMutation: (input) =>
host.request("dashboard.action.invoke", input) as Promise<InvokeMutationResult>,
host.request("dashboard.action.invoke", input, invokeCtx) as Promise<InvokeMutationResult>,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Bind broker tools to the calling agent

When one host serves multiple agent sessions, this install-time invokeCtx makes every session use the same scoped broker-tool identity. Since host.registerTool() and createAgentChatAgent({ host }) expose a global host.tools() set, passing agentId: "alice" here means Bob receives tools filtered for Alice, read-only broker tools can run under Alice's scoped grant, and mutations are parked with Alice's identity. The adapter needs a per-call/per-session agent context instead of a single captured identity.

Useful? React with 👍 / 👎.

Comment on lines +288 to +289
// This is the enforcement point for the direct `readOnly` execute path below, which
// does not go through the server-side invoke gate.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Rate-limit direct read-only broker tools

For the normal agent-tool path, granted read-only broker tools execute directly through broker.callTool() and never call dashboard.action.invoke or dashboard.connector.read; this added block even notes that assembly is their only gate. As a result, when perAgentInvokeRateMax is configured, an agent can exceed its per-agent budget simply by repeatedly calling an advertised read-only external tool, while the limit only applies to the RPC paths. Apply the same rate check to direct read-only agent tools or route them through the engine.

Useful? React with 👍 / 👎.

Comment on lines +222 to +226
const normalized = raw
.replace(/[^A-Za-z0-9._-]+/g, "-")
.replace(/^-+|-+$/g, "")
.slice(0, 64);
return normalized ? (`agent:${normalized}` as DashboardActor) : 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.

P2 Badge Preserve already-normalized agent actors

If a caller threads RequestContext.agentId as the full actor string already used in grants, e.g. agent:assistant (several existing demos use that convention for host request context), this normalization turns the colon into a hyphen and returns agent:agent-assistant. A grant scoped to agent:assistant will then reject that same agent as out of scope, so boundAgentActor should accept/strip an existing agent: prefix before sanitizing.

Useful? React with 👍 / 👎.

...(toolsHash !== undefined ? { toolsHash } : {}),
...(autoConfirm !== undefined ? { autoConfirm } : {}),
...(expiresAt !== undefined ? { expiresAt } : {}),
...(agents !== undefined ? { agents } : {}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject scopes on un-enforced data grants

This now persists agents on any capability grant, including connector grants that only have methods or streams. Those data connectors are still served by installConnector, whose gate only checks status === "granted" before serving reads or broadcasting streams, so an operator can approve a grant that appears scoped to agent:alice while every client continues to receive the data once it is granted. Either reject agents on method/stream grants here or enforce the same agent gate in the data connector path.

Useful? React with 👍 / 👎.

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.

Multi-agent workspaces: provenance chips + per-agent grant scoping + rate budgets

1 participant