Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Customising agents

Every step of the pipeline is carried out by an agent: a model running in a loop with a system prompt, a set of tools, a model choice and a thinking level. Vibe Factory ships twelve of them. You can change any of their settings, replace their prompts, or add your own, by dropping TOML files in .vibe/agents/. No code is needed.

The built-in roles

RoleWhat it does
complexity_assessorReads the task and a little of the code, then classifies it as trivial, simple, standard or complex. This picks how much of the pipeline runs.
spec_gathererExplores the codebase and turns your description into numbered, testable requirements with the relevant files and conventions.
spec_researcherChecks the external libraries and APIs the requirements rely on, so the spec does not assume functions that do not exist.
spec_writerWrites the specification: a readable spec.md and the matching JSON.
spec_criticReviews the specification for gaps and contradictions and returns a corrected version.
plannerBreaks the spec into ordered phases of small subtasks, each with the files to touch, its dependencies and how to verify it.
coderImplements one subtask in a fresh session, then runs its verification.
coder_recoveryTakes over a subtask that keeps failing, diagnoses why, and completes it another way.
qa_reviewerChecks every acceptance criterion against the code, runs the checks, and issues a verdict with a list of issues.
qa_fixerFixes the issues listed in a QA report.
merge_resolverResolves the conflict markers of one file.
commit_messageWrites a conventional commit message for a set of changes.

Their default settings:

RoleToolsThinkingEnds with JSONMax steps
complexity_assessorread onlylowyes30
spec_gathererread onlymediumyes100
spec_researcherread onlymediumno100
spec_writerread + write_filehighyes100
spec_criticread onlyhighyes60
plannerread onlyhighyes100
coderalllowno300
coder_recoveryallmediumno300
qa_reviewerallhighyes200
qa_fixerallmediumno200
merge_resolvernonelowno3
commit_messagenonelowno2

All of them use the model configured for the current phase (see Providers and models).

Overriding a role

Create .vibe/agents/<anything>.toml. The file names the role it applies to and sets only the keys you want to change; everything else keeps its current value.

# .vibe/agents/qa_reviewer.toml
role = "qa_reviewer"
model = "anthropic/claude-opus-5"
thinking = "max"
max_steps = 250
KeyTypeMeaning
rolestring, requireda built-in role name, or a new name for a custom agent
descriptionstringshown in listings
system_promptstringthe whole system prompt, replacing the current one
system_prompt_filepathread the prompt from a file, relative to the TOML file
tools"none", "read_only", "read_write", "all", or a list of tool namestools the agent may use
model"phase" or "provider/model"phase follows the phase configuration; a reference pins one model
thinkingoff, low, medium, high, maxreasoning budget
max_stepsinteger ≥ 1maximum number of model round trips
max_tokensinteger ≥ 1output token budget per round trip
structured_outputboolthe agent must end with a JSON document

Rules:

  • Unknown keys are an error, so a typo such as max_step is reported instead of ignored.
  • system_prompt and system_prompt_file are mutually exclusive.
  • A tool list only keeps tools that exist; an unknown name is silently dropped. read_only means read_file, glob, grep and list_dir; read_write adds write_file; all includes tools from plugins.
  • model must contain a / unless it is phase.
  • Errors name the file: .vibe/agents/qa.toml: TOML error: unknown field `max_step` ….

Precedence

Files are read in file-name order. Each file applies on top of the agent already known for its role: the built-in agent, an agent provided by a plugin, or an earlier file in the same directory. Two files for the same role therefore layer, the later one winning on the keys both set. Keep one file per role unless you have a reason not to.

Replacing a prompt

Copy the built-in prompt from crates/vibe-agents/src/prompts/<role>.md as a starting point and point system_prompt_file at your copy:

# .vibe/agents/coder.toml
role = "coder"
system_prompt_file = "prompts/coder.md"   # .vibe/agents/prompts/coder.md

Prompts are templates: {{name}} is replaced by a variable, and unknown variables become empty text. Every agent gets {{task_title}}, {{task_description}}, {{workspace_root}} and {{date}}. The built-in prompts start with an HTML comment listing the other variables their role receives ({{spec}}, {{plan}}, {{subtask}}, {{progress}}, {{memory}}, {{qa_report}}, {{prior_context}}, …); that comment is stripped before the prompt reaches the model, so you can keep it as documentation. The full table is in Agent runtime.

If a role ends with JSON, your prompt must still ask for the same JSON shape: the pipeline parses it into typed data.

Adding a custom role

Any role that is not a built-in name defines a new agent. It must provide a prompt; the other keys default to all tools, the phase model, medium thinking, 200 steps and 8 192 output tokens.

# .vibe/agents/doc_writer.toml
role = "doc_writer"
description = "Keeps the user guide in sync with the code"
system_prompt_file = "doc_writer.md"
tools = ["read_file", "glob", "grep", "write_file"]
thinking = "medium"
max_steps = 80
<!-- .vibe/agents/doc_writer.md -->
You maintain the documentation of the project at {{workspace_root}}.
Task: {{task_title}}

{{task_description}}

Update only files under docs/. Finish with a short list of the pages you changed.

The default pipeline drives the built-in roles; a custom role is available to plugins and programs that look agents up by name in the registry. Plugins can also contribute agents over the plugin protocol (see Plugins).

Writing prompts that return valid JSON

Agents with structured_output = true must end their session with one JSON document. The runtime is tolerant, but these habits keep the retries (and their cost) away:

  • Show the exact shape in the prompt, as a fenced json block with every key, and list allowed enum values literally ("approved | changes_requested | inconclusive"). Enum values are snake_case and must match exactly.
  • Ask for the JSON last, in a single json fence, with nothing after it. The extractor takes the whole answer if it is pure JSON, else the last json fence, else the last untagged fence, else the last balanced {…} or […] in the text. An example block quoted earlier in the answer is harmless; a second document after the real one is not.
  • Objects or arrays only. A bare string or number is never accepted as the answer.
  • No comments, no trailing commas. They are the usual reason for the repair step.
  • Give enough output budget. Plans and specs are long; the built-ins use 32 000 tokens. If an answer is cut by the output limit, the agent is asked once to continue, which can split the JSON across two messages.
  • Let it converge. Structured agents are told “Converge now” at 75 % of max_steps; keep max_steps large enough that exploration fits before that point.

When extraction fails, the runtime makes one cheap repair call without tools, then resumes the agent once with “Your previous answer was not valid JSON”. A second failure is reported as an error; for the QA reviewer it counts as an inconclusive round.