-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy path.env.example
More file actions
161 lines (151 loc) · 15.3 KB
/
Copy path.env.example
File metadata and controls
161 lines (151 loc) · 15.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# Copy to .env and fill in. Never commit a real .env (it is gitignored).
# --- Provider credential ---
# pi supports ~30 providers; set the key for the one you use, under the variable name pi expects.
# The worker forwards ONLY the configured provider's key into the job container -- nothing else.
# Anthropic: ANTHROPIC_API_KEY (or ANTHROPIC_OAUTH_TOKEN, which takes precedence)
# OpenAI: OPENAI_API_KEY Google: GEMINI_API_KEY Groq: GROQ_API_KEY ... etc.
# You can LEAVE THIS BLANK if you are already logged into pi: when the env has no key, the worker reads the
# API key from ~/.pi/agent/auth.json (host-side) and env-injects it -- on by default, nothing to set.
# API-key logins only; an OAuth/subscription login is refused (it expires; use an API key for a service).
ANTHROPIC_API_KEY=
# PI_AUTH_FROM_PI=0 # uncomment to force env-only (fail loudly on a missing env key instead of using your pi login)
# --- Which provider/model to run by default (override per job with --provider / --model) ---
PI_PROVIDER=anthropic
# A DATED model id is deterministic; a floating alias (e.g. claude-sonnet-4-5) can change cost.
PI_MODEL=claude-sonnet-4-5-20250929
# --- Spend + concurrency guards (money bounds; all have conservative defaults) ---
PI_MAX_TURNS=30 # per-job turn cap -- pi has none of its own, so the harness imposes one
PI_DAILY_CAP=25 # max job containers started per day (mandatory window)
# PI_WEEKLY_CAP=100 # optional weekly ceiling on container starts; unset = weekly window disabled
# PI_MONTHLY_CAP=400 # optional monthly ceiling on container starts; unset = monthly window disabled
# PI_SOFT_HOLD_PCT=80 # optional soft-hold band (1-99): once any window hits this % of its cap, new starts
# pause (in-flight jobs finish) and the panel meter turns amber; unset = disabled
# PI_MAX_TOKENS= # optional per-job token budget: the runner aborts the agent once cumulative usage exceeds it.
# LAGGING (spends before it can see the total) -- a single-job runaway backstop, not before-the-spend;
# PI_MAX_TURNS stays the proactive lever. Unset = per-job token budget disabled (usage is still recorded).
# PI_DAILY_TOKEN_CAP= # optional daily token cap: once a day's recorded spend reaches it, the NEXT job is refused pre-container.
# Check-AFTER by nature (token cost is only known post-run), unlike PI_DAILY_CAP; unset = disabled
PI_CONCURRENCY=3 # how many jobs run in parallel
# --- Infrastructure ---
VALKEY_URL=redis://127.0.0.1:6379
PI_JOB_IMAGE=pi-job:latest # the DEFAULT job image. Any trigger may name its own with "image" in triggers.json (docs/job-image.md)
# Jobs run with --pull=never: pull or BUILD every image you name -- the worker never fetches one at job time, and doctor checks presence
# docker pull ghcr.io/edgehero/pi-job:latest && docker tag ghcr.io/edgehero/pi-job:latest pi-job:latest (or build image/Dockerfile)
# PI_JOBS_DIR= # where per-job /job inputs live (default: your OS temp dir)
# PI_LOGS_DIR= # where per-job status records (and optional raw logs) land (default: OS temp /pi-dispatch/logs)
# PI_CAPTURE_JOB_LOGS= # default 0; set 1 to ALSO write raw container output to logs/<jobId>.log -- PII-bearing (issue/comment text), host-only (never mounted into the container), off by default (opt-in)
# PI_LOG_RETENTION_DAYS= # default 30; boot-time prune of logs older than N days; 0 = keep forever
# PI_SANDBOX_DIR= # where a finished run's directory is kept so you can re-open it (default: <PI_JOBS_DIR>/sandboxes, mode 0700)
# `pi-dispatch sandbox <jobId>` starts a fresh container from the same image with the same workspace and NO credentials (docs/sandbox.md)
# A retained directory holds the run's clone plus its prompt.md/event.json -- so issue text. Host-only; never mounted into a job
# PI_SANDBOX_RETENTION_HOURS= # default 24. NOTE: 0 means OFF here -- nothing is retained and cleanup deletes as it always did
# This is the OPPOSITE of PI_LOG_RETENTION_DAYS/PI_SESSIONS_TTL_DAYS, where 0 means keep forever
# There is deliberately no keep-forever value: one repo clone per run with no ceiling is a disk bomb. Use --pin for the one run worth keeping
# PI_SANDBOX_PIN_DAYS= # default 7; `pi-dispatch sandbox <jobId> --pin` extends THAT run to now + this many days. Still swept afterwards
# PI_SANDBOX_IDLE_MINUTES= # default 30; bash's own TMOUT inside a sandbox, so a forgotten shell closes itself; 0 = no idle logout
# Honest gap: TMOUT does not tick while a foreground command runs, so a sandbox left serving an app stays up (`pi-dispatch sandbox --list` finds it)
# PI_SESSIONS_DIR= # NO DEFAULT, on purpose. Where persisted agent transcripts live, so a trigger with "resume": true can continue the session that opened the PR (docs/sessions.md)
# Unset = the feature is unavailable and an armed trigger refuses PRE-SPEND rather than running unpersisted and looking like it worked
# A transcript is the most PII-bearing thing this system stores -- issue text, file contents, tool output, the agent's own reasoning. Mode 0700, host-only, OUTSIDE any git repo
# Deliberately not defaulted into the OS temp dir the way PI_LOGS_DIR is: that is mode 1777 on POSIX
# PI_SESSIONS_TTL_DAYS= # default 14; a transcript older than this is not resumed AND is swept at boot; 0 = keep forever
# Enforced at OPEN as well as at boot: a stale transcript is a live input to a future job, not debris
# PI_SESSION_MAX_BYTES= # default 8388608 (8 MiB); a transcript larger than this is not resumed; 0 = no cap
# Not disk hygiene -- an oversized transcript is a prefill nobody sized PI_MAX_TOKENS for
# PI_SESSIONS_ALLOW_GH_SOURCE= # unset = a run.resume job REFUSES to mint under GITHUB_AUTH_SOURCE=gh, pre-spend
# That source is your whole gh login: full-scope and non-expiring, and a transcript is a FILE -- any command that echoed an auth header persists it
# Prefer GITHUB_AUTH_SOURCE=app or a short-expiry fine-grained PAT. Set exactly 1 to accept the trade explicitly (SECURITY.md, docs/sessions.md)
# Not disk hygiene -- an oversized transcript is a prefill nobody sized PI_MAX_TOKENS for
# PI_TRIGGERS_FILE= # ABSOLUTE path to the unified triggers.json, read by BOTH worker and receiver (a relative path resolves against the service's WorkingDirectory).
# Unset = cron disabled for the worker; the receiver falls back to ./triggers.json in the folder it starts from (what `pi-dispatch init` scaffolds)
# and refuses to start when neither exists (it holds the label/comment/pull_request trigger config)
# PI_PAUSE_WINDOWS_FILE= # ABSOLUTE path to pause-windows.json — "quiet hours" per folder/repo (pause runs between certain times/days/dates, auto-resume). Unset = feature off. See docs/pause-windows.md
# PI_SUBSCRIPTIONS_FILE= # path to subscriptions.json — operator-declared subscription plan prices (the admin defaults to ./subscriptions.json in its working directory). Read by the ADMIN EXTENSION only, never at job time.
# Subscription-backed providers bill 0 per run (their rate tables are all zeros), so this file is where the real price lives — cost analytics only; it changes no routing, auth, or job behavior
# PI_SETTINGS_FILE= # ABSOLUTE path to the runtime settings overlay (default: OS temp /pi-dispatch/settings.json); edited by the admin extension, read by the worker per job
# --- Reuse your existing pi setup in every job (see docs/global-pi-overlay.md) ---
# PI_GLOBAL_PI_DIR= # dir with your host pi setup (models.json/skills/APPEND_SYSTEM.md), mounted /opt/pi-global:ro into every job, layered UNDER each repo's .pi/. Unset = off. Stage it with: pi-dispatch import-pi
# PI_GLOBAL_ALLOW_EXTENSIONS= # the overlay's extensions LOAD by default (staging them with import-pi, which prints each one, is the vetting step). Set exactly 0 to keep them staged but dormant.
# Unset, empty and the legacy 1 all mean LOAD. ANY other value refuses to boot -- a typo must never silently leave code running against adversarial input with open egress.
# This knob covers the OVERLAY only. A serviced repo's own /workspace/.pi/extensions load regardless (they are default-branch, merge-gated) -- see SECURITY.md.
# PI_PACKAGES_FILE= # path to pi-packages.json (default: ./pi-packages.json; --packages-file wins). Read ONLY by `pi-dispatch import-pi --with-packages`, never at job time.
# Staged packages/ rides INSIDE PI_GLOBAL_PI_DIR -- no separate mount, no separate env dir -- and loads for EVERY job once staged; decline it PER TRIGGER with "packages": false in triggers.json, NOT by an env flag.
# Versions must be EXACT (no ^ ~ * or latest); staging uses --ignore-scripts, so a package needing a build step is staged INCOMPLETE and import-pi warns.
# PI_FORWARD_ENV= # comma-separated extra env var NAMES to forward into the container (e.g. a CUSTOM provider's key). Explicit allowlist, not a pass-through.
# GITHUB_TOKEN/GH_TOKEN are refused here -- the worker mints per-job tokens
PI_SCHEDULER_STALL_MAX=2 # tear down a scheduler after N consecutive stalls (money backstop)
# PI_DISPATCH_RUN_ROOTS= # OS-path-delimited allowlist (; on Windows, : elsewhere) of folders the model-callable dispatch_run may target; default empty = fail-closed (dispatch_run refuses every folder until you opt in)
# PI_DISPATCH_RUN_PER_HOUR=3 # per-hour cap on model-invoked dispatch_run enqueues; 0 disables the tool
# PI_DISPATCH_ASCII= # set 1 to render the admin extension's views with plain ASCII instead of box-drawing/sparkline-ramp glyphs (for glyph-width-hostile terminals); read at extension load
# PI_CHAIN_DEPTH_MAX=1 # max follow-up chain depth from a job's /outbox; 0 = chaining kill-switch
# PI_CHAIN_MAX_PER_JOB=2 # max request-<n>.json collected per completed job
# --- GitHub trigger (receiver + worker auth) ---
# Webhook receiver. Required only when your triggers name github (or GITHUB_AUTH_SOURCE is set): every forge arm is
# conditional, so a gitlab/forgejo/azure-only deployment needs no value here and answers 404 on the github endpoint
WEBHOOK_SECRET=
RECEIVER_PORT=3000
RECEIVER_BIND=0.0.0.0
# Worker GitHub auth: source is gh | pat | app (default gh)
# gh = your full login scopes reach token-carrying jobs (doctor warns and names them); pat/app = narrower
GITHUB_AUTH_SOURCE=gh
# For GITHUB_AUTH_SOURCE=pat: a repo-scoped, short-expiry fine-grained PAT
GITHUB_PAT=
# For GITHUB_AUTH_SOURCE=app (optional; required for multi-tenant)
# `pi-dispatch setup github` fills all three in one browser click (App Manifest flow) and writes the PEM 0600
GITHUB_APP_ID=
GITHUB_APP_INSTALLATION_ID=
GITHUB_APP_PRIVATE_KEY_PATH=
# --- GitLab trigger (receiver + worker auth) ---
# Optional. Set these only to service GitLab projects; leaving GITLAB_TOKEN unset means no /gitlab
# endpoint exists at all, rather than one that answers 401. See docs/gitlab.md.
#
# A PROJECT access token with the `api` scope and the Developer role or above. `api` is the narrowest
# scope that can post a note -- GitLab offers no contents-vs-issues split -- so scope it to one project
# and rotate it (CONST-TOKEN-SCOPED-PER-JOB). A GROUP token reaches every project in the group.
GITLAB_TOKEN=
# Your instance root. Only for self-hosted GitLab.
GITLAB_URL=https://gitlab.com
# How the receiver verifies a delivery. REQUIRED once any GITLAB_* variable is set, and deliberately not
# defaulted -- the two are not equally strong, so it must be a choice somebody made:
# signature HMAC-SHA256 over the body. Needs GitLab 19.0+. Use this if you can.
# token a shared-secret compare. Works on any version, and proves nothing about the body.
GITLAB_WEBHOOK_MODE=
# The signing token (signature mode) or the secret token (token mode) from the webhook's settings.
GITLAB_WEBHOOK_SECRET=
# --- Forgejo / Gitea trigger (receiver + worker auth) --- issue #61
# Forgejo's webhook transport is byte-compatible with GitHub's (HMAC-SHA256 over the raw body,
# X-Hub-Signature-256), so there is no mode to choose here: there is one mechanism and it is the strong one.
# Point the webhook at /forgejo -- NOT at / -- because Forgejo also sends X-GitHub-* headers, so the path is
# the only thing that can tell the two apart, and a sender must never choose which gate it faces.
FORGEJO_URL=
# A REPOSITORY-scoped token ("Specific repositories") carrying only write:repository and write:issue.
# Narrower than GitLab's equivalent -- Forgejo has no all-or-nothing `api` scope. What it cannot do is
# expire: there is no App or installation token, so rotation is the whole mitigation
# (CONST-TOKEN-SCOPED-PER-JOB).
FORGEJO_TOKEN=
# The harness account's NUMERIC id. Required when the token above is repository-scoped, because such a
# token may not carry read:user and therefore cannot call GET /user. The receiver refuses to boot without an
# identity from one source or the other: the bot-loop guard compares against it, and an unresolved identity
# never matches -- so it would fail open silently and the harness's own comments would start more jobs.
FORGEJO_BOT_ID=
FORGEJO_WEBHOOK_SECRET=
# --- Azure DevOps trigger (receiver + worker auth) --- issue #43
# READ docs/azure-devops.md BEFORE ENABLING. Azure Service Hooks offer no HMAC of any kind: the credential
# proves the sender knew a secret and covers no bytes, there is no delivery-id header (the dedup key comes
# from the body), and there is no signed timestamp and so no replay window. OQ-015 records the residual.
# HTTPS is not optional here -- over plain HTTP the credential is on the wire in base64.
AZURE_ORG_URL=
# A PAT for a DEDICATED identity. Azure gives you a real expiry and cannot scope below the organization
# (vso.code_write reaches every repo in the org), so the bound comes from that identity's per-repository
# permissions in Project Settings -- not from the token's scopes. It also needs vso.graph, to resolve the
# actor's project membership before a job may be enqueued.
AZURE_TOKEN=
# REQUIRED once any AZURE_* variable is set, and deliberately not defaulted: both modes are shared-secret
# compares that cover no bytes, so which header carries the secret must be a choice somebody made.
# basic -- Authorization: Basic <base64>, the credential you set on the subscription
# header -- one custom header, named below
AZURE_WEBHOOK_MODE=
# For basic: the base64 of "user:password" exactly as the subscription sends it.
AZURE_WEBHOOK_SECRET=
# Required only when AZURE_WEBHOOK_MODE=header.
AZURE_WEBHOOK_HEADER=