Skip to content

Protocol

This is the contract between Shipper and the agents it runs. Commands prepare context, agents do the stage work, and the result file tells Shipper how to interpret the stage.

Stage prompt bodies are authored once under packages/core/src/prompts/templates/. Intentional per-agent wording differences, such as setup instruction-file wording and sandbox wording, come from the capability table in packages/core/src/lib/agent-capabilities.ts. Prompt invocation settings and issue/PR append flags are structured data in packages/core/src/lib/prompts.ts.

runPrompt() in packages/core/src/lib/prompt-runner.ts consumes the rendered prompt text plus the structured invocation metadata for the selected agent and step. It then applies runtime mode/model/MCP/worktree normalization, appends issue or PR context when requested, injects environment settings, and spawns the configured agent. Repository files under .shipper/prompts/ are ignored legacy leftovers and do not change prompt text.

Result files use one of three valid verdicts:

| Verdict | Meaning | | -------- | ---------------------------------------------------------------------- | | accept | The stage completed and Shipper should move the issue forward. | | reject | The stage found a problem and Shipper should roll back as configured. | | fail | The stage could not complete and the issue should be marked as failed. |

A crash, timeout, or missing result file is treated as a terminal failure. reject is not a command failure for next or ship; it is an interior workflow event as long as Shipper can apply the rollback transition from the agent’s result.

Rollback targets come from STAGE_TRANSITIONS in packages/core/src/lib/stage-transitions.ts. For example, an implementation reject rolls the issue back to shipper:designed, and a PR review reject rolls it back to shipper:implemented. For recovery from crashes, missing results, timeouts, and reject loops, see failed issues and rollback loops.

For result-protocol stages, agents do not transition workflow labels directly. The agent writes the result file. Shipper reads that file, resolves the verdict through STAGE_TRANSITIONS, and then applies the transition.

executeTransition() in packages/core/src/lib/output-protocol/protocol-actions.ts builds one atomic gh issue edit call containing every --add-label and --remove-label argument for the resolved transition.

PR-stage labels are mirrored onto the pull request with a separate best-effort gh pr edit call. The issue label transition is still the source of truth for workflow state.

shipper new is the narrow exception to the normal result-file shape because it runs before an issue exists. The new agent writes pre-create draft files under .shipper/output/: result.json contains issue_draft, which points at issue-draft.json; that draft contains a structured title and an issue-body.md path. The new draft does not use verdict or comment, and comment is optional only for this stage. Shipper validates the draft with the standard correction retry budget, creates the GitHub issue, applies shipper:new, and overwrites result.json with the final created_issue identity.

Grooming still updates the issue body/comments as part of its workflow. Those direct edits are separate from the result-file transition contract used by downstream stages.

Settings are merged in this order:

  1. Built-in defaults
  2. .shipper/settings.json
  3. .shipper/settings.local.json
  4. One-run CLI flags

commands.default.disableMcp defaults to false. commands.groom.disableMcp defaults to true. Any commands.<stage>.disableMcp value overrides the default for that stage, and --disable-mcp or --enable-mcp overrides settings for one invocation.

When MCP loading is disabled, Shipper logs:

MCP loading disabled for stage <name>.

It then injects the empty MCP configuration or disable flags appropriate to the selected agent. When MCP loading remains enabled, Shipper does not print an extra MCP line. For setup or tool-loading symptoms, see MCP tool loading issues.