AI coding agents

Shipmoor Team
June 30, 2026
11 min read

This is the heart of Shipmoor Code Review: you bring your own agent as the reviewer. Shipmoor never hosts a model and never uploads your source. It drives the change selection and the review loop locally, and hands the actual judgement to a command you supply, typically your existing coding agent (for example Claude Code).

shipmoor review —agent is not shipmoor scan —agent. This page is about the code-review agent below. shipmoor scan has a separate —agent: the stage-3 BYO-Judge for Claim Check, which speaks a different, single-shot prompt contract and weighs in on intent claims, not code-review comments. The two do not share a wire protocol. Do not reuse an agent across them.

The model: local, no upload

When you pass --agent <command>, Shipmoor runs that command as a subprocess on your machine. It writes a JSON request to the command’s stdin and reads a JSON response from its stdout. That is the entire boundary. The consequences:

  • No Shipmoor-hosted model. The intelligence is whatever your agent calls.
  • No source upload by Shipmoor. The only network traffic is whatever your agent already performs to reach its provider. A purely local or offline agent makes no network calls.
  • No API key for Shipmoor. Your agent owns the provider relationship: model, credentials, rate limits.

The agent reviews locally, over the diff (and, via tools, the working tree). It sees the change and review knowledge, on your machine, and nothing leaves it through Shipmoor.

The wire contract

Shipmoor speaks a small, provider-neutral, OpenAI-chat-shaped protocol over stdin and stdout. Your agent reads one request and returns one response per turn.

Request (Shipmoor writes to your agent’s stdin)

A JSON object with these fields:

FieldTypeNotes
modelstringThe model name to use (your agent may ignore it and use its own).
messagesarrayThe chat turns: each { "role", "content", "tool_call_id"?, "tool_calls"? }. role is one of system, user, assistant, or tool.
toolsarrayAvailable tools (omitted when empty): each { "type": "function", "function": { "name", "description", "parameters" } }.
max_tokensnumberEmitted only when > 0.

There is no temperature field (the request runs at the provider default). JSON is UTF-8 (ensure_ascii=False). Non-finite numbers (NaN, Infinity) are rejected.

{
  "model": "your-model",
  "messages": [
    { "role": "system",  "content": "You are a code reviewer. Use the tools to inspect the change and post comments." },
    { "role": "user",    "content": "Review this diff:\n@@ -1,2 +1,2 @@\n-    return a + b\n+    return a - b  # bug" }
  ],
  "tools": [
    { "type": "function", "function": { "name": "code_comment", "description": "Post a review comment.", "parameters": { "type": "object", "properties": {} } } }
  ],
  "max_tokens": 58888
}

Response (your agent writes to its stdout)

A JSON object whose choices array is required and non-empty (an empty choices aborts the turn fail-safe). Shipmoor reads choices[0].message:

FieldTypeNotes
choices[0].message.rolestringUsually assistant.
choices[0].message.contentstring or nullAssistant text. May be null or empty when the turn is only tool calls.
choices[0].message.reasoning_contentstringFallback text used only when content is null or empty.
choices[0].message.tool_callsarrayThe model’s tool calls: each { "id", "type": "function", "function": { "name", "arguments" } }.
choices[0].finish_reasonstringParsed for fidelity but never read: completion is signalled by a task_done tool call, not by finish_reason.
usageobjectOptional token usage.

arguments is always a JSON-encoded string on both edges, for example "arguments": "{\"comments\":[…]}", not a nested object. Output is fence-tolerant (a fenced ```json … ``` block or surrounding prose is unwrapped).

{
  "choices": [
    {
      "message": {
        "role": "assistant",
        "content": "Found an issue.",
        "tool_calls": [
          {
            "id": "call_1",
            "type": "function",
            "function": {
              "name": "code_comment",
              "arguments": "{\"comments\":[{\"content\":\"Subtraction where addition is expected.\",\"existing_code\":\"return a - b  # bug\",\"suggestion_code\":\"return a + b\",\"severity\":\"high\",\"confidence\":\"high\"}]}"
            }
          }
        ]
      },
      "finish_reason": "tool_calls"
    }
  ],
  "usage": { "prompt_tokens": 100, "completion_tokens": 20, "total_tokens": 120 }
}

The tools the agent can call

Shipmoor offers the agent a fixed tool set. It calls these to inspect the change and to post results:

ToolPurpose
file_readRead a file’s contents.
file_read_diffRead the diff for a file.
code_searchSearch the codebase.
file_findFind files by pattern.
code_commentPost a review comment, which becomes an advisory finding. Each comment may carry an optional severity and confidence, each one of high, medium, or low.
task_doneSignal completion, which ends the review turn for that file.

A comment posted via code_comment becomes a shipmoor.scan.v1 finding with category: "code_review" and rule_id: "shipmoor.review.llm". Its content maps to the finding message, existing_code to evidence.existing_code, and suggestion_code to the finding recommendation.

Each comment may also carry an optional severity of high, medium, or low, which flows onto the finding severity:

  • high for a correctness or security bug, or a change that will break at runtime.
  • medium for a context-dependent concern worth a second look.
  • low for a nit or a likely false positive.

Severity is advisory guidance only. It is model-assigned and varies from run to run, so treat it as a hint rather than a contract. A missing or unrecognized value defaults to medium, which keeps older agents that emit no severity working unchanged. Crucially, severity never changes whether a review finding gates: code_review findings are excluded from --fail-on at every severity, so a high advisory finding still exits 0 and never blocks a build. In SARIF, review findings render as warning (high and medium) or note (low) and never as error, and the IDE keeps them in a separate advisory collection, never as an error squiggle and never counted in the problem totals.

Each comment may also carry an optional confidence of high, medium, or low, which flows onto the finding confidence:

  • high when the code plainly shows the issue.
  • medium when it depends on context the reviewer cannot fully see.
  • low for a plausible but uncertain concern still worth flagging.

Confidence is a separate axis from severity, so a high-severity but uncertain issue stays expressible. Like severity, it is advisory only, model-assigned, defaults to medium on a missing or unrecognized value, and never affects whether a finding gates.

The advisory review is deliberately recall-biased. Its findings are informational and get validated downstream by a person or a second agent, so the reviewer surfaces every plausible issue and labels it with severity and confidence rather than staying silent when it is less than fully certain. It still never fabricates: every finding must point to real code in the change. This is the opposite posture from the deterministic scan gate, which stays precision-first so it rarely blocks a build by mistake. Use confidence to triage a broader list. Lead with the high-confidence findings and treat the low-confidence ones as leads to check.

Wiring Claude Code (and the general contract)

The fastest path is the built-in claude preset: shipmoor review --agent claude drives your local claude CLI through a wire-contract adapter bundled in the binary. No script, no setup beyond having claude on your PATH. The codex and cursor presets are wired the same way via --agent codex and --agent cursor (the cursor preset drives cursor-agent in read-only --mode ask, so a review never edits your files). Override any preset’s underlying command with SHIPMOOR_REVIEW_CLAUDE_CMD, SHIPMOOR_REVIEW_CODEX_CMD, or SHIPMOOR_REVIEW_CURSOR_CMD. Everything below describes the contract a custom agent must speak. The preset is what saves you from writing one for claude.

In a Shipmoor Skill, the convention is shipmoor review --agent self, where self means “the agent already running this skill is the reviewer”. The agent reading the skill substitutes its own invocation as the --agent command.

Important for direct CLI use: apart from the claude, codex, and cursor presets, the bare CLI has no keywords. —agent is a shell command that is shlex-split and executed. In particular it has no self keyword: passing self literally to shipmoor review just tries to run a program called self. Since that cannot be launched, the CLI exits 2 with agent command ‘self’ not found. Pass —agent with a reviewing agent: use the built-in preset —agent claude (drives your claude CLI), or a command that speaks the wire contract …. Note that a raw claude -p is not such a command (it takes a prompt and prints text, not the JSON wire shape). That is exactly what the claude preset exists to bridge. Outside a Skill, use —agent claude, or point —agent at a wrapper or program that implements the wire contract.

A runnable reference example

The examples/agents/ folder in the Shipmoor CLI repository is a copyable starting point: a working agent you can run today and then adapt:

  • example-agent.py is a dependency-free, protocol-faithful reference agent. It runs end-to-end with no API key (it posts one advisory comment, then completes), so you can confirm the wiring before plugging in a real model:

    shipmoor review --agent "$(pwd)/examples/agents/example-agent.py" --scan .

    Edit its decide_turn() to call your real agent.

  • claude-wrapper.sh is a thin shell wrapper that turns a coding-agent CLI (claude -p, codex exec, cursor-agent -p, and similar) into a bring-your-own-agent: it flattens the request into a prompt, runs the agent, and wraps the answer in a ChatResponse.

The folder’s README documents the full request and response JSON shape (the input contract: model, messages, tools, max_tokens, alongside the output contract), the turn loop, and the tool vocabulary, next to the runnable code.

If your agent is not already a stdin-to-stdout filter matching the contract, wrap it (the reference example-agent.py is a more complete version of this skeleton):

#!/usr/bin/env bash
# my-agent: read the Shipmoor request on stdin, return a ChatResponse on stdout.
# Adapt to however your agent or model is invoked.
exec your-agent --json-passthrough
shipmoor review . --agent "./my-agent"

The review then fix loop (Skills)

Shipmoor ships Agent Skills that turn the advisory review into a review then fix workflow. Two are relevant (both are Pro; the review skill is cli_pro):

  • shipmoor-code-review (Pro) wraps the advisory AI reviewer (shipmoor review --agent self). The loop it implements:

    1. Review the change: shipmoor review --agent self --json --output .shipmoor/review.json
    2. Read the advisory findings (category: "code_review").
    3. Fix the High and Medium introduced findings.
    4. Re-run the review until findings are addressed or explicitly justified.
    5. Then run the deterministic gate plus runtime verification to decide readiness. The review itself never proves readiness and never returns “ready”.
  • shipmoor-review (Community) is a different, structural skill that wraps shipmoor scan (the deterministic gate), not the AI reviewer. Do not confuse the two: shipmoor-review is the gate; shipmoor-code-review is the advisory AI reviewer.

The skill is explicit that the review is advisory: “shipmoor review never returns exit 1, never reports ready on its own, and never proves readiness.” The thing that actually blocks is the deterministic gate plus runtime verification.

Install Shipmoor Skills into your agent’s instruction files with shipmoor skills. See Agent Skills.

The harness (advisory forwarding)

The Agent Harness is the local feedback loop: it watches what your AI agent changes, runs shipmoor scan against those edits, and routes the findings back into the agent’s context.

Review (code_review) findings flow through it as ordinary advisory findings, forwarded field-for-field. There is no special handling. Two properties hold by construction:

  • They never gate. Because code_review is gate-excluded at the CLI, an advisory-only report never produces a blocked harness exit, in any mode (observe, feedback, or even an un-degraded block). An advisory finding riding alongside a deterministic blocking finding is a passenger: it neither causes nor suppresses the block.
  • They surface by rule id. The watch event lists shipmoor.review.llm in its findings_summary.rule_ids and counts it in the total, but the event carries counts and rule ids only, never the review message.

Run the harness with shipmoor harness <cmd> (status, watch, inbox, and the rest).

See also

Last updated on June 30, 2026

Was this article helpful?

Your response is saved on this device.