DefangLabs / DefangLabs/defang

agent: true read-only mode that does not register mutable tools

Open
#2,271 0 comments 0 reactions 1 assignee View on GitHub

@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) to StackConfig, or a new agent.Option (e.g. WithReadOnly()), that CollectDefangTools uses 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 future WithReadOnly() are orthogonal: the CI debugger today wants non-interactive and now has per-handler refusal (#2270); a read-only mode 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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.