@nanocollective/roster 0.1.0-alpha.3 → 0.1.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/cli.js +848 -164
  2. package/docs/README.md +10 -5
  3. package/docs/agents.md +320 -9
  4. package/docs/commands.md +5 -6
  5. package/docs/concepts.md +27 -9
  6. package/docs/cost.md +3 -2
  7. package/docs/doctor-codes.md +13 -4
  8. package/docs/export.md +2 -1
  9. package/docs/extending.md +11 -2
  10. package/docs/getting-started.md +89 -84
  11. package/docs/images/brain.jpg +0 -0
  12. package/docs/images/org.jpg +0 -0
  13. package/docs/images/prompt.jpg +0 -0
  14. package/docs/images/setup-org.jpg +0 -0
  15. package/docs/images/setup-plan.jpg +0 -0
  16. package/docs/images/staff.jpg +0 -0
  17. package/docs/manual-steps.md +36 -13
  18. package/docs/memory.md +9 -6
  19. package/docs/org-yaml.md +37 -9
  20. package/docs/portal.md +197 -31
  21. package/docs/prompts.md +50 -11
  22. package/docs/security.md +19 -7
  23. package/docs/session-workflow.md +8 -10
  24. package/docs/staff-yaml.md +3 -5
  25. package/docs/troubleshooting.md +17 -14
  26. package/docs/writing-a-charter.md +18 -17
  27. package/package.json +1 -1
  28. package/templates/brain/.github/workflows/%%STAFF%%-daily.yaml +1 -0
  29. package/templates/brain/.github/workflows/%%STAFF%%-mention.yaml +10 -4
  30. package/templates/brain/staff.yaml +0 -1
  31. package/templates/ops/.github/workflows/session.yaml +9 -26
  32. package/templates/ops/agents.mjs +121 -6
  33. package/templates/ops/compose.mjs +61 -4
  34. package/templates/ops/org/operating.md +0 -6
  35. package/templates/ops/prompts/_identity.md +8 -1
  36. package/templates/ops/prompts/mention.md +16 -2
  37. package/templates/portal/css/base.css +116 -8
  38. package/templates/portal/css/brain.css +8 -1
  39. package/templates/portal/css/diff.css +6 -2
  40. package/templates/portal/css/health.css +21 -2
  41. package/templates/portal/css/inbox.css +93 -5
  42. package/templates/portal/css/layout.css +26 -4
  43. package/templates/portal/css/markdown.css +23 -3
  44. package/templates/portal/css/setup.css +11 -6
  45. package/templates/portal/index.html +7 -1
  46. package/templates/portal/js/api.js +33 -0
  47. package/templates/portal/js/app.js +28 -8
  48. package/templates/portal/js/dialog.js +47 -4
  49. package/templates/portal/js/dom.js +25 -0
  50. package/templates/portal/js/icons.js +8 -1
  51. package/templates/portal/js/lightbox.js +273 -0
  52. package/templates/portal/js/md.js +23 -6
  53. package/templates/portal/js/mention.js +264 -0
  54. package/templates/portal/js/refresh.js +136 -6
  55. package/templates/portal/js/state.js +47 -5
  56. package/templates/portal/js/views/checklist.js +20 -7
  57. package/templates/portal/js/views/docs.js +94 -4
  58. package/templates/portal/js/views/files.js +58 -14
  59. package/templates/portal/js/views/health.js +163 -35
  60. package/templates/portal/js/views/inbox.js +882 -96
  61. package/templates/portal/js/views/memory.js +16 -1
  62. package/templates/portal/js/views/org.js +142 -62
  63. package/templates/portal/js/views/prompt.js +50 -63
  64. package/templates/portal/js/views/staff.js +62 -2
  65. package/templates/portal/js/yaml.js +134 -0
  66. package/templates/brain/.github/workflows/%%STAFF%%-pr-mention.yaml +0 -50
  67. package/templates/ops/prompts/pr-mention.md +0 -57
package/docs/README.md CHANGED
@@ -11,7 +11,7 @@ An agent-run organisation, powered by GitHub.
11
11
  A staff member is a private repository. The repo *is* the brain: what it knows, what it is
12
12
  working on, what it has decided. A scheduled workflow wakes it each morning, hands it a prompt
13
13
  composed from the org's shared rules plus its own charter, and it does a day's work and hands
14
- off. You read the result on GitHub, or in a local portal.
14
+ off. You read the result in a local portal, or on GitHub.
15
15
 
16
16
  roster is the thing that sets that up and keeps it consistent.
17
17
 
@@ -22,18 +22,18 @@ Read in this order.
22
22
  | | |
23
23
  |---|---|
24
24
  | [Getting started](getting-started.md) | One command, in a browser: stand up an org, or join one that exists. |
25
+ | [The portal](portal.md) | Where the work happens: setup, every screen, every action. |
25
26
  | [Manual steps](manual-steps.md) | Every human action, why it cannot be automated, and what breaks if you skip it. **Read this one.** |
26
27
  | [Concepts](concepts.md) | What a charter, a manifest, a surface and the ops repo are. |
27
28
  | [Choosing a coding agent](agents.md) | Claude, Codex, Nanocoder, or anything with a command line. |
28
29
  | [Writing a charter](writing-a-charter.md) | The one file nothing can generate for you. |
29
30
  | [Extending it](extending.md) | The four seams, and which one to reach for. |
30
31
  | [Memory](memory.md) | The grammar, and why deleting is the maintenance. |
31
- | [Commands](commands.md) | Every CLI command and flag. |
32
- | [The portal](portal.md) | Setup, every view, and every action. |
33
32
  | [Upgrading](upgrading.md) | How framework changes reach a tenant without eating your edits. |
34
33
  | [Troubleshooting](troubleshooting.md) | Every trap we have actually hit, and what it looks like. |
35
34
  | [Hosting the portal](hosting.md) | Local is the default, and why. |
36
35
  | [Cost](cost.md) | What this spends, and on what. |
36
+ | [Commands](commands.md) | Every CLI command and flag, for when you want the terminal. |
37
37
 
38
38
  ## Reference
39
39
 
@@ -45,7 +45,6 @@ Look things up.
45
45
  | [`staff.yaml`](staff-yaml.md) | Every field in a staff member's manifest. |
46
46
  | [Prompts](prompts.md) | The template syntax, the context, and what to guard. |
47
47
  | [The session workflow](session-workflow.md) | Inputs, secrets, and what runs in what order. |
48
- | [The portal](portal.md) | Every view and every action. |
49
48
  | [`roster export`](export.md) | The JSON shape. |
50
49
  | [doctor codes](doctor-codes.md) | Every finding, what it means, what to do. |
51
50
 
@@ -81,7 +80,7 @@ Nano-Collective/roster the framework. Never a runtime dependency of any
81
80
  ├── staff.yaml the machine-readable half of the charter
82
81
  ├── memory/INDEX.md one line per fact, read at every boot
83
82
  ├── memory/notes/ the argument behind a fact, read on demand
84
- └── .github/workflows/ three callers, about forty lines each
83
+ └── .github/workflows/ two callers, about forty lines each
85
84
  ```
86
85
 
87
86
  **The framework never runs anything.** It writes templates out; a tenant runs its own copies.
@@ -97,3 +96,9 @@ deleted, and an air-gapped install is a supported case rather than a special one
97
96
  - **Write `org/business.md`.** Everything the staff say is downstream of it.
98
97
  - **Install a GitHub App.** Installing grants access to specific repositories and GitHub asks a
99
98
  human which. See [manual steps](manual-steps.md).
99
+
100
+ None of those is a dead end. roster holds no model credential, so for the first two the portal
101
+ does both halves of the round trip instead: it copies a brief that carries every file it refers
102
+ to, and turns the reply you paste back into a file with a diff and a save button. For the third
103
+ it runs everything either side of the confirmation GitHub insists a human gives, and tells you
104
+ exactly which repositories to grant.
package/docs/agents.md CHANGED
@@ -21,19 +21,56 @@ That is the whole interface. Everything else is a convenience.
21
21
 
22
22
  ## Picking one
23
23
 
24
- In `org.yaml`:
24
+ In `org.yaml`. This block is the whole surface: choose an agent, say how much freedom it gets,
25
+ and pass anything else through in that agent's own words.
25
26
 
26
27
  ```yaml
27
28
  agent:
28
29
  id: codex
30
+ permissions: full # full | workspace | read-only
31
+ options: # optional, and in codex's vocabulary rather than roster's
32
+ model_reasoning_effort: high
29
33
  ```
30
34
 
31
- Or the short form, which is the same thing:
35
+ The short form is the same thing with the defaults:
32
36
 
33
37
  ```yaml
34
38
  agent: codex
35
39
  ```
36
40
 
41
+ ### `permissions`
42
+
43
+ One word here, because "how much may this thing do without asking" is a question about your
44
+ org rather than about a vendor. Each agent hears it in its own vocabulary:
45
+
46
+ | | `read-only` | `workspace` | `full` |
47
+ |---|---|---|---|
48
+ | **claude** | `--allowedTools Read,Glob,Grep,WebFetch,WebSearch` | the same plus `Bash,Write,Edit` | plus `WebFetch,WebSearch` |
49
+ | **codex** | `--sandbox read-only` | `--sandbox workspace-write` | `--sandbox danger-full-access` |
50
+ | **nanocoder** | `--mode plan` | `--mode auto-accept` | `--mode yolo` |
51
+
52
+ `full` is the default and is what a daily session needs: the whole point of a run is that it
53
+ edits the checkout, commits and pushes. `workspace` keeps it off the network. `read-only` is
54
+ for a staff member you are not ready to trust yet, and it is genuinely read-only in all three:
55
+ nanocoder's `plan` mode reasons and proposes and edits nothing.
56
+
57
+ Codex also gets `approval_policy="never"` at every level. A sandbox that permits writes still
58
+ stops to ask by default, and a run that stops to ask at 07:00 is a run that times out having
59
+ done nothing.
60
+
61
+ A staff member can be trusted less than the org:
62
+
63
+ ```yaml
64
+ # marketing/staff.yaml
65
+ permissions: workspace
66
+ ```
67
+
68
+ ### `options`
69
+
70
+ Anything roster does not model, in the agent's own words, spelled onto its command line by the
71
+ preset: `-c key=value` for codex, `--key value` for the others. It is the escape hatch that
72
+ means a flag roster has never heard of is still reachable without waiting for us.
73
+
37
74
  A staff member can override it in their own `staff.yaml`, which is worth doing when roles
38
75
  differ in kind. A research role on a long-context model and an engineering role on a coding
39
76
  model is a reasonable thing to want:
@@ -61,7 +98,15 @@ agent: claude-code-action
61
98
 
62
99
  - credential: `CLAUDE_CODE_OAUTH_TOKEN`
63
100
  - default model: `claude-opus-5`
64
- - tool permissions come from `allowed_tools` on the caller
101
+ - tool permissions come from `allowed_tools` on the caller, which roster renders from
102
+ `defaults.allowed_tools` in org.yaml
103
+
104
+ That allowlist is Claude's own vocabulary, and it is the one setting on this page that does not
105
+ translate. Codex takes a sandbox mode rather than a tool list; nanocoder takes a development
106
+ mode. Both presets therefore ignore `$AGENT_TOOLS` entirely, and neither is any less restricted
107
+ for it: what bounds them is the sandbox flag in their own `run` command. If you switch agents,
108
+ `allowed_tools` stops being the thing that governs what the agent may touch, and the `run`
109
+ command becomes it.
65
110
 
66
111
  It is the only preset that is a GitHub Action rather than a CLI. `uses:` in a workflow cannot
67
112
  be an expression, so an Action-based runner has to be written into `session.yaml` literally.
@@ -97,7 +142,8 @@ produces a run that succeeds having done nothing. If you would rather keep it na
97
142
 
98
143
  ```
99
144
  install: npm install -g @nanocollective/nanocoder
100
- run: nanocoder --model "$AGENT_MODEL" --mode yolo --trust-directory --plain run "$(cat "$AGENT_PROMPT_FILE")"
145
+ run: NANOCODER_PROVIDERS_FILE="${NANOCODER_PROVIDERS_FILE:-roster-ops/agents.config.json}" \
146
+ nanocoder --model "$AGENT_MODEL" --mode yolo --trust-directory --plain run "$(cat "$AGENT_PROMPT_FILE")"
101
147
  token_env: NANOCODER_API_KEY
102
148
  ```
103
149
 
@@ -105,8 +151,234 @@ Three flags matter for unattended use. `run` is its non-interactive mode. `--tru
105
151
  skips the first-run directory trust prompt, which would otherwise hang the runner until it
106
152
  times out. `--plain` avoids the TUI, which has nothing to draw to in CI.
107
153
 
108
- Nanocoder resolves a provider from `agents.config.json` in the working directory, so you will
109
- want that file in the brain repo, and the provider's own key in `token_env`.
154
+ **Nanocoder needs one thing the other two do not: a provider.** It is a client rather than a
155
+ model, so `NANOCODER_API_KEY` on its own tells it nothing about where to send anything. That is
156
+ what the config file is for, and it is the part of this that catches people out. It has its own
157
+ section: [wiring up nanocoder](#wiring-up-nanocoder).
158
+
159
+ ## Setting one up, end to end
160
+
161
+ The preset only says how to invoke the agent. Three more things have to be true before a run
162
+ works, and Health, or `roster doctor`, checks all three.
163
+
164
+ ### 1. The credential exists, and you have it
165
+
166
+ | Agent | Where the credential comes from |
167
+ |---|---|
168
+ | `claude-code-action`, `claude` | `claude setup-token` in a terminal where Claude Code is signed in. It prints a long-lived OAuth token. A plain Anthropic API key also works if you would rather bill that way. |
169
+ | `codex` | an API key from the OpenAI platform console. `codex login` is for interactive use and does not produce something a runner can hold. |
170
+ | `nanocoder` | whatever the provider you point it at wants. Nanocoder is a client, not a model: the key belongs to the provider in `agents.config.json`. |
171
+
172
+ ### 2. It is a secret on every brain repo, under the right name
173
+
174
+ Each staff member's caller workflow reads the secret **from their own repo**, so the credential
175
+ goes on each brain, not on the ops repo:
176
+
177
+ ```bash
178
+ gh secret set CODEX_API_KEY --repo playpip/technology --body "$KEY"
179
+ gh secret set CODEX_API_KEY --repo playpip/marketing --body "$KEY"
180
+ ```
181
+
182
+ The name is the preset's `token_env`, and it is the same name the caller references. If you
183
+ override `token_env`, the callers have to be regenerated so they reference the new name:
184
+ `roster upgrade --apply`.
185
+
186
+ Inside the run it arrives twice: as `AGENT_TOKEN`, which is what the caller passes, and under
187
+ the agent's own `token_env`, which is what the agent reads. That indirection is why a preset
188
+ change does not require touching `session.yaml`.
189
+
190
+ ### 3. The agent's own config, if it has one
191
+
192
+ `claude` and `codex` need none: they are a model and a client in one thing, and the model comes
193
+ from `$AGENT_MODEL`. `nanocoder` needs a providers file, below.
194
+
195
+ ### Then prove it
196
+
197
+ ```bash
198
+ roster doctor # secrets present, callers reachable, prompts compose
199
+ gh workflow run cto-daily.yaml --repo playpip/technology
200
+ ```
201
+
202
+ Read the log of that first run rather than waiting for the schedule. What goes wrong is
203
+ specific to the agent and obvious in the log: an unknown flag, a sandbox that refuses to write,
204
+ a model id the provider does not recognise, a first-run prompt waiting for a keypress that will
205
+ never come.
206
+
207
+ ## Three worked examples
208
+
209
+ An org called `acme` with two staff members, `cto` in `acme/technology` and `cmo` in
210
+ `acme/marketing`. Every file each one touches, in full.
211
+
212
+ ### Claude, through the Action
213
+
214
+ ```yaml
215
+ # roster-ops/org.yaml
216
+ org: acme
217
+ agent:
218
+ id: claude-code-action # the default; the whole block can be left out
219
+
220
+ defaults:
221
+ model: claude-opus-5
222
+ ```
223
+
224
+ ```bash
225
+ claude setup-token # prints a long-lived token
226
+ gh secret set CLAUDE_CODE_OAUTH_TOKEN --repo acme/technology --body "$TOKEN"
227
+ gh secret set CLAUDE_CODE_OAUTH_TOKEN --repo acme/marketing --body "$TOKEN"
228
+ ```
229
+
230
+ Nothing else. No file in the brain repos, no per-staff config.
231
+
232
+ ### Codex
233
+
234
+ ```yaml
235
+ # roster-ops/org.yaml
236
+ agent:
237
+ id: codex
238
+ permissions: full # --sandbox danger-full-access, approval_policy never
239
+
240
+ defaults:
241
+ model: gpt-5-codex # what $AGENT_MODEL becomes
242
+ ```
243
+
244
+ ```bash
245
+ gh secret set CODEX_API_KEY --repo acme/technology --body "$OPENAI_KEY"
246
+ gh secret set CODEX_API_KEY --repo acme/marketing --body "$OPENAI_KEY"
247
+ roster upgrade --apply # repoints the callers at the new secret name
248
+ ```
249
+
250
+ Commit and push the regenerated callers. The secret name changed from
251
+ `CLAUDE_CODE_OAUTH_TOKEN` to `CODEX_API_KEY`, and that name is written into each caller.
252
+
253
+ ### Nanocoder
254
+
255
+ Two files rather than one, because a provider has to be named.
256
+
257
+ ```yaml
258
+ # roster-ops/org.yaml
259
+ agent:
260
+ id: nanocoder
261
+ permissions: full # --mode yolo
262
+
263
+ defaults:
264
+ model: qwen/qwen3-coder # must be one of the models listed below
265
+ ```
266
+
267
+ `roster init --agent nanocoder` writes this file for you, with the blanks marked. Fill them in:
268
+
269
+ ```json
270
+ // roster-ops/agents.config.json
271
+ {
272
+ "nanocoder": {
273
+ "providers": [
274
+ {
275
+ "name": "openrouter",
276
+ "baseUrl": "https://openrouter.ai/api/v1",
277
+ "apiKey": "${NANOCODER_API_KEY}",
278
+ "models": ["qwen/qwen3-coder"]
279
+ }
280
+ ]
281
+ }
282
+ }
283
+ ```
284
+
285
+ ```bash
286
+ gh secret set NANOCODER_API_KEY --repo acme/technology --body "$OPENROUTER_KEY"
287
+ gh secret set NANOCODER_API_KEY --repo acme/marketing --body "$OPENROUTER_KEY"
288
+ roster upgrade --apply
289
+ ```
290
+
291
+ Commit `agents.config.json` and the regenerated callers. **The key is not in the file.**
292
+ `${NANOCODER_API_KEY}` is expanded from the environment when nanocoder reads it, and the
293
+ environment is where roster puts the secret.
294
+
295
+ ## Wiring up nanocoder
296
+
297
+ The other two presets are one thing. Nanocoder is a harness you point at a model, so it needs
298
+ to be told which model, from whom, at what URL, with which key. That is `agents.config.json`.
299
+
300
+ ### Where it looks
301
+
302
+ In order, and the first hit wins:
303
+
304
+ 1. `$NANOCODER_PROVIDERS`: the JSON itself, in an environment variable.
305
+ 2. `$NANOCODER_PROVIDERS_FILE`: a path to the JSON. Ignored if the file is not there.
306
+ 3. `agents.config.json` in the **working directory**.
307
+ 4. `agents.config.json` in the user config directory (`~/.config/nanocoder/` on Linux,
308
+ `~/Library/Preferences/nanocoder/` on macOS).
309
+
310
+ Options 3 and 4 are what you use at your own desk, and neither of them works in a session. The
311
+ working directory of a run is the **workspace root**: the directory the repos are checked out
312
+ *into*, one level above `technology/` and `roster-ops/`. It belongs to no repository, so there
313
+ is nothing there to commit a config into. And the user config directory on a fresh GitHub
314
+ runner is empty.
315
+
316
+ That is why roster's preset sets option 2 for you:
317
+
318
+ ```
319
+ NANOCODER_PROVIDERS_FILE="${NANOCODER_PROVIDERS_FILE:-roster-ops/agents.config.json}"
320
+ ```
321
+
322
+ The ops repo is checked out at a known path on every run, and it is the one repo every staff
323
+ member has. So the org's providers live in one version-controlled file, and each staff member
324
+ picks a model from it with `model:` in their own `staff.yaml`.
325
+
326
+ ### The shape
327
+
328
+ Either of these; the wrapper is what nanocoder writes itself, the bare form is accepted too.
329
+
330
+ ```json
331
+ { "nanocoder": { "providers": [ … ] } }
332
+ { "providers": [ … ] }
333
+ ```
334
+
335
+ A provider is:
336
+
337
+ | Field | |
338
+ |---|---|
339
+ | `name` | what `--provider` and the model list refer to. Any string. |
340
+ | `baseUrl` | the OpenAI-compatible endpoint. Omit for a provider the SDK already knows. |
341
+ | `apiKey` | the credential. Use `${VAR}`, never a literal, in a file you are committing. |
342
+ | `models` | the models this provider offers. **If it is non-empty, `$AGENT_MODEL` must be one of them**, or the run fails with "Model not available for provider". |
343
+ | `sdkProvider` | `openai-compatible` (default), `anthropic`, `google`, `chatgpt-codex`, `github-copilot`. |
344
+
345
+ `${VAR}` and `$VAR` are both expanded, anywhere in the file, with `${VAR:-fallback}` for a
346
+ default. That is what lets a committed config carry no secrets.
347
+
348
+ ### A local model
349
+
350
+ Nothing says the provider has to be remote. Ollama on a self-hosted runner needs no key at all:
351
+
352
+ ```json
353
+ {
354
+ "providers": [
355
+ {
356
+ "name": "ollama",
357
+ "baseUrl": "http://localhost:11434/v1",
358
+ "models": ["qwen2.5-coder:32b"]
359
+ }
360
+ ]
361
+ }
362
+ ```
363
+
364
+ `token_env` still has to name a variable, because the session refuses to start an agent with no
365
+ credential at all. Point it at something harmless and set it to any non-empty string:
366
+
367
+ ```yaml
368
+ agent:
369
+ id: nanocoder
370
+ token_env: NANOCODER_API_KEY # set it to "unused" on the brain repos
371
+ ```
372
+
373
+ ### When it goes wrong
374
+
375
+ | In the log | What it means |
376
+ |---|---|
377
+ | `No agents.config.json found` | the file is not where nanocoder looked. Check it is committed to the ops repo at `agents.config.json`, and that `roster upgrade --apply` has been run so the caller carries the current preset. |
378
+ | `No providers configured` | the file is there but `providers` is empty, or spelled as an object instead of an array. |
379
+ | `Provider 'x' not found` | `--provider` names something the file does not define. |
380
+ | `Model 'y' not available for provider` | `model:` in `org.yaml` or `staff.yaml` is not in that provider's `models` list. This is the common one: the roster default is a Claude model, and nanocoder is strict about the list. |
381
+ | a run that hangs and then times out | a first-run prompt. The preset passes `--trust-directory` and `--plain` to avoid both known ones. |
110
382
 
111
383
  ## Writing your own
112
384
 
@@ -120,6 +392,19 @@ agent:
120
392
  token_env: MY_AGENT_TOKEN
121
393
  ```
122
394
 
395
+ Five questions decide the `run` command, and they are the same five for every tool:
396
+
397
+ 1. **What is its non-interactive mode?** Most have one, and it is rarely the default:
398
+ `-p` for Claude, `exec` for Codex, `run` for nanocoder.
399
+ 2. **How does it take a long prompt?** Stdin (`< "$AGENT_PROMPT_FILE"`) if it accepts it, an
400
+ argument (`"$(cat "$AGENT_PROMPT_FILE")"`) if it does not. Never a literal.
401
+ 3. **What does it do with no TTY?** Anything that draws a full-screen interface needs the flag
402
+ that turns it off, or CI gets a run full of escape codes and no work.
403
+ 4. **Does it ask anything on first run?** Trust prompts, telemetry consent, a config wizard.
404
+ Each one hangs an unattended run until the job times out. Find the flag that skips it.
405
+ 5. **Is it allowed to write?** A sandbox that forbids edits produces a run that reports success
406
+ having done nothing, which is the worst failure this arrangement has.
407
+
123
408
  You can also override a single field of a preset, which is the common case when a flag changes:
124
409
 
125
410
  ```yaml
@@ -130,13 +415,34 @@ agent:
130
415
 
131
416
  The rest of the preset still applies.
132
417
 
418
+ ### Per staff member
419
+
420
+ Anything the org sets, a staff member can override in their own `staff.yaml`. Roles that differ
421
+ in kind are worth splitting: a research role on a long-context model, an engineering role on a
422
+ coding one.
423
+
424
+ ```yaml
425
+ # marketing/staff.yaml
426
+ agent: nanocoder
427
+ model: qwen/qwen3-coder
428
+ ```
429
+
430
+ ```yaml
431
+ # technology/staff.yaml
432
+ model: claude-opus-5 # keeps the org's agent, changes only the model
433
+ ```
434
+
435
+ Two staff members on two different agents need both credentials present, each on its own brain
436
+ repo, under each agent's own `token_env`. `roster doctor` reads the callers and tells you which
437
+ secret each repo is missing.
438
+
133
439
  ## What the runner gets
134
440
 
135
441
  | Variable | |
136
442
  |---|---|
137
443
  | `$AGENT_PROMPT_FILE` | absolute path to the composed prompt |
138
444
  | `$AGENT_MODEL` | the staff member's model, or the agent's default |
139
- | `$AGENT_TOOLS` | the `allowed_tools` string from the caller |
445
+ | `$AGENT_TOOLS` | the `allowed_tools` string from the caller, which comes from `defaults.allowed_tools` in org.yaml |
140
446
  | `$GH_TOKEN` | a token for the private trackers, already authenticated |
141
447
  | `$PUBLIC_TOKEN` | a token for the public product repo, if there is one |
142
448
  | *`token_env`* | the agent's credential, under whatever name it wants |
@@ -154,10 +460,15 @@ that only edits files and cannot run commands will produce a run that changes no
154
460
  ## Changing agent on a live org
155
461
 
156
462
  1. Set `agent:` in `org.yaml`.
157
- 2. Put the new credential on each brain repo, named as `token_env`.
158
- 3. `roster upgrade --apply`, then commit and push the regenerated callers.
463
+ 2. Put the new credential on each brain repo, named as `token_env`
464
+ (`gh secret set CODEX_API_KEY --repo <org>/<brain> --body "$KEY"`).
465
+ 3. `roster upgrade --apply`, then commit and push the regenerated callers. This is what
466
+ repoints them at the new secret name; skipping it leaves every run reaching for the old one.
159
467
  4. Trigger one run by hand and read the log before trusting the schedule.
160
468
 
469
+ The old secret can stay where it is until the new agent has had a clean run. Nothing reads it
470
+ once the callers have been regenerated, and it is the fastest way back if the first run is bad.
471
+
161
472
  Step 4 is not optional. Prompts are written against a model's habits as much as its
162
473
  capabilities, and the first run on a new agent is where you find out which parts of your
163
474
  charter were load-bearing.
package/docs/commands.md CHANGED
@@ -48,7 +48,7 @@ Will not write `org/business.md`. That is yours.
48
48
 
49
49
  ## `roster hire <handle>`
50
50
 
51
- Scaffold a staff member: repo, three callers, manifest, memory index, charter stub, labels,
51
+ Scaffold a staff member: repo, two callers, manifest, memory index, charter stub, labels,
52
52
  pinned status issue, and peer wiring in both directions.
53
53
 
54
54
  ```
@@ -58,7 +58,6 @@ pinned status issue, and peer wiring in both directions.
58
58
  --model <id>
59
59
  --timeout <n> daily ceiling, minutes
60
60
  --mention-timeout <n>
61
- --pr-timeout <n>
62
61
  --secret-prefix <X> secrets become <X>_APP_ID and <X>_APP_PRIVATE_KEY
63
62
  --app <slug> defaults to the pattern the peers use
64
63
  --public-app <slug> the shared public identity
@@ -144,7 +143,7 @@ amend <who> change what a staff member is told, with the whole prompt attach
144
143
  ```
145
144
 
146
145
  ```
147
- --kind <k> for amend: daily | mention | pr-mention (default: daily)
146
+ --kind <k> for amend: daily | mention (default: daily)
148
147
  --want <text> for amend: what you want changed
149
148
  --ops <dir>
150
149
  ```
@@ -177,14 +176,14 @@ roster brief discover > roster-ops/.claude/commands/discover.md
177
176
  Compose and print what a staff member is actually sent.
178
177
 
179
178
  ```
180
- --kind daily|mention|pr-mention
179
+ --kind daily|mention
181
180
  --diff <workflow.yaml>
182
181
  ```
183
182
 
184
- `mention` and `pr-mention` need trigger context:
183
+ `mention` needs trigger context:
185
184
 
186
185
  ```bash
187
- ROSTER_CONTEXT='{"issue_number":"1","comment_id":"1","pr_number":"1","repo":"o/r"}' \
186
+ ROSTER_CONTEXT='{"issue_number":"1","comment_id":"1","repo":"o/r"}' \
188
187
  roster prompt cto --kind mention
189
188
  ```
190
189
 
package/docs/concepts.md CHANGED
@@ -11,9 +11,10 @@ sidebar_order: 3
11
11
  `<org>/roster-ops` holds two different kinds of thing, and the split matters.
12
12
 
13
13
  **`org/` is yours.** `business.md`, `voice.md`, `guardrails.md`, `operating.md`. This is the
14
- business truth and the shared half of every staff member's instructions. Edit it freely. A
15
- change here reaches everybody on their next run, which is the point: a concision rule that used
16
- to mean editing twelve files is now one file.
14
+ business truth and the shared half of every staff member's instructions. Edit it freely: the
15
+ [Org screen](portal.md#org) lists every one of these off disk with an Edit button, and saving
16
+ commits and pushes. A change here reaches everybody on their next run, which is the point: a
17
+ concision rule that used to mean editing twelve files is now one file.
17
18
 
18
19
  **Everything else is machinery** and belongs to the framework: `compose.mjs`, `agents.mjs`,
19
20
  `runner-plan.mjs`, `.github/workflows/session.yaml`. Editing these works right up until the
@@ -34,12 +35,12 @@ A staff member's repository *is* their memory. There is no database.
34
35
  | `memory/INDEX.md` | one line per fact, read in full at every boot |
35
36
  | `memory/notes/` | the argument behind a fact, read only when that fact is in play |
36
37
  | `log/decisions.md` | why things were decided. Not boot context. |
37
- | `.github/workflows/` | three callers, about forty lines each |
38
+ | `.github/workflows/` | two callers, about forty lines each |
38
39
 
39
40
  ## Charter and manifest
40
41
 
41
42
  Two halves of one thing. The charter is prose for the agent; the manifest is fields for the
42
- machinery. `roster lint` fails if they disagree.
43
+ machinery. [Health](portal.md#health), and `roster lint`, fail if they disagree.
43
44
 
44
45
  The charter is the only file roster refuses to generate. A generated charter produces a generic
45
46
  agent, and a generic agent produces work that is plausible, competent-looking and about nothing
@@ -67,7 +68,9 @@ org/operating.md + org/guardrails.md + org/voice.md + org/business.md
67
68
  + <staff>/CHARTER.md + prompts/<kind>.md
68
69
  ```
69
70
 
70
- Built at run time by `compose.mjs` in the tenant's own repo. See it for yourself:
71
+ Built at run time by `compose.mjs` in the tenant's own repo. See it for yourself on the
72
+ [Prompt screen](portal.md#prompt), which shows the composed text and every layer that went into
73
+ it, or from a terminal:
71
74
 
72
75
  ```bash
73
76
  roster prompt cto --kind daily
@@ -83,10 +86,25 @@ control.
83
86
  |---|---|
84
87
  | `daily` | the scheduled session. Boot, work, hand off. |
85
88
  | `mention` | `@handle` in a comment or a new issue body. A task, not a session. |
86
- | `pr-mention` | a review comment on the public product repo, forwarded in. |
87
89
 
88
- `mention` and `pr-mention` prompts refuse to compose without trigger context, because they are
89
- written for the comment that woke them. That is correct behaviour, not a bug.
90
+ A `mention` prompt refuses to compose without trigger context, because it is written for the
91
+ comment that woke it. That is correct behaviour, not a bug.
92
+
93
+ There used to be a third, `pr-mention`: a review comment on the public product repo, forwarded
94
+ into the brain by a workflow in that repo. It was removed. Two repos, a dispatch, a forwarder
95
+ with its own author gate and a second reaction path bought one thing: asking for a change
96
+ without leaving the diff. It cost more than that was worth, in explaining and in debugging.
97
+
98
+ What replaced it is the reply box. A pull request is on the product repo, and **nothing in a
99
+ product repo wakes anybody**: a staff member's caller workflow is in their own brain repo and
100
+ gates on their `@handle` appearing *there*. So naming somebody in a reply where a comment will
101
+ not reach them offers, under the box, to open the request on their tracker as well. One press
102
+ posts your words on the thread and sends them the pull request, the branch, the hunk you were
103
+ looking at if you started from a file, and an instruction to answer on the pull request rather
104
+ than in the tracker it arrived in.
105
+
106
+ It is two `gh` calls as you, rather than a workflow, a dispatch and a second gate, which is the
107
+ difference that got the forwarder deleted. See [the portal](portal.md#asking-for-a-change).
90
108
 
91
109
  ## Identities
92
110
 
package/docs/cost.md CHANGED
@@ -22,7 +22,8 @@ words to about 6,000 by moving from a narrative status file to one line per fact
22
22
  direct, repeated saving on every run of every staff member.
23
23
 
24
24
  **Watch for sessions growing into their ceiling.** A run that gets killed at
25
- `timeout_minutes` has been paid for and produced nothing. `roster doctor` reports the ratio.
25
+ `timeout_minutes` has been paid for and produced nothing. Health, and `roster doctor`, report
26
+ the ratio.
26
27
 
27
28
  ## GitHub Actions minutes
28
29
 
@@ -57,5 +58,5 @@ on your machine when you ask it to, and the machinery is vendored into the tenan
57
58
  being raised is a session that has stopped fitting its job.
58
59
  - **Give a mention workflow a shorter ceiling than a daily one.** A focused task that runs for
59
60
  an hour has gone wrong, and the ceiling is the only thing that stops it.
60
- - **Check the ratio, not the last run.** `roster doctor` reports how many of the last ten runs
61
+ - **Check the ratio, not the last run.** Health reports how many of the last ten runs
61
62
  succeeded. One bad run is noise; four is a bill.
@@ -6,6 +6,9 @@ sidebar_order: 19
6
6
 
7
7
  # doctor codes
8
8
 
9
+ The portal's [Health](portal.md#health) screen shows these same findings, each with its fix, and
10
+ turns the ones an agent could fix into a single brief. This page is the reference behind both.
11
+
9
12
  Every finding `roster doctor` can emit. Each carries a stable `id`, which is what
10
13
  `--json` reports and what to quote in an issue.
11
14
 
@@ -22,9 +25,12 @@ ran at all.
22
25
  |---|---|
23
26
  | `gh` | Whether `gh` is installed and authenticated. A warning here means every network check was skipped, not that anything is wrong. |
24
27
  | `org.yaml` | The org manifest parsed, and how much it declares. |
25
- | `human` | **fail.** `org.yaml` has no `human.github`. The mention callers gate on that login, so nothing can wake an agent. |
28
+ | `human` | **fail.** `org.yaml` names nobody with a `github` login, in either `human` or `humans`. The mention callers gate on those logins, so nothing can wake an agent. |
29
+ | `human.login` | One of the people in `humans` has a name but no `github` login. They read as somebody the staff answer to and are not: the gate can never match them. |
26
30
  | `repo` | Every repo in `org.yaml` is reachable. A failure means it does not exist or your `gh` cannot see it. |
27
31
  | `repo.visibility` | A repo's real visibility disagrees with what `org.yaml` records. Cosmetic, but the posture it records is then fiction. |
32
+ | `agent` | Which runner this org uses, resolved from the tenant's own `agents.mjs`. **fail** if `org.yaml` names one it does not know. |
33
+ | `agent.config` | **fail.** The agent needs a config file of its own and it is missing, or still has a `FILL IN` in it. Nanocoder is the one preset that does: it is a client rather than a model, so without a provider it starts, finds nothing to call, and exits. |
28
34
  | `business` | **fail** if `org/business.md` is missing. Every prompt is composed on top of it. |
29
35
  | `business.stub` | `org/business.md` is still the questions it shipped with. Nothing errors; the agents just write competent work about a business that does not exist. |
30
36
  | `actions-access` | **fail** unless the ops repo is callable from the whole organisation. This is the "workflow not found" trap. See [manual steps](manual-steps.md#1-allow-the-ops-repos-workflow-to-be-called). |
@@ -54,7 +60,7 @@ ran at all.
54
60
  | `peer-labels` | The `from-<handle>` label exists on the *peer's* tracker, which is where this staff member's asks land. |
55
61
  | `status-issue` | The declared status issue is actually pinned. If not, the place you look is not the place the agent maintains. |
56
62
  | `runs` | A window of recent runs. See below. |
57
- | `runs.timeout` | **fail.** Runs were killed at a ceiling. |
63
+ | `runs.timeout` | **fail.** Runs were killed at a ceiling. Drops to `ok` once the ceiling has been raised *and* a run has finished since the last kill: the fix is made and proved, and the old runs are history rather than a problem. |
58
64
  | `runs.cancelled` | Runs were cancelled short of any ceiling, with their durations. |
59
65
 
60
66
  ## Reading `runs`
@@ -63,8 +69,11 @@ This is the only check that proves the whole chain works, so it is worth underst
63
69
 
64
70
  - **"has never run"** is a warning, not an `ok`. Nothing has exercised the App grant or the
65
71
  secrets, so nothing is known.
66
- - **"all gated out before doing anything"** means every recent trigger was `skipped`. That is
67
- normal for a mention workflow, but it means the credentials are still unproven.
72
+ - **"all gated out"** means every recent trigger was `skipped`, which is a mention workflow's
73
+ normal state: every comment on the tracker fires it and the gate drops all but the real ones.
74
+ It is only a warning when *nothing* in that repo has finished a run, because the App grant
75
+ belongs to the repository rather than to the workflow, so one finished run proves it for all
76
+ of them.
68
77
  - **"ran to a Nm ceiling and were killed"** is a timeout. GitHub reports those as `cancelled`,
69
78
  so doctor identifies them by duration. If the ceiling it names differs from the one the
70
79
  caller sets today, it says so: those runs happened under the old setting.
package/docs/export.md CHANGED
@@ -22,7 +22,8 @@ that reads it gets the same view without reimplementing the memory grammar.
22
22
  | `org` | the GitHub organisation |
23
23
  | `name` | the business name |
24
24
  | `opsName` | the ops repo's directory, so a consumer can address `org.yaml` and `org/*.md` by path |
25
- | `human` | the `human` block from `org.yaml` |
25
+ | `human` | the first human, normalised: `{ github, name, marker, role }`. Kept for consumers written when an org had exactly one |
26
+ | `humans[]` | everyone the staff answer to, in order, read from `humans` or the singular `human`. `human` is `humans[0]` |
26
27
  | `generatedAt` | ISO timestamp |
27
28
  | `staff[]` | one entry per staff member |
28
29