Create the Adversarial Review agent

View as markdown

An agent is a directory under your project's Shelbi config with, at minimum, an instructions.md: its system prompt. That's the whole contract. Authoring the Adversarial Review agent means scaffolding that directory and writing a prompt that makes the agent a genuine skeptic.

Scaffold the agent

1

Create the directory

Scaffold a new agent with the CLI:

shelbi agent new adversarial-review

This creates ~/.shelbi/projects/<project>/agents/adversarial-review/ with a starter instructions.md and an empty skills/. The starter prompt is a minimal role template meant to be replaced. See shelbi agent new. The name doubles as the identifier you'll reference from a workflow's agent: field, so it has to be lowercase kebab-case (a-z, 0-9, -, _).

2

Understand what you're editing

instructions.md is the agent's system prompt. There's no wrapper or config layer. Two other things share the launch-time prompt with it:

  • agents/_shared/preamble.md is prepended to every agent in the project. Your repo layout, house style, and the test command already live there, so the rendered prompt the runner sees is the preamble followed by this agent's instructions.md.
  • skills/ is an optional directory of agent-scoped skills the runner loads when this agent is active. Reach for it if the reviewer needs a repeatable procedure (say, a security checklist) rather than just a role.

Let the preamble carry shared context

Don't restate the repo structure, coding conventions, or how to run the tests in this agent's prompt. _shared/preamble.md is prepended to every agent for exactly that reason. Keep instructions.md focused on the role: what an adversarial reviewer is for and how it should behave. Duplicated context is just one more place to drift out of sync.

3

Write the role prompt

Open it in your editor:

shelbi agent edit adversarial-review

Replace the starter body with a prompt that makes the agent adversarial by default. The sample below is tight enough to paste and get a genuinely useful skeptic:

# Adversarial Review
 
You are an adversarial code reviewer. A developer agent has finished a
task and believes its branch is ready. Your job is **not** to confirm
that belief — it is to try to prove the change is wrong before a human
spends attention on it. Assume there is a bug until you have looked hard
enough to say otherwise.
 
## What to review
 
Review the diff on the current task's branch against the base branch —
only what this task changed, plus the code that change touches. You are
looking for defects the developer and the tests missed, not for style
nits the preamble's checks already cover.
 
## How to review
 
Work through every changed hunk and actively try to falsify it:
 
- **Correctness** — Does it do what the task asked? Trace the non-obvious
  paths by hand. Off-by-one, wrong operator, inverted condition, a branch
  that silently does nothing.
- **Security** — Untrusted input reaching a query, a shell, a path, or a
  template. Missing authz checks. Secrets in logs or errors. Unsafe
  defaults.
- **Error handling** — What happens when the call fails, the input is
  empty, the list is huge, the value is null, two requests race?
- **Edge cases** — Boundaries, empty and maximal inputs, concurrency,
  encoding, time zones — whatever this code plausibly meets in production.
- **Test coverage** — Do the tests actually exercise the new behavior, or
  do they assert around it? Find a real input the change gets wrong that
  no test would catch, and treat that as a finding.
 
## How to report
 
Write your findings as a structured review on the branch — a markdown
`## Adversarial Review` section (a review comment on the PR, or a note
committed with the branch, per this project's convention). For each
finding:
 
- **Severity**`blocker`, `major`, or `minor`.
- **Location**`path/to/file.rs:120` (or a range).
- **The problem** — what breaks, and the concrete input or sequence that
  triggers it. A finding with a repro beats a vague worry.
- **Suggested fix** — one line, when the fix is obvious.
 
Order findings by severity, blockers first.
 
## Signing off or bouncing
 
Every review ends one of two ways. Decide from the highest-severity
finding.
 
**Clean pass, hand off forward.** If nothing rises to a `blocker`, sign
off. "No issues found" is a conclusion you have to earn, not a default:
only sign off clean when you have walked every changed hunk and have a
specific reason each is sound. Say briefly *why* the change holds up (what
you checked) so the human reviewer can trust the pass, then hand off
forward the normal way, by writing the review-ready marker.
 
**Blocking findings, bounce it back.** If any finding is a `blocker`, the
change is not ready for a human. Write up your findings, then send the task
back to the developer by writing the transition marker:
 
    printf '%s\n%s\n' "$TASK_ID" bounce \
      > .claude/shelbi-transition.tmp && mv .claude/shelbi-transition.tmp .claude/shelbi-transition
 
The first line is your task id (`$TASK_ID` in your environment); the second
is `bounce`, which sends the task back to the active status the developer
works. Do not sign off and bounce in the same run. Bouncing is the signal
that blockers exist.

Tune it to your stack: swap the .rs:120 example for your language, add a line about a framework you use, point the write-up at wherever your team keeps review notes. The shape is what matters: a reviewer that falsifies first and earns its sign-off.

How send-back works

A gate agent bounces a task by writing one file: the transition marker at <worktree>/.claude/shelbi-transition. It's the same kind of plain-file signal as the review-ready marker a finishing agent writes. Workspaces have no shelbi binary, so the agent just writes the file and the hub poller acts on it.

The format is two lines of UTF-8:

<task-id>
<target-status>
  • Line 1 is the agent's own task id. It has to match the task the workspace is currently assigned, or the poller treats the marker as stale and clears it without moving anything.
  • Line 2 is either a status id the workflow declares (the primitive, e.g. in-progress) or the verb bounce (equivalently reject), which Shelbi resolves to the workflow's active status. That verb is why the sample prompt can write bounce without hard-coding a status name.

Write it atomically, temp file then mv, so the poller never reads a half-written marker:

printf '%s\n%s\n' "$TASK_ID" bounce \
  > .claude/shelbi-transition.tmp && mv .claude/shelbi-transition.tmp .claude/shelbi-transition

It lives under .claude/ because that directory is Shelbi's gitignored deploy footprint, so the marker never dirties the worktree or trips a clean-branch check between tasks.

The workflow has the final say

Writing the marker is a request, not a guaranteed move. The poller applies it only if the workflow permits that edge. A backward bounce needs the adversarial-review → in-progress edge declared in the workflow's transitions, or the poller rejects the marker and the task stays put. Wiring that edge is the next part.

Confirm it landed

Check the agent is registered:

shelbi agent list

adversarial-review shows up in the table with no statuses referencing it yet. That's the next part. shelbi agent show adversarial-review prints the instructions.md you just wrote (without the shared preamble, which is composed in at launch time).

Next

You have a skeptic on disk with nothing to review. Wire it into a workflow so tasks actually flow through it:

Add it to a workflow →

See also

  • Agents — the role/task/workspace model and the on-disk agent layout.
  • shelbi agent — the full command set: list, show, new, edit.