Problem
Today the per-role worker / orchestrator overrides in a project's config cover only three things — agent, model, and permissions (RoleOverride{ Harness, AgentConfig{ Model, Permissions } } in domain/projectconfig.go). Everything else about a worker's environment is inherited globally and can't be scoped per role:
- MCP servers — claude-code reads them from the shared
~/.claude / .mcp.json; AO passes no per-session --mcp-config, so every worker gets the orchestrator's full MCP set.
- Plugins / skills — loaded globally from
~/.claude/, never scoped per role.
- System prompt — generated by AO from the session role; there's no per-role user-configurable base prompt.
(Side note: even the existing per-role model / permissions are wired in the Go domain and the API schema but only agent is exposed per-role in the settings UI — so the per-role surface is under-exposed today too.)
Guiding principle: least privilege
A worker should run naked by default — no MCP servers, no plugins — unless the user explicitly opts it in. A worker is given work to do; it should not inherit the orchestrator's keys to the safe. Carrying the orchestrator's whole toolset is slower, noisier, and grants more power than a code-change task needs.
This is the default, not an opt-in toggle. The user opts in to specific tools per worker role; absence of config means isolation, not inheritance. (Inheritance stays available as an explicit choice — e.g. an orchestrator role, or a worker the user deliberately trusts with the global set.)
This supersedes the earlier "defaulting to inherit so nothing breaks" wording — that framing is what led the first implementation astray (it shipped inherit-by-default, defeating the whole point). Least privilege is the principle; anything that ships inherit-by-default is a bug against this issue.
Ask
Widen the per-role override from "agent/model/permissions" into a proper worker environment profile, with least-privilege defaults. Candidates, in rough priority:
- MCP servers — workers default to none (isolated), opt in explicitly (code-graph, browser) when a task needs tools. This was the original scope of this issue. Isolation mechanism: claude-code's
--strict-mcp-config with no --mcp-config loads zero MCP servers (verified against claude --help), so MCPConfig{Strict: true, Configs: []} is the worker default; an explicit --worker-mcp-config opts back in.
- Plugins / skills — workers default to none. Mechanism: claude-code has no single "plugins off" flag, but
--settings '{"enabledPlugins": {...all false...}}' disables them per-session without --bare/--safe-mode (which would also kill AO's activity-detection hooks) — verified: claude --settings '{"enabledPlugins": {"context7@claude-plugins-official": false}}' plugin list → Status: ✘ disabled. AO would emit this from the worker's global plugin set, inverted.
- System prompt — a per-role base prompt for workers (replace, not concatenate).
- Env — already worker-forwardable via
project.env; keep, and make sure per-role overrides don't leak into the stored project config (deep-copy).
Why
The per-role mechanism already exists and is proven for agent/model/permissions; this widens the same RoleOverride to the rest of the environment — but with least-privilege defaults, so a worker with no explicit config is isolated, not endowed.
Open question (rollout)
Flipping worker MCP/plugins from inherit (today) to naked-by-default is a behavior change for existing projects — a worker that quietly used a global MCP tool or plugin would run half-blind until the user configures it. The direction is settled (naked); the rollout shape is tracked in #2844 (naked for everyone + migration note / naked for new projects only / opt-in now). Implementation of this issue lands once that's decided, so it's built the right way once.
Scope
- claude-code first (it has the isolation primitives above); other adapters inherit as today and gain isolation as follow-ups.
- UI (per-role settings form) deferred to a follow-up; the config + flags land first.
Problem
Today the per-role
worker/orchestratoroverrides in a project's config cover only three things —agent,model, andpermissions(RoleOverride{ Harness, AgentConfig{ Model, Permissions } }indomain/projectconfig.go). Everything else about a worker's environment is inherited globally and can't be scoped per role:~/.claude/.mcp.json; AO passes no per-session--mcp-config, so every worker gets the orchestrator's full MCP set.~/.claude/, never scoped per role.(Side note: even the existing per-role
model/permissionsare wired in the Go domain and the API schema but onlyagentis exposed per-role in the settings UI — so the per-role surface is under-exposed today too.)Guiding principle: least privilege
A worker should run naked by default — no MCP servers, no plugins — unless the user explicitly opts it in. A worker is given work to do; it should not inherit the orchestrator's keys to the safe. Carrying the orchestrator's whole toolset is slower, noisier, and grants more power than a code-change task needs.
This is the default, not an opt-in toggle. The user opts in to specific tools per worker role; absence of config means isolation, not inheritance. (Inheritance stays available as an explicit choice — e.g. an orchestrator role, or a worker the user deliberately trusts with the global set.)
This supersedes the earlier "defaulting to inherit so nothing breaks" wording — that framing is what led the first implementation astray (it shipped inherit-by-default, defeating the whole point). Least privilege is the principle; anything that ships inherit-by-default is a bug against this issue.
Ask
Widen the per-role override from "agent/model/permissions" into a proper worker environment profile, with least-privilege defaults. Candidates, in rough priority:
--strict-mcp-configwith no--mcp-configloads zero MCP servers (verified againstclaude --help), soMCPConfig{Strict: true, Configs: []}is the worker default; an explicit--worker-mcp-configopts back in.--settings '{"enabledPlugins": {...all false...}}'disables them per-session without--bare/--safe-mode(which would also kill AO's activity-detection hooks) — verified:claude --settings '{"enabledPlugins": {"context7@claude-plugins-official": false}}' plugin list→Status: ✘ disabled. AO would emit this from the worker's global plugin set, inverted.project.env; keep, and make sure per-role overrides don't leak into the stored project config (deep-copy).Why
The per-role mechanism already exists and is proven for agent/model/permissions; this widens the same
RoleOverrideto the rest of the environment — but with least-privilege defaults, so a worker with no explicit config is isolated, not endowed.Open question (rollout)
Flipping worker MCP/plugins from inherit (today) to naked-by-default is a behavior change for existing projects — a worker that quietly used a global MCP tool or plugin would run half-blind until the user configures it. The direction is settled (naked); the rollout shape is tracked in #2844 (naked for everyone + migration note / naked for new projects only / opt-in now). Implementation of this issue lands once that's decided, so it's built the right way once.
Scope