@nanocollective/roster 0.1.0-alpha.5 → 0.1.0-alpha.50
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 +70 -84
- package/dist/cli.js +4833 -2752
- package/docs/README.md +9 -6
- package/docs/agents.md +24 -20
- package/docs/architecture.md +13 -5
- package/docs/charters/analyst.md +65 -0
- package/docs/charters/cmo.md +69 -0
- package/docs/charters/community.md +63 -0
- package/docs/charters/cto.md +71 -0
- package/docs/charters/designer.md +65 -0
- package/docs/charters/devops.md +65 -0
- package/docs/charters/pm.md +70 -0
- package/docs/charters/qa.md +65 -0
- package/docs/charters/support.md +60 -0
- package/docs/charters/writer.md +63 -0
- package/docs/commands.md +93 -7
- package/docs/concepts.md +61 -14
- package/docs/cost.md +36 -1
- package/docs/developing.md +16 -21
- package/docs/doctor-codes.md +10 -2
- package/docs/export.md +2 -0
- package/docs/extending.md +2 -2
- package/docs/getting-started.md +128 -78
- package/docs/images/brain.jpg +0 -0
- package/docs/images/org.jpg +0 -0
- package/docs/images/prompt.jpg +0 -0
- package/docs/images/setup-org.jpg +0 -0
- package/docs/images/setup-plan.jpg +0 -0
- package/docs/images/staff.jpg +0 -0
- package/docs/manual-steps.md +94 -123
- package/docs/memory.md +21 -3
- package/docs/org-yaml.md +40 -2
- package/docs/portal.md +165 -48
- package/docs/prompts.md +31 -4
- package/docs/security.md +37 -5
- package/docs/session-workflow.md +63 -17
- package/docs/staff-yaml.md +30 -3
- package/docs/troubleshooting.md +8 -8
- package/docs/upgrading.md +9 -3
- package/docs/writing-a-charter.md +28 -0
- package/package.json +18 -20
- package/templates/brain/.github/workflows/%%STAFF%%-daily.yaml +26 -1
- package/templates/brain/.github/workflows/%%STAFF%%-mention.yaml +41 -7
- package/templates/brain/CHARTER.md +3 -3
- package/templates/brain/README.md +1 -0
- package/templates/brain/log/decisions.md +3 -0
- package/templates/brain/staff.yaml +4 -1
- package/templates/brain/strategy/ideas.md +7 -0
- package/templates/briefs/priorities.md +46 -0
- package/templates/ops/.github/workflows/session.yaml +236 -15
- package/templates/ops/agents.mjs +7 -3
- package/templates/ops/compose.mjs +31 -3
- package/templates/ops/inflight.mjs +157 -0
- package/templates/ops/org/operating.md +43 -4
- package/templates/ops/org/voice.md +9 -0
- package/templates/ops/prompts/_inflight.md +14 -0
- package/templates/ops/prompts/_paths.md +2 -1
- package/templates/ops/prompts/daily.md +37 -9
- package/templates/ops/prompts/mention.md +21 -0
- package/templates/ops/run-record.mjs +146 -0
- package/templates/portal/css/base.css +167 -73
- package/templates/portal/css/brain.css +23 -20
- package/templates/portal/css/diff.css +10 -9
- package/templates/portal/css/graph.css +12 -7
- package/templates/portal/css/health.css +13 -11
- package/templates/portal/css/home.css +93 -0
- package/templates/portal/css/inbox.css +45 -25
- package/templates/portal/css/layout.css +90 -46
- package/templates/portal/css/markdown.css +36 -14
- package/templates/portal/css/runs.css +13 -0
- package/templates/portal/css/setup.css +116 -34
- package/templates/portal/index.html +21 -9
- package/templates/portal/js/api.js +44 -4
- package/templates/portal/js/app.js +94 -9
- package/templates/portal/js/dialog.js +83 -0
- package/templates/portal/js/homesort.js +174 -0
- package/templates/portal/js/icons.js +37 -0
- package/templates/portal/js/inflight.js +18 -0
- package/templates/portal/js/md.js +5 -2
- package/templates/portal/js/mdedit.js +84 -0
- package/templates/portal/js/readiness.js +70 -0
- package/templates/portal/js/refresh.js +10 -2
- package/templates/portal/js/state.js +11 -5
- package/templates/portal/js/views/app.js +24 -7
- package/templates/portal/js/views/brain.js +1 -1
- package/templates/portal/js/views/checklist.js +10 -4
- package/templates/portal/js/views/credential.js +84 -0
- package/templates/portal/js/views/graph.js +1 -1
- package/templates/portal/js/views/health.js +17 -4
- package/templates/portal/js/views/hire.js +593 -0
- package/templates/portal/js/views/home.js +546 -0
- package/templates/portal/js/views/inbox.js +226 -70
- package/templates/portal/js/views/org.js +46 -106
- package/templates/portal/js/views/orgedit.js +234 -0
- package/templates/portal/js/views/paste.js +87 -21
- package/templates/portal/js/views/prompt.js +11 -4
- package/templates/portal/js/views/repos.js +20 -15
- package/templates/portal/js/views/runonce.js +94 -0
- package/templates/portal/js/views/runs.js +170 -0
- package/templates/portal/js/views/setup.js +261 -75
- package/templates/portal/js/views/staff.js +170 -243
- package/templates/portal/js/views/todo.js +62 -0
package/docs/portal.md
CHANGED
|
@@ -18,10 +18,12 @@ Keep the repos checked out beside each other, in the same shape the runner uses.
|
|
|
18
18
|
|
|
19
19
|
## The sidebar
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
Anything that asks before it acts (a merge, a push, a hire, a retire, a paid run) asks in the
|
|
22
|
+
page's own dialog, never the browser's `confirm()`, which blocks the whole tab.
|
|
23
|
+
|
|
24
|
+
**The counts are right on load.** The badges beside Home and Trackers are fetched once at
|
|
25
|
+
boot, and the screens share that request rather than making a second one. A sidebar that
|
|
26
|
+
says nothing until you look at it is not a sidebar.
|
|
25
27
|
|
|
26
28
|
While the first answer is outstanding the badge is a placeholder rather than blank, because an
|
|
27
29
|
empty badge reads as zero and zero is a different claim from "still counting". The screens
|
|
@@ -50,26 +52,86 @@ It asks GitHub which of two things this is:
|
|
|
50
52
|
nothing is written until you apply. Same `initFiles` the CLI runs, so the browser and the
|
|
51
53
|
terminal cannot disagree about what a new tenant contains.
|
|
52
54
|
|
|
53
|
-
Then
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
Then what is left. **The Actions setting**, read on arrival: if it is not set, *Set it for me*
|
|
56
|
+
asks GitHub to set it, and a refusal comes back with GitHub's reason and a link to the page to
|
|
57
|
+
click. **The agent credential**: a paste box, a line on where your agent's credential comes from,
|
|
58
|
+
and where it will be stored (one org secret shared with the brains, or each brain where an org
|
|
59
|
+
secret would not arrive, with the reason). The value goes to the local server in a POST and from
|
|
60
|
+
there to `gh` on standard input; it is never written to disk or echoed back. Until somebody is
|
|
61
|
+
hired the box says so, because nothing would read it. Then which repos the staff work in, as a
|
|
62
|
+
picker over what your `gh` can see minus what `org.yaml` already has, recorded with the
|
|
63
|
+
visibility GitHub reports. Then the two files only you can write: a prompt for writing
|
|
64
|
+
`org/business.md`, and `org/priorities.md` opened in place, stub and all, to replace with what
|
|
65
|
+
matters this month. Each says whether it is written yet.
|
|
56
66
|
|
|
57
67
|
Nothing here stores which step you are on. Setup takes days rather than minutes: an App has to be
|
|
58
68
|
installed, a credential set, a first run finished. So the page derives its state from `roster
|
|
59
|
-
doctor` every time it is drawn
|
|
60
|
-
|
|
61
|
-
|
|
69
|
+
doctor` every time it is drawn, and runs the check again after anything on it is saved. A stored
|
|
70
|
+
step counter would disagree with the world within an hour.
|
|
71
|
+
|
|
72
|
+
### Getting started
|
|
73
|
+
|
|
74
|
+
**Once the org exists, the same steps stay in the sidebar as *Getting started*** for as long as
|
|
75
|
+
anything is left: nobody hired, or `org/business.md` or `org/priorities.md` still the stub. An
|
|
76
|
+
org nobody has been hired into opens on it rather than on an empty Inbox, with *Hire your first
|
|
77
|
+
staff member* first, then the Actions setting, the credential, the repo picker, both files, and
|
|
78
|
+
what doctor still says. A link to another screen still wins. When the list is empty the entry
|
|
79
|
+
goes away; the repo picker lives on under [Org](#org).
|
|
80
|
+
|
|
81
|
+
## Home
|
|
82
|
+
|
|
83
|
+
Where the portal opens. An Ask box at the top, then these, in order.
|
|
84
|
+
|
|
85
|
+
- **Ask.** Pick a staff member, type what you want, **Send**. It opens an issue on their
|
|
86
|
+
tracker with their `@handle` in front, which wakes them in about a minute.
|
|
87
|
+
- **Latest reports.** Each staff member's run report from the last day: the three lines they
|
|
88
|
+
post on their status issue at the end of a daily run. Nothing shows until there is one.
|
|
89
|
+
- **Needs you.** Every ask a staff member has put on you, and every pull request ready to
|
|
90
|
+
merge, oldest first. Each card has one action on its right:
|
|
91
|
+
- a `decision` has **Reply**, and **Go with the default** when it carries one. The default
|
|
92
|
+
and its date are shown, and turn amber once the date has passed.
|
|
93
|
+
- a `review` has **Approve**.
|
|
94
|
+
- a `chore` has **Done**, which closes it.
|
|
95
|
+
- a pull request has **Merge**, unless its checks fail, are still running, or it conflicts.
|
|
96
|
+
|
|
97
|
+
Answers go out as you, with the staff member's `@handle`, so they act on them straight away.
|
|
98
|
+
Once you have had the last word, the card leaves Needs you for Your requests, since it is
|
|
99
|
+
waiting on them now. If they reply, it comes back. When nothing is waiting, it says so.
|
|
100
|
+
- **Unread.** Anything on a staff member's own tracker, or elsewhere, with activity you have not
|
|
101
|
+
seen: a peer's ask, a status issue, their own work. Unread comes from your GitHub
|
|
102
|
+
notifications, as on Trackers. Cards and rows anywhere on Home carry the same blue mark, and
|
|
103
|
+
opening one marks it read here and on GitHub.
|
|
104
|
+
- **Working now.** Each staff member: what they are running and what started it (the daily run,
|
|
105
|
+
a follow-on, answering an issue, a peer's ask), for how long, with a link to the log. When
|
|
106
|
+
they are idle, how their last run ended. Peer and follow-on runs today are counted against
|
|
107
|
+
`max_runs_per_day`. This asks GitHub every few seconds while a run is going and every half
|
|
108
|
+
minute otherwise, and only while Home is on screen.
|
|
109
|
+
- **Your requests.** What you asked for, and asks of theirs you have answered: waiting, being worked on (a run for it is going), or
|
|
110
|
+
answered (a staff member had the last word).
|
|
111
|
+
- **Closed today.** What the staff closed today, with the line they closed it with, and
|
|
112
|
+
**Reopen** beside each. Staff close finished issues themselves, so this is where you check.
|
|
113
|
+
|
|
114
|
+
**Every item opens in a side sheet.** Click anywhere on a card or a row and its thread opens
|
|
115
|
+
on the right: the conversation, Reply, Close or Reopen, Merge for a pull request, and Open in
|
|
116
|
+
GitHub. Escape or the × closes it, and Home repaints where you were.
|
|
117
|
+
|
|
118
|
+
**Nothing is missed.** Every open issue and pull request has exactly one place: on Home, or on
|
|
119
|
+
the staff member's own tracker (their status issue, a peer's ask, their own work), or elsewhere
|
|
120
|
+
(draft pull requests, contributor issues). The line at the bottom counts the last two, with a
|
|
121
|
+
link to Trackers.
|
|
122
|
+
|
|
123
|
+
## Trackers
|
|
62
124
|
|
|
63
125
|
Everything open across the org, from one GraphQL call per repo. Bodies and full timelines come
|
|
64
126
|
down with the list, so opening a thread is a render rather than a request.
|
|
65
127
|
|
|
66
|
-
- **
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
- **Open
|
|
128
|
+
- **The links under Trackers in the sidebar filter it.** All; Unread; each staff member, which
|
|
129
|
+
lists the issues on their own tracker whoever filed them; and Issues, the product repos.
|
|
130
|
+
- **Unread comes from your GitHub notifications.** A thread with activity you have not read has
|
|
131
|
+
a bar on the left and a bold title, and Unread lists only those, with a count. Opening one
|
|
132
|
+
marks it read on GitHub too, and reading it on GitHub clears it here. GitHub only notifies you
|
|
133
|
+
about repos you watch and threads you are part of.
|
|
134
|
+
- **Open or closed.** Open by default: an inbox is what is waiting on
|
|
73
135
|
somebody, and months of finished work mixed into that answers a different question. Closed
|
|
74
136
|
work reaches back 45 days, up to 30 issues and 30 pull requests per repository, and carries
|
|
75
137
|
a shorter timeline than open work because it is there to be read rather than triaged. A
|
|
@@ -80,9 +142,8 @@ down with the list, so opening a thread is a render rather than a request.
|
|
|
80
142
|
silently doing nothing.
|
|
81
143
|
- **A new issue asks one question: who is it for.** One dropdown, over the staff. It goes to
|
|
82
144
|
that person's brain repo and their `@handle` is written into the body for you, because those
|
|
83
|
-
are the two things that make an issue reach an agent rather than sit there.
|
|
84
|
-
|
|
85
|
-
nobody. To file in a product repo instead, use GitHub: this form is for asking the staff for
|
|
145
|
+
are the two things that make an issue reach an agent rather than sit there. Asking for a
|
|
146
|
+
repo as well would let you pick a pair that wakes nobody. To file in a product repo instead, use GitHub: this form is for asking the staff for
|
|
86
147
|
something.
|
|
87
148
|
- **Labels are the repo's own, as toggles.** They are the labels that exist on the recipient's
|
|
88
149
|
repository, fetched from GitHub and cached. A text box was a spelling test: `from-cmo` and
|
|
@@ -125,7 +186,7 @@ down with the list, so opening a thread is a render rather than a request.
|
|
|
125
186
|
|
|
126
187
|
## Pending work
|
|
127
188
|
|
|
128
|
-
The same screen, scoped to pull requests
|
|
189
|
+
The same screen, scoped to pull requests. It is where a pull request opens from Home. An inbox is what is
|
|
129
190
|
waiting on you; a pull request is work that is finished and waiting on a merge, and the count
|
|
130
191
|
that matters is not how many are open but how many are green and still sitting there.
|
|
131
192
|
|
|
@@ -149,8 +210,7 @@ of every repo to show one of them is the wrong trade.
|
|
|
149
210
|
second decision and not this button's to make. It runs `gh pr merge` as you, so a protected
|
|
150
211
|
branch, a failing required check or a merge queue behaves exactly as it would on the site.
|
|
151
212
|
|
|
152
|
-
**It does not ask how.**
|
|
153
|
-
That is a question about git rather than about the pull request in front of you, the repository
|
|
213
|
+
**It does not ask how.** Squash, merge commit or rebase is a question about git rather than about the pull request in front of you, the repository
|
|
154
214
|
has already answered it in its own settings, and on any given repository most of the answers are
|
|
155
215
|
wrong. So the repository is asked instead: squash where it is allowed, then a merge commit, then
|
|
156
216
|
rebase.
|
|
@@ -160,8 +220,8 @@ rebase.
|
|
|
160
220
|
**`@cto` on a pull request wakes nobody.** A staff member's caller workflow lives in their own
|
|
161
221
|
brain repo and gates on their handle appearing *there*; on a product repo the same mention
|
|
162
222
|
posts, renders as a chip, and does nothing. That is deliberate, for the reasons in
|
|
163
|
-
[security](security.md#trust-in-a-prompt)
|
|
164
|
-
restriction itself.
|
|
223
|
+
[security](security.md#trust-in-a-prompt). Saying so out loud is the portal's job, because a
|
|
224
|
+
silent no-op is worse than the restriction itself.
|
|
165
225
|
|
|
166
226
|
**Reply is the one box, and it handles this.** Name somebody in a reply where a comment will
|
|
167
227
|
not reach them and the offer appears under the box, ticked: *open it on their tracker too*. One
|
|
@@ -183,16 +243,30 @@ On the **Files** tab each file's heading has its own Reply, which opens the same
|
|
|
183
243
|
one file. "This bit is wrong" is what you want to say while looking at a diff, and the
|
|
184
244
|
alternative is describing in prose which of thirty files you meant.
|
|
185
245
|
|
|
186
|
-
There was briefly a second button up here called *Ask a staff member*. It did almost the same
|
|
187
|
-
thing as Reply, differing mainly in making you pick a name from a dropdown rather than typing
|
|
188
|
-
it, and nothing on the page said which one you wanted. Two ways to do one thing is worse than
|
|
189
|
-
either of them.
|
|
190
|
-
|
|
191
246
|
Both writes go through your own `gh`, as you. Nothing is dispatched between repositories and no
|
|
192
247
|
credential is put on a public repo. The tracker issue goes first, because it is the half that
|
|
193
248
|
reaches anybody; if the copy on the pull request then fails you are told, rather than being
|
|
194
249
|
shown an error that invites you to ask the same person the same thing twice.
|
|
195
250
|
|
|
251
|
+
## Runs
|
|
252
|
+
|
|
253
|
+
What each staff member ran in the last 30 days: when, daily or mention, how it ended, how long
|
|
254
|
+
it took, turns and cost where known, and a link to the log. Above the tables, the 30-day total
|
|
255
|
+
for the org, and each staff member's own in their heading.
|
|
256
|
+
|
|
257
|
+
Runs are not in any repo, so this is the one screen that is only ever on GitHub. It reads the
|
|
258
|
+
run lists through your own `gh`, the same way the inbox does, and offline it says so rather
|
|
259
|
+
than drawing an empty table that reads as "nothing ran".
|
|
260
|
+
|
|
261
|
+
Cost comes from the record each run leaves behind (see [cost](cost.md#what-each-run-cost)). A
|
|
262
|
+
run from before records existed, or from an agent that does not report cost, shows a dash, and
|
|
263
|
+
a total says how many runs it could price. Each record is downloaded once and kept for as long
|
|
264
|
+
as the portal runs, so the first visit is the slow one.
|
|
265
|
+
|
|
266
|
+
A skipped mention is not a run and is not listed. A `setup-failure` is a run that failed before
|
|
267
|
+
the agent started, usually a token or a checkout. Past a [`budget`](org-yaml.md#budget), the
|
|
268
|
+
total turns amber.
|
|
269
|
+
|
|
196
270
|
## Org
|
|
197
271
|
|
|
198
272
|
The layer every staff member inherits, in one place: `org.yaml`, every `org/*.md`, and the
|
|
@@ -200,9 +274,12 @@ prompt files in `prompts/`, each with a line saying what it is for, because a fi
|
|
|
200
274
|
you nothing about which to open. Anything roster does not ship falls back to its own first
|
|
201
275
|
heading.
|
|
202
276
|
|
|
203
|
-
**
|
|
204
|
-
|
|
205
|
-
|
|
277
|
+
**Add a product repo** opens the same picker setup uses, so a repo created later does not mean
|
|
278
|
+
typing `role: product` into `org.yaml` by hand. Adding one commits `org.yaml` and pushes. A new
|
|
279
|
+
hire picks it up; somebody already hired keeps the `works_in` in their own `staff.yaml`.
|
|
280
|
+
|
|
281
|
+
**The list comes off disk**, not out of the page, so a tenant that adds `org/pricing.md` can
|
|
282
|
+
open it like any other. Only files the
|
|
206
283
|
portal may actually write are listed: an editor that offers a file it cannot save is a trap.
|
|
207
284
|
|
|
208
285
|
Every one of them is editable from the screen it is read on: an **Edit** button on the file,
|
|
@@ -225,18 +302,50 @@ have more than one human, and a card that names one of two reads as the only one
|
|
|
225
302
|
|
|
226
303
|
## Staff
|
|
227
304
|
|
|
228
|
-
Everyone on the roster, and the
|
|
229
|
-
|
|
230
|
-
**Hiring**
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
305
|
+
Everyone on the roster, and the things you would otherwise do from a terminal.
|
|
306
|
+
|
|
307
|
+
**Hiring** starts from a role. With nobody hired the Staff screen opens on the role picker;
|
|
308
|
+
after that it is behind **Hire someone**. The cards are CTO, CMO and Support, each with a line
|
|
309
|
+
on what they do, and **Something else**, which asks for the role's name and a sentence about it.
|
|
310
|
+
A role that is already hired is not offered. Picking one fills in the handle, name and repo
|
|
311
|
+
(`cto`, `cmo`, `support`, or a handle made from the name) and leaves the schedule empty, so the
|
|
312
|
+
server picks a free slot. All four are under **Advanced**, still editable. The first hire has
|
|
313
|
+
nobody to copy App names from, so Advanced also holds the App's name and the shared public
|
|
314
|
+
App's, which are `--app` and `--public-app`, filled in as `<org>-<handle>` and `<org>-robot`.
|
|
315
|
+
The public one only matters when a product repo is public; on private ones the session uses the
|
|
316
|
+
staff member's own App.
|
|
317
|
+
|
|
318
|
+
The role opens a numbered list on the same screen:
|
|
319
|
+
|
|
320
|
+
1. **Hire.** One sentence from the plan: the repo it creates and when it runs. **Show details**
|
|
321
|
+
has the full plan, from the same `buildPlan` and `applyPlan` that `roster hire` runs on the
|
|
322
|
+
server: every file, every label, the schedule and why, the commits it makes as you in repos
|
|
323
|
+
that already exist (each peer's `staff.yaml`, `org.yaml`), and whether the new brain joins
|
|
324
|
+
the credential's org secret. **Hire** asks before it acts.
|
|
325
|
+
2. **Create their GitHub App.** The private App, and the shared public one only when a product
|
|
326
|
+
repo in `org.yaml` is public (or has no visibility written, which hire treats as public).
|
|
327
|
+
3. **Write their charter.** Three tabs: an AI interview (the default), the matching worked
|
|
328
|
+
example with your business's name in place of Acme's, and the file itself. A role that
|
|
329
|
+
matches no example has no template tab.
|
|
330
|
+
4. **Add your agent credential.** Only while none is stored.
|
|
331
|
+
5. **Run once now.**
|
|
332
|
+
|
|
333
|
+
Steps 2 to 5 say *Hire first* until the hire is done. Each step's Done comes from real data:
|
|
334
|
+
the hire from the staff member appearing in `org.yaml`, the charter from `CHARTER.md` no longer
|
|
335
|
+
being the stub (the same test as doctor's `charter.stub`), the App from its `_APP_ID` secret on
|
|
336
|
+
the brain repo, the credential from the org secret, and the run from a successful daily run.
|
|
337
|
+
After **Hire** the screen reloads the org and stays on the same staff member, on step 2.
|
|
338
|
+
|
|
339
|
+
A card whose setup is not finished (a stub charter, no App secrets, or no successful run yet)
|
|
340
|
+
shows **Finish setting up**, which opens the same list for them. The App secrets and the run
|
|
341
|
+
are read from GitHub, so offline only the charter counts.
|
|
235
342
|
|
|
236
343
|
**Writing the charter** is the copy-a-prompt loop below, aimed at `CHARTER.md`. `hire`
|
|
237
344
|
deliberately does not write it, because a generated charter produces exactly the generic agent
|
|
238
|
-
this whole arrangement exists to avoid.
|
|
239
|
-
|
|
345
|
+
this whole arrangement exists to avoid. It is the same brief as `roster brief charter
|
|
346
|
+
<handle>`, with somewhere to put the answer, and a picker for the worked example it carries as a
|
|
347
|
+
model: matched to the role, or another, or none. For a role added with **Something else**, the
|
|
348
|
+
sentence you typed goes into the brief.
|
|
240
349
|
|
|
241
350
|
**The GitHub App** is `roster app`, on this server rather than a second one. There is no API that
|
|
242
351
|
creates an App: the only route is the manifest flow, where you post a manifest to a settings page,
|
|
@@ -246,9 +355,17 @@ one origin. The private key is still held in memory and written straight to a re
|
|
|
246
355
|
|
|
247
356
|
GitHub redirects the tab *it* opened, not the one you clicked from, so the original polls for the
|
|
248
357
|
result. What it cannot do is install the App: that is a grant of access to specific repositories
|
|
249
|
-
and GitHub asks a
|
|
250
|
-
|
|
251
|
-
|
|
358
|
+
and GitHub asks a person to confirm it, which is correct and should not be worked around. So the
|
|
359
|
+
panel's **Install it** opens the install page with the org and the repos already selected: the
|
|
360
|
+
brain, every peer tracker it writes to, and the product repos. Any whose id could not be read are
|
|
361
|
+
listed for you to tick.
|
|
362
|
+
|
|
363
|
+
**Agent credential** is the setup screen's paste box, reachable from each card, because the
|
|
364
|
+
moment you look for it is while setting somebody up. It is once for the org.
|
|
365
|
+
|
|
366
|
+
**Run once now** starts the daily workflow, follows it, and shows how it ended with the log's
|
|
367
|
+
link and, on a failure, the step it failed at. It asks first, because it is a real run. A success
|
|
368
|
+
is what turns doctor's *unproven* into proven. Health has the same button.
|
|
252
369
|
|
|
253
370
|
**Retiring** is `roster retire`, and it is deliberately not deletion. A brain repo is that
|
|
254
371
|
agent's entire memory and there is no undo, so retiring disables the workflows, unwires them
|
|
@@ -264,7 +381,7 @@ already declared `memory/` as a surface.
|
|
|
264
381
|
The navigator has three boxes, because a parsed memory section and a file on disk are
|
|
265
382
|
different kinds of thing.
|
|
266
383
|
|
|
267
|
-
**
|
|
384
|
+
**What they know** is the fact sections, the notes behind them, and `INDEX.md` itself. A note is the
|
|
268
385
|
argument behind one fact, read only when that fact is in play, which is what keeps the index
|
|
269
386
|
cheap enough to read at every boot. Both live here rather than among the files: `INDEX.md` is
|
|
270
387
|
literally what "All facts" renders.
|
|
@@ -284,8 +401,8 @@ rather than going nowhere.
|
|
|
284
401
|
|
|
285
402
|
## Prompt
|
|
286
403
|
|
|
287
|
-
**What this staff member is actually sent**,
|
|
288
|
-
|
|
404
|
+
**What this staff member is actually sent**, the same as `roster prompt <handle> --kind daily`.
|
|
405
|
+
Composed on the server by the tenant's own
|
|
289
406
|
`compose.mjs`, so there is no second implementation to drift.
|
|
290
407
|
|
|
291
408
|
Pick the kind: `daily` or `mention`. A mention prompt is written for the comment
|
package/docs/prompts.md
CHANGED
|
@@ -58,12 +58,12 @@ ROSTER_CONTEXT='{"issue_number":"1","comment_id":"1","repo":"o/r"}' \
|
|
|
58
58
|
`comment_id`; a mention typed into the body of a *new* issue does not, and there is no comment
|
|
59
59
|
for the agent to fetch. So `compose.mjs` derives `event.no_comment` from the absence, and
|
|
60
60
|
`prompts/mention.md` branches on it: one side points at the comment, the other at the issue
|
|
61
|
-
body. Without that, the first instruction in the prompt
|
|
61
|
+
body. Without that, the first instruction in the prompt would be a `gh api .../issues/comments/`
|
|
62
62
|
call with no id on the end, which 404s.
|
|
63
63
|
|
|
64
|
-
The second route is the busier one
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
The second route is the busier one. It is what the portal produces when you [ask for a
|
|
65
|
+
change](portal.md#asking-for-a-change), and those issues often carry a pull request that lives
|
|
66
|
+
somewhere else and say to answer there instead.
|
|
67
67
|
|
|
68
68
|
## Syntax
|
|
69
69
|
|
|
@@ -99,6 +99,12 @@ than a hang.
|
|
|
99
99
|
| `ops.dir` | ops repo directory in the checkout |
|
|
100
100
|
| `staff` | the whole of this staff member's `staff.yaml` |
|
|
101
101
|
| `staff.dir` | where their brain lands in the checkout |
|
|
102
|
+
| `staff.handle`, `staff.name` | their handle (`cto`) and role name (`Chief Technology Officer`) |
|
|
103
|
+
| `staff.brain` | their brain repo, `owner/name` |
|
|
104
|
+
| `staff.bot` | the login they post as on private repos, e.g. `acme-cto[bot]` |
|
|
105
|
+
| `staff.public_bot` | the login they post as on public repos |
|
|
106
|
+
| `staff.public_token_env` | the environment variable holding the public repo token during a run |
|
|
107
|
+
| `staff.status_issue` | the number of their pinned status issue |
|
|
102
108
|
| `staff.product` | **first entry of `works_in`, or null** |
|
|
103
109
|
| `staff.product.repo` | that repo's `owner/name` |
|
|
104
110
|
| `peers` | list of the other staff members |
|
|
@@ -108,10 +114,31 @@ than a hang.
|
|
|
108
114
|
| `event` | trigger context, from `ROSTER_CONTEXT` |
|
|
109
115
|
| `event.issue_number`, `event.comment_id`, `event.repo`, `event.actor` | |
|
|
110
116
|
| `event.no_comment` | true when the ask is the issue body rather than a comment. There is no `{{#unless}}`, so the absence is a value |
|
|
117
|
+
| `inflight` | the pull requests people have open on the product repos, as a markdown list. **Empty outside a run**, and when there are none. See below |
|
|
111
118
|
|
|
112
119
|
Anything else in a manifest is reachable under `staff.`, so `staff.status_issue` and
|
|
113
120
|
`staff.public_token_env` work without being listed here.
|
|
114
121
|
|
|
122
|
+
## Human work in flight
|
|
123
|
+
|
|
124
|
+
Before composing, a run looks at the staff member's product repos (their `works_in`, or every
|
|
125
|
+
`role: product` repo) for open pull requests opened by people rather than by any App. Each one
|
|
126
|
+
goes in as a line: title, author, age, branch, and the files it touches, capped at twenty with
|
|
127
|
+
the top directories named when there are more.
|
|
128
|
+
|
|
129
|
+
`prompts/_inflight.md` carries that list under a short instruction, inside `{{#if inflight}}`,
|
|
130
|
+
and both kinds include it: do not open competing work on files a human branch is changing, and
|
|
131
|
+
raise anything about it on that pull request instead.
|
|
132
|
+
|
|
133
|
+
It exists because nothing else tells them. Without it, a person's long-running branch is
|
|
134
|
+
invisible, and the staff open pull requests and issues chasing the same files, which the branch
|
|
135
|
+
then overtakes.
|
|
136
|
+
|
|
137
|
+
The list is a value, never a template. Titles are a person's words, and a `{{` in one must not
|
|
138
|
+
be able to break composition, so `compose.mjs` reads `.roster-run/inflight.md` and substitutes it
|
|
139
|
+
as it stands. Locally there is no such file, so `roster prompt` leaves the section out; pass
|
|
140
|
+
`--inflight` to fetch the real list through your own `gh`.
|
|
141
|
+
|
|
115
142
|
## Guarding
|
|
116
143
|
|
|
117
144
|
`{{staff.product}}` is null for a staff member with an empty `works_in`, and an unresolved
|
package/docs/security.md
CHANGED
|
@@ -45,6 +45,39 @@ whole organisation can reach anything in it.
|
|
|
45
45
|
Install narrowly. The Staff card's **GitHub App** panel prints the list it actually needs, and
|
|
46
46
|
so does `roster app`.
|
|
47
47
|
|
|
48
|
+
## The review gate
|
|
49
|
+
|
|
50
|
+
"Nothing goes out unread" is enforced by GitHub, not by the prompt. An App with
|
|
51
|
+
`contents: write` on a product repo can push to its default branch, and with
|
|
52
|
+
`pull_requests: write` it can merge its own PR, unless a rule on the branch says otherwise.
|
|
53
|
+
|
|
54
|
+
The rule: **the default branch of every product repo requires a pull request with at least one
|
|
55
|
+
approving review, and no staff App is on the list of who may bypass it.** A ruleset or classic
|
|
56
|
+
branch protection both count.
|
|
57
|
+
|
|
58
|
+
**It is off unless you turn it on**, with `review_gate: true` in `org.yaml`. Without it, staff
|
|
59
|
+
are told to leave merging to you, and on Pip they always have, but GitHub does not stop them.
|
|
60
|
+
Most orgs keep product repos private on the Free plan, where GitHub cannot enforce the rule.
|
|
61
|
+
|
|
62
|
+
With it on, `roster doctor` reads it for every `role: product` repo in `org.yaml` and every repo a staff
|
|
63
|
+
member `works_in`, as `review-gate`. It fails when nothing requires a PR or a staff App can
|
|
64
|
+
bypass the rule, and warns when a PR is required with no approval, since then whoever opened
|
|
65
|
+
it can merge it.
|
|
66
|
+
|
|
67
|
+
With it on, `roster hire --apply` adds a ruleset named `roster: review before merge` to each product repo
|
|
68
|
+
that does not already require an approving review, and leaves anything stricter alone. Pass
|
|
69
|
+
`--no-review-gate` to skip it. The ruleset lets repository admins bypass it only by merging a
|
|
70
|
+
pull request, so your own merge is still the approval (GitHub will not let you approve your
|
|
71
|
+
own PR) and an App, which is never an admin, cannot merge at all.
|
|
72
|
+
|
|
73
|
+
**On GitHub Free, private repos can't have this rule.** GitHub only enforces rulesets and
|
|
74
|
+
branch protection on private repos for paid plans. To use the gate there, make the product repo
|
|
75
|
+
public or move the org to GitHub Team.
|
|
76
|
+
|
|
77
|
+
To set it by hand: repo **Settings -> Rules -> Rulesets -> New branch ruleset**, target the
|
|
78
|
+
default branch, tick **Require a pull request before merging** with one required approval, and
|
|
79
|
+
keep the staff Apps off the bypass list.
|
|
80
|
+
|
|
48
81
|
## Permissions a new App asks for
|
|
49
82
|
|
|
50
83
|
```
|
|
@@ -106,13 +139,12 @@ into a world-readable log.
|
|
|
106
139
|
|
|
107
140
|
So a mention on a public pull request is decoration. It posts, it renders as a chip, and
|
|
108
141
|
nothing happens, which is safe and also invisible. The portal is what makes it visible and what
|
|
109
|
-
gets you out of it: replying with an `@handle` where nothing listens says so, and
|
|
110
|
-
|
|
111
|
-
|
|
142
|
+
gets you out of it: replying with an `@handle` where nothing listens says so, and offers to
|
|
143
|
+
open the request on that person's own private tracker as well, carrying the pull request and
|
|
144
|
+
the hunk. It writes through your own `gh`, as you, so no credential lives on the
|
|
112
145
|
public repo and nothing is dispatched across a boundary.
|
|
113
146
|
|
|
114
|
-
|
|
115
|
-
see [concepts](concepts.md#kinds-of-run). If you reinstate one, its author gate is
|
|
147
|
+
If you add a workflow to a product repo that bridges this automatically, its author gate is
|
|
116
148
|
load-bearing. Do not relax it.
|
|
117
149
|
|
|
118
150
|
## The loop guard
|
package/docs/session-workflow.md
CHANGED
|
@@ -14,8 +14,8 @@ A caller is about forty lines and does nothing but pass arguments.
|
|
|
14
14
|
## Why it lives in the tenant
|
|
15
15
|
|
|
16
16
|
A reusable workflow in a **private** repo can only be called from inside its own organisation.
|
|
17
|
-
A tenant therefore cannot call the framework's copy. That constraint
|
|
18
|
-
design, and it
|
|
17
|
+
A tenant therefore cannot call the framework's copy. That constraint shapes the whole
|
|
18
|
+
design, and it is the better one anyway: the framework is never a runtime dependency, so nothing
|
|
19
19
|
breaks if it moves, goes private, or is deleted.
|
|
20
20
|
|
|
21
21
|
This is also why `roster-ops` needs **Settings -> Actions -> General -> accessible from
|
|
@@ -28,11 +28,13 @@ repositories in this organisation**. Without it, callers fail with "workflow not
|
|
|
28
28
|
| `staff` | string | required | Handle, as in `org.yaml`. |
|
|
29
29
|
| `kind` | string | `daily` | `daily` or `mention`. |
|
|
30
30
|
| `ops_repo` | string | required | `owner/name` of the ops repo. |
|
|
31
|
-
| `model` | string | `claude-opus-5` | Passed to the agent, unless the agent resolves its own. |
|
|
31
|
+
| `model` | string | `claude-opus-5-5` | Passed to the agent, unless the agent resolves its own. |
|
|
32
32
|
| `timeout_minutes` | number | `90` | Job ceiling. |
|
|
33
33
|
| `allowed_tools` | string | `Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch` | Tool permissions, for agents that take them. |
|
|
34
34
|
| `issue_number` | string | `""` | Trigger context. |
|
|
35
35
|
| `comment_id` | string | `""` | Trigger context. |
|
|
36
|
+
| `trigger` | string | `""` | What started the run: `daily`, `manual`, `follow-on`, `mention` or `peer`. Passed to the prompt. |
|
|
37
|
+
| `max_runs_per_day` | number | `6` | How many `peer` and `follow-on` runs may start in a UTC day. Mentions are never counted. |
|
|
36
38
|
|
|
37
39
|
## Secrets
|
|
38
40
|
|
|
@@ -51,23 +53,66 @@ authentication error forty lines into a log.
|
|
|
51
53
|
|
|
52
54
|
## What it does, in order
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
Before the session job, a small **budget** job. For a `peer` or `follow-on` run it counts this
|
|
57
|
+
brain's runs today with those names in their `run-name`, this one included and skipped ones
|
|
58
|
+
left out. Over `max_runs_per_day`, the session does not start and the issue that woke it gets a
|
|
59
|
+
comment saying so. If the runs cannot be read, the run does not start either: a run held back
|
|
60
|
+
costs a day, and a loop costs a bill. Every other trigger goes straight through.
|
|
61
|
+
|
|
62
|
+
Then the session:
|
|
63
|
+
|
|
64
|
+
1. **Start the clock**, for the run record.
|
|
65
|
+
2. **Mint the private-tracker token** from the staff member's App.
|
|
66
|
+
3. **Mint the public-repo token**, if a public App was passed.
|
|
67
|
+
4. **React to the request** with eyes, on a `mention` only. Before any checkout, so it lands in
|
|
57
68
|
seconds. `continue-on-error`: a missing reaction must never cost the answer.
|
|
58
|
-
|
|
69
|
+
5. **Check out the ops repo.** It is the only thing that can be cloned without having read a
|
|
59
70
|
manifest, so it goes first and then says what else to clone.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
10. **
|
|
66
|
-
11. **
|
|
67
|
-
12. **
|
|
68
|
-
13. **
|
|
71
|
+
6. **Work out what to check out**, by running `runner-plan.mjs`.
|
|
72
|
+
7. **Check out the brain**, full history. The agent reads its own past.
|
|
73
|
+
8. **Check out peers and product repos**, per the plan.
|
|
74
|
+
9. **Gather human work in flight**: open pull requests people have on the product repos, via
|
|
75
|
+
`inflight.mjs`, for the prompt. Never fatal. See [prompts](prompts.md#human-work-in-flight).
|
|
76
|
+
10. **Set git identity** to the App.
|
|
77
|
+
11. **Set up Node and pnpm**, if the plan found a `package.json`.
|
|
78
|
+
12. **Compose the prompt**, to a step output and to `.roster-prompt.txt`.
|
|
79
|
+
13. **Check the agent has a credential.**
|
|
80
|
+
14. **Work out which agent runs this**, by running `agents.mjs`.
|
|
81
|
+
15. **Run the session**, by one of two steps: the Action-based reference runner, or the generic
|
|
69
82
|
CLI one. See [choosing a coding agent](agents.md).
|
|
70
|
-
|
|
83
|
+
Then, for a mention, **check the request was answered**: a reply in the thread, or the issue
|
|
84
|
+
closed. Neither fails the job, so the notice below tells the human.
|
|
85
|
+
Then, for a daily run that finished and wrote `.roster-run/continue`, **start a follow-on
|
|
86
|
+
run**: one more daily run with `trigger: follow-on`, which waits for this one to end.
|
|
87
|
+
16. **Write down the run**, whatever happened: staff, kind, outcome, duration, and turns, cost
|
|
88
|
+
and tokens where the agent reports them. Into the job summary, and kept as an artifact
|
|
89
|
+
called `roster-run`. Never fatal. See [cost](cost.md#what-each-run-cost).
|
|
90
|
+
17. **Say so if the run did not finish.** A comment on the status issue, or on the issue that
|
|
91
|
+
woke a mention, linking the run.
|
|
92
|
+
|
|
93
|
+
## When the failure is the token
|
|
94
|
+
|
|
95
|
+
The failure notice cannot rely on anything that might be what failed. A renamed repo, a rotated
|
|
96
|
+
key or an uninstalled App breaks the App token first, and an alert that posts with that token
|
|
97
|
+
says nothing at exactly the moment it is needed. So the notice uses the App token when there is
|
|
98
|
+
one, and falls back to the job's own `github.token` when there is not or it is refused. It then
|
|
99
|
+
posts as `github-actions`, and says to check the App.
|
|
100
|
+
|
|
101
|
+
That needs `issues: write` on the job token. `session.yaml` asks for it, but a called workflow
|
|
102
|
+
can only narrow what its caller grants, so both callers grant it too:
|
|
103
|
+
|
|
104
|
+
```yaml
|
|
105
|
+
jobs:
|
|
106
|
+
session:
|
|
107
|
+
permissions:
|
|
108
|
+
contents: read
|
|
109
|
+
issues: write
|
|
110
|
+
uses: acme/roster-ops/.github/workflows/session.yaml@main
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Callers generated before this lack it, and their fallback cannot post. `roster upgrade --apply`
|
|
114
|
+
regenerates them. `roster doctor` separately reports any other workflow in the ops or brain
|
|
115
|
+
repos that has failed run after run, as `workflows.failing`.
|
|
71
116
|
|
|
72
117
|
## What `runner-plan.mjs` emits
|
|
73
118
|
|
|
@@ -92,6 +137,7 @@ Consumed by later steps as `steps.plan.outputs.*`.
|
|
|
92
137
|
| `GH_TOKEN` | private-tracker token, already authenticated |
|
|
93
138
|
| `PUBLIC_TOKEN` | public product repo token |
|
|
94
139
|
| `AGENT_PROMPT_FILE` | absolute path to the composed prompt |
|
|
140
|
+
| `AGENT_RESULT_FILE` | where to write the agent's own result JSON, if it has one. Optional; it is how cost gets into the run record |
|
|
95
141
|
| `AGENT_MODEL` | resolved model |
|
|
96
142
|
| `AGENT_TOOLS` | the `allowed_tools` string |
|
|
97
143
|
| *the agent's own* | its credential, under whatever name it declares |
|
package/docs/staff-yaml.md
CHANGED
|
@@ -22,13 +22,14 @@ brain: acme/technology
|
|
|
22
22
|
status_issue: 15
|
|
23
23
|
|
|
24
24
|
schedule: "0 7 * * 1-5"
|
|
25
|
-
model: claude-opus-5
|
|
25
|
+
model: claude-opus-5-5
|
|
26
26
|
timeout_minutes: 90
|
|
27
27
|
mention_timeout_minutes: 90
|
|
28
|
+
max_runs_per_day: 6
|
|
28
29
|
|
|
29
30
|
bot: acme-cto[bot]
|
|
30
31
|
public_bot: acme-robot[bot]
|
|
31
|
-
public_token_env:
|
|
32
|
+
public_token_env: PUBLIC_TOKEN
|
|
32
33
|
agent_secret: CLAUDE_CODE_OAUTH_TOKEN
|
|
33
34
|
|
|
34
35
|
identities:
|
|
@@ -49,7 +50,7 @@ surfaces:
|
|
|
49
50
|
|
|
50
51
|
labels:
|
|
51
52
|
owner: [will, cto, cmo]
|
|
52
|
-
kind: [decision,
|
|
53
|
+
kind: [decision, review, chore, keep-open, build, blocked]
|
|
53
54
|
```
|
|
54
55
|
|
|
55
56
|
## Identity
|
|
@@ -59,6 +60,7 @@ labels:
|
|
|
59
60
|
| `handle` | yes | Must match `org.yaml`. The composer looks them up by the `org.yaml` one, so a mismatch composes the wrong brain. |
|
|
60
61
|
| `name` | yes | Role name in prose. |
|
|
61
62
|
| `mention` | yes | What wakes them, as in `@cto`. The caller's condition tests for this string. |
|
|
63
|
+
| `icon` | no | The icon the portal shows for them: `code`, `megaphone`, `life-buoy`, `compass`, `brush`, `bug`, `server`, `book-open`, `bar-chart`, `users` or `person`. Unset, it is picked from the role. |
|
|
62
64
|
| `brain` | yes | `owner/name` of this repo. Without it nothing can check secrets, labels or runs. |
|
|
63
65
|
| `status_issue` | yes in practice | Number of the pinned status issue. The prompts reference it, so a run cannot compose without it. `roster hire --apply` writes it. |
|
|
64
66
|
|
|
@@ -70,6 +72,7 @@ labels:
|
|
|
70
72
|
| `model` | org default | Model id. |
|
|
71
73
|
| `timeout_minutes` | 90 | Ceiling on the daily session. |
|
|
72
74
|
| `mention_timeout_minutes` | 90 | Ceiling on a mention run. |
|
|
75
|
+
| `max_runs_per_day` | 6 | Runs a UTC day that start without a person asking: a peer's ask, or a follow-on to a daily run. Mentions never count. Rendered into the callers, so change it and run `roster upgrade`. |
|
|
73
76
|
|
|
74
77
|
The three ceilings are separate on purpose. Raising the daily one because sessions have grown
|
|
75
78
|
should not double the budget for a PR amendment. A job killed by a ceiling is reported by
|
|
@@ -154,6 +157,30 @@ Labels this staff member expects to exist on its own tracker, grouped for readab
|
|
|
154
157
|
value across every group is checked by `roster doctor`. An agent applying a label that does not
|
|
155
158
|
exist gets an API error mid-run.
|
|
156
159
|
|
|
160
|
+
Four labels are roster's own and are checked on every tracker whatever this lists:
|
|
161
|
+
|
|
162
|
+
| Label | Means |
|
|
163
|
+
|---|---|
|
|
164
|
+
| `decision` | An ask for the human to rule on. It carries a default and a date; past the date, the staff member acts on the default and closes it. |
|
|
165
|
+
| `review` | An ask for the human to read or approve something. |
|
|
166
|
+
| `chore` | Something only the human can do: a setting, an account, a key. |
|
|
167
|
+
| `keep-open` | A standing thread. A sweep never closes it. `roster hire` puts it on the status issue. |
|
|
168
|
+
|
|
169
|
+
Every ask on the human carries exactly one of the first three, which is how the portal sorts
|
|
170
|
+
what needs them. Staff close any issue on their tracker once nothing is left to do on it,
|
|
171
|
+
including the human's requests, with one line saying what closed it.
|
|
172
|
+
|
|
173
|
+
## `memory`
|
|
174
|
+
|
|
175
|
+
This staff member's own memory budgets, overriding the org's. Same three fields as
|
|
176
|
+
[`memory` in org.yaml](org-yaml.md#memory); any left out fall back to the org, then to the
|
|
177
|
+
defaults.
|
|
178
|
+
|
|
179
|
+
```yaml
|
|
180
|
+
memory:
|
|
181
|
+
max_index_kb: 32
|
|
182
|
+
```
|
|
183
|
+
|
|
157
184
|
## What `roster upgrade` does to this file
|
|
158
185
|
|
|
159
186
|
Nothing. It is `scaffold` class: written once by `roster hire`, and yours from that moment.
|