@nanocollective/roster 0.1.0-alpha.2 → 0.1.0-alpha.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +65 -84
  2. package/dist/cli.js +4733 -2490
  3. package/docs/README.md +19 -11
  4. package/docs/agents.md +328 -13
  5. package/docs/architecture.md +13 -5
  6. package/docs/charters/cmo.md +69 -0
  7. package/docs/charters/cto.md +71 -0
  8. package/docs/charters/support.md +60 -0
  9. package/docs/commands.md +90 -11
  10. package/docs/concepts.md +64 -12
  11. package/docs/cost.md +39 -3
  12. package/docs/developing.md +16 -21
  13. package/docs/doctor-codes.md +21 -6
  14. package/docs/export.md +2 -1
  15. package/docs/extending.md +13 -4
  16. package/docs/getting-started.md +118 -80
  17. package/docs/images/brain.jpg +0 -0
  18. package/docs/images/org.jpg +0 -0
  19. package/docs/images/prompt.jpg +0 -0
  20. package/docs/images/setup-org.jpg +0 -0
  21. package/docs/images/setup-plan.jpg +0 -0
  22. package/docs/images/staff.jpg +0 -0
  23. package/docs/manual-steps.md +94 -101
  24. package/docs/memory.md +29 -8
  25. package/docs/org-yaml.md +76 -11
  26. package/docs/portal.md +261 -47
  27. package/docs/prompts.md +77 -11
  28. package/docs/security.md +51 -7
  29. package/docs/session-workflow.md +51 -21
  30. package/docs/staff-yaml.md +16 -7
  31. package/docs/troubleshooting.md +23 -20
  32. package/docs/upgrading.md +6 -0
  33. package/docs/writing-a-charter.md +33 -17
  34. package/package.json +1 -1
  35. package/templates/brain/.github/workflows/%%STAFF%%-daily.yaml +7 -0
  36. package/templates/brain/.github/workflows/%%STAFF%%-mention.yaml +16 -4
  37. package/templates/brain/CHARTER.md +3 -3
  38. package/templates/brain/README.md +1 -0
  39. package/templates/brain/log/decisions.md +3 -0
  40. package/templates/brain/staff.yaml +0 -1
  41. package/templates/brain/strategy/ideas.md +7 -0
  42. package/templates/ops/.github/workflows/session.yaml +117 -40
  43. package/templates/ops/agents.mjs +127 -8
  44. package/templates/ops/compose.mjs +77 -7
  45. package/templates/ops/inflight.mjs +157 -0
  46. package/templates/ops/org/operating.md +21 -7
  47. package/templates/ops/org/voice.md +9 -0
  48. package/templates/ops/prompts/_identity.md +8 -1
  49. package/templates/ops/prompts/_inflight.md +14 -0
  50. package/templates/ops/prompts/_paths.md +2 -1
  51. package/templates/ops/prompts/daily.md +16 -7
  52. package/templates/ops/prompts/mention.md +18 -2
  53. package/templates/ops/run-record.mjs +144 -0
  54. package/templates/portal/css/base.css +238 -64
  55. package/templates/portal/css/brain.css +30 -20
  56. package/templates/portal/css/diff.css +15 -10
  57. package/templates/portal/css/graph.css +12 -7
  58. package/templates/portal/css/health.css +32 -11
  59. package/templates/portal/css/inbox.css +117 -14
  60. package/templates/portal/css/layout.css +93 -41
  61. package/templates/portal/css/markdown.css +57 -15
  62. package/templates/portal/css/runs.css +13 -0
  63. package/templates/portal/css/setup.css +83 -39
  64. package/templates/portal/index.html +24 -3
  65. package/templates/portal/js/api.js +65 -4
  66. package/templates/portal/js/app.js +112 -12
  67. package/templates/portal/js/dialog.js +94 -4
  68. package/templates/portal/js/dom.js +25 -0
  69. package/templates/portal/js/icons.js +8 -1
  70. package/templates/portal/js/lightbox.js +273 -0
  71. package/templates/portal/js/md.js +23 -6
  72. package/templates/portal/js/mdedit.js +84 -0
  73. package/templates/portal/js/mention.js +264 -0
  74. package/templates/portal/js/refresh.js +136 -6
  75. package/templates/portal/js/state.js +55 -8
  76. package/templates/portal/js/views/app.js +23 -5
  77. package/templates/portal/js/views/checklist.js +29 -10
  78. package/templates/portal/js/views/credential.js +98 -0
  79. package/templates/portal/js/views/docs.js +94 -4
  80. package/templates/portal/js/views/files.js +58 -14
  81. package/templates/portal/js/views/graph.js +1 -1
  82. package/templates/portal/js/views/health.js +178 -37
  83. package/templates/portal/js/views/inbox.js +938 -98
  84. package/templates/portal/js/views/memory.js +16 -1
  85. package/templates/portal/js/views/org.js +124 -104
  86. package/templates/portal/js/views/orgedit.js +213 -0
  87. package/templates/portal/js/views/paste.js +33 -7
  88. package/templates/portal/js/views/prompt.js +61 -67
  89. package/templates/portal/js/views/repos.js +20 -15
  90. package/templates/portal/js/views/runonce.js +94 -0
  91. package/templates/portal/js/views/runs.js +165 -0
  92. package/templates/portal/js/views/setup.js +311 -83
  93. package/templates/portal/js/views/staff.js +143 -22
  94. package/templates/portal/js/yaml.js +134 -0
  95. package/templates/brain/.github/workflows/%%STAFF%%-pr-mention.yaml +0 -50
  96. 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
- See it for yourself before changing anything:
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
- `mention` and `pr-mention` refuse to compose without context, because they are written for the
41
- comment that woke them. That is correct behaviour. To see one locally:
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","pr_number":"1","repo":"o/r"}' \
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 `human` block from `org.yaml` |
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`, `mention` or `pr-mention` |
113
+ | `kind` | `daily` or `mention` |
85
114
  | `event` | trigger context, from `ROSTER_CONTEXT` |
86
- | `event.issue_number`, `event.comment_id`, `event.pr_number`, `event.repo`, `event.actor` | |
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
- Before pushing a prompt change to a live org, diff the composed result:
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
- That is the only test there is for a prompt change. A change that composes fine and reads
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. `roster app` prints the list it actually needs.
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 the product lane is split: a mention on a public PR is caught by a forwarder in that repo
100
- which does no work of its own, and dispatches to the private lane. The run that prints a
101
- charter and a chain of reasoning happens where the logs are not world-readable.
102
-
103
- The forwarder has an author gate, and it is load-bearing: anyone can comment on a public PR,
104
- and a run started from a comment executes with repository secrets. Do not relax it.
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
 
@@ -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 is what forced the whole
18
- design, and it turned out better: the framework is never a runtime dependency, so nothing
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`, `mention` or `pr-mention`. |
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 | `60` | Job ceiling. |
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. **Mint the private-tracker token** from the staff member's App.
56
- 2. **Mint the public-repo token**, if a public App was passed.
57
- 3. **React to the request** with eyes, on a `mention` only. Before any checkout, so it lands in
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
- 4. **Check out the ops repo.** It is the only thing that can be cloned without having read a
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
- 5. **Work out what to check out**, by running `runner-plan.mjs`.
62
- 6. **Check out the brain**, full history. The agent reads its own past.
63
- 7. **Check out peers and product repos**, per the plan.
64
- 8. **Set git identity** to the App.
65
- 9. **Check out the PR branch**, on a `pr-mention`.
66
- 10. **Set up Node and pnpm**, if the plan found a `package.json`.
67
- 11. **Compose the prompt**, to a step output and to `.roster-prompt.txt`.
68
- 12. **Check the agent has a credential.**
69
- 13. **Work out which agent runs this**, by running `agents.mjs`.
70
- 14. **Run the session**, by one of two steps: the Action-based reference runner, or the generic
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
- 15. **Say so if the run did not finish.**
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 |
@@ -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: 30
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: PIPWEB_TOKEN
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` | 60 | Ceiling on the daily session. |
73
- | `mention_timeout_minutes` | 30 | Ceiling on a mention run. |
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.
@@ -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 `roster doctor`. It groups by staff member and every finding that is not `ok` says
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
- Settings -> Actions -> General on `roster-ops`. `roster doctor` checks this explicitly, and the
24
- portal's setup screen links straight to the page.
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. `roster doctor` does this for you and names the number:
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
- `roster doctor` ignores them. A workflow whose runs are *all* skipped is reported differently,
53
- because nothing has exercised the credentials.
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. `roster doctor` reads recent runs for this reason and calls a workflow that has
67
- never run **unproven** rather than fine.
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. `roster app` prints that list.
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
- This happened here: a fix went into `roster-ops/.github/workflows/session.yaml` instead of
91
- `templates/ops/...`, and nothing noticed because the framework had not touched that file *yet*.
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 `roster hire --apply` has opened the pinned
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 pr-mention is acknowledged by the forwarder in the
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 created before the merge base was recorded has nothing to merge against. Reconstruct
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
@@ -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
- ```bash
19
- roster brief charter <handle>
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
- That prints a self-contained brief. Paste it into whatever agent you use, or pipe it:
23
- `roster brief charter cto | pbcopy`. In Claude Code, `cd <staff-dir> && claude` then
24
- `/charter` runs the same text, because `roster hire` generates the slash command from it.
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
- **Or do the whole round trip in the portal.** *Write the charter* on that staff member's card
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
- The brief tells the agent to read `org/business.md`, the shared operating
32
- layer, and every peer's charter, then interviews you and drafts from your answers. It also tells
33
- you what it cut and why.
40
+ ```bash
41
+ roster brief charter <handle> # or: roster brief charter cto | pbcopy
42
+ ```
34
43
 
35
- There is nothing agent-specific in it. Claude Code gets a slash command because it is the
36
- reference runner and the shape happens to fit; everything else gets the same words from
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` fails if the charter and `staff.yaml` disagree. The manifest is the
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanocollective/roster",
3
- "version": "0.1.0-alpha.2",
3
+ "version": "0.1.0-alpha.20",
4
4
  "description": "An agent-run org, powered by GitHub. Scaffold AI staff members whose brain is a repo.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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 }}