Agents
Tool policies
What an agent may call, how the seeded roles differ, and how the column an agent is working in narrows that further.
A tool policy is an allow-list: the tool names (and MCP server ids) an agent's sessions are permitted to call. It lives on the agent's Settings tab under Tool policy, alongside the model and provider. An agent with an empty allow-list is unrestricted — every registered tool is available to it — so scoping an agent down is something you do deliberately, not something that happens by leaving a field blank.
Two independent things narrow what actually runs in a given session: the agent's policy (what this agent, as a role, is allowed to do at all) and the column a task sits in (what makes sense for the job that column's run is there to do). The agent's policy is the wider of the two; the column narrows it further for that one run.
What the seeded roles get
The three developer roles (backend-developer, frontend-developer,
mobile-developer) get essentially everything a coding agent needs:
- The terminal (
run_terminal) and every file writer (write_file,edit_file,edit_lines,delete_file,move_file,download_file) — the shell and the writers always travel together, since anything a shell can do withsed -ia file tool can do explicitly. - Web search and page fetch.
- The read-only code tools (
codebase_search,grep_code,get_repo_tree,get_symbol_skeleton,expand_symbol_context,read_file). - The browser tools, so a developer can open the page it just changed on
127.0.0.1before handing the task on, not just get a green build. - Board read tools, claiming a task, memory, skills, and reading/replying to the task's pull request and committing to it.
- The mobile developer additionally gets the device tools
(
mobile_launch_app,mobile_tap,mobile_screenshot, and so on) — the other two developer roles do not, since the device is one shared phone and only mobile work has a use for it.
product-manager gets no shell and no file writers — it works the board,
not the code. It gets the read-only code tools (to verify a real file or
endpoint name before writing a technical description, never to change one),
the browser and mobile tools (for walking a build in PM UAT), board create and
delete, workspace management (creating/renaming projects), and its own
criterion verdicts (review_criterion).
system-architect gets the shell, the read-only code tools, board create
and claim, and read/reply on pull requests — but not the file writers or
commit_task_changes. It is the code reviewer: it reads the diff it is
judging and can comment on it, but cannot push a fix into the branch under
review.
qa-agent gets the shell, the browser and mobile tools, and a
deliberately narrow slice of the code tools — see below. It does not get
the merge tool or any release tool: QA's work ends at its own verdict in
in_qa, and everything after sign-off belongs to the release engineer.
release-engineer owns everything after sign-off. It holds the one merge
tool (merge_task_pull_request), the release tools (get_release,
deploy_release, watch_release, run_smoke_checks, finish_release,
release_rollback) and get_deploy_logs, plus the shell, read-only lookup
and browser-read tools, and board read/move tools — but no file writers and
no commit_task_changes: it ships and rolls back through the release tools,
never by editing the branch. The developer must not merge its own branch, the
reviewer and PM judge the change rather than land it, and QA tests it rather
than ship it.
The QA black-box rule
QA does not get the general code-reading tools
(codebase_search, get_symbol_skeleton, expand_symbol_context). It gets a
narrower lookup set instead:
get_repo_treegrep_codeget_task_pull_request
read_file is also in QA's base policy, but the column narrowing (below)
strips it back out in in_qa/ready_for_qa — the two columns QA actually
tests in. That leaves QA with tree- and diff-level lookups there, never a full
file body.
This is deliberate: QA's evidence is a running product, not a read of the
diff. Early on, QA held the full code-reading set and used it exactly as
those tools invite — a round on a UI task opened with a tree listing and a
handful of read_file calls, and the "test" that came out was a reading of
the source, not a test of the behavior. The tools that exist to help someone
understand code are the reviewer's tools; QA's job is to exercise the
build and report what it observed, so its lookup set only goes as far as
finding the start command and the route to open.
Code and MR reading is forbidden by default for a QA round — never to decide a criterion passes, never to write down an "expected" result that was never observed, never to skip running a case QA believes it can predict. Exactly three named exceptions exist, none of which substitutes for execution:
- Debugging an observed failure — after a scenario fails against the
running product,
grep_code/get_repo_treeand the failure's own surfaced output (stack traces, logs,browser_read_dom) pinpoint and report the defect location.read_fileis gone here too — the defect is traced from the failure's own output and the tree, never a full file read. - Re-test scope on a resubmission — a task bounced to
need_revisionand returned toin_qa/ready_for_qa:get_task_pull_requestdecides whether the fix is scoped narrowly enough that only the previously-failing cases need re-running, or whether the full matrix must be re-executed. This is a scoping decision made before execution, never a verdict. - Case-matrix completeness/validity check — while building the case
matrix, before touching the app,
get_repo_tree/grep_codeconfirm a case already written down is reachable/valid, or surface a scenario the diff implies that QA had not already listed. This only feeds the matrix; it never substitutes for executing the case afterward.
The PM UAT black-box rule
PM's policy carries the full read-only code-reading set
(codebase_search, grep_code, get_repo_tree, get_symbol_skeleton,
expand_symbol_context, read_file) for backlog grooming — verifying a real
file or endpoint name before writing a technical_description. But the column
narrowing strips every one of those names in pm_uat/human_uat, the only two
columns PM actually signs a criterion off in. In those columns PM has no way to
look at the implementation at all; its
verdict can only come from an executed browser/mobile session against the
running product, or from a QA case that was itself executed and passed.
Two gates back this up at the run level, mirroring QA's:
isUngroundedPMUATfails apm_uatrun outright if it never called any ofdomain.PMUATExecutionTools— the same browser/mobile call names asQAExecutionTools, minusrun_terminal(PM has no shell) and minusget_deploy_target(resolving a stage URL is a lookup, not exercising the product). A run that approved every criterion using nothing but board reads is rejected the same way an ungrounded QA round is.- The coverage-gap gate (
pmApprovedUncoveredCriterion) closes a narrower hole: QA'sreview_criterion(approved=true)is a claim, not proof, if nothing in QA's own recorded test-case list (list_test_cases) actually backs it. For every criterion PM approves in this run, the gate checks whether adomain.TaskTestCasewith a matchingcriterion_idwas recordedpassed. If every approval this run made is backed by such a case, PM passes. If even one approval was not backed by a passed case, PM must also have usedPMUATExecutionToolsthis run — approving an uncovered criterion off QA's note and board-read tools alone is ungrounded and fails the run just asisUngroundedPMUATdoes.
How a column narrows a policy
A single function, RestrictToolsForStage(policy, stage, taskType), applies
after the agent's own policy has already decided what it may do at all. It
reads three behaviours off the task's type and the column (stage) it sits
in and strips tools accordingly — type-first, then the stage's own. An empty
(unrestricted) policy is left untouched; narrowing only ever removes names
from a non-empty allow-list.
BehaviourNoWorkspaceWrites comes from the task type and strips the
workspace writers from any run on that type, regardless of column. It is set
on analiz (analysis) tasks: the deliverable is a spec and a plan document
attached to the task, never a code change — there is nothing for a file writer
to do, and without it an architect dispatched on an analysis task has, in
practice, gone ahead and edited files that then died with the run's workspace,
having never been committed or reviewed.
BehaviourStripWriters comes from the column and strips the workspace
writers and commit_task_changes — plus the merge tool and the release tools,
unless that column keeps them with an allow list — from a run in a column
whose job is to judge, not to change: Code Review, Analysis Review, Ready
for QA, In QA, PM UAT, Human UAT. The terminal stays — QA still has to boot and
run the product — but a reviewer or QA run that finds a bug cannot go fix it
itself; the finding has to go back to the developer. The merge and the release
tools are kept only in done/released, the two columns the release engineer
works, where landing the change and watching the deploy is the whole point.
BehaviourNoCodeReading comes from the column and strips the read-only
code-reading tools (CodeExplorationTools) on top of what
BehaviourStripWriters already removes, from a run whose job is to exercise
the running product, not to read the diff and decide it looks right. It applies
to the two roles that sign a criterion off, each differently:
- PM loses every
CodeExplorationToolsname (codebase_search,grep_code,get_repo_tree,get_symbol_skeleton,expand_symbol_context,read_file) inpm_uat/human_uat— the only two columns PM signs a criterion off in. Outside those columns PM keeps the set for backlog grooming. - QA loses only
read_fileinin_qa/ready_for_qa.get_repo_tree,grep_codeandget_task_pull_requestsurvive there, because QA's three named exceptions (see The QA black-box rule above) are tree-level and diff-level, never "read this file's full body and decide from it".
RestrictToPlannedTools is separate, and applies only inside chat orchestration, when a
request is broken into subtasks. A subtask's declared tool names decide
only the board-write tools (create_board_task, move_board_task,
update_board_task) — that declaration exists purely to stop two concurrent
subtasks from writing the same board record twice. It is not a capability
budget: every other tool the agent is configured for (code reading, the
shell, load_skill) stays available on every subtask regardless of what that
subtask declared, because narrowing them by declaration once left a subtask
holding only board tools unable to answer even "where are the files" and
asking a human instead.
The columns BehaviourStripWriters treats as judging, not changing (and where
BehaviourNoCodeReading strips code reading on top):
| Column | Writers/commit removed | Merge/release removed | Code-exploration tools also removed |
|---|---|---|---|
| Code Review | Yes | Yes | No — the reviewer's job is to read the diff |
| Analysis Review | Yes | Yes | No |
| Ready for QA | Yes | Yes | Only read_file (QA keeps get_repo_tree/grep_code/get_task_pull_request) |
| In QA | Yes | Yes | Only read_file (QA keeps get_repo_tree/grep_code/get_task_pull_request) |
| PM UAT | Yes | Yes | Yes — all of CodeExplorationTools |
| Human UAT | Yes | Yes | Yes — all of CodeExplorationTools |
| Done | Yes | No — the merge and the deploy watch/rollback happen here | No |
| Released | Yes | No — the deploy watch and rollback happen here | No |
The release engineer's observability jobs
The jobs behind Ask AI to define errors and Ask AI to map signals on the Operations console run the release engineer under a fixed set of tools, whatever its own policy allows elsewhere. The agent only ever gets the tools that are in both lists:
| Job | Tools |
|---|---|
| Define errors from recent errors | list_error_groups, propose_error_definitions |
| Define errors from the code's error types | propose_error_definitions |
| Map signals | list_data_source_metrics, propose_signal_binding, propose_alert_rule |
Every job also keeps the code readers (codebase_search, grep_code,
read_file, get_repo_tree), get_project_brief and load_skill. A job
gets no terminal, no web fetch, no file writes, no merge, deploy or rollback,
no memory or skill edits, and none of your MCP servers. It reads production
error text and metric names, which anyone who can make your service log
something can influence, so the job cannot do anything with what it reads
beyond proposing suggestions you accept or dismiss.
Inside a job, the observability tools also refuse any repository or component other than the job's own. Data sources are limited too: the job sees only the sources its repository's components already read through, plus sources no component uses yet. Outside a job, a run bound to a repository may name only that repository.
Editing a policy
Open the agent's Settings tab; the tool policy editor sits below the system prompt. It lets you add or remove individual tools and MCP servers from the allow-list. Two things to know before narrowing one:
- An empty list means unrestricted, not "nothing" — to actually restrict an agent you list what it may use. MCP servers are the exception: an empty MCP servers list gets only the servers set to All agents, and a server set to Only agents that list it reaches an agent only once you pick it there (see MCP servers). Picking any server makes the list exact.
- Your tool-policy edits survive every sync. The catalog sync applies a role's tool policy from the catalog, but the moment you hand-edit a seeded agent it is parked (see Role agents), so any change you make here is permanent across restarts and upgrades rather than being reconciled back to the catalog's version.
The terminal and outbound URL guards
Two settings-level guards apply underneath every agent's policy, regardless of what tools it is allowed:
tools.terminalgatesrun_terminalitself: it must be explicitly enabled, and itssandbox.mode(allowlist,blocklist, oroff) can restrict which commands run at all, independent of which agents are allowed to call the tool.- The outbound URL guard sits under every tool that dials a
model-chosen URL —
fetch_url, the browser tools, search, MCP HTTP endpoints, health probes. It refuses loopback, link-local, private-network, and other internal address ranges by default, pins the request to the address it vetted so DNS cannot redirect it after the fact, and re-checks every redirect hop. The one exception is the browser tools, which default to allowing loopback — a QA run routinely needs to open the build it just started on127.0.0.1. Both defaults are controlled by theALLOW_LOOPBACK_TOOL_URLSprocess environment variable, not a config file or admin setting, so nothing reachable through the API or the UI can widen it.