Skip to content
titah

Capabilities

Sub-agents

Running several of your own agents inside one turn — who may be dispatched, who runs concurrently, and what `/tim` actually is.

Titah’s own model can run several of its own configured agents as sub-agents, in parallel, inside one turn — without leaving the process or spawning an external CLI.

{
  "agent": {
    "explore": {
      "mode": "subagent",
      "description": "Codebase explorer — read only",
      "permission": { "edit": "deny", "write": "deny", "bash": "deny" }
    },
    "qc-developer": {
      "mode": "all",
      "permission": { "bash": "allow" }
    }
  }
}

mode decides who may be dispatched

mode decides whether the coordinator’s own model may hand an agent work:

ValueMeaning
"primary"The default. Never offered to the task tool; refused outright if something tries to dispatch it anyway
"subagent"Dispatchable, and still selectable as your own top-level agent
"all"Both

The default is "primary", not "all", deliberately. Flipping that default would have quietly handed every agent already in your config — including ones with a wide-open permission — to the model the moment this feature shipped, without you writing a single line asking for it.

mode does not otherwise change an agent. A "subagent" agent is still selectable with Tab or --agent, exactly as before sub-agents existed.

delegate routes a sub-agent through an external CLI

delegate sends a sub-agent’s work through an external CLI listed in externalAgent instead of Titah’s own loop — the same engine @claude uses, reached from task / /tim instead of a mention.

It is mutually exclusive with model: an agent has one engine, and a config setting both is rejected when it loads.

!!! warning “permission does nothing to a delegating agent” The external CLI runs under its own policy and never asks Titah for anything, so Titah’s edit / write / bash settings are not applied to it, and no permission dialog appears on its behalf. Because Titah cannot know what that CLI will touch, a delegating agent is always treated as a writer, whatever its permission block says. To restrain it, restrain it in that CLI’s own configuration.

Readers run together, writers take turns

Whether a sub-agent may run at the same time as others is read from its permission, never from tools.

An agent counts as a reader only when edit, write and bash are all explicitly "deny", and it does not delegate. Everything else is a writer — including an agent whose config never mentions permission at all, and every agent with delegate set.

bash counts as writing on purpose: an allowed shell can run sed -i just as well as the edit tool can, and treating it as read-only would open exactly the hole this rule exists to close.

  • Readers run with no limit on how many are concurrent.
  • Writers are serialised on a queue keyed by working directory, so two sub-agents editing the same project never race over the same shadow-git snapshot. One waits its turn while the panel shows it “waiting for a turn”.

This is why /tim tells the model not to order writers itself: Titah already does.

task and /tim

The coordinator hands work to a sub-agent with the task tool; several calls in the same step run concurrently.

The prompt asks the model for one call per agent, but nothing enforces it: the same agent named twice in one step is two sub-agents, and if it can write, the second waits for the first on the write lock.

/tim <task> is not a separate orchestration engine. It is an ordinary turn with one extra system-prompt section, listing the current dispatchable roster and instructing the model to split the work and dispatch it with task, doing any leftover work itself rather than inventing an agent for it. The model does the coordinating; Titah only tells it who is on the roster.

Running /tim with no dispatchable agent configured does not hang or guess — it answers directly with what to add: an agent block with "mode": "subagent" (or "all") in titah.json.

A sub-agent never gets the task tool itself, no matter its own mode. Dispatch depth is capped at exactly one level, so nothing can spawn a tree of sub-agents that burns through your provider quota with no way to stop it.

A sub-agent inherits the coordinator’s ceiling

A dispatched sub-agent’s permission is computed from its parent, not from the global config, and then clamped by the stricter of parent and child on every axis. One sentence: a parent can never hand out more than it holds.

That is what makes Plan mode a real boundary. Under a plan coordinator whose write and edit are "deny", a sub-agent that declares nothing inherits the deny, and a sub-agent that declares "write": "allow" is clamped back down to deny. Earlier this leaked: task carried no check of its own and the child fell through to its own config, so /tim in Plan mode could run a qc-developer with "bash": "allow" and get a shell command executed with no confirmation. That hole is closed.

It fixed the mirror-image annoyance at the same time. build-auto opens every axis so it never interrupts, but sub-agents used to fall back to the user’s global block — usually ask everywhere — so the mode kept its “no confirmations” promise exactly until it delegated, and then asked about ls. Inheriting from the parent removes the dialog without widening anything: the child still cannot do what the parent could not have done itself, directly.

rules and allowlist are merged rather than intersected. They work at a different layer — the axis decides the class of action, the rule decides the argument — and a child’s allow rule cannot open an axis that is already deny, because the axis is checked first.

One rule differs from top level, deliberately, and in both directions:

“Always” from a sub-agent lasts the turn, not the session. Answering a to a dialog raised by a sub-agent adds the pattern to an allowlist discarded when the coordinator’s turn ends — unlike the same answer at top level, which lasts the whole session. And for that turn it covers every sub-agent, not only the one that asked.

Watching them work

Ctrl+X then ↓ toggles a panel listing every sub-agent of the current turn, with each row’s status: queued, running, done, failed, stopped.

Rows that have started show a running clock. A queued row shows none, on purpose — a clock on something that has not begun reads exactly like something stuck.

The list is cleared when you send your next message, so it is always about the work in front of you. It lives in the TUI’s memory, not in the session: quit and come back, or switch away with /session and back, and the panel starts empty even though the sub-agents’ own child sessions are still on disk.

While the panel is open it owns the keyboard — ↑ / ↓ select, Esc closes, and everything else is swallowed rather than typed into your prompt behind it. Ctrl+X chords, Ctrl+C and Ctrl+D still reach through.

Reading a sub-agent’s transcript

A sub-agent runs in its own child session, so what it did is a real transcript rather than a summary. Enter on a panel row — or a click — opens it as a full page, streaming along live while the work is still going. Ctrl+X S opens the same page for every sub-agent this session has run, finished turns included: the panel is cleared on each new prompt, so without that list the work of an earlier turn sat complete in the database with no way in from the screen.

The page is read-only, and Esc returns to the conversation. A prompt box there would promise messages you could send into a session the coordinator is already driving.

A super agent is the exception. It runs a CLI outside Titah and only sends its final answer back, so its page says that instead of drawing an empty screen.

Child sessions stay hidden from GET /session and from /session in the TUI. Picking one there would swap the active session, and your next prompt would land inside the sub-agent’s own conversation. They are reachable on their own route — GET /session/:id/children, see HTTP API.

Cancelling one

x cancels the selected sub-agent, and asks first: the first press arms that row and says so in the panel’s title, a second x on the same row does it, and any other key — including moving the selection — disarms it. Stopping cannot be undone, so a single keystroke is not allowed to do it.

A cancelled sub-agent reports rather than fails. Its task call returns STOPPED BY USER after 48s. as an ordinary tool result, the coordinator reads it like any other result and carries on with the rest of the team, and the history line is marked ⊘, not ✓.

This holds for both engines: a sub-agent running an external CLI has that CLI killed, exactly as an internal one has its turn stopped.

Undo after a /tim turn

Read Permissions & undo before relying on it. In short: a sub-agent’s writes snapshot on its own session, so a coordinator that wrote nothing has no snapshot, and undo walks back to an earlier turn — reverting that as well, silently, in the same operation.