@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.
Files changed (96) hide show
  1. package/README.md +70 -84
  2. package/dist/cli.js +4775 -2495
  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 +95 -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 +121 -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 +9 -3
  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 +139 -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/org-yaml.md CHANGED
@@ -31,11 +31,15 @@ experiment_private: true
31
31
 
32
32
  agent:
33
33
  id: claude-code-action
34
+ permissions: full
34
35
 
35
36
  defaults:
36
- model: claude-opus-5
37
- timeout_minutes: 60
38
- allowed_tools: [Bash, Read, Write, Edit, Glob, Grep, WebFetch, WebSearch]
37
+ model: claude-opus-5-5
38
+ timeout_minutes: 90
39
+ mention_timeout_minutes: 90
40
+
41
+ budget: 300
42
+ review_gate: true
39
43
 
40
44
  staff:
41
45
  - { handle: cto, dir: technology, name: Chief Technology Officer, schedule: "0 7 * * 1-5" }
@@ -57,18 +61,40 @@ repos:
57
61
  | `name` | yes | What the business is called, in prose. Appears in prompts. |
58
62
  | `ops_dir` | no | Directory name of the ops repo in the runner checkout. Defaults to `roster-ops`. |
59
63
  | `experiment_private` | no | Whether the fact that this org is agent-run is itself private. Read by the guardrails fragment. |
64
+ | `budget` | no | USD over any trailing 30 days, for the whole org. See [below](#budget). |
65
+ | `review_gate` | no | `true` to have GitHub require a reviewed pull request before anything merges into a product repo. Off by default, because private repos need a paid plan for it. See [security](security.md#the-review-gate). |
66
+
67
+ ### `human` and `humans`
60
68
 
61
- ### `human`
69
+ Who the staff answer to. One person is a `human` map; more than one is a `humans` list:
62
70
 
63
- Who the staff answer to. There is exactly one.
71
+ ```yaml
72
+ humans:
73
+ - { name: Will, github: will-lamerton, marker: will, role: founder }
74
+ - { name: Sam, github: sam-x, marker: sam, role: operations }
75
+ ```
64
76
 
65
77
  | Field | Required | Means |
66
78
  |---|---|---|
67
- | `github` | yes | Login. **The mention callers gate on this**, so without it nothing can wake an agent. |
79
+ | `github` | yes | Login. **The mention callers gate on these**, so without one nothing can wake an agent. |
68
80
  | `name` | no | What to call them in prose. Defaults to the login. |
69
- | `marker` | no | Provenance tag on a fact they ruled on, as in `[will]`. Also used as a label. |
81
+ | `marker` | no | Provenance tag on a fact they ruled on, as in `[will]`. Also used as a label. Defaults to the first part of the name, lowercased. |
70
82
  | `role` | no | Prose only. |
71
83
 
84
+ Both keys are read, and the singular is not deprecated: an org with one human should keep
85
+ writing `human`. When both are present, anyone in `human` who is not already in the list is
86
+ appended rather than dropped.
87
+
88
+ **The first entry is the primary.** Prompts are prose addressed to somebody ("*Will* is not
89
+ here"), and a list of two cannot go in that sentence, so the first one goes there, their
90
+ `marker` is what `%%HUMAN_MARKER%%` renders, and the rest are named by
91
+ `{{humans_extra}}` in the identity fragment. Everything that *gates* on identity reads all of
92
+ them: the mention caller's `if:` is `contains(fromJSON('["will-lamerton","sam-x"]'), …)`.
93
+
94
+ Adding a human changes every generated caller workflow, so it takes a `roster upgrade` to reach
95
+ the brain repos. Until that lands, the new person can open issues and read everything, and
96
+ mentioning a staff member does nothing.
97
+
72
98
  ### `agent`
73
99
 
74
100
  Which coding agent runs a session. Either a string, or a map. See
@@ -81,10 +107,16 @@ Which coding agent runs a session. Either a string, or a map. See
81
107
  | `run` | if `id` is unknown | Shell command that runs it, reading `$AGENT_PROMPT_FILE`. |
82
108
  | `token_env` | if `id` is unknown | Environment variable its credential goes in. |
83
109
  | `model` | no | Default model for this agent. A staff member's own `model` wins. |
110
+ | `permissions` | no | `full`, `workspace` or `read-only`. Defaults to `full`. One word, translated into each agent's own vocabulary: a tool list for Claude, a sandbox and an approval policy for Codex, a development mode for nanocoder. A staff member can set their own, and be trusted less than the org. |
111
+ | `options` | no | A map, in that agent's own vocabulary, spelled onto its command line untranslated. The escape hatch for anything roster does not model. |
84
112
 
85
113
  Any field given overrides the preset's, so a preset that is right except for one flag needs
86
114
  one line.
87
115
 
116
+ An agent that needs a config file of its own gets one written when `roster init` chooses it,
117
+ with the parts only a person can supply left as `FILL IN` blanks. `roster doctor` fails while
118
+ any of them are still there.
119
+
88
120
  ### `defaults`
89
121
 
90
122
  Fallbacks for staff members who do not set their own.
@@ -92,8 +124,23 @@ Fallbacks for staff members who do not set their own.
92
124
  | Field | Means |
93
125
  |---|---|
94
126
  | `model` | Model id passed to the agent. |
95
- | `timeout_minutes` | Ceiling on a daily session. |
96
- | `allowed_tools` | Tool permission string. Meaningful to agents that take one, ignored by those that do not. |
127
+ | `timeout_minutes` | Ceiling on a daily session. `90` if unset. |
128
+ | `mention_timeout_minutes` | Ceiling on a mention run. Falls back to `timeout_minutes`, then `90`. |
129
+ | `allowed_tools` | Claude's own spelling of a permission level, kept because it predates `agent.permissions` and still wins for the agents that take a tool list. Nothing translates it for the others: a list written for one agent is not a permission level for another. Prefer [`agent.permissions`](agents.md#permissions), which every agent understands. |
130
+
131
+ ### `memory`
132
+
133
+ Budgets `roster lint` holds every staff member's memory to. All optional; a staff member's own
134
+ [`memory:`](staff-yaml.md#memory) overrides these.
135
+
136
+ ```yaml
137
+ memory:
138
+ max_fact_chars: 400 # one fact's line in memory/INDEX.md
139
+ max_index_kb: 24 # the whole index, read in full at every boot
140
+ max_decisions_kb: 24 # log/decisions.md
141
+ ```
142
+
143
+ Over budget is a warning naming what to cut, never an error. See [memory](memory.md#budgets).
97
144
 
98
145
  ### `staff`
99
146
 
@@ -106,10 +153,26 @@ lives in their own `staff.yaml`.
106
153
  | `dir` | no | Directory and repo name. Defaults to the handle. |
107
154
  | `name` | no | Role name in prose. |
108
155
  | `schedule` | no | Cron. Informational here; the caller workflow is what actually schedules. |
156
+ | `budget` | no | USD over any trailing 30 days, for this staff member alone. |
109
157
 
110
158
  `roster hire` appends to this list. An empty list (`staff: []`) is valid and is what a fresh
111
159
  org has.
112
160
 
161
+ ### `budget`
162
+
163
+ A number of dollars, on the org, on a staff entry, or both:
164
+
165
+ ```yaml
166
+ budget: 300
167
+ staff:
168
+ - { handle: cto, dir: technology, name: Chief Technology Officer, budget: 200 }
169
+ ```
170
+
171
+ Spend over the trailing 30 days is added up from each run's record. Past a budget, `roster
172
+ doctor` warns and the portal's Runs screen marks the total. **Nothing stops a run.** A cap that
173
+ ends a session fails it after the work is done and committed, which is a false red rather than a
174
+ saved penny; `timeout_minutes` is the real bound. See [cost](cost.md#budgets).
175
+
113
176
  ### `repos`
114
177
 
115
178
  Every repository the org owns, and what it is for.
@@ -127,11 +190,13 @@ Every repository the org owns, and what it is for.
127
190
 
128
191
  | Reader | Uses |
129
192
  |---|---|
130
- | `compose.mjs` | `org`, `name`, `human`, `ops_dir`, `staff` |
193
+ | `compose.mjs` | `org`, `name`, `human`, `humans`, `ops_dir`, `staff` |
131
194
  | `runner-plan.mjs` | `org`, `staff`, and each manifest's `works_in` and `peers` |
132
195
  | `agents.mjs` | `agent`, `staff` |
133
196
  | `roster hire` | all of it, plus every existing manifest |
134
- | `roster doctor` | all of it |
197
+ | `roster doctor` | all of it, including `budget` |
198
+ | `roster lint` | `staff`, `memory` |
199
+ | `roster portal` | all of it; the Runs screen reads `budget` |
135
200
 
136
201
  ## Editing it
137
202
 
package/docs/portal.md CHANGED
@@ -16,6 +16,21 @@ authentication and no API quota, and works offline. It can write to GitHub throu
16
16
 
17
17
  Keep the repos checked out beside each other, in the same shape the runner uses.
18
18
 
19
+ ## The sidebar
20
+
21
+ Anything that asks before it acts (a merge, a push, a hire, a retire, a paid run) asks in the
22
+ page's own dialog, never the browser's `confirm()`, which blocks the whole tab.
23
+
24
+ **The counts are right on load.** The badges beside Inbox and Pending work are fetched once at
25
+ boot, and the Inbox screen shares that request rather than making a second one. A sidebar that
26
+ says nothing until you look at it is not a sidebar.
27
+
28
+ While the first answer is outstanding the badge is a placeholder rather than blank, because an
29
+ empty badge reads as zero and zero is a different claim from "still counting". The screens
30
+ themselves wait the same way: the inbox draws rows and the thread pane draws a page, in
31
+ placeholder shapes, for the seconds GitHub takes. Anything you start writing in that pane
32
+ survives the answer landing under it.
33
+
19
34
  ## Setup
20
35
 
21
36
  **With no tenant where you started it, the portal is the setup screen instead.** That is not an
@@ -37,13 +52,31 @@ It asks GitHub which of two things this is:
37
52
  nothing is written until you apply. Same `initFiles` the CLI runs, so the browser and the
38
53
  terminal cannot disagree about what a new tenant contains.
39
54
 
40
- Then: the Actions setting, deep-linked to the exact page with the failure it causes if skipped;
41
- which repos the staff work in, as a picker over what your `gh` can see minus what `org.yaml`
42
- already has; and a prompt for writing `org/business.md`.
55
+ Then what is left. **The Actions setting**, read on arrival: if it is not set, *Set it for me*
56
+ asks GitHub to set it, and a refusal comes back with GitHub's reason and a link to the page to
57
+ click. **The agent credential**: a paste box, a line on where your agent's credential comes from,
58
+ and where it will be stored (one org secret shared with the brains, or each brain where an org
59
+ secret would not arrive, with the reason). The value goes to the local server in a POST and from
60
+ there to `gh` on standard input; it is never written to disk or echoed back. Until somebody is
61
+ hired the box says so, because nothing would read it. Then which repos the staff work in, as a
62
+ picker over what your `gh` can see minus what `org.yaml` already has, recorded with the
63
+ visibility GitHub reports. Then the two files only you can write: a prompt for writing
64
+ `org/business.md`, and `org/priorities.md` opened in place, stub and all, to replace with what
65
+ matters this month. Each says whether it is written yet.
43
66
 
44
67
  Nothing here stores which step you are on. Setup takes days rather than minutes: an App has to be
45
68
  installed, a credential set, a first run finished. So the page derives its state from `roster
46
- doctor` every time it is drawn. A stored step counter would disagree with the world within an hour.
69
+ doctor` every time it is drawn, and runs the check again after anything on it is saved. A stored
70
+ step counter would disagree with the world within an hour.
71
+
72
+ ### Getting started
73
+
74
+ **Once the org exists, the same steps stay in the sidebar as *Getting started*** for as long as
75
+ anything is left: nobody hired, or `org/business.md` or `org/priorities.md` still the stub. An
76
+ org nobody has been hired into opens on it rather than on an empty Inbox, with *Hire your first
77
+ staff member* first, then the Actions setting, the credential, the repo picker, both files, and
78
+ what doctor still says. A link to another screen still wins. When the list is empty the entry
79
+ goes away; the repo picker lives on under [Org](#org).
47
80
 
48
81
  ## Inbox
49
82
 
@@ -62,7 +95,37 @@ down with the list, so opening a thread is a render rather than a request.
62
95
  a shorter timeline than open work because it is there to be read rather than triaged. A
63
96
  closed row is dimmed and marked; the sidebar badge keeps counting only what is open.
64
97
  - **Reply, close, reopen, open an issue.** All as you, through your own `gh`, so they are
65
- indistinguishable from doing it on the site. Closing asks for confirmation.
98
+ indistinguishable from doing it on the site. Closing asks for confirmation. A half-written
99
+ reply survives a repaint, and pressing Comment with an empty box says so rather than
100
+ silently doing nothing.
101
+ - **A new issue asks one question: who is it for.** One dropdown, over the staff. It goes to
102
+ that person's brain repo and their `@handle` is written into the body for you, because those
103
+ are the two things that make an issue reach an agent rather than sit there. Asking for a
104
+ repo as well would let you pick a pair that wakes nobody. To file in a product repo instead, use GitHub: this form is for asking the staff for
105
+ something.
106
+ - **Labels are the repo's own, as toggles.** They are the labels that exist on the recipient's
107
+ repository, fetched from GitHub and cached. A text box was a spelling test: `from-cmo` and
108
+ `from-CMO` are different labels and only one of them exists.
109
+ - **Typing `@` offers the org.** A mention is the mechanism, not decoration: a staff member's
110
+ workflow gates on their handle, so a misspelled one is a message nobody is woken by. The list
111
+ is the staff and the humans, and nothing else. The Apps are deliberately absent: `@acme-cto`
112
+ is a login rather than an inbox, GitHub delivers nothing for mentioning one, and an agent
113
+ wakes on its own handle and not on the identity it posts as, so offering them is offering
114
+ entries that do nothing. Each row says which it is, the list appears under the caret, arrow
115
+ keys and Enter pick, Escape dismisses the list rather than the dialog around it, and an email
116
+ address is not a mention.
117
+ - **Attachments.** Drop a file on the box, pick one, or paste one. Paste is the one that
118
+ matters, because a screenshot is on the clipboard and never on disk. GitHub's own drag-and-drop
119
+ attachments are minted by its web app and cannot be made with `gh`, so the file is committed
120
+ to `attachments/<date>-<name>` in the repo the issue lives in and pushed. The body gets the
121
+ link for you and the repo-relative path for the agent, which reads it off its own checkout
122
+ rather than needing a token for a private repo. 25MB a file; a failed push is reported rather
123
+ than hidden.
124
+ - **Reactions.** The 👀 an agent leaves when it picks something up, under the comment it is on,
125
+ with who left it. Without it you post a comment, see nothing change, and have to open GitHub
126
+ to find out it landed.
127
+ - **How much conversation** is on each row, which is most of what tells a live thread from
128
+ something filed and never answered.
66
129
  - Issue and PR references in a body become chips you can click through, and `@handle`
67
130
  mentions become chips too. A mention of somebody on this roster goes to their repository
68
131
  rather than to a GitHub profile of that name, which for `@cto` is a stranger.
@@ -79,32 +142,141 @@ down with the list, so opening a thread is a render rather than a request.
79
142
  disclosure. An item a cross-reference points at opens in the portal when the inbox already
80
143
  holds it, and on GitHub when it does not.
81
144
 
145
+ ## Pending work
146
+
147
+ The same screen, scoped to pull requests, in its own place in the sidebar. An inbox is what is
148
+ waiting on you; a pull request is work that is finished and waiting on a merge, and the count
149
+ that matters is not how many are open but how many are green and still sitting there.
150
+
151
+ It is called **Pending work** rather than "Pull requests" because that is what is behind it: a
152
+ piece of work a staff member finished and cannot land alone. A pull request is how it arrives,
153
+ not what it is.
154
+
155
+ A pull request's thread has three tabs.
156
+
157
+ - **Conversation** is the thread: body, timeline, reactions, reply box, same as an issue.
158
+ - **Commits**, with subject, author and sha, each linking to GitHub.
159
+ - **Files**: the full patch, rendered as a diff with line numbers, the same renderer *What
160
+ changed* uses. A file with no patch is binary or too large for the API to send one, and says
161
+ so rather than showing nothing.
162
+
163
+ Both are fetched when you open the tab, not carried by the inbox. A diff is the biggest thing
164
+ on this screen by an order of magnitude, and paying for every open PR's diff on every refresh
165
+ of every repo to show one of them is the wrong trade.
166
+
167
+ **Merge** sits beside Close and asks before it goes. It never deletes the branch: that is a
168
+ second decision and not this button's to make. It runs `gh pr merge` as you, so a protected
169
+ branch, a failing required check or a merge queue behaves exactly as it would on the site.
170
+
171
+ **It does not ask how.** Squash, merge commit or rebase is a question about git rather than about the pull request in front of you, the repository
172
+ has already answered it in its own settings, and on any given repository most of the answers are
173
+ wrong. So the repository is asked instead: squash where it is allowed, then a merge commit, then
174
+ rebase.
175
+
176
+ ### Asking for a change
177
+
178
+ **`@cto` on a pull request wakes nobody.** A staff member's caller workflow lives in their own
179
+ brain repo and gates on their handle appearing *there*; on a product repo the same mention
180
+ posts, renders as a chip, and does nothing. That is deliberate, for the reasons in
181
+ [security](security.md#trust-in-a-prompt). Saying so out loud is the portal's job, because a
182
+ silent no-op is worse than the restriction itself.
183
+
184
+ **Reply is the one box, and it handles this.** Name somebody in a reply where a comment will
185
+ not reach them and the offer appears under the box, ticked: *open it on their tracker too*. One
186
+ press of Comment posts your words on the thread and opens the request on their tracker, which
187
+ carries
188
+
189
+ - the pull request, its link and its branch
190
+ - their `@handle`, which is what actually wakes them
191
+ - an instruction to **answer on the pull request**, because `prompts/mention.md` otherwise tells
192
+ them to answer where the request came from, which here is the tracker, leaving the diff silent
193
+ and you watching the wrong page
194
+ - the diff for the file you asked from, if you started from one, clipped: it is there to say
195
+ *which part*, not to be a copy of the diff that goes stale on the next push
196
+
197
+ Untick it and the reply is just a comment. Who gets asked comes off the text you actually sent,
198
+ so a handle you typed and then deleted is not asked.
199
+
200
+ On the **Files** tab each file's heading has its own Reply, which opens the same box about that
201
+ one file. "This bit is wrong" is what you want to say while looking at a diff, and the
202
+ alternative is describing in prose which of thirty files you meant.
203
+
204
+ Both writes go through your own `gh`, as you. Nothing is dispatched between repositories and no
205
+ credential is put on a public repo. The tracker issue goes first, because it is the half that
206
+ reaches anybody; if the copy on the pull request then fails you are told, rather than being
207
+ shown an error that invites you to ask the same person the same thing twice.
208
+
209
+ ## Runs
210
+
211
+ What each staff member ran in the last 30 days: when, daily or mention, how it ended, how long
212
+ it took, turns and cost where known, and a link to the log. Above the tables, the 30-day total
213
+ for the org, and each staff member's own in their heading.
214
+
215
+ Runs are not in any repo, so this is the one screen that is only ever on GitHub. It reads the
216
+ run lists through your own `gh`, the same way the inbox does, and offline it says so rather
217
+ than drawing an empty table that reads as "nothing ran".
218
+
219
+ Cost comes from the record each run leaves behind (see [cost](cost.md#what-each-run-cost)). A
220
+ run from before records existed, or from an agent that does not report cost, shows a dash, and
221
+ a total says how many runs it could price. Each record is downloaded once and kept for as long
222
+ as the portal runs, so the first visit is the slow one.
223
+
224
+ A skipped mention is not a run and is not listed. A `setup-failure` is a run that failed before
225
+ the agent started, usually a token or a checkout. Past a [`budget`](org-yaml.md#budget), the
226
+ total turns amber.
227
+
82
228
  ## Org
83
229
 
84
- The layer every staff member inherits, in one place: `org.yaml` and the four org documents,
85
- each with a line saying what it is for, because five filenames tell you nothing about which to
86
- open. All five are editable, and saving commits and pushes.
230
+ The layer every staff member inherits, in one place: `org.yaml`, every `org/*.md`, and the
231
+ prompt files in `prompts/`, each with a line saying what it is for, because a filename tells
232
+ you nothing about which to open. Anything roster does not ship falls back to its own first
233
+ heading.
234
+
235
+ **Add a product repo** opens the same picker setup uses, so a repo created later does not mean
236
+ typing `role: product` into `org.yaml` by hand. Adding one commits `org.yaml` and pushes. A new
237
+ hire picks it up; somebody already hired keeps the `works_in` in their own `staff.yaml`.
238
+
239
+ **The list comes off disk**, not out of the page, so a tenant that adds `org/pricing.md` can
240
+ open it like any other. Only files the
241
+ portal may actually write are listed: an editor that offers a file it cannot save is a trap.
242
+
243
+ Every one of them is editable from the screen it is read on: an **Edit** button on the file,
244
+ ⌘S to save, and saving commits and pushes. Only `org.yaml` asks for confirmation first; a
245
+ prose edit is one commit to revert, and asking every time teaches people to click through the
246
+ question.
87
247
 
88
248
  `org.yaml` is the exception to the write allowlist. It belongs to the person rather than the
89
249
  agent, so it is writable. But it is the one file here that stops every prompt composing when
90
250
  it is wrong, so the server parses it with the tenant's own `compose.mjs` first and refuses
91
- anything that is not YAML it can read, or that has lost `org`, `name`, or a handle on a staff
92
- entry.
251
+ anything that is not YAML it can read, or that has lost `org`, `name`, a handle on a staff
252
+ entry, or a github login on an entry in [`humans`](org-yaml.md#human-and-humans).
253
+
254
+ YAML is coloured, here and anywhere else the portal shows it: comments, keys, strings, numbers
255
+ and the three keywords. `org.yaml` and `staff.yaml` are the two files anybody reads closely,
256
+ and finding a key in a flat grey wall means reading every line.
257
+
258
+ The card at the top names **everyone** the staff answer to, not the first of them. An org can
259
+ have more than one human, and a card that names one of two reads as the only one who counts.
93
260
 
94
261
  ## Staff
95
262
 
96
- Everyone on the roster, and the four things you could previously only do from a terminal.
263
+ Everyone on the roster, and the things you would otherwise do from a terminal.
97
264
 
98
265
  **Hiring** runs the same `buildPlan` and `applyPlan` that `roster hire` does, on the server.
99
- Only the handle is required; everything else is copied from whoever is already here. You see
100
- the plan first, listing every file, every label, the schedule it chose and why, and the manual
101
- steps it cannot do for you. Nothing happens until you apply. What the terminal would have
102
- printed is shown when it finishes.
266
+ Only the handle is required; everything else is copied from whoever is already here. The first
267
+ hire has nobody to copy App names from, so its form also asks for the App's name and the shared
268
+ public App's, which are `--app` and `--public-app`. The public one only matters when a product
269
+ repo is public; on private ones the session uses the staff member's own App. You see
270
+ the plan first, listing every file, every label, the schedule it chose and why, the commits it
271
+ will make as you in repos that already exist (each peer's `staff.yaml`, `org.yaml`), whether the
272
+ new brain joins the credential's org secret, and what is left for you. Nothing happens until you
273
+ apply. What the terminal would have printed is shown when it finishes.
103
274
 
104
275
  **Writing the charter** is the copy-a-prompt loop below, aimed at `CHARTER.md`. `hire`
105
276
  deliberately does not write it, because a generated charter produces exactly the generic agent
106
- this whole arrangement exists to avoid. So this is the route that was previously `roster brief
107
- charter <handle>` and a terminal.
277
+ this whole arrangement exists to avoid. It is the same brief as `roster brief charter
278
+ <handle>`, with somewhere to put the answer, and a picker for the worked example it carries as a
279
+ model: matched to the role, or another, or none.
108
280
 
109
281
  **The GitHub App** is `roster app`, on this server rather than a second one. There is no API that
110
282
  creates an App: the only route is the manifest flow, where you post a manifest to a settings page,
@@ -114,9 +286,17 @@ one origin. The private key is still held in memory and written straight to a re
114
286
 
115
287
  GitHub redirects the tab *it* opened, not the one you clicked from, so the original polls for the
116
288
  result. What it cannot do is install the App: that is a grant of access to specific repositories
117
- and GitHub asks a human to choose them, which is correct and should not be worked around. The panel
118
- says so loudly, and says to grant every tracker the staff member writes to rather than only their
119
- own.
289
+ and GitHub asks a person to confirm it, which is correct and should not be worked around. So the
290
+ panel's **Install it** opens the install page with the org and the repos already selected: the
291
+ brain, every peer tracker it writes to, and the product repos. Any whose id could not be read are
292
+ listed for you to tick.
293
+
294
+ **Agent credential** is the setup screen's paste box, reachable from each card, because the
295
+ moment you look for it is while setting somebody up. It is once for the org.
296
+
297
+ **Run once now** starts the daily workflow, follows it, and shows how it ended with the log's
298
+ link and, on a failure, the step it failed at. It asks first, because it is a real run. A success
299
+ is what turns doctor's *unproven* into proven. Health has the same button.
120
300
 
121
301
  **Retiring** is `roster retire`, and it is deliberately not deletion. A brain repo is that
122
302
  agent's entire memory and there is no undo, so retiring disables the workflows, unwires them
@@ -152,11 +332,11 @@ rather than going nowhere.
152
332
 
153
333
  ## Prompt
154
334
 
155
- **What this staff member is actually sent**, which was previously only reachable through
156
- `roster prompt <handle> --kind daily` in a terminal. Composed on the server by the tenant's own
335
+ **What this staff member is actually sent**, the same as `roster prompt <handle> --kind daily`.
336
+ Composed on the server by the tenant's own
157
337
  `compose.mjs`, so there is no second implementation to drift.
158
338
 
159
- Pick the kind: `daily`, `mention` or `pr-mention`. A mention prompt is written for the comment
339
+ Pick the kind: `daily` or `mention`. A mention prompt is written for the comment
160
340
  that woke it, so a preview fills in obviously-fake context.
161
341
 
162
342
  Beneath the composed text, the files it was made of, in two groups.
@@ -172,25 +352,9 @@ tells the agent to open these; it does not contain them. Editing a charter chang
172
352
  agent does without changing a byte of the composed prompt, and that distinction is easy to
173
353
  miss.
174
354
 
175
- ### Problems
176
-
177
- The audit, beside the layers. Nothing here judges prose: every check is something a machine
178
- can be sure about, because a linter you stop believing is worse than no linter.
179
-
180
- | Check | Why it matters |
181
- |---|---|
182
- | a file is still the scaffold | `org/business.md` is composed into every run. Leaving it as questions is invisible: the run works and the output is just generic |
183
- | a placeholder never resolved | `{{staff.product.repo}}` is null for a staff member who contributes to no other repo, and the agent reads the braces literally |
184
- | the same line in two layers | the layers are inherited, so a rule a charter repeats from `org/` is duplication nobody reading either file can see |
185
- | the prompt is long | it is read in full on every run, forever |
186
- | it names a file that is not there | an instruction to read something absent is a quiet no-op inside a run nobody watches |
187
- | an included layer is empty | it contributes nothing and costs a line of includes |
188
-
189
- **Every finding carries the fix.** "Copy a prompt to fix this" builds a brief containing the
190
- finding, the composed prompt, and every layer, and puts it on your clipboard. Paste it into
191
- whatever agent you use. Knowing there is a problem is the hard part; writing the paragraph is
192
- not. The box comes pre-filled with what the finding worked out, so you can add to it rather
193
- than retype it.
355
+ What is *wrong* with any of this is on [Health](#health). This screen answers "what is sent";
356
+ whether what is sent is any good is a different question, and a tree that was half prompt and
357
+ half complaints answered neither well.
194
358
 
195
359
  ### Getting help changing it
196
360
 
@@ -284,7 +448,11 @@ opening the diff that did it: one block per file, coloured, with line numbers.
284
448
 
285
449
  ## Health
286
450
 
287
- Three parts.
451
+ Four parts: the rig, the org, the prompts and the memory. The rig is about this staff member,
452
+ so it is first; the rest widens out from there.
453
+
454
+ **The rig**: schedule in words, workflows present, last commit, last commit touching `memory/`,
455
+ index and notes size, charter, status issue, missing surfaces.
288
456
 
289
457
  **The org, from `roster doctor`.** Every finding that is not `ok`, with what to do about it, and
290
458
  a button that turns the lot into one brief for a coding agent. That is `roster fix`: `doctor`,
@@ -300,20 +468,66 @@ an agent that has started editing has stopped reading.
300
468
 
301
469
  Paste it into whatever edits files here, then press *Check again*. The ids should be gone.
302
470
 
303
- **Memory problems**, and **the rig**.
471
+ **Prompt problems.** The audit, over all three kinds of run at once. Nothing here judges prose:
472
+ every check is something a machine can be sure about, because a linter you stop believing is
473
+ worse than no linter. A finding true of more than one prompt is one row, and it says which.
474
+
475
+ | Check | Why it matters |
476
+ |---|---|
477
+ | a file is still the scaffold | `org/business.md` is composed into every run. Leaving it as questions is invisible: the run works and the output is just generic |
478
+ | a placeholder never resolved | `{{staff.product.repo}}` is null for a staff member who contributes to no other repo, and the agent reads the braces literally |
479
+ | the same line in two layers | the layers are inherited, so a rule a charter repeats from `org/` is duplication nobody reading either file can see |
480
+ | the prompt is long | it is read in full on every run, forever |
481
+ | it names a file that is not there | an instruction to read something absent is a quiet no-op inside a run nobody watches |
482
+ | an included layer is empty | it contributes nothing and costs a line of includes |
483
+
484
+ **Every finding carries the fix.** "Copy a prompt to fix this" builds a brief containing the
485
+ finding, the composed prompt, and every layer, and puts it on your clipboard. Paste it into
486
+ whatever agent you use. Knowing there is a problem is the hard part; writing the paragraph is
487
+ not. The box comes pre-filled with what the finding worked out, so you can add to it rather
488
+ than retype it. "Open the file" takes you to that layer on the Prompt screen.
304
489
 
305
- Memory problems are the same checks `roster lint` runs. Each one has a button that opens an
490
+ **Memory problems** are the same checks `roster lint` runs. Each one has a button that opens an
306
491
  issue in that staff member's own repo asking them to fix it, which is usually right, because
307
492
  they wrote it.
308
493
 
309
- **The rig**: schedule in words, workflows present, last commit, last commit touching `memory/`,
310
- index and notes size, charter, status issue, missing surfaces.
494
+ ## Any image opens
495
+
496
+ **Click a picture and it opens over the page, fitted to the window.** Bottom right there is a
497
+ `-`, the current zoom, and a `+`. The percentage is a button too: it refits. Scrolling zooms,
498
+ dragging moves, clicking the picture goes between fitted and actual size, and `+`, `-`, `0` and
499
+ Escape do the same from the keyboard. Zooming is anchored on the pointer, so the thing you
500
+ aimed at stays where you aimed.
501
+
502
+ The percentage is of **actual size**, not of fitted, so 100% means one image pixel per screen
503
+ pixel. How far in it will go depends on the picture: a scaled image is a composited layer and
504
+ the browser allocates it at the rendered size, so the ceiling is whatever keeps that within
505
+ budget. Actual size is always reachable, however large the original is.
506
+
507
+ This is every image the portal renders as content: a screenshot on a doc page, a picture inside
508
+ a brain document, a file open in the Brain screen's viewer. A screenshot laid out for the column
509
+ it sits in is unreadable exactly when it matters, which is when it is a picture of an interface
510
+ and the part you need is the small print.
511
+
512
+ Gallery tiles are the exception, and deliberately: their click already means "open this file",
513
+ and the file's own view is one of the things that does open.
514
+
515
+ Fitted, the picture is inset from the window edges and framed. That is not decoration. Most of
516
+ these are screenshots *of this interface*, and one fitted edge to edge reads as the app having
517
+ navigated rather than as a picture of it.
311
518
 
312
519
  ## Docs
313
520
 
314
521
  The framework's own documentation, rendered where you already are. Links between pages navigate
315
522
  the portal.
316
523
 
524
+ **Search reads the pages, not their titles.** Twenty-odd pages is too many to scan by eye and
525
+ few enough for the server to read in full on every keystroke, and what you are looking for
526
+ ("which page explains the mention gate") is a sentence in a paragraph rather than a word in a
527
+ heading. A page has to contain every word you typed; results rank a title hit over a heading
528
+ hit over sheer frequency, carry the lines the words were found in, and the first one opens as
529
+ you type. The query is in the URL, so a search is a link.
530
+
317
531
  ## After upgrading roster
318
532
 
319
533
  **Restart the portal.** Its stylesheets and modules are read per request, so editing one and