Configuration

Agent prompts

Extend how the planner, builder, reviewer, and QA agents behave, without editing SHIP's own prompt source.

Overview

Every agent in the pipeline runs on a charter SHIP writes and versions: how a reviewer weighs severity, how a builder scopes a diff, how QA reads acceptance criteria. You cannot edit that charter directly, because it ships with the platform.

What you can do is extend it. Drop a markdown file into your own repository and SHIP appends it after its own charter for that role, every time that role runs. Your repository never overwrites SHIP's instructions; it adds to them.

This is a two-layer model:

LayerContentOwner
SHIP's charterHow the role reasons: review severity, diff scope, CI triageSHIP
Your extensionYour repository's own conventions for that roleYou

SHIP's layer always runs first. Your extension is appended after it, never in place of it.

The .ship/agents/<role>.md file

The canonical location for an extension is .ship/agents/<role>.md, where <role> is one of planner, builder, reviewer, or qa. The file has an optional YAML frontmatter fence followed by a markdown body, the same shape as a .claude/agents/*.md sub-agent file, so if you already author one of those, you can copy it in unchanged.

Here is a complete, worked example, a reviewer extension at .ship/agents/reviewer.md:

---
name: reviewer
description: House review conventions for this repository
---

## House conventions

- Flag any new dependency added to `package.json` without a comment explaining why it was needed.
- Treat a missing test for a new exported function as a blocking comment, not a suggestion.
- Do not block on formatting, since the pre-commit hook already enforces it.

## Domain context

This repository is a payments library. A review comment about rounding or currency
precision should always be treated as `critical` severity, regardless of what the
diff otherwise looks like.

The frontmatter's name and description are used for display only. The body, meaning everything after the closing ---, is what gets appended to the reviewer's charter. A file with no frontmatter fence at all is also valid: the whole file becomes the body, which is what lets you point this convention at a plain file like AGENTS.md (see below).

Linking an existing file with the ship.yml pointer

If your extension already lives somewhere else, such as AGENTS.md, a .claude/agents/*.md file, or anything else in the repository, point at it instead of duplicating it at the canonical path. Add a prompt field under agents.<role> in your ship.yml:

version: 1

agents:
  reviewer:
    prompt: .claude/agents/security.md

deployments:
  # ...

The pointer takes precedence over .ship/agents/<role>.md. If both exist, the pointer wins. If neither exists, that role runs with SHIP's charter alone, with no extension and no error.

AGENTS.md and the CLAUDE.md migration path

AGENTS.md is the canonical file for your repository's general conventions, covering coding style, architecture and module boundaries, the context every agent should have regardless of role. SHIP reads CLAUDE.md at the repository root, and if its body contains a standalone @AGENTS.md import line, that import is resolved and the referenced content is injected in its place. In practice this means AGENTS.md stays the single place your conventions live, and CLAUDE.md stays a one-line pointer to it. SHIP does not read AGENTS.md directly, so a repository with AGENTS.md but no CLAUDE.md (or a CLAUDE.md whose pointer line isn't the exact @AGENTS.md import syntax) has nothing injected.

If your repository's conventions currently live in CLAUDE.md with no AGENTS.md, you have two options:

  1. Move the content into AGENTS.md, and leave CLAUDE.md containing exactly the line @AGENTS.md. This keeps one canonical source (AGENTS.md) while preserving the import that SHIP (and other tools that read CLAUDE.md) resolve. A prose pointer such as "See AGENTS.md" is not the same thing, because only the bare @AGENTS.md import line is resolved.
  2. Point at it directly. If you would rather not touch CLAUDE.md at all, set agents.<role>.prompt: CLAUDE.md for the roles that should read it. This is the same pointer mechanism described above, and it is not limited to files under .ship/agents/.

Prompt files take effect from your default branch

Every role's prompt extension, whether it is the canonical .ship/agents/<role>.md file or a ship.yml pointer target, is read from your main branch (the base branch the pipeline compares pull requests against), never from a pull request's own branch. If your repository's default branch is named something other than main, prompt extensions are not found there today, because the canonical path and pointer target are both read from main specifically.

This matters most for the reviewer and QA roles: a pull request cannot rewrite the rules its own review is graded against. If a change to .ship/agents/reviewer.md were read from the pull request's head, a diff could loosen the very conventions used to evaluate it. Reading from the default branch closes that loophole, so an extension change takes effect for the next pull request, once it has merged.

Limits

  • A missing file at either the pointer path or the canonical path is not an error. It simply means that role has no extension.
  • A file with an unparseable frontmatter fence, or a file over 30,000 characters (after any @import is resolved), is skipped in its entirety, with a warning surfaced on the mission. There is never a partial extension.
  • One level of @import is resolved inside the body, the same convention-import mechanism your AGENTS.md already supports.

Reserved frontmatter fields

Three frontmatter keys are parsed today but not yet acted on. They are reserved for future capabilities so that using them now will not require a breaking change later:

FieldReserved for
skillsAttaching specific skills to a role's extension
extendsDefining a custom agent instance built on top of an existing role
onTriggering an agent from a platform event, rather than a pipeline stage

Setting any of these today has no effect. They parse without error, but nothing reads them yet.

Why model, tools, and harness are ignored

You may see a .claude/agents/*.md file elsewhere that sets model:, tools:, or harness: in its frontmatter. If you point SHIP at a file like that, or copy those keys into a .ship/agents/<role>.md file, they are ignored: parsed, but never applied.

This is deliberate. Execution configuration, meaning which harness a role runs on and which model it uses, is controlled entirely by ship.yml rather than by a prompt file. Tool access isn't a ship.yml field either; it's scoped per role by the platform itself, which is why a frontmatter tools: key has no effect regardless of where you set it. Keeping harness and model in exactly one place means:

  • There is one source of truth. A model choice buried in a prompt file's frontmatter, invisible unless you open that specific file, would silently diverge from what ship.yml says is configured.
  • Performance comparisons stay meaningful. SHIP attributes cost, latency, and success rate back to the harness/model combination that actually ran. If a prompt file could quietly override that, the same configuration label would cover runs that behaved differently, and any comparison across configurations would be unreliable.

An ignored key does not fail silently. It produces a visible warning on the mission, so you can see that it had no effect rather than assuming it did.

How is this page?

On this page