Skip to content
titah

Using Titah

Modes & custom agents

Plan, Build Manual and Build Auto — and how to write your own agent as a prompt, a tool filter, a permission override and a model.

The three built-in modes

Tab switches between them in the TUI — the same key as opencode — or -a/--agent from the CLI.

ModeIdBehaviour
PlanplanDrafts a plan only. Every attempt to change a file or run a command is refused
Build ManualbuildDoes the work, but every change asks for confirmation. The default
Build Autobuild-autoWorks 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" }
ValueBehaviour
"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:

  • tools removes a tool from the list the model can see. Tools not listed stay enabled — the list is an exception list, not an allowlist.
  • permission leaves the tool visible but refuses its use, overriding the global permission. 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.