The three built-in modes
Tab switches between them in the TUI — the same key as opencode — or
-a/--agent from the CLI.
| Mode | Id | Behaviour |
|---|---|---|
| Plan | plan | Drafts a plan only. Every attempt to change a file or run a command is refused |
| Build Manual | build | Does the work, but every change asks for confirmation. The default |
| Build Auto | build-auto | Works to completion on its own, no confirmations |
titah -a plan # open the TUI straight into planning
titah run -a build-auto "fix the failing tests"
Change the default with "defaultAgent": "plan" in the config.
Why Plan mode refuses instead of hiding the tools
Plan mode locks changes through permissions, not by removing the tools. The difference is visible:
⊘ create result.txt (1 lines) — Denied by agent "plan": write = "deny".
Refused — plan mode cannot change files. Switch to Build mode
(Tab in the TUI, or --agent build) to use write.
When the tools were removed entirely, the model instead stopped without a single word. The safety is identical — permission is checked before execution, so nothing ever runs — but the refusal gives the model a reason it can pass on to you.
Build Auto still takes snapshots, so titah undo reverts the whole turn even
though you were not watching it happen.
Build asks before decisions that shape everything after
build and build-auto stop and ask when a choice determines the shape of
everything built on top of it — which datastore, how auth works, REST or GraphQL
— and they ask with a recommendation and the reason, drawn from the code
they just read rather than from a template.
The bar is deliberately high. Measured: 3/3 on a datastore choice, 0/3 on a trivial one. An agent that asks about everything is an agent nobody lets run; one that asks about nothing picks your architecture for you.
The question tool is what does the asking, and with no client connected it
does not hang — see Tools.
When work is handed to sub-agents
delegation decides when the coordinator splits work up:
{ "delegation": "ask" }
| Value | Behaviour |
|---|---|
"ask" | Default. After the plan is written, Titah judges whether the work is worth splitting; if so, the model asks you |
"auto" | The model decides on its own, silently |
"always" | Matching work is always handed over, without asking |
"never" | Off. The roster is not even sent to the model, so no tokens are spent on a list nobody uses |
It exists because the three fixes before it were all persuasion — a complete
tool inventory, a prompt that did not say otherwise, criteria the model could
judge. Persuasion shifts the odds; it never gives a guarantee, and two models can
decide differently on the same prompt. Measured: delegation happened 1 turn in 5
before, 2 in 2 with "always" after.
Custom agents
The three modes above are ordinary agents. You can override them by id, or add your own. An agent is a prompt + tool filter + permission override + model override behind a name.
{
"defaultAgent": "explore",
"agent": {
"explore": {
"description": "Codebase explorer — read only",
"prompt": "Always verify with tools, never guess. Answer very briefly.",
"tools": { "write": false, "edit": false, "bash": false },
"steps": 5
},
"qc": {
"description": "Run tests and report",
"model": "9router/gapis",
"permission": { "bash": "allow", "edit": "ask" },
"skills": ["team:project-analyzer"]
}
}
}
tools versus permission
Two ways to restrict an agent, both applied before anything runs:
toolsremoves a tool from the list the model can see. Tools not listed stay enabled — the list is an exception list, not an allowlist.permissionleaves the tool visible but refuses its use, overriding the globalpermission. Fields not mentioned inherit the global value.
Use permission when you want the model to know why it was refused; use tools
when you do not want it thinking about the tool at all.
steps
steps caps how many tool-calling iterations one turn may take for this agent —
five for a scout, sixty for a refactor.
Unset, the cap is 40. Declaring limits.turnTokens lifts it to 200,
because at that point steps have stopped being the thing that bounds the turn —
tokens are. See Cost & limits.
When the cap is reached, the final iteration runs with no tools at all, so the model has to report what it found rather than stopping mid-air, and the turn says it stopped at a limit — to you and to the model. Measured, 13.2% of real turns were hitting the old wall silently.
effort
effort (low | medium | high) sets how much closing analysis an answer
ends with — not how hard the model thinks. Unset means the model decides.
Ctrl+R cycles it live: default → low → medium → high → default. <leader>r
does the same, so the action appears in the leader menu and the command palette.
Every answer ends with a conclusion — what changed, what is verified, what is still open — skipped where there is nothing to conclude.
skills
skills loads those skills into this agent’s system prompt in full, and
takes fully-qualified ids (namespace:name), exactly like skills.always.
A bare name such as "project-analyzer" never resolves. Run /skills or
titah doctor to see the ids you have and which configured ids were not found.
See Skills.
model
Overrides the model for this agent. It is mutually exclusive with
delegate — an agent has one engine, and a config setting both is rejected
when it loads. See Sub-agents.
mode
mode decides whether the coordinator’s own model may hand this agent work
through the task tool: "primary" (the default), "subagent", or "all".
Covered in Sub-agents.
A worked example
{
"agent": {
"scout": {
"description": "Answer questions about the codebase, read only",
"mode": "subagent",
"prompt": "Answer in at most five sentences. Cite file:line for every claim.",
"permission": { "edit": "deny", "write": "deny", "bash": "deny" },
"steps": 8
},
"fixer": {
"description": "Apply a specific fix and run the checker",
"mode": "all",
"model": "anthropic/claude-sonnet-5",
"permission": { "edit": "allow", "write": "allow", "bash": "ask" },
"steps": 40
}
}
}
scout counts as a reader — edit, write and bash are all explicitly
"deny" — so any number of them run concurrently. fixer is a writer and is
serialised per working directory. That rule, and why bash counts as writing,
is in Sub-agents.