An EmDash issue and pull-request author service built on Pi Durable, hosted in Cloudflare Durable Objects through PiHarness. It preserves completed source changes through interruptions, publishes only saved candidates through trusted code, carries review findings across revisions, and reports readiness from exact-head evidence. Human approval is always required to merge.
IMPLEMENTATION_PLAN.md is the design and records the implementation status of each slice. docs/staging.md is the guide for human testing on the staging Worker.
The bot keeps as little machinery as possible on top of Pi. Trusted code decides who may talk to the author, keeps source safe, publishes, and enforces budgets. Everything else is the author's judgement.
- Intake. A maintainer applies the intake label (
intakeLabel:emdash-botby default,bot:fixin production) to an issue or same-repository PR, or assigns a configured machine user; removing it cancels. A maintainer's comment that starts by addressing the bot (@emdash-autofix ...) assigns it too, with the comment recorded as maintainer instructions in the case record; on a case the bot already has, it is delivered as their comment. The bot keeps the intake label on whatever it is working on (an issue assigned by comment, its own pull request, an accepted auto-intake issue), and removing the label from the issue or the pull request stops it and takes the bot's labels off both. Taking over a contributor's pull request, the author is told who opened it, gets its linked issues in the case record, the maintainers' earlier comments and reviews, and any other active bot case on a linked issue. WithautoIntakeon, every newly opened issue is admitted too, except issues maintainers open (they assign the bot explicitly;autoIntakeMaintainerslifts this, for staging tests). Clef (@cf/cloudflare/clef) triages it first: likely spam is declined silently, feature requests, questions and large changes are declined with a short message, and only clear, small bugs or tasks reach the author (with a "needs information" note when the report is not actionable). The author can stilldecline. An issue the bot accepts gets the intake label at once, so it is visible and a maintainer can remove the label to stop it; the bot's own label event assigns nothing, and it removes the label again if it declines. Thresholds aretriagein the config; a Clef failure declines silently. Webhooks are verified, allowlisted and recorded durably byRepositoryDO, then forwarded to oneCaseDOper issue or PR. - Messages to the author. Every admitted event becomes one message to Pi, labelled with who sent it and how their authority was established: the assignment, comments from maintainers, the configured reviewer or the reporter, reviews with their inline comments, maintainer pushes, failing checks, and the bot's own milestones (published, ready for human review). Pi's inbox queues messages that arrive mid-run. There are no rounds or state machine.
- Source and publication. Pi's read, write, edit and bash run in a Cloudflare Sandbox container through a Pi
ExecutionEnvadapter. Every source mutation is checkpointed to R2 and committed in the case's SQLite before the tool returns.submit_candidateseals a checkpoint; trusted code publishes it through GitHub's Git Data API after materializing it in a fresh sandbox and checking the tree hash, with durable intents and reconciliation for lost responses. Pull requests open ready for review. When GitHub reports that the pull request no longer merges, the author is told once per head;merge_default_branchmerges the default branch's head into its work (using the merge base from GitHub, so the shallow checkout needs no history) and leaves conflict markers to resolve, and the resulting candidate publishes as a merge commit of the branch head and that default-branch head, as GitHub's "Update branch" does. - Talking. The author answers with
reply(on the PR, in a review thread, or on the issue), keeps a short plan withupdate_work_plan, and records the reporter's confirmation with evidence. The bot keeps one progress comment, with the plan and the evidence-based status, on the issue until the PR opens and on the PR after. On the PR head it also keeps anEmDash botcheck run: in progress while the author works,successwhen ready for human review, otherwiseneutral(neverfailure; readiness comes from required checks and review). This needs the App'schecks: writepermission; without it only the comment is posted. The bot also labels the issue and its PR so people can find work:triage/awaiting-reporter(ready, reporter not yet confirmed),triage/verified(reporter confirmed),triage/skipped(declined) andtriage/failed(model or platform failure), plusarea/*labels from the PR's changed paths (areaLabelsper repository). Labels are derived from recorded evidence and never read back. - Reporter confirmation. With a repository
previewrule, the first time the preview of the bot's pull request is up (the configured check passes on its head), trusted code collects the preview URL from the configured bot's comment and any package builds from the packages check, and tells the author, which asks the reporter on the issue to try it. Later previews refresh the links incase_status. Without a preview, the author asks once the pull request is ready for human review. A confirmation is recorded withrecord_reporter_confirmationagainst the reporter's own comment and labels the issue and pull requesttriage/verified; it never gates merging. - Public board.
GET /is a read-only board of every case, grouped by who acts next (needs a maintainer, ready for review, working or queued, waiting, finished this week), andGET /api/casesis the same data as JSON;?issue=Nreturns the cases for one issue or pull request. Each case publishes a summary row to itsRepositoryDOwhen what it shows changes, so the board reads one table per repository, and responses are cached for 30 seconds. It shows only what is already public on GitHub; transcripts, prompts and costs stay behind/ops.POST /ops/board/rebuildasks every case to publish its row again, andGET /ops/servicereports the deployed version, model and prompt hashes. Production also serves onautofix.emdashcms.com. - Readiness. Status is computed from evidence and the live PR: required checks on the exact head (a skipped check counts as passing, as in GitHub), a review of that head (a maintainer's approval or change request, else the review bot's latest review; only a change request blocks, and what a commented review needs is the author's judgement), and mergeability. The bot carries everything else itself: it re-runs required Actions jobs that failed for infrastructure reasons (twice per head), asks the review service for a review when someone else pushed or the review failed, retries failed model calls with backoff, continues in a fresh run when a run hits its time limit, rebuilds when publication stops, and is reminded when a run ends without a next step. A maintainer's review and merge is the step that needs a person. It reports; it never gates the author. Human approval is always required to merge.
Use Node 24 and pnpm (pinned in package.json). Production builds and deploys also build the sandbox image and need Docker; tests do not.
pnpm install --frozen-lockfile
pnpm check # format, lint and type check
pnpm test # build, then unit, recovery and revision suites in Miniflare
pnpm evals recovery --target localThe integration tests run the built Worker in Miniflare from Node, with a fake GitHub (real bare git repositories behind Miniflare's outbound service) and a fake sandbox (real git in temporary directories). A scripted author, registered only outside production, drives deterministic runs and fault injection.
| Path | Contents |
|---|---|
src/cases/ |
CaseDO (Pi host, admission, rounds, status), RepositoryDO (routing and delivery inbox), case store |
src/runtime/ |
Model wiring (Workers AI and AI Gateway) and the scripted author |
src/tools/ |
Case-scoped author tools wrapping Pi's built-ins with checkpoints |
src/source/ |
Patch validation and trusted git operations |
src/execution/ |
Sandbox Durable Object, egress gate and Pi ExecutionEnv adapter |
src/github/ |
Webhooks, auth, publisher, reviewer adapter and check classification |
src/reports/ |
Deterministic status and PR text |
evals/ |
Evaluation controller, fixture manifests and private graders |
scripts/ |
Operator CLI and GitHub App setup |
profiles/staging.json |
Staging bot configuration |
cloudflare.config.ts configures the staging Worker emdash-bot-pi on the EmDash CMS account. pnpm run deploy:staging deploys it with secrets from the ignored .env.staging. The production Worker (emdash-bot) is not configured here; a production profile and Workers Builds are slice 6 operator actions.