# Tmux Pilot Role Authoring

> Create or edit a tmux-pilot subagent role - the YAML role entry in tmux-pilot.config.yaml (models, thinking, provider, harness, lifecycle guards, worktree, widget style) plus the optional Markdown agent-definition document with YAML frontmatter that carries the role's instructions. Use when adding a new role, tuning an existing role's routing or guards, wiring a role to a non-pi harness (cmd, claude-code, codex, hermes), or writing or fixing an agent-definition .md that a spawn rejects. Triggers on tmux-pilot.config.yaml, subagent-roles, agent-definition, lifecycle-guards, or role authoring tasks.

- Skill: `r3b1s/tmux-pilot-role-authoring` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add r3b1s/tmux-pilot-role-authoring`
- Raw SKILL.md: https://api.skillmd.com/api/skills/r3b1s/tmux-pilot-role-authoring/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- License: MIT
- Author: r3b1s (https://skillmd.com/u/r3b1s)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/r3b1s/tmux-pilot-role-authoring

---


# tmux-pilot Role Authoring

A role is two artifacts:

1. A **YAML entry** under `subagent-roles.<name>` in the entry config file —
   `$PI_CODING_AGENT_DIR/tmux-pilot.config.yaml`, falling back to
   `~/.pi/agent/tmux-pilot.config.yaml`.
2. An optional **agent-definition Markdown document** (YAML frontmatter +
   instruction body) referenced by the role's `agent-definition` key.

Config is read at **spawn time** — edits apply to the next spawn, never to a
running agent (widget-style is captured on the record at spawn; startup-scoped
widget settings apply next session). Role names are matched
**case-insensitively** at spawn.

The canonical, exhaustively commented schema reference is
`tmux-pilot.config.example.yaml` in the tmux-pilot repo. Templates:
[resources/role-config-template.yaml](resources/role-config-template.yaml) and
[resources/agent-definition-template.md](resources/agent-definition-template.md).

## Naming and schema rules (hard cutover — no legacy keys)

Every tmux-pilot-owned key is **lower-kebab-case**; controlled enum values too
(`overlay`, `above-editor`). External identifiers keep their spelling (model
IDs, provider IDs like `opencode-go`, harness IDs, role names, tmux keys like
`C-c`). Legacy camelCase keys (`agentDef`, `guards`, `new-worktree`,
`config-dir`, `maxTurns`, `stopKeyCombo`, …) are **not recognized** — they
warn and are ignored. Unknown keys at known scopes warn; role names, model
IDs, harness IDs stay open and never warn.

## Part 1 — the YAML role entry

### Inheritance model (memorize this first)

Every defaultable field resolves `model-level > role-level >
subagent-global-defaults > hardcoded`:

- `thinking`, `provider`, `harness` — all three layers.
- `agent-definition`, `models` — role > global only (no per-model form).
- `models` is a **full replacement**: a role with its own list loses all
  global entries. No `models:` key → inherit the global list.
- `lifecycle-guards` is a **shallow per-field merge** (role field > global
  field > hardcoded), EXCEPT `turn-thresholds`, which is **replaced
  wholesale** — defining any role thresholds discards the global list.
- `worktree-enabled` — role > global > `false`. Not allowed per-model.

### Role keys

```yaml
subagent-roles:
  reviewer:
    thinking: high              # off|minimal|low|medium|high|xhigh|max
    provider: opencode-go       # pi provider id (default for bare-string models)
    harness: pi                 # pi (default) | cmd | claude-code | codex | hermes
    agent-definition: reviewer.md
    worktree-enabled: false
    models:                     # ordered fallback — first available wins
      - deepseek-v4-flash                  # bare string: inherits role/global
      - claude-sonnet-4-20250514:          # object: per-model overrides
          provider: anthropic
          thinking: high
      - mimo-v2.5:
          harness: cmd                     # per-model harness override
    lifecycle-guards:
      max-turns: 25             # 0 = unlimited (hardcoded default)
      grace-turns: 5            # extra turns past max before forced stop
      session-file-timeout-ms: 60000
      max-runtime-ms: 0         # 0 = disabled (default) — recommended
      staleness-timeout-ms: 0   # 0 = disabled (default) — recommended
      turn-thresholds:          # REPLACES the global list wholesale
        - at-turns-remaining: 5
          action:
            type: steer
            prompt: >
              You have {{turnsRemaining}} turns remaining. Wrap up.
    # cleanup-linger-ms: 30000              # keep finished window visible
    # harness-config-directory: { path: ~/curated/reviewer, mode: overlay }
    # widget-style: { role-color: "#c98f5f", portrait: { glyph: "⚖" } }
    # webui-theme: role-pack:inquisitor     # web-console presentation only
    # sandbox: { enabled: true, filesystem: { write: ["."] } }
```

Notes that prevent real mistakes:

- `session-poll-interval-ms` is **global-only** (under
  `subagent-global-defaults.lifecycle-guards`) — a role-level value is ignored.
- Leave `max-runtime-ms` and `staleness-timeout-ms` at 0 unless you
  specifically want wedge detection; completion is driven by the per-harness
  lifecycle-end sentinel, and staleness kills agents mid-long-tool-call.
- Threshold prompts get `{{turnsRemaining}}` and `{{agentId}}` template
  variables; the only action `type` is `steer`.
- `sandbox` merges per-leaf over the global `sandbox` block; arrays replace
  wholesale. Linux x64 only, opt-in.

### Harness-specific constraints

| Harness | Constraint to respect in the role entry |
| --- | --- |
| `pi` | Default; full routing support. |
| `cmd` | Full routing; config dir redirect is `HOME`-based. |
| `claude-code` | **Anthropic model ids only** (`haiku`/`sonnet`/`opus`/…); `provider` is ignored — never write one expecting a gateway. |
| `codex` | **OpenAI-only**; `provider` ignored with a warning; `thinking` maps to `model_reasoning_effort` (off→minimal, xhigh→high); account-global ChatGPT rate limits — `max-concurrent-agents` is the lever. |
| `hermes` | model/provider map verbatim to `-m`/`--provider`; **no `harness-config-directory` support** (warned-and-ignored); a user-set hermes `max_concurrent_sessions` can fail parallel spawns. |

Harness-scoped defaults (`stop-key-combo`, `cleanup-linger-ms`,
`harness-config-directory`) live under `harness-global-defaults.<id>`, not in
the role; the role-level `harness-config-directory` / `cleanup-linger-ms` win
over them when both are set.

### Imports

The entry file may `imports:` other YAML files (paths relative to the
declaring file; globs sorted; maps deep-merge, lists replace, later wins,
entry file wins last). Duplicate role names warn, later declaration wins.
**Relative `agent-definition` paths always resolve from the ENTRY file's
directory**, never from the imported file that declared them.

## Part 2 — the agent-definition Markdown document

### What it is

Runtime role-instruction input. The **body** is delivered to each new
matching subagent ahead of its task; frontmatter contributes **only
`description`** (surfaced in role listings and the web console — every other
frontmatter key is ignored). All current harnesses use prompt-context
delivery: the composed spawn prompt is

```
[worktree context, if any]

[parent conversation context, if inherited]

# Role Definition
Role: <role>
Source: role|global

--- BEGIN ROLE INSTRUCTIONS ---
<your body, verbatim>
--- END ROLE INSTRUCTIONS ---

# Your Task
<the spawn prompt>
```

Write the body knowing it lands between those markers: address the agent in
second person, define scope/outputs/constraints, and don't restate the task —
the task follows it.

### Shape

```markdown
---
description: One-line summary shown in role listings and the web console.
---

You are the <role> agent. <instructions…>
```

Frontmatter is optional — a plain body with no `---` block is valid. If the
file DOES start with `---`, the frontmatter must be well-formed:

- Opening `---` on its own line, closing `---` on its own line.
- Content between them must parse as a **YAML mapping** (not a list/scalar).

### Validation (spawn-rejecting — fail closed)

A configured definition that is **missing, unreadable, not a regular file,
not valid UTF-8, larger than 64 KiB, has malformed frontmatter, or has an
empty body** rejects the spawn *before* any tmux window is created, with a
`RoleDefinitionError` naming role, source, reference, and resolved path. There
is no fall-through to "spawn without instructions." The document is captured
immutably (content + sha256) at spawn — editing the file never affects a
running agent.

When debugging a rejected spawn, check in order: file exists at the resolved
path → regular file → ≤ 64 KiB → UTF-8 → frontmatter delimiters/YAML-mapping →
non-empty body after frontmatter.

### Path resolution

`agent-definition` values resolve as:

1. `~/…` or `~` — home-expanded, used as-is.
2. Absolute path — used as-is (ignores `agent-definition-directory`).
3. Relative path — resolved against
   `subagent-global-defaults.agent-definition-directory`, which itself may be
   absolute, `~`-prefixed, or relative (relative resolves against the entry
   config file's directory / `PI_CODING_AGENT_DIR`). Unset → the entry
   config directory itself.

A role without `agent-definition` inherits the global
`subagent-global-defaults.agent-definition-directory` + global
`agent-definition` if one is set; multiple roles may share one document.

## Workflow

1. **Read the current entry config** (`~/.pi/agent/tmux-pilot.config.yaml` or
   `$PI_CODING_AGENT_DIR/tmux-pilot.config.yaml`) and any `imports:` it pulls
   in before editing — respect existing global defaults instead of restating
   them per-role, and check the role name isn't already declared in an import.
2. **Write/edit the role entry** with only the fields that differ from
   inherited defaults. Kebab-case; no legacy keys.
3. **If the role needs standing instructions**, create the `.md` under the
   `agent-definition-directory` and reference it by bare filename; otherwise
   omit `agent-definition` entirely.
4. **Sanity-check**: valid YAML; model entries either bare strings or
   single-key maps; thinking values from the enum; harness constraints from
   the table above; definition file passes the validation checklist.
5. Remind the user: changes apply on the **next spawn** (no restart needed for
   routing/guards; widget placement/toggle keys need a new session). The
   `/tmux-pilot:webui` console edits the same entry file with validation, and
   is the recommended place to repair a definition a spawn rejected.

