@@ -51,15 +51,14 @@ folders you can restore, and commit first.
5151
5252## What runs, and what protects you
5353
54- ```
55- pi-dispatch run ── enqueue ──▶ Valkey + BullMQ ──▶ worker (on your host)
56- the wait-list budget check (before any spend)
57- docker run: one ephemeral container
58- │ --cap-drop=ALL, non-root, no new privileges
59- │ /job read-only, /workspace = your folder
60- ▼
61- pi + Playwright + git + gh
62- guardrails + your .pi/ → edits your folder
54+ ``` mermaid
55+ flowchart LR
56+ CLI["pi-dispatch run ./folder --task ..."] -->|enqueue| Q[("Valkey + BullMQ<br/>the wait-list, AOF")]
57+ Q --> B{"under the daily cap<br/>and turn budget?"}
58+ B -->|no| STOP["refused before any spend"]
59+ B -->|yes| C["docker run --rm: one ephemeral container<br/>--cap-drop=ALL, non-root, no-new-privileges<br/>/job read-only, /workspace = your folder"]
60+ C --> PI["pi + Playwright + git + gh<br/>guardrails + your .pi/"]
61+ PI -->|"edits in place"| F[("your folder")]
6362```
6463
6564- ** The container is the security boundary.** pi has no permission system, so every job runs
@@ -73,21 +72,55 @@ pi-dispatch run ── enqueue ──▶ Valkey + BullMQ ──▶ worker (on yo
7372
7473Read [ ` SECURITY.md ` ] ( SECURITY.md ) before you rely on it — it states plainly what is and is not defended.
7574
76- ## Advanced: GitHub automation * (in progress) *
75+ ## Advanced: GitHub automation
7776
7877pi-dispatch can also be triggered by GitHub — label an issue, and a container works it on a fresh clone,
79- opens a PR, and comments back. This path needs a ** GitHub App** and is ** not yet built** in this
80- repository; the local-folder path above is complete. When it lands:
78+ opens a PR, and comments back. A repo ** webhook** drives it (set a ` WEBHOOK_SECRET ` ), and the worker
79+ authenticates to GitHub via ` GITHUB_AUTH_SOURCE ` : ` gh ` (a ` gh auth token ` ) or a repo-scoped fine-grained
80+ ** PAT** by default. A GitHub ** App is optional** — it buys stronger token scoping and is what you need
81+ for multi-tenant.
82+
83+ ``` mermaid
84+ flowchart LR
85+ GH["GitHub repo<br/>issue labeled, or @pi comment"] -->|"webhook, HMAC-signed"| R
86+ subgraph EDGE["receiver/ — public edge, binds 0.0.0.0"]
87+ R["verify raw-body HMAC (401 on mismatch)<br/>filter: label allowlist, author gate, bot-loop"]
88+ end
89+ R -->|"enqueueGitHubJob (jobId = gh-<delivery>)"| Q[("Valkey + BullMQ<br/>pi-jobs, AOF, 31d+ retention")]
90+ subgraph HOST["worker/ — host process"]
91+ W["mint scoped token, refuse an unprotected branch,<br/>hardened clone at the default-branch SHA, run container"]
92+ end
93+ Q --> W
94+ W -->|"docker run --rm"| C["job container: the agent commits,<br/>pushes --force-with-lease, gh pr create, comments"]
95+ C -->|"GITHUB_TOKEN via env only, never merges"| GH
96+ ```
8197
8298- Only a collaborator's label or ` @pi ` comment starts a job (the label * is* the approval step).
83- - The agent gets a ** 1-hour, single-repo token** — and, honestly: that token * can* merge, because GitHub
84- gates push and merge behind the same ` contents: write ` scope. ** Branch protection on your default
85- branch is the real control** , so the worker will refuse an unprotected repo. ` SECURITY.md ` has the
86- detail.
99+ - The agent gets a ** repo-scoped, short-lived token** — and, honestly: that token * can* merge, because
100+ GitHub gates push and merge behind the same ` contents: write ` scope. ** Branch protection on your
101+ default branch is the real control** , so the worker ** refuses ** an unprotected repo. ` SECURITY.md ` has
102+ the detail.
87103- A separate ** admin panel** on ` 127.0.0.1 ` (never on the internet-facing receiver) will turn the queue
88104 on/off, show jobs, and set the model/budgets. It will not edit your persona or skills — those live in
89105 your project's ` .pi/ ` , in git, reviewed.
90106
107+ Every delivery runs the same gate before anything is queued — the signature is checked over the raw bytes
108+ * before* the body is parsed, and the ` sender.id ` bot-loop guard fires before the author check (so the
109+ receiver's own comments can never re-trigger a job):
110+
111+ ``` mermaid
112+ flowchart TD
113+ D["POST delivery"] --> V{"HMAC over the<br/>raw body valid?"}
114+ V -->|no| E401["401 — reject, enqueue nothing"]
115+ V -->|yes| S{"sender.id ==<br/>our own id?"}
116+ S -->|"yes"| D204a["204 — drop (bot-loop guard)"]
117+ S -->|no| A{"allowlisted label,<br/>or collaborator @pi?"}
118+ A -->|no| D204b["204 — drop"]
119+ A -->|yes| EN{"enqueue to Valkey"}
120+ EN -->|ok| A202["202 — queued<br/>(duplicate delivery = no-op, deduped by GUID)"]
121+ EN -->|"Valkey down"| E503["503 — GitHub redelivers,<br/>deduped by GUID"]
122+ ```
123+
91124## Should you use this instead of the Claude Code GitHub Action?
92125
93126For GitHub automation, often no — and you should know that up front.
@@ -99,8 +132,9 @@ minutes.
99132
100133## Status
101134
102- The local-folder path (image, worker, ` pi-dispatch run ` / ` worker ` ) is built and works. The GitHub
103- webhook path, the admin panel, and scheduled (cron) triggers are in progress. The design is specified in
135+ The local-folder path (image, worker, ` pi-dispatch run ` / ` worker ` ) and the GitHub webhook path
136+ (receiver → queue → clone → PR) are built and work. The admin panel and scheduled (cron) triggers are in
137+ progress. The design is specified in
104138[ ` specs/ ` ] ( specs/ ) — start with [ ` specs/constitution.md ` ] ( specs/constitution.md ) for the non-negotiables
105139and [ ` specs/design.md ` ] ( specs/design.md ) for the decisions and what was rejected.
106140
0 commit comments