DefangLabs / DefangLabs/defang
agent: true read-only mode that does not register mutable tools
@defangdevs is already working on this.
Since Sep 10, 2026.
- Dominant language
- Go
- Stars
- 166
- Forks
- 24
- Avg merge
- 10h 8m
- Merged PRs (30d)
- 33
Description
Describe the issue
Follow-up from #2269 / #2270, requested by @lionello: "we need a read-only mode, agent without mutable tools."
#2270 makes each write-capable agent tool handler (deploy, destroy, set_config, remove_config) refuse up front when elicitations.Controller.IsSupported() is false — i.e. when there's no user to confirm with. The tools are still registered and offered to the model; they just no-op-and-report an error at call time. That fixes the specific incident (unattended CI debugger silently mutating production) but isn't the same thing as a read-only mode.
This issue is about the broader ask: a mode where mutable tools are never registered/offered to the model at all, so the model can't even attempt a write — closer to the "gate registration in agent.go" variant that was considered and set aside for #2270 in favor of the narrower per-handler-refusal fix (see #2269's plan comment).
Where this would live
src/pkg/agent/agent.go's New() builds the tool list unconditionally:
defangTools := tools.CollectDefangTools(ec, tools.StackConfig{...})
toolManager.RegisterTools(defangTools...)
tools.CollectDefangTools (src/pkg/agent/tools/tools.go) returns one flat []ai.Tool including deploy, destroy, set_config, remove_config alongside read-only tools (services, logs, estimate, list_configs, current_stack, ...).
Proposed shape
- Add a
ReadOnly bool(or similar) toStackConfig, or a newagent.Option(e.g.WithReadOnly()), thatCollectDefangToolsuses to skip the four write-capable tool constructors entirely. - Decide how it's exposed: a CLI flag on the debugger invocation, an MCP integration setting, or both — the sibling hook session that flagged this noted it could be reusable beyond the CI-debugger path (e.g. for MCP).
WithNonInteractive()(used only by the CI debugger) and a futureWithReadOnly()are orthogonal: the CI debugger today wants non-interactive and now has per-handler refusal (#2270); aread-onlymode might be requested independently by an interactive session too (e.g. someone deliberately wants read-only chat access).- #2270's per-handler refusal stays as defense in depth regardless — this issue doesn't replace it, it adds a second layer that also stops the model from seeing the tools in the first place.
Not decided yet
- Exact trigger/flag naming and where it's threaded through (CLI, MCP config, or both).
- Whether this fully supersedes #2270's refusal-at-call-time for the CI-debugger path once the CI debugger opts into read-only mode, or whether both stay for defense in depth (leaning toward: keep both).
Referenced from #2269.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.