pi-superagent 0.0.0-stage → 0.1.1

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.
Files changed (67) hide show
  1. package/ADAPTATION.md +71 -0
  2. package/LICENSE +21 -0
  3. package/NOTICE +19 -0
  4. package/README.md +98 -2
  5. package/config.example.env +81 -0
  6. package/docs/architecture.md +139 -0
  7. package/docs/design-decisions.md +130 -0
  8. package/docs/formal-verification.md +102 -0
  9. package/docs/operator-guide.md +149 -0
  10. package/formal/Intake.cfg +18 -0
  11. package/formal/Intake.tla +206 -0
  12. package/formal/README.md +31 -0
  13. package/formal/Ticket.cfg +23 -0
  14. package/formal/Ticket.tla +244 -0
  15. package/formal/Workers.cfg +20 -0
  16. package/formal/Workers.tla +152 -0
  17. package/launchd/com.example.superagent-health.plist +31 -0
  18. package/package.json +72 -4
  19. package/scripts/superagent +219 -0
  20. package/src/core/assignments.ts +140 -0
  21. package/src/core/engine.ts +270 -0
  22. package/src/core/errors.ts +61 -0
  23. package/src/core/evals.ts +340 -0
  24. package/src/core/ids.ts +5 -0
  25. package/src/core/index.ts +10 -0
  26. package/src/core/intake.ts +63 -0
  27. package/src/core/memory.ts +50 -0
  28. package/src/core/model.ts +353 -0
  29. package/src/core/part.ts +80 -0
  30. package/src/core/ports.ts +134 -0
  31. package/src/core/reasons.ts +38 -0
  32. package/src/core/replies.ts +95 -0
  33. package/src/core/stage.ts +26 -0
  34. package/src/core/tickets.ts +423 -0
  35. package/src/pi/assessor.ts +97 -0
  36. package/src/pi/authors.ts +10 -0
  37. package/src/pi/executor.ts +267 -0
  38. package/src/pi/extension.ts +703 -0
  39. package/src/pi/gh-api.ts +147 -0
  40. package/src/pi/herdr-workers.ts +299 -0
  41. package/src/pi/herdr.ts +91 -0
  42. package/src/pi/judge.ts +60 -0
  43. package/src/pi/log.ts +35 -0
  44. package/src/pi/outbox.ts +82 -0
  45. package/src/pi/pulls.ts +154 -0
  46. package/src/pi/receipts.ts +43 -0
  47. package/src/pi/report-tool.ts +35 -0
  48. package/src/pi/router.ts +125 -0
  49. package/src/pi/sdk-contract.ts +51 -0
  50. package/src/pi/server.ts +171 -0
  51. package/src/pi/sink-session-inbox.ts +28 -0
  52. package/src/pi/source-github.ts +209 -0
  53. package/src/pi/store.ts +132 -0
  54. package/src/pi/worker-role.ts +79 -0
  55. package/src/pi/worker-view.ts +80 -0
  56. package/src/pi/workspace.ts +155 -0
  57. package/vendor/dsh-superagent/LICENSE +21 -0
  58. package/vendor/dsh-superagent/UPSTREAM_SHA +1 -0
  59. package/vendor/dsh-superagent/docs/000-philosophy.md +33 -0
  60. package/vendor/dsh-superagent/docs/001-domain.md +112 -0
  61. package/vendor/dsh-superagent/docs/002-authorization.md +155 -0
  62. package/vendor/dsh-superagent/docs/003-execution.md +203 -0
  63. package/vendor/dsh-superagent/docs/004-prompts.md +96 -0
  64. package/vendor/dsh-superagent/docs/005-panel.md +58 -0
  65. package/vendor/dsh-superagent/docs/006-deployment.md +274 -0
  66. package/vendor/dsh-superagent/docs/007-architecture.md +617 -0
  67. package/vendor/dsh-superagent/docs/008-cookbook.md +425 -0
package/ADAPTATION.md ADDED
@@ -0,0 +1,71 @@
1
+ # Adaptation ledger
2
+
3
+ If you follow a request from GitHub into the code, you’ll cross a boundary at `src/core/`.
4
+ DeepSeek’s dsh-superagent-prototype supplies the rules for opening a ticket, assigning a worker
5
+ and recording the outcome. This project supplies the pieces that talk to GitHub, run pi and keep
6
+ the worker’s terminal open in herdr.
7
+
8
+ That split gives maintainers a place to start. For a question about what a *ready* report means,
9
+ look in the engine. For a question about how that report becomes a GitHub comment, look in
10
+ `src/pi/`. The tables below identify the upstream files and the local code on either side.
11
+
12
+ ## The baseline
13
+
14
+ The files below match upstream commit `8c32a9dc5d72c033816c6a8110b37f40cf94fba4` byte for byte.
15
+ The pinned revision is recorded in `vendor/dsh-superagent/UPSTREAM_SHA`.
16
+
17
+ | Here | Upstream |
18
+ | --- | --- |
19
+ | `src/core/` | `src/core/` |
20
+ | `test/core-engine.spec.ts` | `tests/core-engine.spec.ts` |
21
+ | `vendor/dsh-superagent/docs/` | `docs/` |
22
+ | `vendor/dsh-superagent/LICENSE` | `LICENSE` |
23
+
24
+ Run these comparisons from the repository root. Each `diff` exits successfully for an exact match.
25
+
26
+ ```sh
27
+ git clone https://github.com/deepseek-ai/dsh-superagent-prototype /tmp/upstream
28
+ git -C /tmp/upstream checkout "$(cat vendor/dsh-superagent/UPSTREAM_SHA)"
29
+ diff -r /tmp/upstream/src/core src/core
30
+ diff /tmp/upstream/tests/core-engine.spec.ts test/core-engine.spec.ts
31
+ diff -r /tmp/upstream/docs vendor/dsh-superagent/docs
32
+ diff /tmp/upstream/LICENSE vendor/dsh-superagent/LICENSE
33
+ ```
34
+
35
+ ## Deviations
36
+
37
+ There are currently no local deviations. Record any future change here with the affected file, upstream
38
+ revision and reason. Maintainers will need that history when they apply the next upstream fix.
39
+
40
+ ## Local adapters
41
+
42
+ The engine is synchronous and does no I/O of its own. The interfaces in `src/core/ports.ts` connect it to storage, processes and
43
+ models. Their implementations live in `src/pi/`.
44
+
45
+ | Port | Bound in | How |
46
+ | --- | --- | --- |
47
+ | `Repositories` | `store.ts` | synchronous `node:sqlite` in WAL mode with `synchronous=FULL`, one transaction per engine command |
48
+ | `Policy` | `router.ts` (`policyOf`) | admits a trigger only from a person who is not on the bot list |
49
+ | `SourceScope` | `router.ts` (`scopeOf`) | filters incoming authors by GitHub association or the configured login list |
50
+ | `Assessor` | `assessor.ts`, `judge.ts` | candidates go one at a time to a headless pi judge, which answers with `ticket_decide` |
51
+ | `Executor` | `executor.ts`, `herdr-workers.ts` | one pi session per assignment, in its own herdr tab, reached through files that outlive the router |
52
+ | `Outbox` | `outbox.ts` | GitHub comments, queued once per id and marked sent only once GitHub accepts them |
53
+
54
+ The engine accepts triggers directly. `source-github.ts` converts issues, comments and reviews
55
+ into triggers and saves its read positions in the engine’s SQLite database. Upstream also ships a
56
+ Feishu source, an executor, a panel and a store. This project supplies its own GitHub integration,
57
+ herdr workers, panel (`server.ts`) and storage adapter (`store.ts`).
58
+
59
+ Only the engine writes a ticket. The worker submits its assignment outcome through
60
+ `ticket_report`. A *blocked* report carries the question that the router posts for the requester.
61
+
62
+ ## Tests
63
+
64
+ `test/core-engine.spec.ts` is the upstream engine suite. `test/sqlite-engine.spec.ts` runs the
65
+ same 113 cases against the local SQLite adapter, exercising the same transitions with persistent storage.
66
+
67
+ ## Taking an upstream change
68
+
69
+ Copy the files listed in the baseline table to their corresponding local paths and update
70
+ `vendor/dsh-superagent/UPSTREAM_SHA`. If a ticket transition changed, update `formal/Ticket.tla`.
71
+ Run `npm run check` and `scripts/check-formal` against the new baseline.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 the pi-superagent authors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/NOTICE ADDED
@@ -0,0 +1,19 @@
1
+ # NOTICE: vendored code
2
+
3
+ `src/core/` and `test/core-engine.spec.ts` are copied, unchanged, from:
4
+
5
+ - project: **dsh-superagent-prototype**, a DeepSeek research prototype
6
+ - repository: https://github.com/deepseek-ai/dsh-superagent-prototype
7
+ - commit: `8c32a9dc5d72c033816c6a8110b37f40cf94fba4` (2026-10-31, "feat: add the dsh-superagent research prototype")
8
+ - license: MIT, full text in `vendor/dsh-superagent/LICENSE`
9
+
10
+ `vendor/dsh-superagent/docs/` keeps upstream's nine design documents. They specify the state machine in
11
+ `src/core`, and they are the place to look when one of its rules seems arbitrary: start with
12
+ `001-domain.md` (the concepts, and the ones rejected), `002-authorization.md` (the gate and the admission
13
+ table) or `003-execution.md` (reports and attention).
14
+
15
+ Upstream calls its code a research prototype. This repository vendors the domain, not the deployment:
16
+ upstream's executor, its Feishu and GitHub sources and its panel are replaced here by pi sessions in
17
+ herdr, a GitHub source of this repository's own, and a local panel.
18
+
19
+ [ADAPTATION.md](ADAPTATION.md) shows how to prove the copy is exact, and where each binding plugs in.
package/README.md CHANGED
@@ -1,3 +1,99 @@
1
- # Temporary Holding Version
1
+ # pi-superagent
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Open a GitHub issue and tell pi-superagent what you’d like to change. A coding agent works on it
4
+ in a checkout on your machine and opens a pull request for you to review. If you ask for a
5
+ revision, the same agent picks it up.
6
+
7
+ You can follow the conversation on GitHub or open the agent’s terminal to see what it’s trying
8
+ and lend a hand.
9
+
10
+ ## Install
11
+
12
+ You’ll need Git, Node 22.19 or later, and three tools on a Mac or Linux machine. Pi runs the coding
13
+ sessions. Herdr gives each session a terminal tab. The GitHub CLI (`gh`) lets the agent read issues
14
+ and open pull requests using your account. [pi](https://github.com/earendil-works/pi) ·
15
+ [herdr](https://herdr.dev) · [GitHub CLI](https://cli.github.com)
16
+
17
+ Sign in to a model provider in pi and check that your model appears in `pi --list-models`. Then
18
+ sign in to GitHub with an account that can write to the repository you want to use.
19
+
20
+ ```sh
21
+ gh auth login
22
+ gh auth setup-git
23
+ pi install npm:pi-superagent
24
+ npm install -g pi-superagent
25
+ ```
26
+
27
+ The first install gives pi the extension. The second gives you the `superagent` command to start,
28
+ stop and check on it. Keep the two at the same version when you update.
29
+
30
+ ## Try it on one issue
31
+
32
+ Choose a repository and open herdr in your terminal.
33
+
34
+ ```sh
35
+ superagent init your-org/your-repo
36
+ herdr
37
+ ```
38
+
39
+ Leave herdr open. In a second terminal, start the **router**, the background session that watches
40
+ your repository and sends work to the right agent.
41
+
42
+ ```sh
43
+ superagent start
44
+ superagent status
45
+ ```
46
+
47
+ In herdr, find the `superagent` workspace and wait for the first GitHub poll in the **engine log**
48
+ tab. That first look records the issues already open. Now open a new issue with a small request.
49
+ For example, “Show the keyboard shortcut next to Export in the Help menu.”
50
+
51
+ When the router accepts the request, it replies on the issue and opens a **ticket**, its record of
52
+ the work. A **worker** is the pi session assigned to that ticket. It gets its own checkout and a
53
+ tab labeled with the issue number. You can type into the tab, though usually a comment on the
54
+ issue is enough. If the worker needs a decision, it asks there.
55
+
56
+ A code change comes back as a pull request. Review the diff as usual. The ticket stays open while
57
+ you discuss the change and ends when you merge or close the PR. You can also ask a question, such
58
+ as where the shortcut is defined. For that, an answer on the issue can finish the job.
59
+
60
+ ## While the worker runs
61
+
62
+ - **On GitHub**, reply on the issue to add a detail, or on the pull request to request a revision.
63
+ To stop work before there’s a PR, close the issue. Reopening it makes a fresh request.
64
+ - **In herdr**, open the tab with the issue number to see the worker’s commands and output. The
65
+ **engine log** tab shows what the router picked up and which requests it accepted.
66
+ - **In the local panel**, look under *Needs a person* to see who’s waiting for an answer. You can
67
+ also see running workers, queued work and recent decisions. [Open the panel](http://127.0.0.1:8788/)
68
+
69
+ By default, the router listens to authors GitHub identifies as owners, organization members or
70
+ collaborators. You can choose particular logins, give the router its own GitHub account, or supply
71
+ a custom workspace setup. Run `superagent stop` when you want to stop the router.
72
+ [Operator guide](docs/operator-guide.md) · [Configuration examples](config.example.env)
73
+
74
+ ## How it works
75
+
76
+ A separate pi session called the **judge** reads a new request and decides whether there’s work to
77
+ do. Once a ticket is open, follow-up comments go straight to its worker. The worker reports the
78
+ outcome through a tool call, which lets the router distinguish a finished run from a question or
79
+ a crash. The router stores the ticket on disk, so after a restart it can find the worker and pick
80
+ up the conversation. [Architecture](docs/architecture.md) · [Design decisions](docs/design-decisions.md)
81
+
82
+ The ticket engine comes from DeepSeek’s dsh-superagent-prototype. Three TLA+ models check how
83
+ tickets, incoming messages and worker tabs behave when events arrive in different orders.
84
+ [Upstream project](https://github.com/deepseek-ai/dsh-superagent-prototype) ·
85
+ [Adaptation ledger](ADAPTATION.md) · [Formal verification](docs/formal-verification.md)
86
+
87
+ ## Development
88
+
89
+ ```sh
90
+ npm install
91
+ npm run check # Type-check and run the test suite
92
+ npm run check:packed # Install the tarball and exercise both the CLI and the pi extension
93
+ scripts/check-formal # Check all three models (about five minutes with a JDK)
94
+ ```
95
+
96
+ The test suite clears `PI_SUPERAGENT_*` settings to isolate its router from yours.
97
+ Publishing runs both npm checks before uploading the package.
98
+
99
+ Both projects use the MIT license. [Upstream attribution](NOTICE)
@@ -0,0 +1,81 @@
1
+ # pi-superagent settings.
2
+ #
3
+ # Copy this file to config.env in the state directory (~/.pi/superagent unless PI_SUPERAGENT_STATE says
4
+ # otherwise), edit it, and run `superagent restart`. The start script reads it on every start, so
5
+ # a restart by hand and a restart by the health check run the same router. A variable already set in the
6
+ # environment wins over this file.
7
+ #
8
+ # One KEY=value per line, taken literally: no quotes, no $VARIABLES, no ~, and no comment after a value.
9
+ # Each commented-out line shows a setting's default.
10
+
11
+ # ---- GitHub, the only way in ----
12
+
13
+ # The repository to watch. Unset, the router runs with intake switched off.
14
+ PI_SUPERAGENT_GITHUB_REPO=your-org/your-repo
15
+
16
+ # The account the agent replies from, if it has one of its own: its comments are never requests. Leave it
17
+ # unset when you and the agent share an account. The agent's replies then carry a hidden marker instead,
18
+ # and naming your own account here would discard every comment you write.
19
+ #PI_SUPERAGENT_GITHUB_SELF=your-agent-bot
20
+
21
+ # The agent's own GitHub account: a file holding a token for it (classic, repo scope), read on every call.
22
+ # The router then reads the watched repository and posts every reply as that account. Give the account
23
+ # access to the watched repository, set PI_SUPERAGENT_GITHUB_SELF to its login, and keep the file readable
24
+ # only by you (chmod 600). Workers still act as the account gh is signed in as, and the router reads their
25
+ # pull requests as that account too, since it opened them. Unset, the router acts as gh's account.
26
+ #PI_SUPERAGENT_GITHUB_TOKEN_FILE=/absolute/path/to/token
27
+
28
+ # Author patterns that are bots, never people. A * matches anything.
29
+ #PI_SUPERAGENT_BOTS=pi-*,fleet-*
30
+
31
+ # Who the router hears. Unset, only people with write access to the repository: GitHub's OWNER, MEMBER
32
+ # (any member of the owning organization) and COLLABORATOR. Everyone else's issues, comments and reviews
33
+ # are dropped before the judge sees them, because a request starts a worker with a shell on this machine.
34
+ # Set, this comma-separated list of logins replaces that rule, so include your own. A * matches anything.
35
+ #PI_SUPERAGENT_ALLOWED=your-login,a-teammate
36
+
37
+ # ---- the router ----
38
+
39
+ # Milliseconds between polls of GitHub when nothing else wakes the router.
40
+ #PI_SUPERAGENT_TICK_MS=30000
41
+
42
+ # The panel's port, on 127.0.0.1.
43
+ #PI_SUPERAGENT_PANEL_PORT=8788
44
+
45
+ # The judge's thinking level, in pi's names for them.
46
+ #PI_SUPERAGENT_JUDGE_THINKING=low
47
+
48
+ # Seconds `superagent stop` waits for the router to exit before closing its tab.
49
+ #PI_SUPERAGENT_GRACE=30
50
+
51
+ # ---- herdr ----
52
+
53
+ # The herdr binary, if it is not on PATH.
54
+ #PI_SUPERAGENT_HERDR=herdr
55
+
56
+ # The workspace that holds the router and its workers.
57
+ #PI_SUPERAGENT_HERDR_WORKSPACE=superagent
58
+
59
+ # The router's agent name in herdr.
60
+ #PI_SUPERAGENT_SESSION=superagent
61
+
62
+ # ---- workers ----
63
+
64
+ # Workers on a run at once. Workers parked between runs do not count; further tickets wait their turn.
65
+ #PI_SUPERAGENT_MAX_WORKERS=4
66
+
67
+ # An absolute path for the per-ticket workspaces. The default is the work directory in the state directory.
68
+ #PI_SUPERAGENT_WORKTREES=/absolute/path/to/workspaces
69
+
70
+ # By default a new worker clones the watched repository and checks out superagent/<ticket-id>.
71
+ # Set this command to replace the default checkout. It runs with PI_SUPERAGENT_TICKET and
72
+ # PI_SUPERAGENT_DIR set. It runs again whenever the ticket gets a fresh worker, so make it safe to repeat.
73
+ # If it fails, the ticket fails with the command's own output.
74
+ #PI_SUPERAGENT_PREPARE=[ -d .git ] || git clone --depth 1 https://github.com/your-org/your-repo .
75
+
76
+ # Command names a worker may not run, comma-separated. Names only: no arguments, no paths.
77
+ #PI_SUPERAGENT_BLOCKED=gh,ssh
78
+
79
+ # Set to 1 to let workers start subagents, workflows and other sessions. Delegated work escapes the
80
+ # ticket's mandate, workspace and report, so it is off by default.
81
+ #PI_SUPERAGENT_ALLOW_DELEGATION=1
@@ -0,0 +1,139 @@
1
+ # Architecture
2
+
3
+ A GitHub issue is where people talk about the work. A ticket is how pi-superagent remembers whose
4
+ turn it is. The **router** keeps the two in step. It watches the repository, asks a **judge** to
5
+ interpret new requests, and gives each accepted task to a **worker** with its own terminal and
6
+ checkout.
7
+
8
+ ## Follow a comment through the system
9
+
10
+ Imagine adding “Please keep the keyboard shortcut” to an issue while a worker is editing the menu.
11
+ On its next poll, the router reads your comment through the GitHub CLI. The GitHub source turns it
12
+ into a **trigger**, a message with an ID and a thread. The engine checks who sent it, finds the
13
+ ticket that already owns the thread and queues the comment for that worker.
14
+
15
+ A new thread takes one extra step. The judge reads the request and calls `ticket_decide`. An
16
+ acceptance opens a ticket and queues an acknowledgement on GitHub. The worker later calls
17
+ `ticket_report` with its outcome. If the report links a pull request, the router records that link
18
+ before the engine processes the report. That’s how an issue can stay active through code review
19
+ even after the worker has finished its first edit.
20
+
21
+ ## If the router restarts halfway through
22
+
23
+ Suppose the router reads your comment and loses power a moment later. Whether the worker received
24
+ it depends on where the router stopped. Three records let it resume the conversation.
25
+
26
+ - **The GitHub read position** records how far the router has read. A poll saves its new position
27
+ at the start of the next poll, after the engine has received the triggers. On replay, stable IDs
28
+ let the engine recognize a comment, issue or review it has already seen.
29
+ - **The ticket and worker records** say who has the assignment. Each engine command commits its
30
+ changes in one SQLite transaction. Worker starts, deliveries and stops follow that commit.
31
+ The worker’s tab and files can outlive the router, which uses a saved position to resume reading
32
+ the worker’s events and reports.
33
+ - **The outbox** records what the router owes the GitHub thread. If a reply was pending when the
34
+ router stopped, a retry looks for the comment on GitHub before posting it again.
35
+
36
+ ## The parts
37
+
38
+ ### Ticket engine
39
+
40
+ The vendored state machine lives in `src/core/`. `intake.ts` routes triggers to an existing ticket
41
+ or queues an evaluation. `evals.ts` turns an accepted evaluation into a ticket.
42
+ `assignments.ts` records reports and tracks the running assignment. `tickets.ts` handles stops,
43
+ retries and wake-ups. `replies.ts` gives each acknowledgement or reply a stable queue key.
44
+ The synchronous engine reaches storage, processes and models through the interfaces in `ports.ts`.
45
+
46
+ ### Router and intake
47
+
48
+ `src/pi/extension.ts` creates the store, GitHub source, judge, executor, outbox and panel. By default
49
+ the router polls every 30 seconds and also wakes when its pi session goes idle. A pass delivers new
50
+ triggers, reconciles PRs and closed threads, evaluates requests, starts pending work, checks for quiet
51
+ workers and flushes replies. Three consecutive GitHub poll failures set an error in `health.json`.
52
+
53
+ `source-github.ts` tracks issues, comments and submitted reviews. It recognizes the router’s own
54
+ comments by the bot login or a hidden marker. `scopeOf` in `router.ts` checks each author’s GitHub
55
+ association or the configured login list before the trigger can reach a judge or worker.
56
+ `gh-api.ts` runs the CLI asynchronously, paginates issue and PR lists, and reads comments forward
57
+ from the saved position.
58
+
59
+ ### Judge
60
+
61
+ `policyOf` in `router.ts` checks human authorship and bot patterns. For a request that needs a
62
+ decision, `assessor.ts` assembles the thread, messages, existing tickets and issue link.
63
+ `judge.ts` runs one candidate at a time in a headless pi session with a single tool, `ticket_decide`.
64
+ The session excludes extensions, skills and context files. The router overrides an acceptance if
65
+ an open ticket already owns the thread.
66
+
67
+ ### Workers
68
+
69
+ `executor.ts` schedules an assignment and `herdr-workers.ts` starts pi in a new tab. By default the
70
+ worker gets a clone of the watched repository on its own ticket branch. Under `workers/<session>/`,
71
+ the router writes prompts to `inbox/` and the worker appends to `events.jsonl` and `reports.jsonl`.
72
+ A restarted router can adopt the tab and resume reading those files.
73
+
74
+ Inside the session, `worker-role.ts` registers `ticket_report` and the tool guards in `workspace.ts`.
75
+ The worker observes the order of its own events, so it can report a run that ended without a
76
+ report. A crash frees the slot immediately. On the first crash the router starts a replacement in
77
+ the same workspace and includes the previous failure in its brief.
78
+
79
+ A parked worker keeps its context and releases its slot. A finished worker’s tab stays open for
80
+ ten minutes. If pi is still busy, the router checks again ten minutes later. At startup it also
81
+ checks for tabs with no owning record or no corresponding herdr agent.
82
+
83
+ ### Pull requests and replies
84
+
85
+ `pulls.ts` binds each reported PR to its ticket with `bindThread` before the engine processes
86
+ the report. That order matters. A ready report on a ticket with an open PR leaves it awaiting review.
87
+ When the PR leaves the watched repository’s open list, the follower checks whether it merged or
88
+ closed and calls `threadClosed`. For another repository it checks every two minutes and routes
89
+ comments and reviews through the qualified thread ID, `owner/repo#N`.
90
+
91
+ `receipts.ts` formats reports, questions, crashes and launch failures for the GitHub thread.
92
+ `outbox.ts` queues messages by ID. Failed posts remain pending, and a recurring launch failure
93
+ produces one notice. The source recognizes the hidden `<!-- superagent -->` marker on outgoing
94
+ comments. Sent entries expire after 30 days.
95
+
96
+ ### Panel and process management
97
+
98
+ `server.ts` reads the engine store for each page or API request. It listens on `127.0.0.1:8788`
99
+ by default and logs the chosen port if it has to bind a free one. It checks the Host header against
100
+ loopback names and requires `x-superagent-request: 1` on API calls.
101
+
102
+ `scripts/superagent` configures the repository, starts or stops the router and reports health.
103
+ The macOS job in `launchd/` checks every five minutes. It restarts a down router and respects the
104
+ operator’s stop marker.
105
+
106
+ ## The state directory
107
+
108
+ The router stores its local state under `~/.pi/superagent`. Set `PI_SUPERAGENT_STATE` to use a different directory.
109
+
110
+ | Path | What it holds |
111
+ | --- | --- |
112
+ | `tickets.db` | the engine's SQLite store, including read positions and the outbox |
113
+ | `config.env` | deployment settings, read by the start script |
114
+ | `engine.log` | the router's log, rotated at 5 MB |
115
+ | `health.json`, `setup.json`, `ready` | what `superagent status` reads |
116
+ | `workers/<session>/` | one directory per worker: its record, inbox, events and reports |
117
+ | `work/<ticket>/` | one workspace per ticket |
118
+ | `worker-bin/` | shims that refuse blocked commands, first on the PATH of each workspace's setup script |
119
+
120
+ To start over, stop the router and delete the directory. On the next start, the first poll records existing open issues as history.
121
+
122
+ ## Where each guarantee lives
123
+
124
+ | Guarantee | Enforced by | Proved in |
125
+ | --- | --- | --- |
126
+ | One ticket per thread | intake routing in the engine; the router overrules a second accept | `Ticket.tla` I4 |
127
+ | A ticket completes only on a *ready* report | `core/assignments.ts` | `Ticket.tla` I2 |
128
+ | A person's stop is final | guards in `core/tickets.ts` and `core/intake.ts` | `Ticket.tla` I1 |
129
+ | One running assignment per ticket | the executor's launch guard | `Ticket.tla` I3 |
130
+ | Each acknowledgement queued and sent once | outbox keys in `core/replies.ts` | `Ticket.tla` I5, I6 |
131
+ | The initial poll treats existing open issues as history | the seed rule | `Intake.tla` C1 |
132
+ | One request per open interval of an issue | the seen set | `Intake.tla` C2 |
133
+ | No comment lost to a crash | deferred read positions | `Intake.tla` C3, C6 |
134
+ | No trigger accepted twice | content-derived ids | `Intake.tla` C4 |
135
+ | A bot never passes for a person | bot patterns and the policy gate | `Intake.tla` C5 |
136
+ | Only authors admitted by the source scope reach the judge or a worker | the source scope, `scopeOf` | tests only |
137
+ | A ticket with a pull request ends with it, as merged or closed | binding before the report, `threadClosed` | tests only |
138
+ | A live agent's tab is never swept | the liveness check in `closeStaleTabs` | `Workers.tla` W2, W4 |
139
+ | Dead workers' tabs do not pile up | the sweep and the linger close | `Workers.tla` W5 |
@@ -0,0 +1,130 @@
1
+ # Design decisions
2
+
3
+ ## Why work starts on GitHub
4
+
5
+ A reviewer who spots a problem in a pull request already has the diff and a place to leave a
6
+ comment. Asking them to copy that comment onto a separate task board would split the conversation
7
+ just when the worker needs its context. Issues, comments and reviews therefore enter through the
8
+ same source, `source-github.ts`. A new request can open a ticket, and feedback can reach the
9
+ worker that made the change.
10
+
11
+ **Check the author before routing the message.** An accepted request starts a worker with a shell
12
+ on the operator’s machine. A comment on an existing ticket goes straight to that worker, so a
13
+ permission check in the judge alone would miss follow-ups. The engine checks `scopeOf` first.
14
+ By default it admits the GitHub associations `OWNER`, `MEMBER` and `COLLABORATOR`. A missing
15
+ association fails the check. Setting `PI_SUPERAGENT_ALLOWED` replaces that rule with a login list.
16
+
17
+ **Establish a baseline on the first poll.** An early version treated an entire backlog as new work
18
+ when the operator connected a repository. The source now saves the initial set of open issues and
19
+ seeds the review cursor. Closing and reopening an issue makes it eligible again. Its updated
20
+ timestamp distinguishes the new trigger from the earlier one. See `Intake.tla` C1 and C2.
21
+
22
+ **Save later read positions after delivery.** A poll stages its read position for the next poll to
23
+ commit. If the router crashes after reading a comment but before the engine receives it, the old
24
+ position lets the router read it again. Stable trigger IDs make that replay safe. The initial
25
+ baseline commits immediately because it represents history already present at setup. TLC found
26
+ the comment-loss case in the original implementation. The regression test reproduces that event
27
+ order. See `Intake.tla` C3 and C6.
28
+
29
+ ## Deciding what deserves a ticket
30
+
31
+ **Apply policy in code.** The router checks the author against current bot patterns even when a
32
+ stored trigger identifies the author as human. An older trigger once caused an agent’s request
33
+ for test approval to become a ticket to run the test. After the judge answers, the router also
34
+ checks whether an open ticket already owns the thread. The judge once accepted a duplicate despite
35
+ having the existing ticket in its prompt.
36
+
37
+ **Give the judge one request at a time.** Judging in the operator’s pane tied it up for an entire
38
+ model turn. Sending 41 candidates into one active turn produced answers out of order, and some
39
+ candidates expired before the judge got to them. The assessor now reuses one headless pi session
40
+ without extensions, skills or context files. It opens the session once because startup took 20 to
41
+ 45 seconds and concurrent requests used to start separate judges.
42
+
43
+ ## Running a worker
44
+
45
+ A worker can write “done” in its terminal while the person who opened the issue is still waiting
46
+ on GitHub. To finish a run, it calls `ticket_report` with an outcome. A *ready* report can carry
47
+ the link to a PR. A *blocked* report carries the question the requester needs to answer. The other
48
+ outcomes are *failed* and *cancelled*. Only the engine writes the result to the ticket.
49
+ See `Ticket.tla` I2.
50
+
51
+ If a run ends without a report, the router posts a failure on the thread. Launch errors and crashes
52
+ also get a reply there, so the person who asked knows what happened. The outbox recognizes repeat
53
+ failures. One broken setup command deserves one notice, even if the router tries it on every poll.
54
+ A quiet worker is a different case. After 15 minutes of silence it appears on the panel, where the
55
+ operator can see whether it needs help or is simply waiting for a build.
56
+
57
+ **Keep tool use within the assignment.** Worker tool hooks check file edits and writes against the
58
+ ticket workspace, block configured commands and disable delegation by default. The prepare hook
59
+ also gets a PATH with shims for blocked commands. Operators can use these controls to restrict
60
+ personal tools such as `gh`. The brief asks workers sharing the machine to run builds sequentially,
61
+ bound parallel jobs and clean up processes when they finish.
62
+
63
+ **Replace a crashed worker once.** The engine records the failed attempt, frees its slot and
64
+ requeues unread comments. The replacement gets the original workspace and a brief explaining the
65
+ crash. A second crash on the same ticket requires attention. The limit applies per router process
66
+ and prevents a persistent fault from spawning a new tab every few seconds.
67
+
68
+ **Count active work against the cap.** A parked worker retains its session for follow-ups and frees
69
+ a slot for queued work. A reply can briefly take the active count above the cap. Counting every
70
+ open session used to leave a queue of 22 tickets waiting while an idle worker occupied one of four
71
+ slots.
72
+
73
+ **Check the tab before closing it.** A finished worker gets ten minutes for follow-ups. The router
74
+ closes its tab once pi is idle and checks again every ten minutes while it is busy. Startup cleanup
75
+ handles tabs with no owning record or whose agent herdr no longer knows. Since herdr has no guarded
76
+ close operation, the liveness check matters whenever someone could still be working in a tab.
77
+ See `Workers.tla` W2, W4 and W5.
78
+
79
+ ## Ending a ticket
80
+
81
+ **Respect an explicit stop.** Once a person stops a ticket, later messages, wake-ups and retries
82
+ cannot resume it. Asking again creates a new ticket with its own history. See `Ticket.tla` I1.
83
+
84
+ **Follow the GitHub conversation.** When an issue disappears from the open set, the router can
85
+ observe the closure even if it cannot distinguish deletion, manual closure and closure by merge.
86
+ It stops the ticket and queues a notice. For a ticket awaiting its own PR, the PR’s merge or close
87
+ determines the outcome. Reopening an issue lets its author ask again.
88
+
89
+ **Bind the PR before processing a ready report.** A worker that changes a repository opens a PR and
90
+ includes its URL in its report. A number such as `#5` could also name an issue. Binding the URL
91
+ first lets the engine hold the ticket open for review. Merging or closing the PR ends the ticket,
92
+ and the issue receives the outcome. An investigation or deployment can finish with a ready report
93
+ alone. The worker knows which repository it changed, even when the request originated elsewhere.
94
+
95
+ The router calls the vendored engine’s `bindThread` and `threadClosed` transitions. It gets review
96
+ activity on the watched repository during the regular poll. For a PR elsewhere it polls every two
97
+ minutes, using three API calls per check.
98
+
99
+ **Route review feedback to the worker.** A submitted review on a bound PR goes to its ticket.
100
+ A review on an unrelated PR can reach the judge as a new request. GitHub accepts replies in the
101
+ PR conversation through its issue number. Opening a PR alone produces no request, so a worker can
102
+ deliver a change without triggering another worker to review it.
103
+
104
+ ## Operating the router
105
+
106
+ **Keep ticket state in one store.** One router owns the synchronous SQLite store in its state
107
+ directory. All writes from an engine command commit together. Recovery reopens the same directory.
108
+ The router role is tied to the appointed process because pi also loads the extension in worker
109
+ and judge sessions. Previously those sessions could each start a router.
110
+
111
+ **Report health through exit codes.** A scheduled check can act on `superagent status`. Code 0
112
+ means healthy or intentionally stopped. Code 1 indicates a router failure that a restart may fix.
113
+ Code 2 directs the operator to a failing component, including a GitHub source that failed three
114
+ polls in a row. Code 3 identifies a setup problem.
115
+
116
+ **Limit panel access to local callers.** The panel binds to loopback, validates the Host header
117
+ and requires `x-superagent-request: 1` for API calls. These checks prevent a page on another
118
+ site from reading the queue or pausing the router through DNS rebinding.
119
+
120
+ ## Maintaining the engine
121
+
122
+ The engine and its test suite match the pinned upstream revision. Local adapters live in
123
+ `src/pi/`, so an upstream fix can be imported and compared directly with its source.
124
+ [Adaptation ledger](../ADAPTATION.md)
125
+
126
+ The three TLA+ models live beside the implementation. Their actions name the functions they mirror
127
+ and their headers record the assumptions. When a transition changes, update its model and run
128
+ `scripts/check-formal`. The intake model’s crash counterexample is one case where checking all
129
+ orderings within a small bound found a failure the tests had missed.
130
+ [Architecture](architecture.md) · [Formal verification](formal-verification.md)