| name | specialists-creator |
| description | Use this skill when creating or fixing a specialist definition. It guides the agent through writing a valid `.specialist.json`, choosing supported models, validating against the schema, and avoiding common specialist authoring mistakes.
|
| version | 1.4 |
| synced_at | fc9168e2 |
| disable-model-invocation | true |
Specialist Author Guide
Source of truth: src/specialist/schema.ts | Runtime: src/specialist/runner.ts
Canonical References
When a custom specialist needs a standard rule or skill, reference the canonical asset by name instead of copying its file into the repo. Runtime/package fallback resolves canonical mandatory rules and skills when no project-local override exists.
Example:
{
"mandatory_rules": { "template_sets": ["git-workflow-safe"] },
"skills": { "paths": ["releasing"] }
}
Only create project-local copies when intentionally changing canonical behavior. After setting references, run sp config show <name> --resolved to verify the resolved runtime surface.
ACTION REQUIRED BEFORE ANYTHING ELSE
Run these commands right now, before reading further, before writing any JSON, before doing anything else:
pi --list-models
Read the output. Pick one primary model and one fallback from different providers. Then ping both:
pi --model <chosen-primary> --print "ping" # must print: pong
pi --model <chosen-fallback> --print "ping" # must print: pong
If a ping fails, pick the next best model in that tier and ping again. Do not proceed until both return "pong".
Model tiers:
- Heavy (deep reasoning, multi-phase): Opus / Pro / GLM-5
- Standard (authoring, review, codegen): Sonnet / Flash-Pro
- Light (fast context, reports, tests): Haiku / Flash
Rules:
- Always pick the highest version in a family (
claude-sonnet-4-6 not 4-5, gemini-3.1-pro-preview not gemini-2.5-pro)
model and fallback_model must be different providers
- If a specialist needs a longer fallback chain, keep first fallback in
fallback_model and let runtime supply any extra retry tier.
- Never write a model string you have not pinged in this session
Model Setup (for a new specialist OR "setup my specialists models")
Quick Reference: Specialists CLI
specialists list # all specialists + current model
specialists models # all pi models, flagged with thinking/images, shows current assignments
specialists edit <name> --model <value> # change primary model
specialists edit <name> --fallback-model <v> # change fallback model
specialists edit <name> --model <v> --dry-run # preview without writing
specialists edit <name> --permission HIGH # change permission level
specialists status # system health
specialists doctor # prereq + hook diagnostics
Scenario: "Setup my specialists models"
When a user asks to set up or re-balance specialist models, run this workflow:
Step 1 — Inventory
specialists list # shows each specialist + its current model
specialists models # shows all available models on pi, with current assignments marked ←
Read both outputs carefully:
specialists list → what specialists exist and what they currently use
specialists models → what models are available, and which specialists already use each one (the ← markers show assignments)
Step 2 — Classify each specialist by tier
| Tier | Specialists (typical) | Recommended model class |
|---|
| Heavy — deep reasoning, multi-phase, architecture | overthinker, feature-design, bug-hunt, planner, parallel-review | Opus / Pro / GLM-5 |
| Standard — code generation, review, authoring, docs | codebase-explorer, specialist-author, sync-docs, xt-merge | Sonnet / Flash-Pro |
| Light — fast context, reporting, test runs | init-session, report-generator, test-runner | Haiku / Flash |
Adjust tiers based on what the user actually has installed. Custom specialists: read their description and permission_required to infer tier.
Step 3 — Select models with provider diversity
Rules:
- Pick the highest version in each family —
glm-5 not glm-4.7, claude-sonnet-4-6 not 4-5, gemini-3.1-pro-preview not gemini-2.5-pro
model and fallback_model must be different providers — never stack two anthropic models
- Spread providers across tiers — don't assign all specialists to anthropic; distribute across anthropic / google-gemini-cli / zai / openai-codex where available
- Match thinking capability to tier — heavy specialists benefit from
thinking: yes models
Example distribution (based on current specialists models output):
| Tier | model | fallback_model |
|---|
| Heavy | anthropic/claude-opus-4-6 | google-gemini-cli/gemini-3.1-pro-preview |
| Standard | anthropic/claude-sonnet-4-6 | google-gemini-cli/gemini-3-flash-preview |
| Light | anthropic/claude-haiku-4-5 | zai/glm-5-turbo |
If anthropic is not available, use zai/glm-5 (heavy), google-gemini-cli/gemini-3.1-pro-preview (standard), google-gemini-cli/gemini-3-flash-preview (light).
Step 4 — ⛔ Ping each chosen model before assigning
# REQUIRED — do not skip, do not assume a model works without pinging
pi --model <provider>/<primary-model-id> --print "ping" # must return "pong"
pi --model <provider>/<fallback-model-id> --print "ping" # must return "pong"
Ping both primary and fallback. If ping fails → pick next best in that tier and ping again. Do not assign a model that did not respond.
Step 5 — Apply with specialists edit
# Example: upgrade heavy-tier specialists
specialists edit overthinker --model anthropic/claude-opus-4-6 --fallback-model google-gemini-cli/gemini-3.1-pro-preview
specialists edit feature-design --model anthropic/claude-opus-4-6 --fallback-model google-gemini-cli/gemini-3.1-pro-preview
specialists edit bug-hunt --model anthropic/claude-opus-4-6 --fallback-model google-gemini-cli/gemini-3.1-pro-preview
# Standard tier
specialists edit codebase-explorer --model anthropic/claude-sonnet-4-6 --fallback-model google-gemini-cli/gemini-3-flash-preview
specialists edit sync-docs --model anthropic/claude-sonnet-4-6 --fallback-model google-gemini-cli/gemini-3-flash-preview
# Light tier
specialists edit init-session --model anthropic/claude-haiku-4-5 --fallback-model zai/glm-5-turbo
specialists edit report-generator --model anthropic/claude-haiku-4-5 --fallback-model zai/glm-5-turbo
Use --dry-run first to preview any change before writing.
Step 6 — Verify
specialists list # confirm all models updated correctly
specialists models # confirm assignments look balanced
For a new specialist (single model selection)
See ⛔ MANDATORY FIRST STEP at the top of this skill.
Use pi --list-models (not specialists models) to discover models, ping both before mutating config.
# 1. pi --list-models — see exactly what's available on pi right now
# 2. Pick tier + pick highest version in family
# 3. pi --model <primary> --print "ping" — must return "pong"
# 4. pi --model <fallback> --print "ping" — must return "pong"
# 5. Run scaffold-specialist.ts first (pre-script already wired in specialists-creator)
# 6. Use sp edit for field-by-field mutations
Rule: Never hardcode a model without pinging it. If ping fails, try the next best in that tier.
Canonical references
Reference any canonical skill or rule by name; runtime finds it.
Quick Start: Scaffold + sp edit
# 1. Create/normalize the specialist JSON with all schema sections present
node config/skills/specialists-creator/scripts/scaffold-specialist.ts config/specialists/my-specialist.specialist.json
# 2. Apply a preset for common model/thinking defaults (optional but preferred)
sp edit my-specialist --preset medium
# 3. Set individual fields via dot.path (primary mutation workflow)
sp edit my-specialist specialist.metadata.name my-specialist
sp edit my-specialist specialist.metadata.version 1.0.0
sp edit my-specialist specialist.execution.model anthropic/claude-sonnet-4-6
sp edit my-specialist specialist.execution.fallback_model google-gemini-cli/gemini-3.1-pro-preview
sp edit my-specialist specialist.execution.permission_required READ_ONLY
sp edit my-specialist specialist.execution.extensions.gitnexus false
# 4. Use --file only for multiline prompt fields
sp edit my-specialist specialist.prompt.system --file .tmp/system.prompt.txt
sp edit my-specialist specialist.prompt.task_template --file .tmp/task-template.prompt.txt
# 5. Verify materialized JSON
sp view my-specialist
# 6. Validate schema
bun config/skills/specialists-creator/scripts/validate-specialist.ts config/specialists/my-specialist.specialist.json
Schema Reference
specialist.metadata (required)
| Field | Type | Required | Notes |
|---|
name | string | yes | kebab-case: [a-z][a-z0-9-]* |
version | string | yes | semver: 1.0.0 |
description | string | yes | Routing summary surfaced by specialists list; see Description writing below |
category | string | yes | Free text (e.g. workflow, analysis, codegen) |
author | string | no | Optional |
created | string | no | Optional date |
updated | string | no | Optional date, quote it: "2026-03-22" |
tags | string[] | no | Optional list |
Description writing for specialists list
specialist.metadata.description is the routing surface that orchestrators see in specialists list. Write it as an operational role definition, not marketing copy. Keep the first clause distinctive because list output may truncate.
A good description answers, in this order:
- Choose when — the task shape that should route here.
- Do not choose when — adjacent roles that should win instead.
- Distinctive capability — what this specialist does that others do not.
- Permission/risk note — READ_ONLY/LOW/MEDIUM/HIGH implication when it affects orchestration.
Pattern:
<role noun>. Use for <specific task shape>. Not for <near misses>; use <better roles>. <permission/workflow distinction>.
Examples:
Scoped implementation only. Use when requirements, files/symbols, constraints, and validation are clear. Not diagnosis, planning, review, tests, release, or research. HIGH worktree.
Debug symptoms/errors/regressions first. Use when cause is unknown or tests fail unexpectedly; traces, fixes targeted code, and verifies. HIGH keep-alive.
Avoid vague descriptions like "general purpose assistant" or "helps with code". Those cause orchestrators to overuse familiar specialists instead of routing to debugger, test-runner, researcher, sync-docs, or other sharper roles.
specialist.execution (required)
| Field | Type | Default | Notes |
|---|
model | string | — | required — ping before using |
fallback_model | string | — | first fallback only; runtime may append more tiers |
mode | enum | auto | tool | skill | auto |
timeout_ms | number | 120000 | ms |
stall_timeout_ms | number | — | kill if no event for N ms |
interactive | boolean | false | enable multi-turn keep-alive by default (also overridable globally via ~/.config/specialists/user.json) |
response_format | enum | text | text | json | markdown |
output_type | enum | custom | codegen | analysis | review | synthesis | orchestration | workflow | research | custom |
permission_required | enum | READ_ONLY | see tier table below |
thinking_level | enum | — | off | minimal | low | medium | high | xhigh |
extensions.serena | boolean | — | DEPRECATED, ignored (Serena retired); kept only so legacy specs keep validating |
|
When to use execution.interactive
- Set
interactive: true for specialists intended for multi-turn workflows (resume, iterative planning, long investigations).
- Leave it unset/
false for one-shot specialists where each run should end immediately.
- Run-level overrides still apply:
- CLI:
--keep-alive enables, --no-keep-alive disables.
- MCP
start_specialist: keep_alive enables, no_keep_alive disables.
- Effective precedence: explicit disable (
--no-keep-alive / no_keep_alive) → explicit enable (--keep-alive / keep_alive) → merged execution.interactive (package / global user.json / repo) → one-shot default.
- When the operator wants a cross-repo default, prefer
sp edit --global --set <name>.execution.interactive true|false over forking per-repo specs.
Permission tiers — controls the native pi tools the specialist gets. The full resolved tool set also includes catalog-defined GitNexus tools per tier; see docs/manifest.md for the complete picture.
| Level | Native tools (cumulative) | Use when |
|---|
READ_ONLY | read, grep, find, ls | Read-only analysis, no bash |
LOW | + bash | Inspect/run commands, no file edits |
MEDIUM | + edit | Can edit existing files |
HIGH | + write | Full access — can create new files |
After choosing a tier, verify the resolved tool list before dispatching:
sp config show <name> --resolved
Common pitfall: READ_WRITE is not a valid value — use LOW or higher.
Per-specialist permissions[<TIER>] override (rarely needed)
Most specialists use the catalog default deny baseline. Do not declare an override unless this specialist's policy genuinely diverges from its tier. When you do override, remember the specialist block replaces catalog defaults for that tier.
If divergence is real, add a top-level permissions block (sibling to execution):
{
"specialist": {
"execution": { "permission_required": "READ_ONLY" },
"permissions": {
"READ_ONLY": {
"denied_natives_when_extension": ["grep", "find", "ls"],
"denied_natives_mode": "hard"
}
}
}
}
| Field | Type | Default | Effect |
|---|
denied_natives_when_extension | string[] | [] | Native tools to deny only when a replacement extension is healthy. Catalog defaults apply first; specialist override replaces them for that tier. |
denied_natives_mode | "soft" | "hard" | "soft" | soft keeps the tool with a preference hint; hard removes it (with auto-restore if the extension degrades) |
The override block can only deny natives — it cannot add new tools beyond the catalog tier. To add tools, change the tier or update the catalog file.
Decision rule when authoring:
- Pick the lowest tier that satisfies the specialist's actual capability needs.
- Run
sp config show <name> --resolved and inspect the --tools line.
- If the tools are right, you're done — no override needed.
- If a native tool is genuinely worse than an extension equivalent for this specialist's task, declare a soft-deny first to observe behavior, then promote to hard-deny once you trust it.
See docs/manifest.md for full deny-mode semantics, extension health gating, and the canonical explorer example.
Per-specialist extension opt-out
Use execution.extensions only when this specialist must suppress default extension injection.
This flag defaults to true, so omit this block unless opt-out is required.
{
"specialist": {
"execution": {
"extensions": {
"gitnexus": false
}
}
}
}
Typical use cases:
gitnexus: false for specialists that should not receive GitNexus graph tooling
Legacy note: serena: false may still appear in older specs; it parses but is
ignored (Serena extension retired, unitAI-e67up.8).
Bare specialists
Use execution.bare: true for non-coding LLM transforms that need a fresh canvas: research synthesis, document analysis, summarization, extraction, translation, or other tasks where specialist runtime framing adds noise instead of help.
Bare mode disables all package-runner prompt injection beyond rendered prompt.system and prompt.task_template: Specialist Run Context, Output Style, GitNexus mandate, STATIC_WORKFLOW_RULES_BLOCK, memory injection, GitNexus pre-query snapshot, reviewer patch retrieval, output contract, and task-side mandatory rules / reviewer diff context.
execution.bare is orthogonal to prompt.system_prompt_mode. Set bare: true without system_prompt_mode: "replace" when you want to keep pi's coding-agent base prompt but skip specialist runtime injections.
Copy bare template from installed npm package, not repo clone:
cp "$(node -p \"require.resolve('@jaggerxtrm/specialists/package.json').replace(/package\\.json$/, '')\")config/specialists/bare.specialist.json" ".specialists/user/<your-name>.specialist.json"
Warning: bare mode bypasses mandatory_rules completely — template sets, inline rules, and global disables all skip runtime injection in bare runs.
specialist.prompt (required)
| Field | Type | Required | Notes |
|---|
task_template | string | yes | Template string with $variable substitution |
system | string | no | System prompt / agents.md content |
system_prompt_mode | enum | no | append | replace — see System Prompt Mode |
skill_inherit | string | no | Single skill folder/file injected via pi --skill (Agent Forge compat) |
output_schema | object | no | JSON schema for structured output — injected into system prompt by runner; post-run validation is warn-only |
examples | array | no | Few-shot examples |
Replace mode warning: replace removes pi's coding-agent base prompt. Teach every command, tool, convention, output format, and behavior explicitly in prompt.system; nothing implicit remains.
Output contract precedence (runner-injected): response_format → output_type → output_schema.
response_format behavior
text: no report template is injected (raw behavior)
json: specialist must return one parseable JSON object
markdown: specialist must use canonical report sections when applicable:
## Summary
## Status
## Changes
## Verification
## Risks
## Follow-ups
## Beads
- Optional:
## Architecture, ## Acceptance Criteria, ## Machine-readable block
output_type (semantic archetype)
codegen: implementation/change manifests
analysis: architecture/exploration reports
review: compliance/review verdicts
synthesis: decision summaries across multiple findings
orchestration: coordinator actions/state handoffs
workflow: procedural/operational run outputs
research: source-backed findings with confidence
custom: no built-in extension (schema still includes base contract fields in structured modes)
output_schema guidance: Add when output must be machine-readable by downstream consumers (beads notes, feed, orchestrators). The schema is injected into the system prompt and validated post-run with warn-only behavior (never hard-fail in v1).
Mandatory markdown+schema rule: if response_format: markdown and output_schema is present, the output must include ## Machine-readable block containing exactly one JSON object in a single ```json fenced block. That JSON object is canonical and must match the schema.
System Prompt Mode
prompt.system_prompt_mode controls whether prompt.system replaces or appends to pi's base prompt.
| Runner | Default when field absent | append sees | replace sees |
|---|
Package-class (sp run → runner.ts → session.ts) | append | pi coding-agent base prompt + specialist agentsMd | only specialist agentsMd |
Script-class (sp script / sp serve → script-runner.ts) | replace | only specialist prompt.system | only specialist prompt.system |
| Combination | Agent sees |
|---|
| Package × append | pi coding-agent base prompt + specialist agentsMd |
| Package × replace | only specialist agentsMd |
| Script × append | pi coding-agent base prompt + specialist prompt.system |
| Script × replace | only specialist prompt.system |
Replace mode warning: use replace only when you intend to author full prompt surface yourself. No coding-agent defaults survive; teach commands, tools, conventions, output contract, and behavior explicitly. Best fit: non-coding transforms where base prompt adds noise.
Standard schemas by specialist type (shown as the output_schema object value):
executor — change manifest:
{
"type": "object",
"properties": {
"status": { "enum": ["success", "partial", "failed"] },
"files_changed": { "type": "array", "items": { "type": "string" } },
"symbols_modified": { "type": "array", "items": { "type": "string" } },
"lint_pass": { "type": "boolean" },
"tests_pass": { "type": "boolean" },
"issues_closed": { "type": "array", "items": { "type": "string" } },
"follow_ups": { "type": "array", "items": { "type": "string" } }
}
}
explorer — analysis report:
{
"type": "object",
"properties": {
"summary": { "type": "string" },
"key_files": { "type": "array", "items": { "type": "string" } },
"architecture_notes": { "type": "string" },
"recommendations": { "type": "array", "items": { "type": "string" } }
}
}
planner — epic result:
{
"type": "object",
"properties": {
"epic_id": { "type": "string" },
"children": { "type": "array", "items": { "type": "string" } },
"test_issues": { "type": "array", "items": { "type": "string" } },
"first_task": { "type": "string" }
}
}
specialist.mandatory_rules (optional)
| Field | Type | Default | Notes |
|---|
template_sets | string[] | [] | Adds rule-set ids from config/mandatory-rules/index.json and local overlays |
disable_default_globals | boolean | false | Suppresses only inline STATIC_WORKFLOW_RULES_BLOCK (Beads Workflow Quick Rules) |
inline_rules | MandatoryRule[] | [] | Inline rules appended without file lookup |
Runtime layering:
config/mandatory-rules/index.json.required_template_sets — always loaded
config/mandatory-rules/index.json.default_template_sets — also always loaded today; current quirk, not suppressed by disable_default_globals
specialist.mandatory_rules.template_sets
specialist.mandatory_rules.inline_rules
disable_default_globals only removes the inline STATIC_WORKFLOW_RULES_BLOCK. It does not suppress index-driven sets. True user-rules-only runs need both disable_default_globals: true and a user overlay index in .specialists/user/mandatory-rules/index.json that clears required/default sets.
Canonical set ids in config/mandatory-rules/*.md:
bead-id-verbatim, changelog-conventions, changelog-keeper-scope, code-quality-defaults, core-session-boundary, diagnose-loop, executor-delivery, explorer-readonly, git-workflow-safe, gitnexus-required, overthinker-4phase, per-turn-handoff-schema, research-tool-routing, researcher-source-discipline, reviewer-verdict-format, security-review-defaults, sync-docs-scope-discipline, test-runner-execution-scope.
Minimal user-rules-only spec:
{
"specialist": {
"mandatory_rules": {
"disable_default_globals": true,
"inline_rules": [
{ "id": "user-rule-1", "level": "error", "text": "Use local policy only." },
{ "id": "user-rule-2", "level": "warn", "text": "Prefer repo overlay index.", "when": "overlay exists" }
]
}
}
}
Inline rule shape:
{ "id": "rule-id", "level": "error", "text": "Rule text", "when": "optional condition" }
specialist.skills (optional)
{
"skills": {
"paths": [
"skills/my-skill/",
"~/.agents/skills/domain/",
"skills/notes.md"
],
"scripts": [
{
"run": "./scripts/pre-check.sh",
"phase": "pre",
"inject_output": true
},
{
"run": "bd ready",
"phase": "pre",
"inject_output": true
},
{
"run": "./scripts/cleanup.sh",
"phase": "post"
}
]
}
}
run accepts either a file path (./scripts/foo.sh, ~/scripts/foo.sh) or a shell command (bd ready, git status). Pre-run validation checks that file paths exist and shell commands are on PATH. Shebang typos (e.g. pytho instead of python) are caught and reported as errors before the session starts.
specialist.capabilities (optional)
Informational declarations used by pre-run validation and future tooling (e.g. specialists doctor).
{
"capabilities": {
"required_tools": ["bash", "read", "grep", "glob"],
"external_commands": ["bd", "git", "gh"]
}
}
external_commands causes a hard failure if any binary is not found on PATH — the session will not start.
specialist.output_file (optional, top-level)
{
"output_file": ".specialists/my-specialist-result.md"
}
When set, the specialist writes its handoff block to this file on every substantive turn — foreground and --background. The content is identical to what is appended to the input bead notes and shown by sp result: a markdown heading, the verbatim specialist output, and an italic metadata footer.
- No env flag required.
output_file is honored whenever it is set (since unitAI-f58ma). It does NOT require SPECIALISTS_JOB_FILE_OUTPUT — that flag now only gates the debug file-mirrors (events.jsonl / status.json / result.txt).
- Single writer. In a supervised run (
sp run) the supervisor owns the file and the runner's own write is suppressed, so the file is never double-written. (In script/serve runs there is no supervisor; the runner writes the raw output instead of the enveloped block.)
- Format is the rendered handoff block, not bare output. A downstream specialist that consumes this file as input will see the header/footer envelope around the body.
- Append vs overwrite is controlled by
notes_mode (see below): full-trail appends each turn; final-only overwrites with just the final [FINAL · DONE] block.
- Gitignore the path if you don't want it committed — specs with
output_file always write, so add the path (e.g. .specialists/*-result.md) to .gitignore.
Relative paths resolve from the working directory.
specialist.notes_mode
Controls how per-turn handoff output is persisted to BOTH the input bead notes and output_file.
| Value | Behavior |
|---|
"full-trail" (default) | Append every substantive turn. Bead notes / output_file accumulate ### … [turn N · WAITING] blocks, ending with a final ## … [FINAL · DONE] block. Best for keep-alive specialists where the operator wants the full trail. |
"final-only" | Persist only the single canonical ## … [FINAL · DONE] block; intermediate turns are skipped and output_file is OVERWRITTEN (not appended). Best for non-coding / chained pipelines where the next specialist reads the previous specialist's bead note or output_file as its input and only wants the final result. |
Empty turns are never persisted. The handoff block is markdown-native (heading + verbatim body + one italic metadata footer); the model string is normalized (provider prefix stripped, e.g. nano-gpt/moonshotai/kimi-k2.5 renders as kimi-k2.5).
{ "notes_mode": "final-only" }
Handoff / pipeline output recipe
For a non-coding pipeline where each specialist reads the previous one's result, set output_file to a known path AND notes_mode: "final-only" so the file (and the bead note) holds exactly the final result, overwritten each run — clean for the next specialist to consume. For a human-monitored keep-alive specialist, leave notes_mode: "full-trail" (default) so the operator sees every turn accumulate. output_file needs no env flag; just gitignore its path.
specialist.validation (optional)
Drives the staleness detection shown in specialists status and specialists list.
| Field | Type | Notes |
|---|
files_to_watch | string[] | Paths to monitor. If any file's mtime is newer than metadata.updated, the specialist is marked STALE |
stale_threshold_days | number | If the specialist has been STALE for more than N days, escalates to AGED |
references | array | Accepted, currently unused |
Staleness states:
| State | Condition |
|---|
OK | No watched files changed, or no files_to_watch / updated set |
STALE | A watched file's mtime > metadata.updated |
AGED | STALE + days since updated > stale_threshold_days |
{
"specialist": {
"metadata": {
"updated": "2026-03-01"
},
"validation": {
"files_to_watch": [
"src/specialist/schema.ts",
"src/specialist/runner.ts"
],
"stale_threshold_days": 30
}
}
}
This specialist goes STALE the moment schema.ts or runner.ts is modified after March 1st, and AGED if that condition persists for more than 30 days.
specialist.beads_integration (optional)
| Value | Behavior |
|---|
auto (default) | Create tracking bead when permission_required is LOW+ |
always | Always create a tracking bead |
never | Never create a tracking bead |
Global User Override Layer (KAN-90/91)
The same per-spec field schema documented above is also exposed at a global,
cross-repo layer since 2026-06-13. Operators do not need to fork a
.specialists/user/<name>.specialist.json per repo just to swap their model or
opt out of an extension — those changes live once in
~/.config/specialists/user.json and apply everywhere.
3-layer field merge
SpecialistLoader.get() resolves fields top-down:
- Package canonical —
config/specialists/<name>.specialist.json shipped
in @jaggerxtrm/specialists. Most fields are concrete defaults; model and
fallback_model ship as null since KAN-90 part 2.
~/.config/specialists/user.json — operator's global override. Wins
over package canonical for the allowlisted user-environment fields.
.specialists/user/<name>.specialist.json — per-repo override. Wins
over global. Can change any field, including ones blocked at the global
layer.
The pre-KAN-90 .specialists/default/<name>.specialist.json mirror is no
longer walked (commit 31a6421c).
CLI surface
| Command | Effect |
|---|
sp init --global | Bootstraps ~/.config/specialists/user.json with null placeholders for every spec; writes _doc sentinel + overrides-guide.md. Idempotent — preserves existing values, only adds entries for newly-discovered specialists on re-run. |
sp edit --global --set <name>.<dot.path> <value> | Writes one field in user.json (schema-validated). |
sp edit --global --get <name>.<dot.path> | Reads the current merged value. |
sp doctor --specialists | Reports N/M specialists have a model configured, lists missing models, and surfaces blocked-field overrides applied with warning. |
sp config show <name> --resolved | Shows the merged spec for one specialist with per-layer attribution. |
The bare positional form (sp edit --global <name>.field value without
--set) currently falls through to $EDITOR in some environments — prefer
--set in scripts.
Allowlist mapping (which fields can be set globally)
The constants in src/specialist/schema.ts define what the global layer may
write:
OVERRIDE_ALLOWED_EXECUTION_FIELDS = [
'model', 'fallback_model', 'fallback_models',
'timeout_ms', 'stall_timeout_ms', 'interactive', 'thinking_level',
'max_retries', 'prompt_limit_bytes', 'stdout_limit_bytes',
]
OVERRIDE_ALLOWED_NESTED_EXECUTION_PATHS = [
'extensions.serena', 'extensions.gitnexus',
]
OVERRIDE_ALLOWED_STALL_DETECTION_PATHS = ['waiting_auto_close_ms']
OVERRIDE_ALLOWED_PROMPT_FIELDS = ['system_prompt_mode']
OVERRIDE_ALLOWED_TOP_FIELDS = ['beads_write_notes', 'notes_mode', 'output_file']
So sp edit --global --set executor.notes_mode final-only is allowed; the
same effect for permission_required, prompt.system, mandatory_rules,
capabilities, output_schema, auto_commit, skills.scripts, or
prompt.task_template is blocked at the global layer and must be done
per-repo in .specialists/user/<name>.specialist.json. A blocked field that
sneaks into user.json is applied with a BlockedFieldWarning and reported
by sp doctor --specialists.
Same fields as the per-spec reference
Every field documented above in ## Schema Reference applies at the global
layer with the same semantics — only the scope changes (one machine vs one
repo). Examples:
# Set notes_mode + output_file globally for a chained pipeline reader
sp edit --global --set sync-docs.notes_mode final-only
sp edit --global --set sync-docs.output_file ".specialists/sync-docs-result.md"
# Opt out of GitNexus MCP injection for a specialist
sp edit --global --set overthinker.execution.extensions.gitnexus false
# Set the default keep-alive behavior globally for one specialist
sp edit --global --set reviewer.execution.interactive false
# Auto-close forgotten waiting sessions after 1h (graceful close first)
sp edit --global --set reviewer.stall_detection.waiting_auto_close_ms 3600000
# Set system_prompt_mode to replace globally for a bare specialist
sp edit --global --set my-bare-spec.prompt.system_prompt_mode replace
# Plural fallback chain (KAN-91 Phase 2; walked on transient failures only)
sp edit --global --set executor.execution.fallback_models \
'["openai-codex/gpt-5.5","anthropic/claude-sonnet-4-6"]'
Preset references (KAN-91 Phase 3)
Both model and fallback_model/fallback_models entries may be a
@preset/<name> reference:
sp edit --global --set executor.execution.model @preset/medium
sp edit --global --set executor.execution.fallback_models '["@preset/cheap"]'
sp edit --list-presets # show available preset names in the installed package
Built-in presets typically include cheap, medium, power. Depth cap = 5;
cycles surface a structured error at dispatch.
_doc sentinel and overrides-guide.md
sp init --global writes a _doc key at the top of user.json pointing at
./overrides-guide.md, and (re)generates that guide with the live field
reference. Any other _-prefixed keys are tolerated as comments. Authors of a
new specialist should run sp init --global after release so existing operators
get a fresh null-placeholder entry on next bootstrap.
Pitfall — thinking_level: "off"
thinking_level: null inherits pi's defaultThinkingLevel (typically high
on most installs). Forcing off on certain thinking-class models (verified:
Kimi-via-nano-gpt) silently produces an empty assistant text turn after
multi-tool runs because the model's tool-result-summary phase needs thinking
budget. Leave it null unless you have a documented reason to force a level.
Built-in Template Variables
These are always available in task_template — no configuration needed:
| Variable | Value |
|---|
$prompt | The user's prompt passed to use_specialist |
$cwd | process.cwd() at invocation time |
$pre_script_output | Stdout of pre scripts with inject_output: true (empty string if none) |
When invoked via --bead (inputBeadId present):
| Variable | Value |
|---|
$bead_context | Full bead content (replaces $prompt) |
$bead_id | The bead ID |
Custom variables can be passed at invocation time via --variables key=value and accessed as $key.
Skills Declaration (skills.paths)
Declared skills.paths define your discovery pool; nothing else is reachable. The sp/xt
runtime spawns pi/claude with --no-skills (isolating the ambient global pool) and re-adds
each declared skill via --skill <path>. Declared skills are then force-loaded at turn-1
via the /skill:name prefix baked into the initial user prompt (deterministic body load,
not merely made available). Same contract on sp run and xt --role — the prefix is
emitted by sp render-skill-prefix (or baked into sp render-task output) and consumed
verbatim by xt --role so the two entry points diverge nowhere.
Files listed under skills.paths are forwarded to pi/claude as native --skill flags:
{
"skills": {
"paths": [
".xtrm/skills/active/specialists-creator/SKILL.md",
".claude/agents.md"
]
}
}
Each file is appended as:
---
# Skill: <path>
<file content>
Missing files are silently skipped (no error).
skill_inherit (in prompt:) works the same way but for a single file — it is an Agent Forge compatibility field, appended under # Service Knowledge.
Pre/Post Scripts
Scripts run locally (not inside the agent session):
{
"skills": {
"scripts": [
{
"run": "scripts/gather-context.sh",
"phase": "pre",
"inject_output": true
},
{
"run": "scripts/notify.sh",
"phase": "post",
"inject_output": false
}
]
}
}
pre scripts run before the agent session starts; use inject_output: true to surface their stdout.
post scripts run after the session completes (cleanup, notifications).
- Timeout: 30 seconds per script.
- Exit code is captured but does not abort the run.
Annotated Full Example
{
"specialist": {
"metadata": {
"name": "code-reviewer",
"version": "1.0.0",
"description": "Reviews a PR diff for correctness, style, and security issues.",
"category": "code-quality",
"author": "team@example.com",
"updated": "2026-03-22",
"tags": ["review", "code-quality", "security"]
},
"execution": {
"mode": "tool",
"model": "anthropic/claude-sonnet-4-6",
"fallback_model": "google-gemini-cli/gemini-3.1-pro-preview",
"timeout_ms": 300000,
"stall_timeout_ms": 60000,
"interactive": true,
"response_format": "markdown",
"permission_required": "READ_ONLY"
},
"prompt": {
"system": "You are an expert code reviewer. Focus on correctness, maintainability, and security.\nDo NOT modify any files -- output a markdown review only.\n",
"task_template": "Review the following changes:\n\n$prompt\n\n$pre_script_output\n\nWorking directory: $cwd\n\nOutput a structured markdown review with sections: Summary, Issues, Suggestions.\n",
"skill_inherit": "skills/code-review/guidelines.md"
},
"skills": {
"paths": [
"skills/code-review/"
],
"scripts": [
{
"run": "scripts/get-diff.sh",
"phase": "pre",
"inject_output": true
}
]
},
"capabilities": {
"required_tools": ["bash", "read"],
"external_commands": ["git"]
},
"output_file": ".specialists/review.md",
"beads_integration": "auto"
}
}
Context Window & Lifecycle Design
Specialists run as long-lived Pi sessions. Context management is not optional — ignoring it causes silent quality degradation before any hard limit is hit.
Context rot starts before the window fills
Quality degrades as the context grows — compressed early context causes inconsistency, missed facts, and instruction drift. Design for bounded, coherent runs rather than arbitrarily long ones.
Rules when authoring a specialist:
- Set
stall_timeout_ms explicitly for any specialist that may idle between turns (keep-alive/interactive). Without it, a stuck session holds resources indefinitely.
- If the operator wants forgotten waiting sessions to retire automatically, set
stall_detection.waiting_auto_close_ms explicitly; keep it unset/0 when human follow-up should remain indefinitely resumable.
- Use
thinking_level: low for orchestration/coordinator specialists that emit structured JSON output — thinking tokens cost context budget without improving structured output quality.
- For research/explorer specialists: bounded scope per session +
handoff_summary in output_schema > one unbounded session.
interactive: true specialists must define what "done" looks like in their system prompt — otherwise they drift.
Context metrics are always available
status.json exposes metrics.token_usage (cumulative input+output tokens) and metrics.turns on every turn. These are written by 08zd Phase 1 and available to any caller (NodeSupervisor, orchestrator, human).
context_pct formula: (cumulative_input_tokens / model_context_window) * 100
Approximate context windows:
| Model family | Window |
|---|
claude-opus-4-6, claude-sonnet-4-6, claude-haiku-4-5 | 200k tokens |
gemini-3.1-pro-preview | 1M tokens |
qwen3.5-plus, dashscope/qwen3.5-plus | 128k tokens |
zai/glm-5, zai/glm-5-turbo | 128k tokens |
For Node members specifically
NodeSupervisor injects member_health into the coordinator resume prompt on every turn — not just at warning thresholds. This is by design: the coordinator needs continuous data to make proactive rotation decisions before quality degrades.
When authoring a specialist intended to run as a Node member:
- Include a
handoff_summary field in output_schema so context can be transferred on rotation
- Keep system prompts concise — the NodeSupervisor will inject additional context on each resume
thinking_level: low or off for coordinator-class specialists; higher levels for deep analysis members
Design checklist for long-running specialists
Before finalising a specialist that uses interactive: true or is expected to run many turns:
[ ] stall_timeout_ms set (not relying on timeout_ms alone)
[ ] waiting_auto_close_ms decision made (unset/0 for indefinite waiting, explicit ms for auto-retire)
[ ] thinking_level set appropriately for the output type
[ ] output_schema includes handoff_summary or equivalent for rotation
[ ] system prompt has explicit termination condition ("you are done when...")
[ ] task_template doesn't inject large static blobs that could be fetched on demand
Common Errors and Fixes
| Zod Error | Cause | Fix |
|---|
Must be kebab-case | name has uppercase or spaces | Use my-specialist not MySpecialist |
Must be semver | version: "v1.0" | Use "version": "1.0.0" (no v prefix) |
Invalid enum value ... 'READ_WRITE' | Wrong permission value | Use READ_ONLY, LOW, MEDIUM, or HIGH |
Invalid enum value ... 'auto' on permission_required | Using auto for permission_required | auto is only valid for beads_integration |
Required on task_template | task_template missing from prompt | Add task_template (even if just "$prompt") |
Required on model | model missing from execution | Add a model string |
Required on description | Missing description in metadata | Add description string |
Required on category | Missing category in metadata | Add category string |
| Silently ignored / no output | JSON valid but task_template doesn't use $prompt | Add $prompt to task_template |
defaults key unrecognized | Using defaults top-level key | Remove it; use --variables at invocation or built-ins |
File Placement
Specialists are discovered from three scopes (highest priority first):
- Project:
<project-root>/specialists/*.specialist.json
- User:
~/.agents/specialists/*.specialist.json
- System: package-bundled specialists
Name your file <metadata.name>.specialist.json.
Validation Workflow
A bundled validator is included with this skill so the agent does not need to reconstruct the bun -e one-liner from memory. It prints OK <file> on success and a field-by-field error list on failure.
# 1. MANDATORY: discover + ping models (see top of this skill)
pi --list-models
pi --model <provider>/<primary-model-id> --print "ping" # must return "pong"
pi --model <provider>/<fallback-model-id> --print "ping" # must return "pong"
# 2. Scaffold first (fills missing schema sections/fields)
node config/skills/specialists-creator/scripts/scaffold-specialist.ts config/specialists/my-specialist.specialist.json
# 3. Mutate with sp edit (dot.path + presets)
sp edit my-specialist --preset medium
sp edit my-specialist specialist.execution.model <provider>/<primary-model-id>
sp edit my-specialist specialist.execution.fallback_model <provider>/<fallback-model-id>
# 4. Use --file only for multiline prompt fields
sp edit my-specialist specialist.prompt.system --file .tmp/system.prompt.txt
sp edit my-specialist specialist.prompt.task_template --file .tmp/task-template.prompt.txt
# 5. Verify rendered config
sp view my-specialist
# 6. Validate schema with the bundled helper
bun config/skills/specialists-creator/scripts/validate-specialist.ts config/specialists/my-specialist.specialist.json
# 7. List to confirm discovery
specialists list
# 8. Smoke test
specialists run my-specialist --prompt "ping" --no-beads
If you need the underlying implementation, read config/skills/specialists-creator/scripts/validate-specialist.ts. It is a thin Bun/TypeScript wrapper over parseSpecialist() from src/specialist/schema.ts, which keeps the helper cross-platform for Windows, macOS, and Linux.
Script-Class vs Package-Class Runtime
Two runtime classes exist:
- Package-class:
sp run → runner.ts → session.ts
- Script-class:
sp script / sp serve → script-runner.ts
| Runner | System-prompt composition (skills declared separately via --skill) |
|---|
| Package-class | prompt.system + Specialist Run Context + caveman output style + GitNexus mandate (if .gitnexus exists) + STATIC_WORKFLOW_RULES_BLOCK + memory injection + mandatory rules + output contract + reviewer patch retrieval (reviewer reuse only). Declared skills go through pi --skill, not into the system prompt. |
| Script-class | prompt.system only. Declared skills go through pi --skill, not into the system prompt. |
Pi flags:
| Runner | Pi flags |
|---|
| Package-class | --no-extensions, then re-enable quality-gates/service-skills/caveman/gitnexus |
| Script-class | --no-extensions --no-tools --offline --no-context-files --no-prompt-templates --no-themes and --no-skills when skills.paths is empty |
Settings that silently do nothing on script-class:
mandatory_rules (all fields)
- beads injection
- GitNexus mandate
- memory injection
- output contract auto-generation
Per-runner default for prompt.system_prompt_mode:
- Package-class:
append
- Script-class:
replace
Script-class warning: mandatory_rules never reaches script-class. If spec must rely on rule sets, it must run package-class.