def index
@deals = policy_scope(Deal).paginate(cursor: params[:cursor])
render json: {
data: @deals,
next: @deals.next_cursor,
}
end// PRODUCT — FEATURED
SWARM
> Agent Development Environment
A desktop IDE for coding agents. Run 1–4 Claude Code or Codex agents in parallel Git worktrees, watch each one in a live PTY, review the diff with per-line comments, then merge or open the PR — all in one window.
// IN THE APP
The real app, not a mockup

// 02 — LIVE
Every task plans before it codes.
A task is born pending, with no worktree — creating one costs neither disk nor money. Start creates the worktree, the planner writes the plan and exits, and only then does on_exit bring up the coder. No non-empty plan, no coder.
// 03 — REVIEW
Attempts side by side, as tabs.
The screen is a tree of splits written into the URL. Any surface becomes a tab — the agent, a terminal, the chat, the diff, the preview, a file, or another attempt at the same prompt. That is how two different CLIs on one prompt end up next to each other.
?panes=agent;(diff|preview) · ; splits · | stacks · , tabs · () nests
def index scope = policy_scope(Deal).order(:id) @deals = scope.after(params[:cursor]).limit(25) render json: DealSerializer.wrap(@deals) end
def index
paginator = CursorPaginator.new(
scope: policy_scope(Deal),
cursor: params[:cursor],
per: 25,
)
render json: paginator.page
enddef index
@deals = DealQuery
.for(current_user)
.cursor_page(params[:cursor])
render json: @deals
end// 04 — CAPABILITIES
An environment, not a wrapper around one CLI.
Eight things it does, all on your machine.
Planner writes a PRD, then coder
The handoff is a PTY phase, not a job: the planner runs in the same pane, writes a PRD — problem, requirements, acceptance criteria, risks, steps — and exits, and on_exit starts the coder pointed at it. A PRD with no acceptance criteria stops the task with that reason: “the planner failed” never turns into “code in the dark”.
Claude Code and Codex
Two adapters behind one map — 1 to 4 agents in parallel, each in its own worktree and branch. A new CLI is a map entry plus a class; the validation and the form follow from there.
Real PTYs
PTY.spawn per pane, registered in memory by (task, pane), streamed base64 over ActionCable. Stop escalates HUP → TERM → KILL, and scrollback flushes to disk every 5s so an abrupt death loses at most 5 seconds.
Panes live in the URL
The layout is a tree of splits serialized into the query string. Switching tabs is reordering, closing is removing, splitting is inserting — every action is a link, so reload, back button and bookmarks all just work.
Diff review that talks back
Committed, uncommitted and untracked against merge-base, parsed server-side. Comment on any file:line and the comment goes back to the agent as an instruction.
PR with real evidence
The body is written server-side by a headless agent from the real diff, the original prompt and the actual check results — “no check command” and “never ran” stay distinct from a real result. The golden path is recorded in headless Chrome and embedded as a GIF.
Preview and Design Mode
The worktree's app boots in a PTY behind a proxy whose port detection does not rely on the stack honoring $PORT. Design Mode annotates the live screen like Figma comments — the element's live state is serialized from the DOM and re-rendered with the user's session, not screenshotted.
Loops with guards
Turn, Goal, Time and Proactive, all behind the same non-negotiable guards: cost ceiling, attempt ceiling, global kill switch, judgment timeout. An unreadable transcript stops the loop instead of counting as zero spent.
// 05 — AUTOMATIONS
Work that starts without you.
One Automations page holds everything that fires on its own — schedules, triggers and boards — with the controls, not just the list. Every run still goes through the cost, attempt and kill-switch guards.
Scheduled tasks
A cron expression, parsed by Fugit, fires a prompt into a fresh worktree on schedule. Optional run cap, pause and resume, and three honest states: active, paused and finished. Lowering the cap stops the schedule, so the panel and the scheduler never disagree.
CI failure starts an agent
A failed GitHub check suite opens a task on its own: HMAC-SHA256 over the raw body, actor allowlist, budget, run cap and cooldown. Branch and SHA pass a strict regex and the repo is pinned to the real git origin, so nothing from the payload reaches the prompt raw.
Webhook relay on AWS
Swarm runs behind NAT in WSL, where GitHub cannot reach it. A Lambda provisioned with Terraform keeps the raw body and headers in SQS without validating anything; the app drains the queue every minute over an outbound connection and runs the same HMAC check and deduplication as a direct delivery.
Trello, both ways
A card you are added to becomes a pending task — no worktree, no agent, nothing spent until you press Start. The other way, the card moves to Doing, PR and Done and gets a comment as the task starts, opens a PR and is merged or discarded.
Jira, Linear and GitHub
Tracker panels next to Tasks: Jira Cloud on the current /search/jql endpoint, Linear over GraphQL, and a GitHub panel for issues, PRs and projects in GitHub's own query syntax. An expired credential shows as an error, never as an empty backlog.
Goal loop with a judge
The PRD's acceptance criteria feed the Goal loop. A judge — QA gate, custom shell or evaluator — checks every attempt and retries until it passes, and every stop is named: passed, judge crash, budget exhausted, usage unavailable or attempts exhausted.
// 06 — INSIDE
What happens when you press Start.
A pending task has no worktree. Start creates it, the planner writes a PRD and hands off to the coder in the same PTY, and everything the agent produces streams back through a single Puma process — the registry lives in memory, so WEB_CONCURRENCY is forbidden by design.
// 07 — STACK
Built on.
// 08 — MILESTONES
What is already in.
Everything below is shipped and running — this is the tool as it exists, not a plan.
- 01
Orchestrator
Isolated worktrees and branches, live PTY streaming, diff review, merge / discard / PR.
- 02
Compare
N attempts at the same prompt reviewed side by side, per-line comments flowing back into the agent.
- 03
Workspace
Splittable panes serialized into the URL, with the explorer, editor, preview, chat and any file as draggable tabs, plus a Jump Palette for tasks, projects and commands.
- 04
Two CLIs, one planner
Claude Code and Codex adapters, and a planner phase that writes a PRD for every task before any code is written.
- 05
PR with evidence
Body written by a headless agent from the real diff and real check results; golden path recorded in headless Chrome as an inline GIF.
- 06
Design Mode
Annotate the running app like a Figma comment — the element is serialized from the live DOM and re-rendered with the user's session.
- 07
Automations
Cron schedules, CI-failure triggers through an AWS relay (Lambda + SQS on Terraform), Trello both ways, and Jira, Linear and GitHub panels.
- 08
Desktop
Tauri 2 window on a fixed port with its own process group; WebView2 on Windows, with Rails running inside WSL.
- 09
CI/CD
CI, infra and release workflows on GitHub Actions: RSpec, RuboCop and a security scan on every PR and push to main, and the Windows .exe built and released by Actions.
- 10
tmux sidecar
tmux as the backend for agent, shell and preview panes — phase A of ADR 001, shipped behind SWARM_TMUX=1.
// 09 — STORY
How it started.
I wanted an IDE for coding agents, not a wrapper around one. Register a local repo, fire a prompt, and 1 to 4 agents go at it in parallel — each in its own Git worktree and branch, each in a live PTY, with the file explorer, the editor, the running app, the chat and the diff all in the same window.
It is a Rails 8 app on Hotwire and SQLite, single-process by design: the PTY registry lives in Puma's memory, so WEB_CONCURRENCY is forbidden — two processes mean two registries and a UI showing an empty terminal for an agent that is very much running. Tauri 2 wraps it as a native window; on Windows that is WebView2 with Rails running inside WSL.
One rule ended up running through the whole thing: unknown must never become a value. A model missing from the price table pays the most expensive row. An unreadable transcript returns :unknown and stops the loop instead of counting as zero spent. And a prompt that assembles evidence has to tell the model where the hole is — because a prompt without evidence does not answer “I don't know”, it answers with a plausible invention.
// 10 — TRY IT
Point it at a repo and press Start.
Swarm runs on your machine, against your local repos. The Claude Code plugin ports the same fan-out into the CLI.