·5 min read

idea-to-prod: from a Notion card to a merged PR, with an agent doing the work

How a six-state Notion pipeline spawns a Spunto worker, hands it to an AI coding agent, and lets it implement, test, and open its own PR — plus the bridge that lets you redirect it from Slack mid-run.

Mathieu

Mathieu

Founder, Spunto

Most "ticket to code" automations stop at spawning an environment. This is what happens after: a pipeline where a single card carries a task from an idea to a merged pull request, with an AI agent doing the implementation, the testing, and the PR itself — on top of nothing more than a worker, a status field, and a couple of webhooks.

youflip statusorchestratorspawn workeragenttests passagentopens PRorchestratorPR mergedpending setupworker not up yetrunningwebhook firesimplementingagent writes codeto be testedgreen + proofin reviewPR openedcompletedPR mergedagent-in-worker · owns everything in betweenany statuscanceledworker torn down, regardless of statusYouOrchestratorAgent-in-workerCanceled
One status field, one database — the whole state machine. Two actors, split at the edges.

Six statuses on one field, on one Notion database, are the whole state machine:

pending setup running implementing to be tested in review completed

canceled at any point tears the worker down regardless of the current status.

Two actors share the states:

  • The orchestrator — a small webhook-driven service — only touches the edges. It reacts to running by spawning a worker, and to a merged PR by moving the card to completed and tearing the worker down.
  • Everything in betweenimplementing, testing, advancing to to be tested / in review, opening the PR — is the agent's job, running inside the worker itself.

That split is deliberate — more on why at the end.

A card just needs a title and a description; concatenated, they become the agent's first prompt. A project field maps the card to a target repo plus an org/project pair to spawn the worker in — leave it empty and the pipeline still spawns a worker, but skips everything repo-specific: no clone, no scripted agent session, just a bare environment.

Flipping the status to running fires a webhook. From there the orchestrator does exactly four things:

1. POST /orgs/{org}/projects/{project}/workers spawn
2. poll GET .../workers/{id} until state == "ready"
3. checkout a dedicated branch in the worker idea/{cardId}-{slug}
4. open a persistent terminal and launch the agent,
   with the card's title + description as its first prompt

It writes the worker URL back on the card and flips the status to implementing. That's the entire surface of the orchestrator for a running card — no test runner, no build step, no push, nothing repo-specific baked in.

test fails → loop back to Implement, card stays putresetrun e2egreenImplementon the branchResetclean, from scratchTestreal end-to-endProofvideo / suite outputall four holdadvance card · commit · pushopen the PR itself → in reviewhappy pathred test → retry
The card never advances on a red test — it just loops.

Every repo wired into the pipeline carries a short, repo-specific runbook at its root — how to reset to a clean environment, how to run the real test suite, what counts as proof, how to advance the card. The agent's first move, before writing any code, is to read that file.

The loop that follows is the same shape for every repo:

  • Implement the request on the checked-out branch.
  • Reset to a clean environment — not "restart and keep my local state", a real from-scratch environment, so what gets tested is what a reviewer starting from zero would actually see.
  • Test for real — drive the feature end to end (a worker actually spawned and taken to completion, a UI flow actually clicked through in a live browser), not a partial suite or an old screenshot.
  • Attach proof — a short video is the default; for a backend-only change, the test suite's output is enough.
  • If any of that fails, loop back to Implement — the card stays in implementing, it never advances on a red test.

Only once all four hold does the agent flip the card to to be tested, commit, push, and open the PR itself — then in review.

A GitHub webhook on the merge event is the only remaining trigger. It reads the branch name back to the card id, flips the card to completed, and tears the worker down. Nothing on the Notion side needs to happen after the merge — the loop closes itself.

session keeps runningopens socket, blockslistens againexits = the wake signallaunches listener as a background taskhuman sends a messagetask done → agent re-invokedagent repliesre-arms the next listenerSlack (human)bg listenercoding agent
No public webhook — the listener trades “stay open” for “exit on message”, so the exit becomes the notification.

The one gap in an otherwise self-driving pipeline: once the agent is deep in implementing, you may want to redirect it — without opening a terminal into the worker yourself.

The constraint: an interactive coding-agent session doesn't expose a live "inject a message" channel from the outside — the only documented entry point is what happens when the session pauses. Blocking that pause to wait on an external message freezes the whole session, which isn't what you want mid-implementation.

The workaround reuses a primitive that's already there for a different purpose: a background command that, when it finishes, wakes the agent up with its output.

  • A small script opens a socket connection to the chat backend and blocks, waiting for the next human message.
  • It's launched as a background task — the coding session keeps running normally in the meantime.
  • The moment a message arrives, the script exits.
  • The background task finishing is itself the wake signal — the agent is re-invoked with that message as new input.
  • The agent handles it, replies, and re-arms the same listener for the next message.

No public webhook, no extra always-on process — just a listener that trades "stay open" for "exit on message", so the exit itself becomes the notification.

The orchestrator only owns state transitions it can verify unambiguously from the outside: worker spawned, PR merged. Everything that requires judgment — is this actually tested, is this actually done — stays with the agent, guided by a plain-text runbook per repo instead of a hardcoded test command.

Adding a new repo to the pipeline is writing one file, not touching the orchestrator's code at all.

Mathieu

Mathieu

Founder, Spunto

Building Spunto — self-hosted dev environments for engineers who want their compute to work for them, not against them.