Skip to content

Mark a tool read-only

View as Markdown

This guide marks a tool that only reads, so dry runs run it and approval gates let it through.

  • A tool, in the quickstarts' pack, my-pack, with kindgi dev running and Kindgi 0.1's CLI.

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.ts
const defined = defineTool({
id: 'my-pack.lookup-order' as ToolId,
// …
effects: [],
mutating: false,
// …
});
Excerpt

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-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:

Terminal window
pnpm exec kindgi 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
},
Excerpt

The first tool that isn't stops the run, before the tool is called:

Terminal window
pnpm exec kindgi 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",
Excerpt

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:

Terminal window
pnpm exec kindgi 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!"
},
Excerpt

Use a dry run to check a flow's wiring before it touches real data.

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.

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.

  • Declare mutating: false on 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.