Mark a tool read-only
This guide marks a tool that only reads, so dry runs run it and approval gates let it through.
Before you start
Section titled “Before you start”- A tool, in the quickstarts' pack,
my-pack, withkindgi devrunning and Kindgi 0.1's CLI.
Mark it read-only
Section titled “Mark it read-only”A tool declares whether it changes anything outside Kindgi. One that only reads, such as a lookup, a search or a calculation, says so. Ask your coding agent: "My order lookup tool only reads. Mark it read-only." Kindgi's skills in your pack tell it how (How it knows Kindgi). For reference, the tool's definition then looks like this:
// in tools/lookup-order/index.tsconst defined = defineTool({ id: 'my-pack.lookup-order' as ToolId, // … effects: [], mutating: false, // …});# in tools/lookup_order.py@tool(id="my-pack.lookup-order", mutating=False)def lookup_order(ref: OrderRef, ctx: ToolContext) -> Order: ...// in src/main/java/mypack/tools/LookupOrder.java public static final Tool<Input, Output> TOOL = Tool.define("my-pack.lookup-order") // … .mutating(false) // …// in src/main/scala/mypack/tools/LookupOrder.scala val tool: Tool[Input, Output] = Tool[Input, Output]("my-pack.lookup-order") // … .readOnly // …Leaving mutating out is the same as mutating: true. That's the safe
default: a tool counts as one that changes things until it says otherwise.
It decides two things: whether the tool runs in a dry run, and whether an
approval gate asks before it.
Dry runs
Section titled “Dry runs”--dry-run runs a flow without changing anything. A tool step runs only if
the tool is read-only. --flow is the flow, and --input its input:
pnpm exec kindgi runs start --flow=my-pack.check-order --input='{"orderId":"ord_1001"}' --dry-runuv run kindgi runs start --flow=my-pack.check-order --input='{"orderId":"ord_1001"}' --dry-run./kindgiw runs start --flow=my-pack.check-order --input='{"orderId":"ord_1001"}' --dry-run./kindgiw runs start --flow=my-pack.check-order --input='{"orderId":"ord_1001"}' --dry-run "status": "completed", "dryRun": true, … "output": { "status": "shipped", "orderId": "ord_1001", "totalCents": 4200 },The first tool that isn't stops the run, before the tool is called:
pnpm exec kindgi runs start --flow=my-pack.note-order --input='{"orderId":"ord_1001","text":"Customer called"}' --dry-runuv run kindgi runs start --flow=my-pack.note-order --input='{"orderId":"ord_1001","text":"Customer called"}' --dry-run./kindgiw runs start --flow=my-pack.note-order --input='{"orderId":"ord_1001","text":"Customer called"}' --dry-run./kindgiw runs start --flow=my-pack.note-order --input='{"orderId":"ord_1001","text":"Customer called"}' --dry-run "status": "failed", "dryRun": true, "failureMessage": "dry-run-effectful-tool: tool \"my-pack.add-order-note\" is not declared read-only (mutating: false), so it does not run in a dry run",An agent step in a dry run skips its model call, so the agent calls no
tools. The sample's echo-flow greets with a read-only tool, then hands the
greeting to its agent:
pnpm exec kindgi runs start --flow=my-pack.echo-flow --input='{"name":"Ada"}' --dry-runuv run kindgi runs start --flow=my-pack.echo-flow --input='{"name":"Ada"}' --dry-run./kindgiw runs start --flow=my-pack.echo-flow --input='{"name":"Ada"}' --dry-run./kindgiw runs start --flow=my-pack.echo-flow --input='{"name":"Ada"}' --dry-run "status": "completed", "dryRun": true, … "output": { "reply": "[dry-run: model call skipped]", "greeting": "Hello, Ada!" },Use a dry run to check a flow's wiring before it touches real data.
Effects
Section titled “Effects”effects lists what a tool touches, for review and policy:
{ kind: 'writes', resource: 'external:orders' }. The kinds are reads,
writes, deletes, network, spawns-run, emits-event,
external-side-effect and sensitive-data-egress.
A tool with a writes, deletes, spawns-run, emits-event or
external-side-effect effect doesn't run in a dry run even when it says
mutating: false. Keep the two consistent: a tool that only reads declares
mutating: false and, at most, reads effects.
Approval gates
Section titled “Approval gates”An agent can make a person approve tool calls, as Ask before a tool runs shows. With its gates on and no rule for a tool, a read-only tool runs without asking. Any other tool waits for approval on its first use.
Getting it right
Section titled “Getting it right”- Declare
mutating: falseon every tool that only reads. Otherwise a dry run stops at it, and an approval gate asks before it. - Never declare it on a tool that writes, sends or deletes. A dry run would then run it for real.
- A tool from an MCP server always counts as one that changes things: it doesn't run in a dry run.