Skip to content

Bind agent tool calls to the run's repositories - #2087

Merged
ppXD merged 1 commit into
mainfrom
fix/bind-agent-tool-calls-to-the-runs-repositories
Oct 7, 2026
Merged

ppXD merged 1 commit into
mainfrom
fix/bind-agent-tool-calls-to-the-runs-repositories

Conversation

@ppXD

@ppXD ppXD commented Oct 7, 2026

Copy link
Copy Markdown
Owner

Summary

  • Agent tool calls now reach only the repositories the run's admitted task bound. This covers agent.run_command, git.fetch_pr_diff, git.list_prs, git.fetch_pr_checks, git.open_pr, git.merge_pr, git.pr_review and git.post_pr_comment.
    • AgentRunExecutor.OpenMcpEndpoint stamps the bound set (id, access, ref) server-side onto AgentRunPosture.
    • NodeAgentTool enforces it once for every node that declares NodeManifest.RepositoryInput.
    • An unbound id gets the same Repository {id} not found. that a missing or foreign id gets.
  • Read-only context is read-only:
    • An agent cannot open, merge, review or comment on its pull requests.
    • agent.run_command checks it out only at its bound or default branch (AgentRepositoryBinding).
    • The ref pin covers what a command checks out. The repository's pull requests stay readable through the git read tools.
  • A patch-only repository refuses agent pull-request writes through the publish guard chain (IAgentRepositoryPolicy).
  • These refusals are answered before an approval-gated call is parked (IAgentTool.RefusalAsync, consulted by McpRequestHandler after the gate's deny check and input validation). Standard and Trusted runs therefore never post a card or claim a ledger row for a call that could only be refused. The tool checks again when the call runs.
  • A supervisor-spawned child's related repositories carry the operator's bound ref, not one the supervisor model authored (SupervisorRepoClamp.IntersectWithBoundRepos).
  • A run whose write scope is read-only (readOnly on agent.run, or a Confined tier) is served McpCatalogMode.NonDestructive. It gets no tool that writes, but can still ask a human through decision.request.
  • Unchanged:
    • The fabric opt-out's ReadOnly slice.
    • Workflow-node calls, which carry no calling run.

Test plan

  • Unit: binding, policy and catalog-mode theories; NodeAgentTool against the production PR write nodes (Read, Write and unknown access); handler refusals before park/claim at every tier; supervisor clamp ref; full unit suite (12,417 passed, 1 skipped)
  • Integration (Postgres):
    • real MCP dispatch with a second same-team repository
    • every PR write refused on a read-bound repository before the provider
    • read context keeps its PR reads
    • Standard tier with a real approval channel: unbound, read-context, off-branch and patch-only calls are refused at once, with no ledger row and no card
    • the tenant filter is pinned with the foreign id bound
    • the real executor's endpoint: Confined and readOnly runs list decision.request and can reach it, while agent.run_command, git.open_pr and git.merge_pr are absent
    • Agents and Workflows.Supervisor namespaces are green
  • E2E: HeadlineFlowE2ETests (readOnly fan-out) 3/3
  • Full Workflows integration namespace in CI

@ppXD
ppXD force-pushed the fix/delete-a-merged-branch-only-in-its-own-repository branch from 59fb645 to ad1a095 Compare October 7, 2026 19:18
@ppXD
ppXD changed the base branch from fix/delete-a-merged-branch-only-in-its-own-repository to main October 7, 2026 19:19
@ppXD
ppXD force-pushed the fix/bind-agent-tool-calls-to-the-runs-repositories branch from 22031af to 86c9a1b Compare October 7, 2026 19:19
agent.run_command and the git.* pull-request tools resolved a
model-supplied repositoryId by id and team only, so an agent could
clone, read, merge or comment on any repository of its team at any ref
it named, including repositories its run was never bound to and
unmerged branches, using that repository's connection credential.
Network Off and a read-only write scope did not stop it.

The run's bound repositories (its admitted task's workspace, with each
repository's access and ref) are now stamped server-side onto the
posture every tool call carries. NodeAgentTool holds every node that
declares a repository input (NodeManifest.RepositoryInput) to that set:
any other id gets the same "not found" a missing or foreign one gets.
Read-only context is the run's to read, not to write: an agent opens,
merges, reviews and comments on none of its pull requests, and a
command checks it out only at its bound or default branch. The pin
covers what a command checks out; the repository's pull requests stay
readable through the git read tools. A patch-only repository refuses
agent pull-request writes through the guard chain an agent's pushed
branch meets.

Each of these refusals is answered before a call is parked for
approval (IAgentTool.RefusalAsync), so a Standard or Trusted run never
asks a human to approve a call that could only be refused; the tool
checks again when the call runs, since a repository can change while a
card waits. RunCommandService keeps its own ref pin for a caller that
reaches it directly.

A supervisor-spawned child's related repositories carry the operator's
bound ref, not one the supervisor model authored: that ref is what the
child clones and what its read-only binding pins commands to.

A run whose write scope is read-only (an agent.run with readOnly, or a
Confined tier) is served a NonDestructive catalog: every tool that does
not write, so it can still ask a human through decision.request. The
fabric opt-out's ReadOnly slice is unchanged. Workflow-node calls carry
no calling run and are unchanged.
@ppXD
ppXD merged commit 3c7b601 into main Oct 7, 2026
6 checks passed
@ppXD
ppXD deleted the fix/bind-agent-tool-calls-to-the-runs-repositories branch October 7, 2026 19:23
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