pinrail

The review inbox for agent work

Before your agent acts,
it asks.

Pinrail is a desktop inbox for the moments your agent needs you: review comments about to be posted, emails about to be sent, an illustration or a voice-over still taking shape.

Download for macOSDownload for LinuxDownload for Linux on ARM64Windows: coming soonDownloadRead the docs

Free and open source · macOS and Linux, Windows soon · every download · in early development

Pinrail

Inbox Retry failed webhook deliveries with exponential backoff

Pendingcode-review v1northwind/api / pr-review #482claude41m ago

The code review view: a diff of deliver.ts and backoff.ts, one finding accepted with a note being written, one rejected with a reason.
Add a note for the agent…Review and hand over ⌘↵

Works with any agent that can run a command

01 · How it works

One command in, one decision out.

You tell the agent when to stop for you, in whatever instructions it follows. When it gets there, it asks through Pinrail and waits.

~/northwind/api — claude
> review PR #482 and post your comments, but ask me first
⏺ I read the diff: 3 files, 5 findings worth a comment. I'll ask you before any of them is posted.
⏺ Bash(pinrail submit code-review --title "Retry failed webhook deliveries" --data findings.json --wait)
⎿ pinrail: review r_01K5R2 submitted; waiting for a decision
⎿ r_01K5R2 · decided · Retry failed webhook deliveries
review · northwind/api · retry-webhooks · decided by maya at 2026-09-23 10:14
- **#1 accepted** `src/deliver.ts:42` — retry with backoff (major)
> re-enqueue with runAt
- **#3 accepted** `src/deliver.ts:57` — add jitter (minor)
- **#4 rejected** `src/log.ts:18` — give-up log (nit)
> the log already has the delivery id
Undecided: #2, #5
⏺ Posting #1 and #3 as you worded them. #4 stays out, and #2 and #5 were left undecided, so I will not post them.
⏺ Bash(gh pr review 482 --comment --body-file comments.md)
⎿ 2 review comments posted
✻Pinrailing… (0s · esc to interrupt)
pinrail plugins new my_plugin

The next one is yours. A plugin is a manifest, two JSON schemas and a web page, built with any web framework you like, or none at all. Storyboards, floor plans, moodboards, a map with a no-go zone scribbled on it: if your agent can make it, you can put it in front of your eyes and mark it up. The only limits are your imagination and your agent's.

Write a plugin →

03 · Rounds

A no comes with a reason, and the reason comes back.

Send something back and the agent answers with a new round, submitted with --revises. It lands beside your earlier verdicts and comments, so you see what changed and why, and a view can set the two rounds side by side to check each fix. Every round and every decision is kept, and can be opened again later.

04 · Stop

“Stop” is a real answer.

Discard a review with a reason and the waiting command exits 5. The agent stops the work the review was gating, reports why, and doesn't try again. Every ending has its code:

0Decided. The decision is on stdout, and in --decision-out if given.
3Withdrawn by the agent, or expired, before anyone decided.
4--timeout ran out. The review is still pending.
5Discarded: stop the work this review was gating.

05 · The CLI

For an agent, and for a script.

An agent reads the decision as markdown. A script keeps the JSON and branches on the exit code, and --decision-out writes the decision to a file for a workflow that already reads one.

agent — zsh
# submit the proposed comments and wait for a person
$ pinrail submit code-review --title "Dedup tickets on save" \
    --origin repo=acme/api,workflow=pr-review,ref=42 \
    --data proposals.json --wait
pinrail: review r_01K5Q8 submitted; waiting for a decision
r_01K5Q8 · decided · Dedup tickets on save
review · acme/api · pr-review · 42 · decided by maya at 2026-09-23 10:14

## Proposals

- **#18 accepted** `lib/acme/tickets.ex:149` — reversing twice is a no-op (major)
- **#19 accepted** `priv/repo/add_hash.exs:6` — missing index on tickets.hash (minor)
  > mention the COALESCE
- **#20 rejected** `lib/acme/tickets.ex:151` — prefer Enum.uniq_by/2 (nit)
  > fine as it is
- **#21 accepted** `test/acme/tickets_test.exs:40` — test for the empty list (minor)
$ echo $?
0

06 · Questions

Before you ask.

Who decides what a decision looks like?

The plugin. Each one declares what the agent sends and what comes back, as JSON Schema. JSON is the transport, HTML is the view.

  1. agent
  2. JSON
  3. HTML · you
  4. JSON
  5. agent
list/
├ manifest.json
├ schemas/payload.schema.json   in
├ schemas/decision.schema.json  out
└ view/index.html               the view
Can I make one for my own work?

Yes, or have your agent make it. A deploy to approve, alerts to triage, three designs to pick from: whatever needs a person.

$ pinrail plugins new my_pluginWrite a plugin →

Which agents work with it?

Any that can run a command: Claude Code, Codex, Cursor, a CI job, a shell script. Its own instructions say when to stop and ask.

Does anything leave my machine?

No. The app runs its server on loopback, and reviews and decisions stay on your computer. There's no account. A plugin's view is sandboxed: it can draw and talk to the app, and nothing else.

What if I'm away when it asks?

A notification tells you when a review arrives, and the menu bar shows how many are waiting. The app keeps listening with its window closed.

The agent waits, even through an app restart. With --timeout it stops waiting (exit 4) and the review stays in your inbox.

Decide when you're back. The agent picks up your decision with the CLI whenever it next looks:

$ pinrail wait <id>   # your decision
$ pinrail show <id>   # where it stands
What does it cost?

Nothing. Pinrail is free and open source under the Apache License 2.0. It runs on macOS and Linux and is in early development.

your move

Put a pinrail
in front of your agents.

macOS and Linux today, Windows soon.

Download for macOSDownload for LinuxDownload for Linux on ARM64Windows: coming soonDownloadStar on GitHub
Or skip the reading: review this page instead →