pi-superagent 0.1.0 → 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.
- package/ADAPTATION.md +29 -31
- package/README.md +69 -122
- package/docs/architecture.md +103 -124
- package/docs/design-decisions.md +128 -228
- package/docs/formal-verification.md +48 -52
- package/docs/operator-guide.md +101 -89
- package/formal/README.md +20 -20
- package/package.json +1 -1
package/docs/architecture.md
CHANGED
|
@@ -1,131 +1,111 @@
|
|
|
1
1
|
# Architecture
|
|
2
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
|
-
engine
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
A crash anywhere on this path loses nothing and doubles nothing:
|
|
38
|
-
|
|
39
|
-
- **GitHub to source.** Read positions are staged during a poll and committed at the next one, after the
|
|
40
|
-
engine holds every trigger they produced. A crash replays the last poll instead of skipping it.
|
|
41
|
-
- **Source to engine.** Trigger ids derive from content: a comment from its author, time and body; an
|
|
42
|
-
issue from its number, title and last update; a review from GitHub's own id. The engine accepts an id
|
|
43
|
-
once, so a replay is a no-op.
|
|
44
|
-
- **Inside the engine.** Each command is one SQLite transaction: its writes land together or not at all.
|
|
45
|
-
- **Engine to executor.** The engine's calls to start, deliver to and stop a worker run only after the
|
|
46
|
-
command commits, so a worker never acts on a write that could still roll back.
|
|
47
|
-
- **Executor and worker.** A worker writes its events and reports to files; the router reads them from a
|
|
48
|
-
cursor it persists. A restarted router resumes reading where the last one stopped, and adopts the
|
|
49
|
-
running worker instead of starting a second.
|
|
50
|
-
- **Outbox to GitHub.** A message is marked sent only once GitHub accepts it. A retry first looks for the
|
|
51
|
-
comment on the issue, in case the last attempt landed before the process died.
|
|
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.
|
|
52
35
|
|
|
53
36
|
## The parts
|
|
54
37
|
|
|
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
|
-
launchd job in `launchd/` runs it every five minutes, restarts a router that is down, and leaves alone a
|
|
123
|
-
router that was stopped on purpose.
|
|
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.
|
|
124
105
|
|
|
125
106
|
## The state directory
|
|
126
107
|
|
|
127
|
-
|
|
128
|
-
otherwise.
|
|
108
|
+
The router stores its local state under `~/.pi/superagent`. Set `PI_SUPERAGENT_STATE` to use a different directory.
|
|
129
109
|
|
|
130
110
|
| Path | What it holds |
|
|
131
111
|
| --- | --- |
|
|
@@ -137,8 +117,7 @@ otherwise.
|
|
|
137
117
|
| `work/<ticket>/` | one workspace per ticket |
|
|
138
118
|
| `worker-bin/` | shims that refuse blocked commands, first on the PATH of each workspace's setup script |
|
|
139
119
|
|
|
140
|
-
To start over, stop the router and delete the directory.
|
|
141
|
-
history.
|
|
120
|
+
To start over, stop the router and delete the directory. On the next start, the first poll records existing open issues as history.
|
|
142
121
|
|
|
143
122
|
## Where each guarantee lives
|
|
144
123
|
|
|
@@ -149,12 +128,12 @@ history.
|
|
|
149
128
|
| A person's stop is final | guards in `core/tickets.ts` and `core/intake.ts` | `Ticket.tla` I1 |
|
|
150
129
|
| One running assignment per ticket | the executor's launch guard | `Ticket.tla` I3 |
|
|
151
130
|
| Each acknowledgement queued and sent once | outbox keys in `core/replies.ts` | `Ticket.tla` I5, I6 |
|
|
152
|
-
|
|
|
131
|
+
| The initial poll treats existing open issues as history | the seed rule | `Intake.tla` C1 |
|
|
153
132
|
| One request per open interval of an issue | the seen set | `Intake.tla` C2 |
|
|
154
133
|
| No comment lost to a crash | deferred read positions | `Intake.tla` C3, C6 |
|
|
155
134
|
| No trigger accepted twice | content-derived ids | `Intake.tla` C4 |
|
|
156
135
|
| A bot never passes for a person | bot patterns and the policy gate | `Intake.tla` C5 |
|
|
157
|
-
|
|
|
136
|
+
| Only authors admitted by the source scope reach the judge or a worker | the source scope, `scopeOf` | tests only |
|
|
158
137
|
| A ticket with a pull request ends with it, as merged or closed | binding before the report, `threadClosed` | tests only |
|
|
159
138
|
| A live agent's tab is never swept | the liveness check in `closeStaleTabs` | `Workers.tla` W2, W4 |
|
|
160
139
|
| Dead workers' tabs do not pile up | the sweep and the linger close | `Workers.tla` W5 |
|