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

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 (108) hide show
  1. package/README.md +70 -84
  2. package/dist/cli.js +4817 -2374
  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/analyst.md +65 -0
  7. package/docs/charters/cmo.md +69 -0
  8. package/docs/charters/community.md +63 -0
  9. package/docs/charters/cto.md +71 -0
  10. package/docs/charters/designer.md +65 -0
  11. package/docs/charters/devops.md +65 -0
  12. package/docs/charters/pm.md +70 -0
  13. package/docs/charters/qa.md +65 -0
  14. package/docs/charters/support.md +60 -0
  15. package/docs/charters/writer.md +63 -0
  16. package/docs/commands.md +95 -11
  17. package/docs/concepts.md +64 -12
  18. package/docs/cost.md +39 -3
  19. package/docs/developing.md +16 -21
  20. package/docs/doctor-codes.md +21 -6
  21. package/docs/export.md +4 -1
  22. package/docs/extending.md +13 -4
  23. package/docs/getting-started.md +133 -79
  24. package/docs/images/brain.jpg +0 -0
  25. package/docs/images/org.jpg +0 -0
  26. package/docs/images/prompt.jpg +0 -0
  27. package/docs/images/setup-org.jpg +0 -0
  28. package/docs/images/setup-plan.jpg +0 -0
  29. package/docs/images/staff.jpg +0 -0
  30. package/docs/manual-steps.md +95 -101
  31. package/docs/memory.md +29 -8
  32. package/docs/org-yaml.md +76 -11
  33. package/docs/portal.md +290 -49
  34. package/docs/prompts.md +77 -11
  35. package/docs/security.md +51 -7
  36. package/docs/session-workflow.md +51 -21
  37. package/docs/staff-yaml.md +17 -7
  38. package/docs/troubleshooting.md +23 -20
  39. package/docs/upgrading.md +9 -3
  40. package/docs/writing-a-charter.md +46 -17
  41. package/package.json +1 -1
  42. package/templates/brain/.github/workflows/%%STAFF%%-daily.yaml +7 -0
  43. package/templates/brain/.github/workflows/%%STAFF%%-mention.yaml +16 -4
  44. package/templates/brain/CHARTER.md +3 -3
  45. package/templates/brain/README.md +1 -0
  46. package/templates/brain/log/decisions.md +3 -0
  47. package/templates/brain/staff.yaml +0 -1
  48. package/templates/brain/strategy/ideas.md +7 -0
  49. package/templates/briefs/priorities.md +46 -0
  50. package/templates/ops/.github/workflows/session.yaml +117 -40
  51. package/templates/ops/agents.mjs +127 -8
  52. package/templates/ops/compose.mjs +77 -7
  53. package/templates/ops/inflight.mjs +157 -0
  54. package/templates/ops/org/operating.md +21 -7
  55. package/templates/ops/org/voice.md +9 -0
  56. package/templates/ops/prompts/_identity.md +8 -1
  57. package/templates/ops/prompts/_inflight.md +14 -0
  58. package/templates/ops/prompts/_paths.md +2 -1
  59. package/templates/ops/prompts/daily.md +16 -7
  60. package/templates/ops/prompts/mention.md +18 -2
  61. package/templates/ops/run-record.mjs +144 -0
  62. package/templates/portal/css/base.css +245 -64
  63. package/templates/portal/css/brain.css +30 -20
  64. package/templates/portal/css/diff.css +15 -10
  65. package/templates/portal/css/graph.css +12 -7
  66. package/templates/portal/css/health.css +32 -11
  67. package/templates/portal/css/inbox.css +117 -14
  68. package/templates/portal/css/layout.css +114 -41
  69. package/templates/portal/css/markdown.css +57 -15
  70. package/templates/portal/css/runs.css +13 -0
  71. package/templates/portal/css/setup.css +126 -39
  72. package/templates/portal/index.html +25 -3
  73. package/templates/portal/js/api.js +74 -4
  74. package/templates/portal/js/app.js +156 -14
  75. package/templates/portal/js/dialog.js +129 -4
  76. package/templates/portal/js/dom.js +25 -0
  77. package/templates/portal/js/icons.js +45 -1
  78. package/templates/portal/js/inflight.js +18 -0
  79. package/templates/portal/js/lightbox.js +273 -0
  80. package/templates/portal/js/md.js +23 -6
  81. package/templates/portal/js/mdedit.js +84 -0
  82. package/templates/portal/js/mention.js +264 -0
  83. package/templates/portal/js/readiness.js +35 -0
  84. package/templates/portal/js/refresh.js +136 -6
  85. package/templates/portal/js/state.js +59 -8
  86. package/templates/portal/js/views/app.js +24 -7
  87. package/templates/portal/js/views/checklist.js +29 -10
  88. package/templates/portal/js/views/credential.js +84 -0
  89. package/templates/portal/js/views/docs.js +94 -4
  90. package/templates/portal/js/views/files.js +58 -14
  91. package/templates/portal/js/views/graph.js +1 -1
  92. package/templates/portal/js/views/health.js +178 -37
  93. package/templates/portal/js/views/hire.js +583 -0
  94. package/templates/portal/js/views/inbox.js +959 -126
  95. package/templates/portal/js/views/memory.js +16 -1
  96. package/templates/portal/js/views/org.js +124 -104
  97. package/templates/portal/js/views/orgedit.js +234 -0
  98. package/templates/portal/js/views/paste.js +87 -21
  99. package/templates/portal/js/views/prompt.js +61 -67
  100. package/templates/portal/js/views/repos.js +20 -15
  101. package/templates/portal/js/views/runonce.js +94 -0
  102. package/templates/portal/js/views/runs.js +165 -0
  103. package/templates/portal/js/views/setup.js +257 -75
  104. package/templates/portal/js/views/staff.js +157 -182
  105. package/templates/portal/js/views/todo.js +62 -0
  106. package/templates/portal/js/yaml.js +134 -0
  107. package/templates/brain/.github/workflows/%%STAFF%%-pr-mention.yaml +0 -50
  108. package/templates/ops/prompts/pr-mention.md +0 -57
package/docs/memory.md CHANGED
@@ -16,8 +16,8 @@ memory/INDEX.md one line per fact. Read in full at every boot.
16
16
  memory/notes/*.md the argument behind a fact. Read only when that fact is in play.
17
17
  ```
18
18
 
19
- That split is the whole design. Boot context here went from about 52,000 words to about 6,000
20
- by making it, and the saving repeats on every run forever.
19
+ That split is the whole design. Boot context here went from about 52,000 words to about 10,000
20
+ today by making it, and the saving repeats on every run forever.
21
21
 
22
22
  ## The grammar
23
23
 
@@ -48,10 +48,27 @@ rumour.
48
48
  Rule 5 is the one that gets skipped and the one that matters. Everything else degrades slowly;
49
49
  this one degrades the boot cost of every future run.
50
50
 
51
+ ## Budgets
52
+
53
+ Prose is advice, and an index nobody prunes grows past what a run can usefully read. So
54
+ `roster lint` warns past three budgets:
55
+
56
+ | Budget | Default | Rule |
57
+ |---|---|---|
58
+ | One fact's line | 400 characters | `too-long`, naming the fact |
59
+ | `memory/INDEX.md` | 24KB | `index-too-big`, naming the three longest facts |
60
+ | `log/decisions.md` | 24KB | `decisions-too-big`: roll older entries into `log/decisions/<YYYY-MM>.md` |
61
+
62
+ Change them under `memory:` in [org.yaml](org-yaml.md#memory), or per staff member in
63
+ [staff.yaml](staff-yaml.md#memory). The daily prompt tells a staff member to check its sizes at
64
+ hand-off and make pruning that run's job when it is over.
65
+
51
66
  ## What does not go in memory
52
67
 
53
68
  - **Why something was decided.** That is `log/decisions.md`, and it is not boot context.
54
69
  - **How a thing works.** That is a draft or a strategy document.
70
+ - **An idea not yet acted on.** That is one line in `strategy/ideas.md`, and it becomes an issue
71
+ only when it needs a ruling.
55
72
  - **What is outstanding.** That is the pinned status issue.
56
73
 
57
74
  Nothing is copied between them. Four places, four jobs, and a fact that appears in two of them
@@ -59,13 +76,17 @@ will disagree with itself within a month.
59
76
 
60
77
  ## Checking it
61
78
 
79
+ **Health**, per staff member, has a **Memory problems** section. Each finding has a button that
80
+ opens an issue in that staff member's own repository asking them to fix it, which is usually the
81
+ right move: they wrote it, and an issue on their tracker is a thing that wakes them.
82
+
83
+ It catches: a missing `So:`, a duplicate slug, a note nothing links to, a link to a note that
84
+ does not exist, an over-long line, a `[measured]` fact with no `n`, an "updated:" chain, and an
85
+ index or decision log over its budget.
86
+
87
+ The same checks, for a terminal or for CI:
88
+
62
89
  ```bash
63
90
  roster lint # everyone
64
91
  roster lint cto # one staff member
65
92
  ```
66
-
67
- Lint catches: a missing `So:`, a duplicate slug, a note nothing links to, a link to a note that
68
- does not exist, an over-long line, a `[measured]` fact with no `n`, and an "updated:" chain.
69
-
70
- The portal's **Health** screen shows the same findings and will open an issue in the staff
71
- member's own repository asking them to fix it, which is usually the right move: they wrote it.
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,168 @@ 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).
93
253
 
94
- ## Staff
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.
95
257
 
96
- Everyone on the roster, and the four things you could previously only do from a terminal.
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.
97
260
 
98
- **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.
261
+ ## Staff
262
+
263
+ Everyone on the roster, and the things you would otherwise do from a terminal.
264
+
265
+ **Hiring** starts from a role. With nobody hired the Staff screen opens on the role picker;
266
+ after that it is behind **Hire someone**. The cards are CTO, CMO and Support, each with a line
267
+ on what they do, and **Something else**, which asks for the role's name and a sentence about it.
268
+ A role that is already hired is not offered. Picking one fills in the handle, name and repo
269
+ (`cto`, `cmo`, `support`, or a handle made from the name) and leaves the schedule empty, so the
270
+ server picks a free slot. All four are under **Advanced**, still editable. The first hire has
271
+ nobody to copy App names from, so Advanced also holds the App's name and the shared public
272
+ App's, which are `--app` and `--public-app`, filled in as `<org>-<handle>` and `<org>-robot`.
273
+ The public one only matters when a product repo is public; on private ones the session uses the
274
+ staff member's own App.
275
+
276
+ The role opens a numbered list on the same screen:
277
+
278
+ 1. **Hire.** One sentence from the plan: the repo it creates and when it runs. **Show details**
279
+ has the full plan, from the same `buildPlan` and `applyPlan` that `roster hire` runs on the
280
+ server: every file, every label, the schedule and why, the commits it makes as you in repos
281
+ that already exist (each peer's `staff.yaml`, `org.yaml`), and whether the new brain joins
282
+ the credential's org secret. **Hire** asks before it acts.
283
+ 2. **Create their GitHub App.** The private App, and the shared public one only when a product
284
+ repo in `org.yaml` is public (or has no visibility written, which hire treats as public).
285
+ 3. **Write their charter.** Three tabs: an AI interview (the default), the matching worked
286
+ example with your business's name in place of Acme's, and the file itself. A role that
287
+ matches no example has no template tab.
288
+ 4. **Add your agent credential.** Only while none is stored.
289
+ 5. **Run once now.**
290
+
291
+ Steps 2 to 5 say *Hire first* until the hire is done. Each step's Done comes from real data:
292
+ the hire from the staff member appearing in `org.yaml`, the charter from `CHARTER.md` no longer
293
+ being the stub (the same test as doctor's `charter.stub`), the App from its `_APP_ID` secret on
294
+ the brain repo, the credential from the org secret, and the run from a successful daily run.
295
+ After **Hire** the screen reloads the org and stays on the same staff member, on step 2.
296
+
297
+ A card whose setup is not finished (a stub charter, no App secrets, or no successful run yet)
298
+ shows **Finish setting up**, which opens the same list for them. The App secrets and the run
299
+ are read from GitHub, so offline only the charter counts.
103
300
 
104
301
  **Writing the charter** is the copy-a-prompt loop below, aimed at `CHARTER.md`. `hire`
105
302
  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.
303
+ this whole arrangement exists to avoid. It is the same brief as `roster brief charter
304
+ <handle>`, with somewhere to put the answer, and a picker for the worked example it carries as a
305
+ model: matched to the role, or another, or none. For a role added with **Something else**, the
306
+ sentence you typed goes into the brief.
108
307
 
109
308
  **The GitHub App** is `roster app`, on this server rather than a second one. There is no API that
110
309
  creates an App: the only route is the manifest flow, where you post a manifest to a settings page,
@@ -114,9 +313,17 @@ one origin. The private key is still held in memory and written straight to a re
114
313
 
115
314
  GitHub redirects the tab *it* opened, not the one you clicked from, so the original polls for the
116
315
  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.
316
+ and GitHub asks a person to confirm it, which is correct and should not be worked around. So the
317
+ panel's **Install it** opens the install page with the org and the repos already selected: the
318
+ brain, every peer tracker it writes to, and the product repos. Any whose id could not be read are
319
+ listed for you to tick.
320
+
321
+ **Agent credential** is the setup screen's paste box, reachable from each card, because the
322
+ moment you look for it is while setting somebody up. It is once for the org.
323
+
324
+ **Run once now** starts the daily workflow, follows it, and shows how it ended with the log's
325
+ link and, on a failure, the step it failed at. It asks first, because it is a real run. A success
326
+ is what turns doctor's *unproven* into proven. Health has the same button.
120
327
 
121
328
  **Retiring** is `roster retire`, and it is deliberately not deletion. A brain repo is that
122
329
  agent's entire memory and there is no undo, so retiring disables the workflows, unwires them
@@ -152,11 +359,11 @@ rather than going nowhere.
152
359
 
153
360
  ## Prompt
154
361
 
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
362
+ **What this staff member is actually sent**, the same as `roster prompt <handle> --kind daily`.
363
+ Composed on the server by the tenant's own
157
364
  `compose.mjs`, so there is no second implementation to drift.
158
365
 
159
- Pick the kind: `daily`, `mention` or `pr-mention`. A mention prompt is written for the comment
366
+ Pick the kind: `daily` or `mention`. A mention prompt is written for the comment
160
367
  that woke it, so a preview fills in obviously-fake context.
161
368
 
162
369
  Beneath the composed text, the files it was made of, in two groups.
@@ -172,25 +379,9 @@ tells the agent to open these; it does not contain them. Editing a charter chang
172
379
  agent does without changing a byte of the composed prompt, and that distinction is easy to
173
380
  miss.
174
381
 
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.
382
+ What is *wrong* with any of this is on [Health](#health). This screen answers "what is sent";
383
+ whether what is sent is any good is a different question, and a tree that was half prompt and
384
+ half complaints answered neither well.
194
385
 
195
386
  ### Getting help changing it
196
387
 
@@ -284,7 +475,11 @@ opening the diff that did it: one block per file, coloured, with line numbers.
284
475
 
285
476
  ## Health
286
477
 
287
- Three parts.
478
+ Four parts: the rig, the org, the prompts and the memory. The rig is about this staff member,
479
+ so it is first; the rest widens out from there.
480
+
481
+ **The rig**: schedule in words, workflows present, last commit, last commit touching `memory/`,
482
+ index and notes size, charter, status issue, missing surfaces.
288
483
 
289
484
  **The org, from `roster doctor`.** Every finding that is not `ok`, with what to do about it, and
290
485
  a button that turns the lot into one brief for a coding agent. That is `roster fix`: `doctor`,
@@ -300,20 +495,66 @@ an agent that has started editing has stopped reading.
300
495
 
301
496
  Paste it into whatever edits files here, then press *Check again*. The ids should be gone.
302
497
 
303
- **Memory problems**, and **the rig**.
498
+ **Prompt problems.** The audit, over all three kinds of run at once. Nothing here judges prose:
499
+ every check is something a machine can be sure about, because a linter you stop believing is
500
+ worse than no linter. A finding true of more than one prompt is one row, and it says which.
304
501
 
305
- Memory problems are the same checks `roster lint` runs. Each one has a button that opens an
502
+ | Check | Why it matters |
503
+ |---|---|
504
+ | 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 |
505
+ | 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 |
506
+ | 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 |
507
+ | the prompt is long | it is read in full on every run, forever |
508
+ | it names a file that is not there | an instruction to read something absent is a quiet no-op inside a run nobody watches |
509
+ | an included layer is empty | it contributes nothing and costs a line of includes |
510
+
511
+ **Every finding carries the fix.** "Copy a prompt to fix this" builds a brief containing the
512
+ finding, the composed prompt, and every layer, and puts it on your clipboard. Paste it into
513
+ whatever agent you use. Knowing there is a problem is the hard part; writing the paragraph is
514
+ not. The box comes pre-filled with what the finding worked out, so you can add to it rather
515
+ than retype it. "Open the file" takes you to that layer on the Prompt screen.
516
+
517
+ **Memory problems** are the same checks `roster lint` runs. Each one has a button that opens an
306
518
  issue in that staff member's own repo asking them to fix it, which is usually right, because
307
519
  they wrote it.
308
520
 
309
- **The rig**: schedule in words, workflows present, last commit, last commit touching `memory/`,
310
- index and notes size, charter, status issue, missing surfaces.
521
+ ## Any image opens
522
+
523
+ **Click a picture and it opens over the page, fitted to the window.** Bottom right there is a
524
+ `-`, the current zoom, and a `+`. The percentage is a button too: it refits. Scrolling zooms,
525
+ dragging moves, clicking the picture goes between fitted and actual size, and `+`, `-`, `0` and
526
+ Escape do the same from the keyboard. Zooming is anchored on the pointer, so the thing you
527
+ aimed at stays where you aimed.
528
+
529
+ The percentage is of **actual size**, not of fitted, so 100% means one image pixel per screen
530
+ pixel. How far in it will go depends on the picture: a scaled image is a composited layer and
531
+ the browser allocates it at the rendered size, so the ceiling is whatever keeps that within
532
+ budget. Actual size is always reachable, however large the original is.
533
+
534
+ This is every image the portal renders as content: a screenshot on a doc page, a picture inside
535
+ a brain document, a file open in the Brain screen's viewer. A screenshot laid out for the column
536
+ it sits in is unreadable exactly when it matters, which is when it is a picture of an interface
537
+ and the part you need is the small print.
538
+
539
+ Gallery tiles are the exception, and deliberately: their click already means "open this file",
540
+ and the file's own view is one of the things that does open.
541
+
542
+ Fitted, the picture is inset from the window edges and framed. That is not decoration. Most of
543
+ these are screenshots *of this interface*, and one fitted edge to edge reads as the app having
544
+ navigated rather than as a picture of it.
311
545
 
312
546
  ## Docs
313
547
 
314
548
  The framework's own documentation, rendered where you already are. Links between pages navigate
315
549
  the portal.
316
550
 
551
+ **Search reads the pages, not their titles.** Twenty-odd pages is too many to scan by eye and
552
+ few enough for the server to read in full on every keystroke, and what you are looking for
553
+ ("which page explains the mention gate") is a sentence in a paragraph rather than a word in a
554
+ heading. A page has to contain every word you typed; results rank a title hit over a heading
555
+ hit over sheer frequency, carry the lines the words were found in, and the first one opens as
556
+ you type. The query is in the URL, so a search is a link.
557
+
317
558
  ## After upgrading roster
318
559
 
319
560
  **Restart the portal.** Its stylesheets and modules are read per request, so editing one and