@edgehero/pi-dispatch-admin 1.10.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,13 +1,29 @@
1
1
  <p align="center">
2
- <img src="https://raw.githubusercontent.com/edgehero/pi-dispatch/main/docs/images/banner.png?v=0.5.0" alt="pi-dispatch: run the pi coding agent as a self-hosted service" width="880">
2
+ <img src="https://raw.githubusercontent.com/edgehero/pi-dispatch/main/docs/images/banner.png?v=2.0.0" alt="pi-dispatch: run the pi coding agent as a self-hosted service" width="880">
3
3
  </p>
4
4
 
5
5
  # pi-dispatch: run the pi coding agent as a self-hosted service
6
6
 
7
- **Let a coding agent work on your repositories while you are not watching, without surprise bills and without giving it the keys to your machine.** [pi](https://github.com/earendil-works/pi) is a superb coding agent, but it has no job queue, no spend limit, and, by its own README, no permission system. pi-dispatch adds exactly those, so you can run pi unattended and safely: on a cron schedule, on your repo's issues and PRs, or straight from the CLI. Every job runs in an isolated container, spend is bounded before a single token is spent (per deployment and per-repo or per-folder), and everything the agent did (and what it cost) is recorded, graphed and priced for you. On a forge you stay in the loop at both ends: a job starts only when someone with write access to the repo asks for one, by a label, an `@pi` comment or a review, and what comes back is a pull request you review and land, because pi-dispatch never merges anything. This is not another agent to weigh against the one you already use; it is the queue, the budget and the box for the pi you already run.
7
+ **Let a coding agent work on your repositories while you are not watching, without surprise bills and
8
+ without giving it the keys to your machine.**
8
9
 
9
- > This npm package, **`@edgehero/pi-dispatch-admin`**, is the **operator console** (a pi extension). The service itself is `@edgehero/pi-dispatch` (worker + CLI) and `@edgehero/pi-dispatch-receiver` (the webhook edge); the [main repo](https://github.com/edgehero/pi-dispatch) has the container image, docs, and SECURITY.md.
10
+ [pi](https://github.com/earendil-works/pi) is an open source coding agent that you normally run in a
11
+ terminal. It has no job queue, no spend limit and, by its own README, no permission system. pi-dispatch
12
+ adds exactly those, so pi can run unattended: on a cron schedule, on your repo's issues and PRs, or from
13
+ the command line. Every job runs in its own container. Spend is checked before a single token is spent,
14
+ for the whole deployment and per repo or folder. Everything the agent did, and what it cost, is recorded,
15
+ graphed and priced.
10
16
 
17
+ On a forge you stay in control at both ends. A job starts only when someone with write access to the repo
18
+ asks for one, with a label, an `@pi` comment or a review. What comes back is a pull request that you
19
+ review and merge yourself, because pi-dispatch never merges anything. It is not another agent to compare
20
+ with the one you use. It is the queue, the budget and the box around the pi you already run.
21
+
22
+ > This npm package, **`@edgehero/pi-dispatch-admin`**, is the **operator console**, a pi extension. The
23
+ > service itself is [`@edgehero/pi-dispatch`](https://www.npmjs.com/package/@edgehero/pi-dispatch)
24
+ > (worker and CLI) and [`@edgehero/pi-dispatch-receiver`](https://www.npmjs.com/package/@edgehero/pi-dispatch-receiver)
25
+ > (the webhook edge). The [main repo](https://github.com/edgehero/pi-dispatch) has the job image, the
26
+ > docs (Docker, Podman, egress, every forge) and SECURITY.md.
11
27
 
12
28
  ## How it works
13
29
 
@@ -20,66 +36,141 @@ Every trigger produces the same job, through the same path: one queue, one conta
20
36
  Valkey + BullMQ durable queue: survives reboots, absorbs bursts
21
37
  │
22
38
  ▼
23
- under the daily cap + if not: refused here, before any spend
24
- per-job turn budget?
39
+ under the day, week and if not: refused here, before any spend
40
+ month caps, the scoped
41
+ limits and the turn budget?
25
42
  │ yes
26
43
  ▼
27
- docker run --rm one ephemeral container per job: --cap-drop=ALL,
28
- --security-opt no-new-privileges, memory/CPU/pids
29
- limits, /job mounted read-only
44
+ one container per job Docker or Podman, removed after the job: all
45
+ capabilities dropped, no-new-privileges,
46
+ memory/CPU/pids limits, /job mounted read-only
30
47
  │
31
48
  ▼
32
49
  pi + your .pi/skills edits your code in place, opens a PR or MR, comments back
33
50
  ```
34
51
 
35
- The container is the boundary (pi's missing permission system, enforced by Docker). Those isolation flags are built by the worker's own `docker run` argv, so nothing an image contains can weaken them; the non-root user is a property of the **image**, which is why an image has to meet the conformance checklist in [`docs/job-image.md`](https://github.com/edgehero/pi-dispatch/blob/main/docs/job-image.md). Spend is checked before a container starts, so a runaway or a junk trigger costs a refusal, not a surprise bill. The job image is yours to shape, per deployment or per trigger, and it ships Playwright and Chromium so a flow can build a frontend, screenshot it, and iterate on the render.
52
+ The container is the boundary, and it is pi's missing permission system. The worker builds the isolation
53
+ flags itself, so nothing an image contains can weaken them. The non root user comes from the **image** on
54
+ Docker Desktop (and for a worker running as uid 1001), and from the worker's own uid on a native Linux
55
+ daemon. That is why an image has to meet the checklist in
56
+ [`docs/job-image.md`](https://github.com/edgehero/pi-dispatch/blob/main/docs/job-image.md). What each
57
+ container runtime guarantees is declared in
58
+ [`docs/backends.md`](https://github.com/edgehero/pi-dispatch/blob/main/docs/backends.md), and
59
+ `pi-dispatch doctor --live` reads it back from real containers.
60
+
61
+ Spend is checked before a container starts, so a runaway or a junk trigger costs a refusal, not a bill.
62
+ The job image ships Playwright and Chromium, so a flow can build a frontend, screenshot it and iterate.
63
+
64
+ **What the agent can see.** Two credentials go into each job as environment values, the provider key and
65
+ the forge token, and **the agent can read both**. The guardrails tell it never to print or send them, but
66
+ that is prompt text, not enforcement. Set a spend limit at your provider. On GitHub, the default `gh` auth
67
+ source forwards **your whole login, full scope and never expiring**, and `pi-dispatch doctor` warns about
68
+ it; the GitHub App gives each job a one hour token for one repo instead. Network egress is denied by
69
+ default and goes through an allowlist proxy, and the allowed hosts are yours to list
70
+ ([`docs/egress.md`](https://github.com/edgehero/pi-dispatch/blob/main/docs/egress.md)).
36
71
 
37
72
  ## Triggers: what starts a job
38
73
 
39
- Every trigger is one `{ on, run }` entry in a single `triggers.json`, read live by the worker (cron) and the receiver (forge webhooks), and editable from the console. **`on` is what fires it; `run` is the skill it runs.**
40
-
41
- | `on.type` | Fires on | What narrows it | What the agent gets as its task |
42
- |---|---|---|---|
43
- | `cron` | your schedule | nothing: a schedule is its own condition | the `task` written in the file |
44
- | `label` | a label on an **issue** (or an Azure work item), never a pull request | a label predicate: `any`, `all`, or `none` (which can suppress a fire, never cause one) | the issue title and body |
45
- | `comment` | a comment containing your phrase, for example `@pi` | the phrase, and one comment trigger per forge | the comment body, plus the issue title and body |
46
- | `pull_request` | a PR or MR event, including its close (the close word rides alone, never mixed with other actions; on GitHub and Forgejo a merged PR counts as closed, on GitLab only an explicit close fires it) | `action`, in your forge's own words, plus the same label predicate; on a close-only rule, `number` and `once` instead | the PR title and body |
47
- | `issue` | an issue closing | `action` (the forge's close word), `number` to pin one issue, `once: true` for a one-shot that spends itself after a single run | the issue title and body |
48
-
49
- Four forges: GitHub, GitLab, Forgejo (and Gitea), Azure DevOps. **Who may fire a trigger is your forge's decision, not this service's**: on GitHub the label *is* the approval, because only collaborators can apply one, while GitLab, Forgejo and Azure resolve the actor's permission through their APIs. A close trigger is gated on the actor who **closed** the item, resolved the same way (Azure has no close trigger yet: it refuses at load rather than never matching). Each forge's action vocabulary is validated when the file loads, so a word from the wrong forge is refused instead of silently never matching.
50
-
51
- **Flows, and workflows.** `run.flow` names a skill committed to the target repo at `.pi/skills/<flow>/SKILL.md`, read from the **default branch**, so the repo owns the prompt and merging it is the repo's consent. A skill may call other skills, which is already a workflow. For typed multi-stage ones, a pi extension such as `@juicesharp/rpiv-workflow` can be staged into the deployment: pinned to an exact version, installed on your host (never at job time, since jobs run offline), loaded in every container, and declinable per trigger. Anything you already installed with `pi install` is staged automatically, at the version your host has, so there is nothing to declare twice. `run.command` is the second entry point: instead of a flow, a trigger may name a registered command a staged extension provides (`"command": "wf run nightly"`), and the job's whole prompt is that dispatch line, handled by the extension with no model turn in between. It picks which vetted command dispatches, never what code runs, and it is never AI-triggerable: job chaining refuses any request naming one, and `dispatch_run` cannot express one.
52
-
53
- **How a workflow gets triggered, in one line:** `label / comment / PR / cron` fires **one job in one container**, that job runs `run.flow` or `run.command`, and the entry point drives whatever stages follow. Four consequences worth knowing before you build on it. A trigger names a **flow or a registered command, never a workflow**, so which stages run is decided by what that entry point does. A job is **not an interactive session** (the container hands pi one assembled prompt and reads the exit line), and that one prompt can be the command itself: `run.command` dispatches a workflow extension's slash command headlessly, while in a flow job the flow's instructions drive the workflow, or a small extension you also staged calls its API from a lifecycle hook. One trigger is **one job, one budget slot and one turn budget**, so ten stages share the same `PI_MAX_TURNS` and per-job token budget, and exhausting either ends the job as a policy refusal that is never retried. And whether the workflow's own state survives depends on the trigger kind: a cron or CLI job has your folder mounted read-write so state persists between runs, while a forge job gets a fresh clone that is discarded with the container. Full reference: [`docs/workflows.md`](https://github.com/edgehero/pi-dispatch/blob/main/docs/workflows.md).
74
+ Every trigger is one `{ on, run }` entry in a single `triggers.json`. The worker reads it live for cron,
75
+ the receiver reads it live for forge webhooks, and the console can edit it. **`on` is what fires it;
76
+ `run` is the skill it runs.**
77
+
78
+ | `on.type` | Fires on | What the agent gets as its task |
79
+ |---|---|---|
80
+ | `cron` | your schedule | the `task` written in the file |
81
+ | `label` | a label on an **issue** (or an Azure work item), never a pull request | the issue title and body |
82
+ | `comment` | a comment containing your phrase, such as `@pi` | the comment, plus the issue title and body |
83
+ | `pull_request` | a PR or MR event, including its close | the PR title and body |
84
+ | `issue` | an issue closing (`once: true` makes it a one-shot) | the issue title and body |
85
+
86
+ Four forges: GitHub, GitLab, Forgejo (and Gitea), Azure DevOps. **Who may fire a trigger is your forge's
87
+ decision, not this service's.** On GitHub the label *is* the approval, because only collaborators can add
88
+ one. GitLab, Forgejo and Azure check the actor's permission through their APIs. A close trigger checks the
89
+ person who **closed** the item (Azure has no close trigger yet, and refuses one at load). Each forge's
90
+ action words are checked when the file loads, so a word from the wrong forge is refused instead of never
91
+ matching. The full reference is
92
+ [`docs/triggers.md`](https://github.com/edgehero/pi-dispatch/blob/main/docs/triggers.md).
93
+
94
+ **Flows and workflows.** `run.flow` names a skill committed to the target repo at
95
+ `.pi/skills/<flow>/SKILL.md`, read from the **default branch**. The repo owns the prompt, and merging it
96
+ is the repo's consent. A skill may call other skills, which is already a workflow. For typed multi-stage
97
+ workflows, stage a pi extension such as `@juicesharp/rpiv-workflow` into the deployment. It is pinned to an
98
+ exact version, installed on your host (never at job time, since jobs run offline), loaded in every
99
+ container and declinable per trigger. Anything you installed with `pi install` is staged automatically at
100
+ your host's version.
101
+
102
+ `run.command` is the second entry point. A trigger may name a registered command that a staged extension
103
+ provides (`"command": "wf run nightly"`). The job's whole prompt is that line, handled by the extension
104
+ with no model turn in between. It picks which vetted command runs, never what code runs, and it is never
105
+ AI triggerable: job chaining refuses a request naming one, and `dispatch_run` cannot express one.
106
+
107
+ Four things to know before you build on it:
108
+
109
+ - A trigger names a **flow or a command, never a workflow**. That entry point decides which stages run.
110
+ - A job is **not an interactive session**. The container hands pi one prompt and reads the exit line.
111
+ - One trigger is **one job, one budget slot and one turn budget**. Ten stages share the same
112
+ `PI_MAX_TURNS` and token budget, and running out ends the job as a policy refusal that is never retried.
113
+ - **State survives only on local jobs.** A cron or CLI job has your folder mounted read-write, so state
114
+ persists. A forge job gets a fresh clone that is thrown away with the container
115
+ ([`docs/workflows.md`](https://github.com/edgehero/pi-dispatch/blob/main/docs/workflows.md)).
54
116
 
55
117
  ## The console: `/dispatch`
56
118
 
57
- One command puts a live TUI over the whole deployment:
119
+ One command puts a live terminal view over the whole deployment:
58
120
 
59
121
  <p align="center">
60
- <img src="https://raw.githubusercontent.com/edgehero/pi-dispatch/main/docs/images/dispatch-dashboard.png?v=1.0.0" alt="The /dispatch panel: status, spend meters, triggers (including a command trigger shown as its /name), pause windows, scoped limits, runs, and settings" width="820">
122
+ <img src="https://raw.githubusercontent.com/edgehero/pi-dispatch/main/docs/images/dispatch-dashboard.png?v=2.0.0" alt="The /dispatch panel: status, spend meters, triggers, pause windows, scoped limits, held and failed jobs, runs, and settings" width="820">
61
123
  </p>
62
124
 
63
- - **Status and spend.** Queue and worker state, day/week/month spend meters, a daily token counter, and a run-history table with per-job tokens and cost.
64
- - **Insights, the one analytics page.** Press `i` on the panel (or type `/dispatch insights`) and a single self contained page opens in your browser: the budget dials (the one lever that actually changes what all of this costs), per-plan verdicts against API rates, daily, cumulative and per-flow spend charts, the four breakdowns (flow, trigger, model, repo), and the whole trigger and flow topology with spend badged onto the triggers that earned it. Every dollar carries its class: a plan-covered run never renders as $0.00, and an estimate is always marked as one. Design your agent loops as triggers and skills, see the loops you actually built, and see what each one costs, all in one place ([`docs/insights.md`](https://github.com/edgehero/pi-dispatch/blob/main/docs/insights.md)).
125
+ - **Status and spend.** Queue and worker state, day, week and month spend meters, a daily token counter,
126
+ and the run history with tokens and cost per job. `Enter` on a run opens its full record, `x` on a
127
+ running job cancels it after asking, and `b` reopens a finished run's workspace.
128
+ - **Insights, the one analytics page.** Press `i` (or type `/dispatch insights`) and one self contained
129
+ page opens in your browser. It shows the budget dials, plan verdicts against API rates, daily,
130
+ cumulative and per flow spend charts, breakdowns by flow, trigger, model and repo, and the trigger and
131
+ flow topology with spend on each trigger. A plan covered run never shows as $0.00, and an estimate is
132
+ always marked as one ([`docs/insights.md`](https://github.com/edgehero/pi-dispatch/blob/main/docs/insights.md)).
65
133
 
66
134
  <p align="center">
67
- <img src="https://raw.githubusercontent.com/edgehero/pi-dispatch/main/docs/images/insights-view.png?v=1.0.0" alt="The insights page: KPI tiles, a plan verdict card, the daily spend chart, the four breakdowns with plan-covered buckets drawn as chips instead of dollar bars, and the topology with spend badges and tier-resolved flows" width="820">
135
+ <img src="https://raw.githubusercontent.com/edgehero/pi-dispatch/main/docs/images/insights-view.png?v=2.0.0" alt="The insights page: KPI tiles, budget dials, a plan verdict, spend charts, the four breakdowns, and the topology with spend badges" width="820">
68
136
  </p>
69
137
 
70
- - **Triggers, editable live.** cron, label, comment, pull_request and issue triggers with colored drill-ins showing what fires each one, what it runs, and its trust model. Added, edited and deleted without a restart. A one-shot (`once: true`) shows armed or spent in its drill-in: a spent entry stays in the list, matches nothing, and re-arms when the operator deletes its `on.disarmed` mark from the file. Triggers that run third-party code or a custom image are badged; opting in or out of either stays an edit to the reviewed `triggers.json`, which neither the console nor a model-callable tool will make for you.
71
- - **Quiet hours.** Scheduled pause windows per folder or repo: defer runs between certain times, timezone-aware, and resume automatically. Deferred, never dropped, at zero budget cost.
72
- - **Scoped limits.** Per-repo or per-folder job caps (day, week, month) refused before any spend, plus a per-scope concurrency ceiling enforced by deferral. Local jobs also hold a one-job-per-folder guard that is always on and has no switch (it lives in the worker process, and one worker per docker daemon is the supported shape), because two agents editing one working tree race each other with no gate and no undo.
73
- - **Held jobs, with what they are waiting for.** A trigger carrying `run.waitFor` holds its job in the queue, unstarted and unbilled, until an instant passes or a check script the operator wrote exits 0. The panel grows a **held** section while anything waits (the target, the condition, and how long it has waited) and hides it again when nothing does, so a wait stops being an anonymous entry in the delayed count. `dispatch_waits` lists them and `dispatch_wait_cancel` stops one behind the operator confirm, which is the only way to cancel a held job short of editing redis by hand ([`docs/wait-for.md`](https://github.com/edgehero/pi-dispatch/blob/main/docs/wait-for.md)).
74
- - **AI-operable, with a human gate.** Model-callable tools let an agent change limits (global and scoped) and manage triggers and pause windows, and every **config** write pops an operator confirmation the model cannot answer, refusing outright when no operator is present. Two tools sit outside that gate on purpose: `dispatch_pause` and `dispatch_resume` write durable queue state but are reversible and spend nothing, so they carry no confirm. One more sits outside it and is **not** money-safe: `dispatch_run` enqueues a **paid** run that edits a local folder in place with no undo, bounded instead by six independent limits (the `PI_DISPATCH_RUN_ROOTS` folder allowlist, a committed per-flow `ai-trigger: allow` opt-in read at a pre-agent SHA, a dirty-tree refusal with no force option, no spend knobs on the tool, a per-hour rate limit, and the worker's daily cap). Commands sit outside its reach entirely: `dispatch_run` speaks flows only, and a chained job's request naming a `command` is refused outright, with no opt-in. Read [`SECURITY.md`](https://github.com/edgehero/pi-dispatch/blob/main/SECURITY.md) on that one before you enable it. The bundled `operate-pi-dispatch` skill teaches the agent those gates.
75
- - **Logs stay put.** Raw container output renders only in the overlay viewer, never into model context.
138
+ - **Triggers, editable live.** Add, edit and delete triggers without a restart. Drill-ins show what
139
+ fires each one, what it runs and who may fire it. A one-shot shows armed or spent. A spent entry
140
+ stays in the list, matches nothing, and is re-armed when you delete its `on.disarmed` mark from the file.
141
+ Triggers that run third party code or a custom image carry a badge. Turning either on or off stays an
142
+ edit to the reviewed `triggers.json`, which neither the console nor a model callable tool makes for you.
143
+ - **Quiet hours.** Pause windows per folder or repo, timezone aware. A paused job waits, never drops, and
144
+ costs nothing.
145
+ - **Scoped limits.** Job caps per repo or folder (day, week, month), refused before any spend, plus a
146
+ ceiling on how many run at once, enforced by making jobs wait. Local jobs also have a fixed guard with
147
+ no switch: one job per folder at a time (it lives in the worker process, and one worker per container
148
+ daemon is the supported shape), because two agents editing one working tree race each other with no
149
+ gate and no undo.
150
+ - **Held and failed jobs.** A trigger with `run.waitFor` holds its job in the queue, unstarted and
151
+ unbilled, until a time passes or your check script exits 0. The panel shows a **held** section while
152
+ anything waits (the target, the condition and how long), and a **failed** section for jobs the queue
153
+ marked failed after their last attempt. `dispatch_waits` lists held jobs and `dispatch_wait_cancel` stops one behind your confirm
154
+ ([`docs/wait-for.md`](https://github.com/edgehero/pi-dispatch/blob/main/docs/wait-for.md)).
155
+ - **AI operable, with a human gate.** Model callable tools can change limits (global and scoped) and
156
+ manage triggers and pause windows. Every **config** change asks the operator in a prompt the model
157
+ cannot answer, and is refused when no operator is present. `dispatch_pause` and `dispatch_resume` skip
158
+ that confirm on purpose: they are reversible and spend nothing.
159
+ - **One tool is not money safe.** `dispatch_run` queues a **paid** run that edits a local folder in place
160
+ with no undo, without a confirm. Six separate limits bound it instead: the `PI_DISPATCH_RUN_ROOTS` folder
161
+ allowlist, a committed per flow `ai-trigger: allow` opt in (read at a commit fixed before the agent
162
+ starts), a dirty tree refusal with no force option, no spend settings on the tool, a per hour rate limit
163
+ and the worker's daily cap. It cannot run commands, and a chained job asking for a `command` is refused.
164
+ Read [`SECURITY.md`](https://github.com/edgehero/pi-dispatch/blob/main/SECURITY.md) before you enable it.
165
+ The bundled `operate-pi-dispatch` skill teaches the agent these gates.
166
+ - **Logs stay put.** Raw container output shows only in the panel's viewer, never in model context.
76
167
 
77
168
  ## More than one machine
78
169
 
79
- The console reads the whole deployment, not one host. The status line names the workers when they have
80
- been named, `RUN_DETAIL` names the machine that ran a job, the pause switch stops every queue rather than
81
- the shared one, and the scheduler view spans hosts. On a single host none of that is visible, because
82
- there is nothing to distinguish. See [`docs/multi-host.md`](../docs/multi-host.md).
170
+ The console reads the whole deployment, not one host. The status line names the workers once they have
171
+ names. The run detail names the machine that ran a job. The pause switch stops every queue, and the
172
+ scheduler view spans hosts. On a single host none of that shows, because there is nothing to tell apart.
173
+ See [`docs/multi-host.md`](https://github.com/edgehero/pi-dispatch/blob/main/docs/multi-host.md).
83
174
 
84
175
  ## Install
85
176
 
@@ -87,14 +178,43 @@ there is nothing to distinguish. See [`docs/multi-host.md`](../docs/multi-host.m
87
178
  pi install npm:@edgehero/pi-dispatch-admin # then, in pi: /dispatch
88
179
  ```
89
180
 
90
- **This is the default way to set up pi-dispatch.** With nothing configured, `/dispatch` takes you straight into guided setup, in this order: an opening choice, a deployment folder, a Docker check with per-OS pointers if it is missing, a consented npm install of the pinned runtime (Docker is checked **first** on purpose, so no bandwidth is spent on a host where `up` cannot work anyway), `pi-dispatch up` running its own prompts in your terminal, the deployment pointer that lets `/dispatch` find this deployment from any directory, a notice naming the file your provider key belongs in, an optional worker service, optional GitHub App credentials (`setup github`, the one step that mints a private key), an optional trigger edge (receiver service, docker compose profile, or the polling command), and an optional first **cron** trigger for the repo you are sitting in. Every step shows what it will do, asks first, and can be declined; nothing is written into your repo and no credential passes through a dialog. A deployment whose queue is merely down keeps the unreachable banner instead: setup appears when there is nothing, never over an outage.
91
-
92
- Already have a deployment? The panel finds it through the deployment pointer setup writes, or through the same env vars your worker uses (`VALKEY_URL`, `PI_LOGS_DIR`, `PI_SETTINGS_FILE`, `PI_TRIGGERS_FILE`, `PI_PAUSE_WINDOWS_FILE`, `PI_SUBSCRIPTIONS_FILE`). Your env always wins.
93
-
94
- `dispatch_run` is inert until you set one more variable yourself: `PI_DISPATCH_RUN_ROOTS` defaults to empty, and an empty allowlist refuses every folder. The deployment pointer deliberately cannot set it (the pointer carries paths, never capability grants), so widening that allowlist is always your own env edit. No allowlist reaches commands either: a `run.command` fires from the reviewed triggers file only.
181
+ **This is the default way to set up pi-dispatch.** With nothing configured, `/dispatch` starts a guided
182
+ setup, in this order:
183
+
184
+ 1. an opening choice and a deployment folder;
185
+ 2. a container runtime check (Docker, or rootless Podman on Linux), with pointers per OS if it is missing.
186
+ It runs **first** on purpose, so no bandwidth is spent where `up` cannot work;
187
+ 3. a consented npm install of the pinned runtime;
188
+ 4. `pi-dispatch up`, running its own prompts in your terminal (Valkey with its password, the egress proxy,
189
+ the config files);
190
+ 5. the deployment pointer, so `/dispatch` finds this deployment from any directory;
191
+ 6. a notice naming the file your provider key belongs in;
192
+ 7. optional: the worker as a service, GitHub App credentials (`setup github`, the one step that mints a
193
+ private key), a trigger edge (receiver service, docker compose profile or the polling command), and a
194
+ first **cron** trigger for the repo you are in.
195
+
196
+ Every step says what it will do, asks first and can be declined. Nothing is written into your repo, and no
197
+ credential passes through a dialog. A deployment whose queue is only down keeps the unreachable banner:
198
+ setup appears when there is nothing, never over an outage.
199
+
200
+ **Already have a deployment?** The panel finds it through the deployment pointer, or through the same env
201
+ vars your worker uses (`VALKEY_URL`, `PI_LOGS_DIR`, `PI_SETTINGS_FILE`, `PI_TRIGGERS_FILE`,
202
+ `PI_PAUSE_WINDOWS_FILE`, `PI_SUBSCRIPTIONS_FILE`). Your env always wins. What your env does not set comes
203
+ from the deployment's own `.env`, by the same rule `pi-dispatch doctor` uses: only the pointer's
204
+ deployment folder (never a `.env` in the folder pi started in, which any repository could ship), and only
205
+ when that file is yours and nobody else can write it. A key your env sets differently is named once as a
206
+ warning, never with its value. The switches that grant a capability (`PI_BACKENDS`, `PI_EGRESS`,
207
+ `DOCKER_HOST`, `PI_DISPATCH_RUN_ROOTS`) are never read from a file.
208
+
209
+ `dispatch_run` does nothing until you set one more variable yourself. `PI_DISPATCH_RUN_ROOTS` is empty by
210
+ default, and an empty allowlist refuses every folder. The deployment pointer cannot set it (it carries
211
+ paths, never capability grants), so widening that allowlist is always your own env edit. No allowlist
212
+ reaches commands either: a `run.command` fires from the reviewed triggers file only.
95
213
 
96
214
  ## Get the whole thing
97
215
 
98
216
  ### → **https://github.com/edgehero/pi-dispatch**
99
217
 
100
- MIT, self-hosted. Read [`SECURITY.md`](https://github.com/edgehero/pi-dispatch/blob/main/SECURITY.md) before you rely on it; it states plainly what is and is not defended. Short version: the trust model is a GitHub Action's, so whoever can merge to your default branch can instruct the agent.
218
+ MIT, self hosted. Read [`SECURITY.md`](https://github.com/edgehero/pi-dispatch/blob/main/SECURITY.md)
219
+ before you rely on it: it states plainly what is and is not defended. Short version: the trust model is a
220
+ GitHub Action's. Whoever can merge to your default branch can instruct the agent.