@nanocollective/roster 0.1.0-alpha.2 → 0.1.0-alpha.21
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 +4775 -2495
- package/docs/README.md +19 -11
- package/docs/agents.md +328 -13
- package/docs/architecture.md +13 -5
- package/docs/charters/cmo.md +69 -0
- package/docs/charters/cto.md +71 -0
- package/docs/charters/support.md +60 -0
- package/docs/commands.md +95 -11
- package/docs/concepts.md +64 -12
- package/docs/cost.md +39 -3
- package/docs/developing.md +16 -21
- package/docs/doctor-codes.md +21 -6
- package/docs/export.md +2 -1
- package/docs/extending.md +13 -4
- package/docs/getting-started.md +121 -80
- 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 -101
- package/docs/memory.md +29 -8
- package/docs/org-yaml.md +76 -11
- package/docs/portal.md +261 -47
- package/docs/prompts.md +77 -11
- package/docs/security.md +51 -7
- package/docs/session-workflow.md +51 -21
- package/docs/staff-yaml.md +16 -7
- package/docs/troubleshooting.md +23 -20
- package/docs/upgrading.md +9 -3
- package/docs/writing-a-charter.md +33 -17
- package/package.json +1 -1
- package/templates/brain/.github/workflows/%%STAFF%%-daily.yaml +7 -0
- package/templates/brain/.github/workflows/%%STAFF%%-mention.yaml +16 -4
- 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 +0 -1
- package/templates/brain/strategy/ideas.md +7 -0
- package/templates/ops/.github/workflows/session.yaml +117 -40
- package/templates/ops/agents.mjs +127 -8
- package/templates/ops/compose.mjs +77 -7
- package/templates/ops/inflight.mjs +157 -0
- package/templates/ops/org/operating.md +21 -7
- package/templates/ops/org/voice.md +9 -0
- package/templates/ops/prompts/_identity.md +8 -1
- package/templates/ops/prompts/_inflight.md +14 -0
- package/templates/ops/prompts/_paths.md +2 -1
- package/templates/ops/prompts/daily.md +16 -7
- package/templates/ops/prompts/mention.md +18 -2
- package/templates/ops/run-record.mjs +144 -0
- package/templates/portal/css/base.css +238 -64
- package/templates/portal/css/brain.css +30 -20
- package/templates/portal/css/diff.css +15 -10
- package/templates/portal/css/graph.css +12 -7
- package/templates/portal/css/health.css +32 -11
- package/templates/portal/css/inbox.css +117 -14
- package/templates/portal/css/layout.css +93 -41
- package/templates/portal/css/markdown.css +57 -15
- package/templates/portal/css/runs.css +13 -0
- package/templates/portal/css/setup.css +83 -39
- package/templates/portal/index.html +24 -3
- package/templates/portal/js/api.js +65 -4
- package/templates/portal/js/app.js +112 -12
- package/templates/portal/js/dialog.js +94 -4
- package/templates/portal/js/dom.js +25 -0
- package/templates/portal/js/icons.js +8 -1
- package/templates/portal/js/lightbox.js +273 -0
- package/templates/portal/js/md.js +23 -6
- package/templates/portal/js/mdedit.js +84 -0
- package/templates/portal/js/mention.js +264 -0
- package/templates/portal/js/refresh.js +136 -6
- package/templates/portal/js/state.js +55 -8
- package/templates/portal/js/views/app.js +23 -5
- package/templates/portal/js/views/checklist.js +29 -10
- package/templates/portal/js/views/credential.js +98 -0
- package/templates/portal/js/views/docs.js +94 -4
- package/templates/portal/js/views/files.js +58 -14
- package/templates/portal/js/views/graph.js +1 -1
- package/templates/portal/js/views/health.js +178 -37
- package/templates/portal/js/views/inbox.js +938 -98
- package/templates/portal/js/views/memory.js +16 -1
- package/templates/portal/js/views/org.js +124 -104
- package/templates/portal/js/views/orgedit.js +213 -0
- package/templates/portal/js/views/paste.js +33 -7
- package/templates/portal/js/views/prompt.js +61 -67
- 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 +165 -0
- package/templates/portal/js/views/setup.js +311 -83
- package/templates/portal/js/views/staff.js +139 -22
- package/templates/portal/js/yaml.js +134 -0
- package/templates/brain/.github/workflows/%%STAFF%%-pr-mention.yaml +0 -50
- package/templates/ops/prompts/pr-mention.md +0 -57
package/docs/prompts.md
CHANGED
|
@@ -8,7 +8,13 @@ sidebar_order: 15
|
|
|
8
8
|
|
|
9
9
|
What a staff member is actually sent, and how to change it.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
**Look at it before changing anything.** The portal's [Prompt screen](portal.md#prompt) is the
|
|
12
|
+
composed text and, beneath it, every file it was made of: which are inlined and in what order,
|
|
13
|
+
which are only named, and which repo each came from. That last column is the one that matters,
|
|
14
|
+
because a change to `roster-ops/org/voice.md` reaches every staff member and a change to a
|
|
15
|
+
brain's own `prompts/work.md` reaches one.
|
|
16
|
+
|
|
17
|
+
From a terminal, the same text:
|
|
12
18
|
|
|
13
19
|
```bash
|
|
14
20
|
roster prompt cto --kind daily
|
|
@@ -35,16 +41,30 @@ does not control.
|
|
|
35
41
|
|---|---|---|
|
|
36
42
|
| `daily` | cron | no |
|
|
37
43
|
| `mention` | `@handle` in a comment, or in a new issue body | yes |
|
|
38
|
-
| `pr-mention` | a review comment on the product repo, forwarded in | yes |
|
|
39
44
|
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
A `mention` refuses to compose without context, because it is written for the comment that woke
|
|
46
|
+
it. That is correct behaviour rather than a bug.
|
|
47
|
+
|
|
48
|
+
The Prompt screen's kind picker handles that for you: pick **Mention** and it fills in
|
|
49
|
+
obviously-fake trigger context so there is something to look at. From a terminal you supply it
|
|
50
|
+
yourself:
|
|
42
51
|
|
|
43
52
|
```bash
|
|
44
|
-
ROSTER_CONTEXT='{"issue_number":"1","comment_id":"1","
|
|
53
|
+
ROSTER_CONTEXT='{"issue_number":"1","comment_id":"1","repo":"o/r"}' \
|
|
45
54
|
roster prompt cto --kind mention
|
|
46
55
|
```
|
|
47
56
|
|
|
57
|
+
**There are two routes in, and the prompt is not the same on both.** A comment carries a
|
|
58
|
+
`comment_id`; a mention typed into the body of a *new* issue does not, and there is no comment
|
|
59
|
+
for the agent to fetch. So `compose.mjs` derives `event.no_comment` from the absence, and
|
|
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 would be a `gh api .../issues/comments/`
|
|
62
|
+
call with no id on the end, which 404s.
|
|
63
|
+
|
|
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
|
+
|
|
48
68
|
## Syntax
|
|
49
69
|
|
|
50
70
|
Four forms, and nothing else.
|
|
@@ -71,23 +91,54 @@ than a hang.
|
|
|
71
91
|
|---|---|
|
|
72
92
|
| `org` | the whole of `org.yaml` |
|
|
73
93
|
| `org.name`, `org.org` | the business name, the GitHub org |
|
|
74
|
-
| `human` | the
|
|
94
|
+
| `human` | the primary human: the first of `humans`, or the singular `human` block |
|
|
75
95
|
| `human.name`, `human.github`, `human.marker` | |
|
|
96
|
+
| `humans` | everyone the staff answer to, in order. See [org.yaml](org-yaml.md#human-and-humans) |
|
|
97
|
+
| `human_list` | all of them as a sentence: "Will (@will-lamerton) and Sam (@sam-x)" |
|
|
98
|
+
| `humans_extra` | the same, minus the primary. **Empty when there is only one**, which is what makes `{{#if humans_extra}}` the way to mention the others |
|
|
76
99
|
| `ops.dir` | ops repo directory in the checkout |
|
|
77
100
|
| `staff` | the whole of this staff member's `staff.yaml` |
|
|
78
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 |
|
|
79
108
|
| `staff.product` | **first entry of `works_in`, or null** |
|
|
80
109
|
| `staff.product.repo` | that repo's `owner/name` |
|
|
81
110
|
| `peers` | list of the other staff members |
|
|
82
111
|
| `peer` | the first peer, or null |
|
|
83
112
|
| `peer_list` | peers pre-rendered as a markdown list |
|
|
84
|
-
| `kind` | `daily
|
|
113
|
+
| `kind` | `daily` or `mention` |
|
|
85
114
|
| `event` | trigger context, from `ROSTER_CONTEXT` |
|
|
86
|
-
| `event.issue_number`, `event.comment_id`, `event.
|
|
115
|
+
| `event.issue_number`, `event.comment_id`, `event.repo`, `event.actor` | |
|
|
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 |
|
|
87
118
|
|
|
88
119
|
Anything else in a manifest is reachable under `staff.`, so `staff.status_issue` and
|
|
89
120
|
`staff.public_token_env` work without being listed here.
|
|
90
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
|
+
|
|
91
142
|
## Guarding
|
|
92
143
|
|
|
93
144
|
`{{staff.product}}` is null for a staff member with an empty `works_in`, and an unresolved
|
|
@@ -105,6 +156,9 @@ This is not hypothetical. The shipped prompts referred to `{{staff.product.repo}
|
|
|
105
156
|
which nobody noticed because every existing staff member had one. The first staff member of any
|
|
106
157
|
new org could not compose a prompt at all.
|
|
107
158
|
|
|
159
|
+
[Health](portal.md#health) checks for exactly this ("a placeholder never resolved"), per staff
|
|
160
|
+
member, which is the only way to catch it before 07:00 rather than in a run nobody watched.
|
|
161
|
+
|
|
108
162
|
## Overriding a fragment for one staff member
|
|
109
163
|
|
|
110
164
|
```
|
|
@@ -120,7 +174,17 @@ else's. `daily.md` already carries this hook.
|
|
|
120
174
|
`prompts/` is `seeded` class: yours to edit, and `roster upgrade` gives you a real three-way
|
|
121
175
|
merge. Changes reach every staff member on their next run.
|
|
122
176
|
|
|
123
|
-
|
|
177
|
+
**Edit them where you read them.** Every layer on the Prompt screen has an Edit button; saving
|
|
178
|
+
writes that one file, commits it and pushes, and the confirmation names the repository and who
|
|
179
|
+
picks it up. The org's own layers (`org/*.md`, `<ops>/prompts/*.md`) are the same edit on the
|
|
180
|
+
[Org screen](portal.md#org). Not everything is writable: `compose.mjs`, `staff.yaml`, the
|
|
181
|
+
workflows and `memory/INDEX.md` are readable and not editable, because a wrong one of those
|
|
182
|
+
stops every prompt composing or overwrites what the next run is about to write.
|
|
183
|
+
|
|
184
|
+
**The thing to check is what the edit did to the composed prompt, not to the file.** Those are
|
|
185
|
+
different questions: a line added to one fragment can land three times or not at all. Saving
|
|
186
|
+
from the Prompt screen shows you the first. From a terminal, the same check is two composes and
|
|
187
|
+
a diff:
|
|
124
188
|
|
|
125
189
|
```bash
|
|
126
190
|
roster prompt cto --kind daily > before.txt
|
|
@@ -129,5 +193,7 @@ roster prompt cto --kind daily > after.txt
|
|
|
129
193
|
diff before.txt after.txt
|
|
130
194
|
```
|
|
131
195
|
|
|
132
|
-
|
|
133
|
-
differently is not caught by anything else.
|
|
196
|
+
Either way, that is the only test there is for a prompt change. One that composes fine and
|
|
197
|
+
reads differently is not caught by anything else. [Health](portal.md#health) runs a prompt audit
|
|
198
|
+
over all the kinds at once, but everything it checks is something a machine can be sure about,
|
|
199
|
+
which never includes whether the prose is any good.
|
package/docs/security.md
CHANGED
|
@@ -42,7 +42,41 @@ workflows that constrain them.
|
|
|
42
42
|
misconfiguration and it fails at run time. Over-granting is the risk: an App installed on the
|
|
43
43
|
whole organisation can reach anything in it.
|
|
44
44
|
|
|
45
|
-
Install narrowly.
|
|
45
|
+
Install narrowly. The Staff card's **GitHub App** panel prints the list it actually needs, and
|
|
46
|
+
so does `roster app`.
|
|
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.
|
|
46
80
|
|
|
47
81
|
## Permissions a new App asks for
|
|
48
82
|
|
|
@@ -86,6 +120,8 @@ constrained rather than sanitised:
|
|
|
86
120
|
- `/api/file` resolves the path and refuses anything outside the workspace root.
|
|
87
121
|
- `/api/diff` requires the directory to be a known staff repo and the sha to look like a sha.
|
|
88
122
|
- `/api/doc` requires the page to be one the listing offered.
|
|
123
|
+
- `/api/docasset` requires the screenshot to be one sitting in `docs/images/`, matched by name
|
|
124
|
+
against that listing. `..` is not a case to get wrong; it is simply a name not on it.
|
|
89
125
|
|
|
90
126
|
`--host` overrides the bind address and prints a warning. It exists for people who know what
|
|
91
127
|
they are doing on a network they control. See [hosting the portal](hosting.md).
|
|
@@ -96,12 +132,20 @@ Everything composed into a prompt is content you or your agents wrote: `org/`, t
|
|
|
96
132
|
memory index. A `mention` run additionally carries the text of a comment.
|
|
97
133
|
|
|
98
134
|
**On a private tracker that is you.** On the public product repo it is not, which is exactly
|
|
99
|
-
why
|
|
100
|
-
|
|
101
|
-
charter and a chain of reasoning
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
135
|
+
why **nothing in a product repo wakes an agent at all**. There is no caller workflow there, by
|
|
136
|
+
design: anyone can comment on a public pull request, a run started from a comment executes with
|
|
137
|
+
repository secrets, and a run that prints a charter and a chain of reasoning would print it
|
|
138
|
+
into a world-readable log.
|
|
139
|
+
|
|
140
|
+
So a mention on a public pull request is decoration. It posts, it renders as a chip, and
|
|
141
|
+
nothing happens, which is safe and also invisible. The portal is what makes it visible and what
|
|
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
|
|
145
|
+
public repo and nothing is dispatched across a boundary.
|
|
146
|
+
|
|
147
|
+
If you add a workflow to a product repo that bridges this automatically, its author gate is
|
|
148
|
+
load-bearing. Do not relax it.
|
|
105
149
|
|
|
106
150
|
## The loop guard
|
|
107
151
|
|
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
|
|
@@ -26,14 +26,13 @@ repositories in this organisation**. Without it, callers fail with "workflow not
|
|
|
26
26
|
| Input | Type | Default | Means |
|
|
27
27
|
|---|---|---|---|
|
|
28
28
|
| `staff` | string | required | Handle, as in `org.yaml`. |
|
|
29
|
-
| `kind` | string | `daily` | `daily
|
|
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. |
|
|
32
|
-
| `timeout_minutes` | number | `
|
|
31
|
+
| `model` | string | `claude-opus-5-5` | Passed to the agent, unless the agent resolves its own. |
|
|
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
|
-
| `pr_number` | string | `""` | Trigger context. |
|
|
37
36
|
|
|
38
37
|
## Secrets
|
|
39
38
|
|
|
@@ -52,24 +51,54 @@ authentication error forty lines into a log.
|
|
|
52
51
|
|
|
53
52
|
## What it does, in order
|
|
54
53
|
|
|
55
|
-
1. **
|
|
56
|
-
2. **Mint the
|
|
57
|
-
3. **
|
|
54
|
+
1. **Start the clock**, for the run record.
|
|
55
|
+
2. **Mint the private-tracker token** from the staff member's App.
|
|
56
|
+
3. **Mint the public-repo token**, if a public App was passed.
|
|
57
|
+
4. **React to the request** with eyes, on a `mention` only. Before any checkout, so it lands in
|
|
58
58
|
seconds. `continue-on-error`: a missing reaction must never cost the answer.
|
|
59
|
-
|
|
59
|
+
5. **Check out the ops repo.** It is the only thing that can be cloned without having read a
|
|
60
60
|
manifest, so it goes first and then says what else to clone.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
10. **Set
|
|
67
|
-
11. **
|
|
68
|
-
12. **
|
|
69
|
-
13. **
|
|
70
|
-
14. **
|
|
61
|
+
6. **Work out what to check out**, by running `runner-plan.mjs`.
|
|
62
|
+
7. **Check out the brain**, full history. The agent reads its own past.
|
|
63
|
+
8. **Check out peers and product repos**, per the plan.
|
|
64
|
+
9. **Gather human work in flight**: open pull requests people have on the product repos, via
|
|
65
|
+
`inflight.mjs`, for the prompt. Never fatal. See [prompts](prompts.md#human-work-in-flight).
|
|
66
|
+
10. **Set git identity** to the App.
|
|
67
|
+
11. **Set up Node and pnpm**, if the plan found a `package.json`.
|
|
68
|
+
12. **Compose the prompt**, to a step output and to `.roster-prompt.txt`.
|
|
69
|
+
13. **Check the agent has a credential.**
|
|
70
|
+
14. **Work out which agent runs this**, by running `agents.mjs`.
|
|
71
|
+
15. **Run the session**, by one of two steps: the Action-based reference runner, or the generic
|
|
71
72
|
CLI one. See [choosing a coding agent](agents.md).
|
|
72
|
-
|
|
73
|
+
16. **Write down the run**, whatever happened: staff, kind, outcome, duration, and turns, cost
|
|
74
|
+
and tokens where the agent reports them. Into the job summary, and kept as an artifact
|
|
75
|
+
called `roster-run`. Never fatal. See [cost](cost.md#what-each-run-cost).
|
|
76
|
+
17. **Say so if the run did not finish.** A comment on the status issue, or on the issue that
|
|
77
|
+
woke a mention, linking the run.
|
|
78
|
+
|
|
79
|
+
## When the failure is the token
|
|
80
|
+
|
|
81
|
+
The failure notice cannot rely on anything that might be what failed. A renamed repo, a rotated
|
|
82
|
+
key or an uninstalled App breaks the App token first, and an alert that posts with that token
|
|
83
|
+
says nothing at exactly the moment it is needed. So the notice uses the App token when there is
|
|
84
|
+
one, and falls back to the job's own `github.token` when there is not or it is refused. It then
|
|
85
|
+
posts as `github-actions`, and says to check the App.
|
|
86
|
+
|
|
87
|
+
That needs `issues: write` on the job token. `session.yaml` asks for it, but a called workflow
|
|
88
|
+
can only narrow what its caller grants, so both callers grant it too:
|
|
89
|
+
|
|
90
|
+
```yaml
|
|
91
|
+
jobs:
|
|
92
|
+
session:
|
|
93
|
+
permissions:
|
|
94
|
+
contents: read
|
|
95
|
+
issues: write
|
|
96
|
+
uses: acme/roster-ops/.github/workflows/session.yaml@main
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Callers generated before this lack it, and their fallback cannot post. `roster upgrade --apply`
|
|
100
|
+
regenerates them. `roster doctor` separately reports any other workflow in the ops or brain
|
|
101
|
+
repos that has failed run after run, as `workflows.failing`.
|
|
73
102
|
|
|
74
103
|
## What `runner-plan.mjs` emits
|
|
75
104
|
|
|
@@ -94,6 +123,7 @@ Consumed by later steps as `steps.plan.outputs.*`.
|
|
|
94
123
|
| `GH_TOKEN` | private-tracker token, already authenticated |
|
|
95
124
|
| `PUBLIC_TOKEN` | public product repo token |
|
|
96
125
|
| `AGENT_PROMPT_FILE` | absolute path to the composed prompt |
|
|
126
|
+
| `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 |
|
|
97
127
|
| `AGENT_MODEL` | resolved model |
|
|
98
128
|
| `AGENT_TOOLS` | the `allowed_tools` string |
|
|
99
129
|
| *the agent's own* | its credential, under whatever name it declares |
|
package/docs/staff-yaml.md
CHANGED
|
@@ -22,14 +22,13 @@ 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
|
-
mention_timeout_minutes:
|
|
28
|
-
pr_mention_timeout_minutes: 60
|
|
27
|
+
mention_timeout_minutes: 90
|
|
29
28
|
|
|
30
29
|
bot: acme-cto[bot]
|
|
31
30
|
public_bot: acme-robot[bot]
|
|
32
|
-
public_token_env:
|
|
31
|
+
public_token_env: PUBLIC_TOKEN
|
|
33
32
|
agent_secret: CLAUDE_CODE_OAUTH_TOKEN
|
|
34
33
|
|
|
35
34
|
identities:
|
|
@@ -69,9 +68,8 @@ labels:
|
|
|
69
68
|
|---|---|---|
|
|
70
69
|
| `schedule` | none | Cron for the daily run. Rendered into the caller. |
|
|
71
70
|
| `model` | org default | Model id. |
|
|
72
|
-
| `timeout_minutes` |
|
|
73
|
-
| `mention_timeout_minutes` |
|
|
74
|
-
| `pr_mention_timeout_minutes` | 60 | Ceiling on a PR-amendment run. |
|
|
71
|
+
| `timeout_minutes` | 90 | Ceiling on the daily session. |
|
|
72
|
+
| `mention_timeout_minutes` | 90 | Ceiling on a mention run. |
|
|
75
73
|
|
|
76
74
|
The three ceilings are separate on purpose. Raising the daily one because sessions have grown
|
|
77
75
|
should not double the budget for a PR amendment. A job killed by a ceiling is reported by
|
|
@@ -156,6 +154,17 @@ Labels this staff member expects to exist on its own tracker, grouped for readab
|
|
|
156
154
|
value across every group is checked by `roster doctor`. An agent applying a label that does not
|
|
157
155
|
exist gets an API error mid-run.
|
|
158
156
|
|
|
157
|
+
## `memory`
|
|
158
|
+
|
|
159
|
+
This staff member's own memory budgets, overriding the org's. Same three fields as
|
|
160
|
+
[`memory` in org.yaml](org-yaml.md#memory); any left out fall back to the org, then to the
|
|
161
|
+
defaults.
|
|
162
|
+
|
|
163
|
+
```yaml
|
|
164
|
+
memory:
|
|
165
|
+
max_index_kb: 32
|
|
166
|
+
```
|
|
167
|
+
|
|
159
168
|
## What `roster upgrade` does to this file
|
|
160
169
|
|
|
161
170
|
Nothing. It is `scaffold` class: written once by `roster hire`, and yours from that moment.
|
package/docs/troubleshooting.md
CHANGED
|
@@ -9,8 +9,10 @@ sidebar_order: 10
|
|
|
9
9
|
Every trap on this page has actually been hit. Most of them fail in a way that points somewhere
|
|
10
10
|
else, which is why they are worth writing down.
|
|
11
11
|
|
|
12
|
-
Start with
|
|
13
|
-
what to do about it
|
|
12
|
+
Start with the portal's [Health](portal.md#health) screen. It groups by staff member, every
|
|
13
|
+
finding that is not `ok` says what to do about it, and a button turns the lot into one brief for
|
|
14
|
+
a coding agent, split into what an agent can fix and what only a person can. `roster doctor` and
|
|
15
|
+
`roster fix` are the same two things from a terminal.
|
|
14
16
|
|
|
15
17
|
---
|
|
16
18
|
|
|
@@ -20,8 +22,10 @@ what to do about it.
|
|
|
20
22
|
|
|
21
23
|
**Is:** the ops repo's Actions access is not set to organisation-wide.
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
`roster init --apply` sets it, and so does *Set it for me* on the setup screen. If GitHub
|
|
26
|
+
refused (it needs admin on the ops repo), set it by hand: Settings -> Actions -> General ->
|
|
27
|
+
Access on `roster-ops`, "accessible from repositories in the organisation". Health and `roster
|
|
28
|
+
doctor` both check it explicitly.
|
|
25
29
|
|
|
26
30
|
---
|
|
27
31
|
|
|
@@ -31,7 +35,7 @@ portal's setup screen links straight to the page.
|
|
|
31
35
|
`timeout-minutes` as `cancelled`, which reads as though somebody pressed a button.
|
|
32
36
|
|
|
33
37
|
Tell them apart by duration. Several cancelled runs all stopping at the same minute is a
|
|
34
|
-
ceiling, not a coincidence.
|
|
38
|
+
ceiling, not a coincidence. Health does this for you and names the number:
|
|
35
39
|
|
|
36
40
|
```
|
|
37
41
|
✗ cto-daily.yaml: 5 of the last 10 ran to a 60m ceiling and were killed
|
|
@@ -49,8 +53,8 @@ so old timeouts keep being reported as timeouts.
|
|
|
49
53
|
gated run still appears in the list with conclusion `skipped`. Most of a mention workflow's
|
|
50
54
|
history is skipped runs.
|
|
51
55
|
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
Health ignores them. A workflow whose runs are *all* skipped is reported differently, because
|
|
57
|
+
nothing has exercised the credentials.
|
|
54
58
|
|
|
55
59
|
---
|
|
56
60
|
|
|
@@ -63,11 +67,12 @@ been **installed** on the repository in question, or which repositories the inst
|
|
|
63
67
|
granted. The two are reported separately and they disagree exactly when you care.
|
|
64
68
|
|
|
65
69
|
Do not verify an installation by reading the API. The only proof of the whole chain is a run
|
|
66
|
-
that finished.
|
|
67
|
-
|
|
70
|
+
that finished. Health reads recent runs for this reason and calls a workflow that has never run
|
|
71
|
+
**unproven** rather than fine.
|
|
68
72
|
|
|
69
73
|
Fix: open the App's installation settings and check the repository list includes every tracker
|
|
70
|
-
the staff member writes to, not just its own.
|
|
74
|
+
the staff member writes to, not just its own. The **GitHub App** panel on that staff member's
|
|
75
|
+
card says so loudly, and prints the list.
|
|
71
76
|
|
|
72
77
|
---
|
|
73
78
|
|
|
@@ -87,11 +92,9 @@ constrains it is not a thing anybody wants.
|
|
|
87
92
|
**Is:** you edited a framework-owned file. `compose.mjs`, `agents.mjs`, `runner-plan.mjs` and
|
|
88
93
|
`session.yaml` are generated. The next `roster upgrade` reconciles them against the template.
|
|
89
94
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
`roster upgrade` now reports an edit to a framework-owned file whether or not anything has
|
|
94
|
-
collided, and `roster upgrade --check` fails on it. Move the change upstream.
|
|
95
|
+
Nothing notices until the framework next touches that file. `roster upgrade` reports an edit
|
|
96
|
+
to a framework-owned file whether or not anything has collided, and `roster upgrade --check`
|
|
97
|
+
fails on it. Move the change upstream.
|
|
95
98
|
|
|
96
99
|
---
|
|
97
100
|
|
|
@@ -107,8 +110,9 @@ first entry in `works_in`, and a staff member who contributes to no other reposi
|
|
|
107
110
|
The shipped prompts guard these. A prompt fragment you have written yourself needs
|
|
108
111
|
`{{#if staff.product}}` around anything that assumes one. Conditionals do not nest.
|
|
109
112
|
|
|
110
|
-
Similarly `{{staff.status_issue}}` is empty until
|
|
111
|
-
issue.
|
|
113
|
+
Similarly `{{staff.status_issue}}` is empty until hiring has actually been applied and opened
|
|
114
|
+
the pinned issue. Health's prompt audit has this check ("a placeholder never resolved"), and the
|
|
115
|
+
[Prompt screen](portal.md#prompt) shows you the composed text with the braces still in it.
|
|
112
116
|
|
|
113
117
|
---
|
|
114
118
|
|
|
@@ -119,8 +123,7 @@ seconds. If it does not:
|
|
|
119
123
|
|
|
120
124
|
- The reaction is `continue-on-error`. A missing reaction never costs the answer, so check
|
|
121
125
|
whether the run itself started at all.
|
|
122
|
-
- It is scoped to `kind == 'mention'`. A
|
|
123
|
-
public repo instead, so that it gets one reaction rather than two.
|
|
126
|
+
- It is scoped to `kind == 'mention'`. A daily run has nothing to react to.
|
|
124
127
|
- On the `issues` route (a mention typed into a new issue body) the eyes go on the issue, not
|
|
125
128
|
on a comment, because that payload has no comment.
|
|
126
129
|
|
|
@@ -178,7 +181,7 @@ landed on GitHub thirty seconds ago and the checkout is behind, that is what you
|
|
|
178
181
|
|
|
179
182
|
## `roster upgrade` says a file has no base
|
|
180
183
|
|
|
181
|
-
A tenant
|
|
184
|
+
A tenant with no recorded merge base has nothing to merge against. Reconstruct
|
|
182
185
|
one from the framework's history:
|
|
183
186
|
|
|
184
187
|
```bash
|
package/docs/upgrading.md
CHANGED
|
@@ -10,9 +10,9 @@ The framework writes templates out. A tenant runs its own copies. So the two dri
|
|
|
10
10
|
`roster upgrade` is what reconciles them without eating your edits.
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
|
-
roster upgrade
|
|
14
|
-
roster upgrade --apply
|
|
15
|
-
roster upgrade --check
|
|
13
|
+
npx @nanocollective/roster@latest upgrade # what would change
|
|
14
|
+
npx @nanocollective/roster@latest upgrade --apply # do it
|
|
15
|
+
npx @nanocollective/roster@latest upgrade --check # exit non-zero if anything is pending (for CI)
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
## How it decides
|
|
@@ -53,6 +53,12 @@ The diff is printed either way, so nothing goes quietly.
|
|
|
53
53
|
If you want a caller to differ, change the thing it is generated from. Timeouts, schedule,
|
|
54
54
|
model and identities all live in `staff.yaml`.
|
|
55
55
|
|
|
56
|
+
**A caller the framework no longer generates is removed.** It would still dispatch into
|
|
57
|
+
`session.yaml` with a kind that no longer composes, and fail at run time. The plan lists it.
|
|
58
|
+
The one that has gone so far is `<handle>-pr-mention.yaml`; if your tenant is old enough to
|
|
59
|
+
have one, a forwarding workflow in the product repo went with it, and that one is yours to
|
|
60
|
+
delete, because `roster upgrade` never writes into product repos.
|
|
61
|
+
|
|
56
62
|
## Conflicts
|
|
57
63
|
|
|
58
64
|
A conflict is never written into a live file. Agents read `org/voice.md` at every boot, and
|
|
@@ -15,26 +15,34 @@ particular, which takes longer to notice than no work at all.
|
|
|
15
15
|
|
|
16
16
|
## Write it with your own AI
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
**Write the charter**, on that staff member's card on the [Staff screen](portal.md#staff), does
|
|
19
|
+
the whole round trip. It copies a brief with every file it refers to already inside it, so a chat
|
|
20
|
+
window with no filesystem is as useful as an agent standing in the repo. Paste the reply back
|
|
21
|
+
into the box and you get a diff and a save button, never a silent write.
|
|
22
|
+
|
|
23
|
+
It runs about 19,000 characters, on purpose: one paste into a large-context model beats six
|
|
24
|
+
rounds of it asking for files it will never get.
|
|
25
|
+
|
|
26
|
+
**The peers' charters are in there**, along with `org/business.md` and the shared operating
|
|
27
|
+
layer. That is the part that matters most, because without them the model writes a second copy
|
|
28
|
+
of whoever it was shown. The brief then interviews you, drafts from your answers, and tells you
|
|
29
|
+
what it cut and why.
|
|
21
30
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
31
|
+
**So is a worked example, when one fits.** A staff member whose handle or role reads as a CTO, a
|
|
32
|
+
CMO or support gets the matching [example](#worked-examples) inside the brief, labelled as a
|
|
33
|
+
model for the shape and not content to copy. The copy-a-prompt panel has a picker to choose
|
|
34
|
+
another or none; in a terminal it is `--example cto|cmo|support|none`. It is still a brief you
|
|
35
|
+
answer: the interview comes first, and nothing in the charter should come from the example
|
|
36
|
+
rather than from you.
|
|
25
37
|
|
|
26
|
-
|
|
27
|
-
copies the same brief with every file it refers to already inside it, including the peers'
|
|
28
|
-
charters, so a chat window with no filesystem can do it. Paste the reply back and you get a diff
|
|
29
|
-
and a save. See [the portal](portal.md).
|
|
38
|
+
From a terminal, the same brief:
|
|
30
39
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
40
|
+
```bash
|
|
41
|
+
roster brief charter <handle> # or: roster brief charter cto | pbcopy
|
|
42
|
+
```
|
|
34
43
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
`roster brief`.
|
|
44
|
+
In Claude Code, `cd <staff-dir> && claude` then `/charter` runs the same text, because `roster
|
|
45
|
+
hire` generates the slash command from it. There is nothing agent-specific in any of it.
|
|
38
46
|
|
|
39
47
|
## What goes in it, and what does not
|
|
40
48
|
|
|
@@ -62,6 +70,14 @@ touches. Be specific. A vague boundary is one that gets crossed at 07:00 with no
|
|
|
62
70
|
**Where the rest of it lives.** Point at `memory/INDEX.md`, `log/decisions.md`, the pinned
|
|
63
71
|
status issue, and the surfaces the manifest declares.
|
|
64
72
|
|
|
73
|
+
## Worked examples
|
|
74
|
+
|
|
75
|
+
Three, for an invented company called Acme: a [CTO](charters/cto.md), a [CMO](charters/cmo.md)
|
|
76
|
+
and a [Head of Support](charters/support.md). They are examples to adapt, not templates to fill
|
|
77
|
+
in. Read them for what a finished charter covers and how specific it gets, then write your own
|
|
78
|
+
about your business. A charter copied from one of these describes Acme. The brief carries the matching one for
|
|
79
|
+
you; these links are for reading them first.
|
|
80
|
+
|
|
65
81
|
## Things worth being concrete about
|
|
66
82
|
|
|
67
83
|
- **Escalation.** Name the label and the mechanism, not the sentiment. "Open an issue labelled
|
|
@@ -73,7 +89,7 @@ status issue, and the surfaces the manifest declares.
|
|
|
73
89
|
|
|
74
90
|
## Keep it agreeing with the manifest
|
|
75
91
|
|
|
76
|
-
`roster lint
|
|
92
|
+
Health, and `roster lint`, fail if the charter and `staff.yaml` disagree. The manifest is the
|
|
77
93
|
machine-readable half of the same document: handle, schedule, peers, surfaces, identities. If
|
|
78
94
|
the charter says it reviews pull requests on the product repo, `works_in` had better include it.
|
|
79
95
|
|
package/package.json
CHANGED
|
@@ -18,6 +18,12 @@ concurrency:
|
|
|
18
18
|
|
|
19
19
|
jobs:
|
|
20
20
|
session:
|
|
21
|
+
# The ceiling on what session.yaml's job token may do: reading the checkout, and the one
|
|
22
|
+
# comment that says a run failed when the App that would normally say so is what broke.
|
|
23
|
+
# A called workflow cannot raise these, so they have to be granted here.
|
|
24
|
+
permissions:
|
|
25
|
+
contents: read
|
|
26
|
+
issues: write
|
|
21
27
|
uses: %%OPS_REPO%%/.github/workflows/session.yaml@main
|
|
22
28
|
with:
|
|
23
29
|
staff: %%STAFF%%
|
|
@@ -25,6 +31,7 @@ jobs:
|
|
|
25
31
|
ops_repo: %%OPS_REPO%%
|
|
26
32
|
model: %%MODEL%%
|
|
27
33
|
timeout_minutes: %%TIMEOUT%%
|
|
34
|
+
allowed_tools: "%%ALLOWED_TOOLS%%"
|
|
28
35
|
secrets:
|
|
29
36
|
APP_ID: ${{ secrets.%%SECRET_PREFIX%%_APP_ID }}
|
|
30
37
|
APP_PRIVATE_KEY: ${{ secrets.%%SECRET_PREFIX%%_APP_PRIVATE_KEY }}
|