@nanocollective/roster 0.1.0-alpha.1

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 (89) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +129 -0
  3. package/dist/cli.js +5679 -0
  4. package/docs/README.md +99 -0
  5. package/docs/agents.md +163 -0
  6. package/docs/architecture.md +121 -0
  7. package/docs/commands.md +223 -0
  8. package/docs/concepts.md +112 -0
  9. package/docs/cost.md +61 -0
  10. package/docs/developing.md +147 -0
  11. package/docs/doctor-codes.md +74 -0
  12. package/docs/export.md +113 -0
  13. package/docs/extending.md +97 -0
  14. package/docs/getting-started.md +134 -0
  15. package/docs/hosting.md +72 -0
  16. package/docs/manual-steps.md +163 -0
  17. package/docs/memory.md +71 -0
  18. package/docs/org-yaml.md +143 -0
  19. package/docs/portal.md +342 -0
  20. package/docs/prompts.md +133 -0
  21. package/docs/security.md +122 -0
  22. package/docs/session-workflow.md +112 -0
  23. package/docs/staff-yaml.md +163 -0
  24. package/docs/troubleshooting.md +189 -0
  25. package/docs/upgrading.md +83 -0
  26. package/docs/writing-a-charter.md +83 -0
  27. package/package.json +60 -0
  28. package/templates/brain/.github/workflows/%%STAFF%%-daily.yaml +33 -0
  29. package/templates/brain/.github/workflows/%%STAFF%%-mention.yaml +65 -0
  30. package/templates/brain/.github/workflows/%%STAFF%%-pr-mention.yaml +50 -0
  31. package/templates/brain/CHARTER.md +49 -0
  32. package/templates/brain/README.md +18 -0
  33. package/templates/brain/drafts/README.md +7 -0
  34. package/templates/brain/log/decisions.md +6 -0
  35. package/templates/brain/memory/INDEX.md +28 -0
  36. package/templates/brain/staff.yaml +44 -0
  37. package/templates/brain/strategy/README.md +7 -0
  38. package/templates/briefs/amend.md +60 -0
  39. package/templates/briefs/charter.md +47 -0
  40. package/templates/briefs/discover.md +61 -0
  41. package/templates/briefs/voice.md +53 -0
  42. package/templates/ops/.github/workflows/session.yaml +333 -0
  43. package/templates/ops/agents.mjs +143 -0
  44. package/templates/ops/compose.mjs +333 -0
  45. package/templates/ops/org/guardrails.md +14 -0
  46. package/templates/ops/org/operating.md +82 -0
  47. package/templates/ops/org/voice.md +40 -0
  48. package/templates/ops/prompts/_identity.md +14 -0
  49. package/templates/ops/prompts/_paths.md +15 -0
  50. package/templates/ops/prompts/daily.md +82 -0
  51. package/templates/ops/prompts/mention.md +53 -0
  52. package/templates/ops/prompts/pr-mention.md +57 -0
  53. package/templates/ops/runner-plan.mjs +65 -0
  54. package/templates/portal/css/base.css +104 -0
  55. package/templates/portal/css/brain.css +106 -0
  56. package/templates/portal/css/diff.css +28 -0
  57. package/templates/portal/css/graph.css +34 -0
  58. package/templates/portal/css/health.css +41 -0
  59. package/templates/portal/css/inbox.css +79 -0
  60. package/templates/portal/css/layout.css +98 -0
  61. package/templates/portal/css/markdown.css +54 -0
  62. package/templates/portal/css/setup.css +106 -0
  63. package/templates/portal/index.html +55 -0
  64. package/templates/portal/js/api.js +74 -0
  65. package/templates/portal/js/app.js +282 -0
  66. package/templates/portal/js/dialog.js +70 -0
  67. package/templates/portal/js/dom.js +106 -0
  68. package/templates/portal/js/icons.js +94 -0
  69. package/templates/portal/js/md.js +386 -0
  70. package/templates/portal/js/refresh.js +59 -0
  71. package/templates/portal/js/router.js +20 -0
  72. package/templates/portal/js/state.js +160 -0
  73. package/templates/portal/js/textdiff.js +96 -0
  74. package/templates/portal/js/views/app.js +128 -0
  75. package/templates/portal/js/views/brain.js +260 -0
  76. package/templates/portal/js/views/changed.js +157 -0
  77. package/templates/portal/js/views/checklist.js +87 -0
  78. package/templates/portal/js/views/docs.js +84 -0
  79. package/templates/portal/js/views/files.js +95 -0
  80. package/templates/portal/js/views/graph.js +436 -0
  81. package/templates/portal/js/views/health.js +158 -0
  82. package/templates/portal/js/views/inbox.js +549 -0
  83. package/templates/portal/js/views/memory.js +135 -0
  84. package/templates/portal/js/views/org.js +175 -0
  85. package/templates/portal/js/views/paste.js +142 -0
  86. package/templates/portal/js/views/prompt.js +412 -0
  87. package/templates/portal/js/views/repos.js +92 -0
  88. package/templates/portal/js/views/setup.js +344 -0
  89. package/templates/portal/js/views/staff.js +290 -0
@@ -0,0 +1,112 @@
1
+ ---
2
+ title: "The session workflow"
3
+ description: "Inputs, secrets, and what runs in what order."
4
+ sidebar_order: 16
5
+ ---
6
+
7
+ # The session workflow
8
+
9
+ `roster-ops/.github/workflows/session.yaml` is the reusable workflow every staff repo calls.
10
+ It is the framework's file: change it in `templates/ops/` and run `roster upgrade`.
11
+
12
+ A caller is about forty lines and does nothing but pass arguments.
13
+
14
+ ## Why it lives in the tenant
15
+
16
+ A reusable workflow in a **private** repo can only be called from inside its own organisation.
17
+ A tenant therefore cannot call the framework's copy. That constraint is what forced the whole
18
+ design, and it turned out better: the framework is never a runtime dependency, so nothing
19
+ breaks if it moves, goes private, or is deleted.
20
+
21
+ This is also why `roster-ops` needs **Settings -> Actions -> General -> accessible from
22
+ repositories in this organisation**. Without it, callers fail with "workflow not found".
23
+
24
+ ## Inputs
25
+
26
+ | Input | Type | Default | Means |
27
+ |---|---|---|---|
28
+ | `staff` | string | required | Handle, as in `org.yaml`. |
29
+ | `kind` | string | `daily` | `daily`, `mention` or `pr-mention`. |
30
+ | `ops_repo` | string | required | `owner/name` of the ops repo. |
31
+ | `model` | string | `claude-opus-5` | Passed to the agent, unless the agent resolves its own. |
32
+ | `timeout_minutes` | number | `60` | Job ceiling. |
33
+ | `allowed_tools` | string | `Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch` | Tool permissions, for agents that take them. |
34
+ | `issue_number` | string | `""` | Trigger context. |
35
+ | `comment_id` | string | `""` | Trigger context. |
36
+ | `pr_number` | string | `""` | Trigger context. |
37
+
38
+ ## Secrets
39
+
40
+ | Secret | Required | Means |
41
+ |---|---|---|
42
+ | `APP_ID` | yes | This staff member's own App. |
43
+ | `APP_PRIVATE_KEY` | yes | |
44
+ | `PUBLIC_APP_ID` | no | The shared public identity. Absent means no product-repo lane. |
45
+ | `PUBLIC_APP_PRIVATE_KEY` | no | |
46
+ | `AGENT_TOKEN` | no | The coding agent's credential. |
47
+ | `CLAUDE_CODE_OAUTH_TOKEN` | no | The name the reference runner has always used. |
48
+
49
+ Neither credential is required on its own and exactly one must be present. A step checks this
50
+ before any checkout, so a missing credential is an obvious failure rather than an
51
+ authentication error forty lines into a log.
52
+
53
+ ## What it does, in order
54
+
55
+ 1. **Mint the private-tracker token** from the staff member's App.
56
+ 2. **Mint the public-repo token**, if a public App was passed.
57
+ 3. **React to the request** with eyes, on a `mention` only. Before any checkout, so it lands in
58
+ seconds. `continue-on-error`: a missing reaction must never cost the answer.
59
+ 4. **Check out the ops repo.** It is the only thing that can be cloned without having read a
60
+ manifest, so it goes first and then says what else to clone.
61
+ 5. **Work out what to check out**, by running `runner-plan.mjs`.
62
+ 6. **Check out the brain**, full history. The agent reads its own past.
63
+ 7. **Check out peers and product repos**, per the plan.
64
+ 8. **Set git identity** to the App.
65
+ 9. **Check out the PR branch**, on a `pr-mention`.
66
+ 10. **Set up Node and pnpm**, if the plan found a `package.json`.
67
+ 11. **Compose the prompt**, to a step output and to `.roster-prompt.txt`.
68
+ 12. **Check the agent has a credential.**
69
+ 13. **Work out which agent runs this**, by running `agents.mjs`.
70
+ 14. **Run the session**, by one of two steps: the Action-based reference runner, or the generic
71
+ CLI one. See [choosing a coding agent](agents.md).
72
+ 15. **Say so if the run did not finish.**
73
+
74
+ ## What `runner-plan.mjs` emits
75
+
76
+ Consumed by later steps as `steps.plan.outputs.*`.
77
+
78
+ | Output | Example |
79
+ |---|---|
80
+ | `brain_dir` | `technology` |
81
+ | `brain_repo` | `acme/technology` |
82
+ | `org` | `acme` |
83
+ | `peers` | `acme/marketing:marketing` |
84
+ | `products` | `acme/acme-web:acme-web:0` |
85
+ | `needs_node` | `true` |
86
+ | `product_dir` | `acme-web` |
87
+ | `product_repo` | `acme/acme-web` |
88
+ | `package_json` | `acme-web/package.json` |
89
+
90
+ ## What the agent gets
91
+
92
+ | Variable | |
93
+ |---|---|
94
+ | `GH_TOKEN` | private-tracker token, already authenticated |
95
+ | `PUBLIC_TOKEN` | public product repo token |
96
+ | `AGENT_PROMPT_FILE` | absolute path to the composed prompt |
97
+ | `AGENT_MODEL` | resolved model |
98
+ | `AGENT_TOOLS` | the `allowed_tools` string |
99
+ | *the agent's own* | its credential, under whatever name it declares |
100
+
101
+ ## The checkout shape
102
+
103
+ ```
104
+ .
105
+ ├── roster-ops/ the ops repo
106
+ ├── technology/ the brain
107
+ ├── marketing/ a peer's brain, if declared
108
+ └── acme-web/ a product repo, if declared
109
+ ```
110
+
111
+ Flat, one level. The prompts say so explicitly, because it is one level flatter than a
112
+ developer would assume from reading the docs on their own machine.
@@ -0,0 +1,163 @@
1
+ ---
2
+ title: "staff.yaml reference"
3
+ description: "Every field in a staff member's manifest."
4
+ sidebar_order: 14
5
+ ---
6
+
7
+ # `staff.yaml` reference
8
+
9
+ A staff member's manifest: the machine-readable half of their charter. Lives at the root of
10
+ their brain repo.
11
+
12
+ `roster lint` fails if this and `CHARTER.md` disagree. Same strict YAML subset as
13
+ [`org.yaml`](org-yaml.md).
14
+
15
+ ## A complete example
16
+
17
+ ```yaml
18
+ handle: cto
19
+ name: Chief Technology Officer
20
+ mention: "@cto"
21
+ brain: acme/technology
22
+ status_issue: 15
23
+
24
+ schedule: "0 7 * * 1-5"
25
+ model: claude-opus-5
26
+ timeout_minutes: 90
27
+ mention_timeout_minutes: 30
28
+ pr_mention_timeout_minutes: 60
29
+
30
+ bot: acme-cto[bot]
31
+ public_bot: acme-robot[bot]
32
+ public_token_env: PIPWEB_TOKEN
33
+ agent_secret: CLAUDE_CODE_OAUTH_TOKEN
34
+
35
+ identities:
36
+ - { app: acme-cto, secret_prefix: CTO, scope: private }
37
+ - { app: acme-robot, secret_prefix: BOT, scope: public }
38
+
39
+ works_in:
40
+ - { repo: acme/acme-web, role: contributor, checkout: true }
41
+
42
+ peers:
43
+ - { handle: cmo, brain: acme/marketing, label: from-cto }
44
+
45
+ surfaces:
46
+ - { path: memory/, render: memory }
47
+ - { path: strategy/, render: doc }
48
+ - { path: assets/, render: gallery }
49
+ - { path: data/, render: table }
50
+
51
+ labels:
52
+ owner: [will, cto, cmo]
53
+ kind: [decision, setup, build, blocked]
54
+ ```
55
+
56
+ ## Identity
57
+
58
+ | Field | Required | Means |
59
+ |---|---|---|
60
+ | `handle` | yes | Must match `org.yaml`. The composer looks them up by the `org.yaml` one, so a mismatch composes the wrong brain. |
61
+ | `name` | yes | Role name in prose. |
62
+ | `mention` | yes | What wakes them, as in `@cto`. The caller's condition tests for this string. |
63
+ | `brain` | yes | `owner/name` of this repo. Without it nothing can check secrets, labels or runs. |
64
+ | `status_issue` | yes in practice | Number of the pinned status issue. The prompts reference it, so a run cannot compose without it. `roster hire --apply` writes it. |
65
+
66
+ ## Schedule and limits
67
+
68
+ | Field | Default | Means |
69
+ |---|---|---|
70
+ | `schedule` | none | Cron for the daily run. Rendered into the caller. |
71
+ | `model` | org default | Model id. |
72
+ | `timeout_minutes` | 60 | Ceiling on the daily session. |
73
+ | `mention_timeout_minutes` | 30 | Ceiling on a mention run. |
74
+ | `pr_mention_timeout_minutes` | 60 | Ceiling on a PR-amendment run. |
75
+
76
+ The three ceilings are separate on purpose. Raising the daily one because sessions have grown
77
+ should not double the budget for a PR amendment. A job killed by a ceiling is reported by
78
+ GitHub as `cancelled`; see [troubleshooting](troubleshooting.md).
79
+
80
+ **These are the source of truth.** The caller workflow carries a rendered copy, so change the
81
+ manifest and run `roster upgrade`, not the other way round.
82
+
83
+ ## Identities
84
+
85
+ | Field | Means |
86
+ |---|---|
87
+ | `bot` | Login this staff member posts as on private trackers, as in `acme-cto[bot]`. |
88
+ | `public_bot` | Login it posts as on the public product repo. |
89
+ | `public_token_env` | Environment variable holding the public token inside a run. |
90
+ | `agent_secret` | Repo secret holding the coding agent's credential. |
91
+ | `identities` | One entry per App. |
92
+
93
+ Each `identities` entry:
94
+
95
+ | Field | Means |
96
+ |---|---|
97
+ | `app` | The GitHub App slug. Carries the house naming scheme, so it is inferred from a sibling rather than built from the handle. |
98
+ | `secret_prefix` | Secrets are `<prefix>_APP_ID` and `<prefix>_APP_PRIVATE_KEY`. |
99
+ | `scope` | `private` (this staff member's own) or `public` (shared by everybody). |
100
+
101
+ The public identity is shared deliberately. A bot opening a pull request on a public repo is
102
+ unremarkable; a bot signing itself with a job title is a tell.
103
+
104
+ **The `[bot]` suffix is how a manifest spells it and not how GitHub reports it.** An issue
105
+ authored by the App has author `acme-cto`. roster normalises this; if you are matching on it
106
+ yourself, strip the suffix.
107
+
108
+ ## Relationships
109
+
110
+ ### `works_in`
111
+
112
+ Repos this staff member contributes to but does not own.
113
+
114
+ | Field | Means |
115
+ |---|---|
116
+ | `repo` | `owner/name`. |
117
+ | `role` | Prose. `contributor` in practice. |
118
+ | `checkout` | Whether the runner clones it. |
119
+
120
+ **The first entry becomes `{{staff.product}}` in prompts.** A staff member with an empty
121
+ `works_in` has no product, and any prompt fragment referring to one must guard with
122
+ `{{#if staff.product}}`.
123
+
124
+ ### `peers`
125
+
126
+ The other staff members this one files work with.
127
+
128
+ | Field | Means |
129
+ |---|---|
130
+ | `handle` | Their handle. |
131
+ | `brain` | Their repo. |
132
+ | `label` | The label **this** staff member uses when filing on **that** tracker. |
133
+
134
+ **The label is not symmetric.** In the CTO's manifest the entry for the CMO carries
135
+ `from-cto`, and that label lives on `acme/marketing`, because that is where the CTO's asks
136
+ land. `roster hire` writes both directions and `roster doctor` checks the label exists on the
137
+ peer's repo, not this one.
138
+
139
+ ## `surfaces`
140
+
141
+ What this staff member keeps, and how the portal renders it.
142
+
143
+ | `render` | Shows as |
144
+ |---|---|
145
+ | `memory` | Parsed fact cards |
146
+ | `doc` | Markdown, rendered |
147
+ | `gallery` | Image grid |
148
+ | `table` | CSV as a table |
149
+ | `code` | Monospace, syntax preserved |
150
+
151
+ A surface is a promise: `roster doctor` warns when one is declared and not on disk.
152
+
153
+ ## `labels`
154
+
155
+ Labels this staff member expects to exist on its own tracker, grouped for readability. Every
156
+ value across every group is checked by `roster doctor`. An agent applying a label that does not
157
+ exist gets an API error mid-run.
158
+
159
+ ## What `roster upgrade` does to this file
160
+
161
+ Nothing. It is `scaffold` class: written once by `roster hire`, and yours from that moment.
162
+ A framework change that adds a manifest field will not reach an existing staff member, which is
163
+ a deliberate trade against the risk of merging into a file a working agent has rewritten.
@@ -0,0 +1,189 @@
1
+ ---
2
+ title: "Troubleshooting"
3
+ description: "Every trap we have actually hit, and what it looks like from the outside."
4
+ sidebar_order: 10
5
+ ---
6
+
7
+ # Troubleshooting
8
+
9
+ Every trap on this page has actually been hit. Most of them fail in a way that points somewhere
10
+ else, which is why they are worth writing down.
11
+
12
+ Start with `roster doctor`. It groups by staff member and every finding that is not `ok` says
13
+ what to do about it.
14
+
15
+ ---
16
+
17
+ ## "workflow not found"
18
+
19
+ **Looks like:** a typo in the `uses:` path, a missing file, a bad branch.
20
+
21
+ **Is:** the ops repo's Actions access is not set to organisation-wide.
22
+
23
+ Settings -> Actions -> General on `roster-ops`. `roster doctor` checks this explicitly, and the
24
+ portal's setup screen links straight to the page.
25
+
26
+ ---
27
+
28
+ ## A run was "cancelled" and nobody cancelled it
29
+
30
+ **Is:** almost always the `timeout_minutes` ceiling. GitHub reports a job killed by
31
+ `timeout-minutes` as `cancelled`, which reads as though somebody pressed a button.
32
+
33
+ Tell them apart by duration. Several cancelled runs all stopping at the same minute is a
34
+ ceiling, not a coincidence. `roster doctor` does this for you and names the number:
35
+
36
+ ```
37
+ ✗ cto-daily.yaml: 5 of the last 10 ran to a 60m ceiling and were killed
38
+ ```
39
+
40
+ Raise `timeout_minutes` in the staff member's `staff.yaml`, then `roster upgrade --apply`.
41
+ Note that raising it does not rewrite history: doctor reads the ceiling those runs actually hit,
42
+ so old timeouts keep being reported as timeouts.
43
+
44
+ ---
45
+
46
+ ## Every mention run says "skipped"
47
+
48
+ **Is:** correct. The job-level condition gates out every comment that is not a mention, and a
49
+ gated run still appears in the list with conclusion `skipped`. Most of a mention workflow's
50
+ history is skipped runs.
51
+
52
+ `roster doctor` ignores them. A workflow whose runs are *all* skipped is reported differently,
53
+ because nothing has exercised the credentials.
54
+
55
+ ---
56
+
57
+ ## The App exists, the API says so, and runs still fail
58
+
59
+ **This is the one that costs the most time.**
60
+
61
+ `GET /apps/<slug>` reports what an App **declares**. It says nothing about whether that App has
62
+ been **installed** on the repository in question, or which repositories the installation was
63
+ granted. The two are reported separately and they disagree exactly when you care.
64
+
65
+ Do not verify an installation by reading the API. The only proof of the whole chain is a run
66
+ that finished. `roster doctor` reads recent runs for this reason and calls a workflow that has
67
+ never run **unproven** rather than fine.
68
+
69
+ Fix: open the App's installation settings and check the repository list includes every tracker
70
+ the staff member writes to, not just its own. `roster app` prints that list.
71
+
72
+ ---
73
+
74
+ ## An agent cannot push a workflow change
75
+
76
+ **Is:** a GitHub restriction, not a misconfiguration. **App tokens cannot push any change under
77
+ `.github/workflows/` in any repository.**
78
+
79
+ This is why agents can never update their own workflows and why `roster upgrade` writes into
80
+ your working tree and asks you to push. It is deliberate: an agent editing the workflow that
81
+ constrains it is not a thing anybody wants.
82
+
83
+ ---
84
+
85
+ ## A fix I applied to the tenant disappeared
86
+
87
+ **Is:** you edited a framework-owned file. `compose.mjs`, `agents.mjs`, `runner-plan.mjs` and
88
+ `session.yaml` are generated. The next `roster upgrade` reconciles them against the template.
89
+
90
+ This happened here: a fix went into `roster-ops/.github/workflows/session.yaml` instead of
91
+ `templates/ops/...`, and nothing noticed because the framework had not touched that file *yet*.
92
+
93
+ `roster upgrade` now reports an edit to a framework-owned file whether or not anything has
94
+ collided, and `roster upgrade --check` fails on it. Move the change upstream.
95
+
96
+ ---
97
+
98
+ ## A new staff member's prompt will not compose
99
+
100
+ ```
101
+ unknown or empty placeholder: {{staff.product.repo}}
102
+ ```
103
+
104
+ **Is:** the prompt refers to something their manifest does not have. `product` comes from the
105
+ first entry in `works_in`, and a staff member who contributes to no other repository has none.
106
+
107
+ The shipped prompts guard these. A prompt fragment you have written yourself needs
108
+ `{{#if staff.product}}` around anything that assumes one. Conditionals do not nest.
109
+
110
+ Similarly `{{staff.status_issue}}` is empty until `roster hire --apply` has opened the pinned
111
+ issue.
112
+
113
+ ---
114
+
115
+ ## A mention gets no reaction
116
+
117
+ The eyes reaction is posted by `session.yaml` before any checkout, so it should land in
118
+ seconds. If it does not:
119
+
120
+ - The reaction is `continue-on-error`. A missing reaction never costs the answer, so check
121
+ whether the run itself started at all.
122
+ - It is scoped to `kind == 'mention'`. A pr-mention is acknowledged by the forwarder in the
123
+ public repo instead, so that it gets one reaction rather than two.
124
+ - On the `issues` route (a mention typed into a new issue body) the eyes go on the issue, not
125
+ on a comment, because that payload has no comment.
126
+
127
+ ---
128
+
129
+ ## The portal opened on a setup screen and I already have an org
130
+
131
+ **Looks like:** roster forgot your organisation.
132
+
133
+ **Is:** you started it somewhere else. The portal walks up from where it was run looking for a
134
+ directory containing `org.yaml`; from an unrelated folder it finds nothing, which is a setup, not
135
+ an error.
136
+
137
+ `cd` to your workspace, or pass `--ops <dir>`. If the workspace is on another machine or was never
138
+ cloned here, the setup screen's own answer is right: pick the org, and it will say it already runs
139
+ roster and offer to check it out.
140
+
141
+ ---
142
+
143
+ ## My AI wrote the charter and the portal will not save it
144
+
145
+ **Looks like:** the paste box rejecting good work.
146
+
147
+ **Is:** almost always one of four things, and the page says which, with a line to send back:
148
+
149
+ - the reply had no `<<<ROSTER FILE …>>>` block, because the model answered in prose
150
+ - it wrote a path the brief did not ask for, which is never offered as a save
151
+ - it handed the template back, which some models do after a long prompt
152
+ - it is a few lines long
153
+
154
+ The file itself is never the problem: text outside the block is ignored and one wrapping fence is
155
+ stripped, so chat and ```` ```markdown ```` are both fine.
156
+
157
+ ---
158
+
159
+ ## The App was created and the portal tab never noticed
160
+
161
+ **Looks like:** the page stuck on "Confirm it in the tab that just opened".
162
+
163
+ **Is:** GitHub redirects the tab *it* opened, not the one you clicked from, so the original polls
164
+ for the result. It gives up after five minutes and tells you to reload.
165
+
166
+ **The App was probably created.** Check the org's App settings before trying again; a second
167
+ attempt fails on the name, which is the safe failure.
168
+
169
+ ---
170
+
171
+ ## The portal shows stale data
172
+
173
+ It reads the repositories on disk. `/api/sync` fetches and fast-forwards on every refresh, and
174
+ refuses to pull a repository that is dirty or has diverged, saying so in a banner. If a run
175
+ landed on GitHub thirty seconds ago and the checkout is behind, that is what you are seeing.
176
+
177
+ ---
178
+
179
+ ## `roster upgrade` says a file has no base
180
+
181
+ A tenant created before the merge base was recorded has nothing to merge against. Reconstruct
182
+ one from the framework's history:
183
+
184
+ ```bash
185
+ roster upgrade --baseline <git-ref>
186
+ ```
187
+
188
+ Use the framework commit the tenant was seeded from. Check it afterwards: files that match the
189
+ reconstruction byte for byte confirm you picked the right one.
@@ -0,0 +1,83 @@
1
+ ---
2
+ title: "Upgrading"
3
+ description: "How framework changes reach a tenant without eating your edits."
4
+ sidebar_order: 9
5
+ ---
6
+
7
+ # Upgrading
8
+
9
+ The framework writes templates out. A tenant runs its own copies. So the two drift, and
10
+ `roster upgrade` is what reconciles them without eating your edits.
11
+
12
+ ```bash
13
+ roster upgrade # what would change
14
+ roster upgrade --apply # do it
15
+ roster upgrade --check # exit non-zero if anything is pending (for CI)
16
+ ```
17
+
18
+ ## How it decides
19
+
20
+ A generated file has three versions: what the framework shipped when this tenant was seeded,
21
+ what it ships now, and what you have today. Upgrading is a three-way merge between them, done
22
+ by `git merge-file`. A tenant that has rewritten half of `org/voice.md` still gets the rest.
23
+
24
+ The base lives in `<ops>/.roster/seed/` and is committed. Without it an upgrade can only be a
25
+ copy, which would take your edits with it.
26
+
27
+ ## Ownership decides what an edit means
28
+
29
+ **In the ops repo:**
30
+
31
+ | | |
32
+ |---|---|
33
+ | `org/`, `prompts/` | yours. Local edits are expected and get a real merge. |
34
+ | everything else | the framework's. An edit is reported whether or not anything has collided. |
35
+
36
+ That last rule is deliberate. A fix applied to a framework-owned file in the tenant is not safe
37
+ just because nothing broke; it survives only until the framework next touches that file. "Not
38
+ broken yet" is the state a lost fix sits in, so it is reported then, not later.
39
+
40
+ **In a brain repo, only the caller workflows are upgraded.** The charter, the memory index, the
41
+ decisions log and the manifest belong to the staff member from the moment they are created. A
42
+ working agent rewrites them beyond recognition, and merging a template into that would be
43
+ vandalism. They are added when new and never rewritten.
44
+
45
+ ## Callers are regenerated, not merged
46
+
47
+ A caller is derived entirely from the manifest and the template, so there is no third version
48
+ to reconcile. What looks like a local edit is either a template change that has not arrived, or
49
+ something that should have been a manifest change.
50
+
51
+ The diff is printed either way, so nothing goes quietly.
52
+
53
+ If you want a caller to differ, change the thing it is generated from. Timeouts, schedule,
54
+ model and identities all live in `staff.yaml`.
55
+
56
+ ## Conflicts
57
+
58
+ A conflict is never written into a live file. Agents read `org/voice.md` at every boot, and
59
+ conflict markers in it would land in every composed prompt.
60
+
61
+ Instead the merged result with markers goes to `<file>.roster-merge`, the live file is
62
+ untouched, and the recorded base does **not** advance for that file. That last part matters:
63
+ advancing it would throw away the only thing that can merge it next time.
64
+
65
+ Resolve by hand, then re-run.
66
+
67
+ ## A tenant with no recorded base
68
+
69
+ Tenants created before this existed have nothing to merge against. Reconstruct it from the
70
+ framework's own history:
71
+
72
+ ```bash
73
+ roster upgrade --baseline <git-ref>
74
+ ```
75
+
76
+ Pick the framework commit the tenant was seeded from. You can tell you picked the right one:
77
+ most files will match it byte for byte, and the ones that do not will be the ones you know you
78
+ edited.
79
+
80
+ ## After applying
81
+
82
+ `roster upgrade --apply` writes into your working trees. It does not commit or push, and it
83
+ cannot: **App tokens cannot push a change under `.github/workflows/`**. Review, commit, push.
@@ -0,0 +1,83 @@
1
+ ---
2
+ title: "Writing a charter"
3
+ description: "The one file nothing can generate for you, and the shape that has worked."
4
+ sidebar_order: 5
5
+ ---
6
+
7
+ # Writing a charter
8
+
9
+ The charter is the personality. It decides what a staff member does when nobody is watching,
10
+ what it refuses, and what it escalates.
11
+
12
+ roster will not write it for you. That is not a missing feature. A generated charter produces a
13
+ generic agent, and a generic agent produces work that is competent-looking and about nothing in
14
+ particular, which takes longer to notice than no work at all.
15
+
16
+ ## Write it with your own AI
17
+
18
+ ```bash
19
+ roster brief charter <handle>
20
+ ```
21
+
22
+ That prints a self-contained brief. Paste it into whatever agent you use, or pipe it:
23
+ `roster brief charter cto | pbcopy`. In Claude Code, `cd <staff-dir> && claude` then
24
+ `/charter` runs the same text, because `roster hire` generates the slash command from it.
25
+
26
+ **Or do the whole round trip in the portal.** *Write the charter* on that staff member's card
27
+ copies the same brief with every file it refers to already inside it, including the peers'
28
+ charters, so a chat window with no filesystem can do it. Paste the reply back and you get a diff
29
+ and a save. See [the portal](portal.md).
30
+
31
+ The brief tells the agent to read `org/business.md`, the shared operating
32
+ layer, and every peer's charter, then interviews you and drafts from your answers. It also tells
33
+ you what it cut and why.
34
+
35
+ There is nothing agent-specific in it. Claude Code gets a slash command because it is the
36
+ reference runner and the shape happens to fit; everything else gets the same words from
37
+ `roster brief`.
38
+
39
+ ## What goes in it, and what does not
40
+
41
+ **Only the difference.** The shared half already exists in `org/`: how anyone here operates, how
42
+ we write, the guardrails everyone is bound by. All of it is composed into the prompt above the
43
+ charter. Restating any of it wastes context on every run of every day and creates two places to
44
+ change one rule.
45
+
46
+ If a sentence would be true of every staff member you will ever hire, it belongs in `org/`.
47
+
48
+ ## The shape that has worked
49
+
50
+ **Who I am.** One paragraph. What this role is for, in this business specifically.
51
+
52
+ **The mission.** The single thing this staff member optimises. If a decision does not serve it,
53
+ it is somebody else's decision.
54
+
55
+ **How I work, that others here do not.** The habits particular to this role.
56
+
57
+ **Decision rights.** What it decides alone, what it proposes and waits on, what it never
58
+ touches. Be specific. A vague boundary is one that gets crossed at 07:00 with nobody awake.
59
+
60
+ **Guardrails on top of the org's.** Only the additions.
61
+
62
+ **Where the rest of it lives.** Point at `memory/INDEX.md`, `log/decisions.md`, the pinned
63
+ status issue, and the surfaces the manifest declares.
64
+
65
+ ## Things worth being concrete about
66
+
67
+ - **Escalation.** Name the label and the mechanism, not the sentiment. "Open an issue labelled
68
+ `decision`, assigned to `@you`" beats "check with the founder".
69
+ - **What it must not do.** Money, legal, anything touching a real person's data. Say it here
70
+ even if `org/guardrails.md` covers it, if this role gets closer to the line than others.
71
+ - **What good output looks like for this role.** A CTO's and a CMO's differ, and the shared
72
+ voice file cannot know that.
73
+
74
+ ## Keep it agreeing with the manifest
75
+
76
+ `roster lint` fails if the charter and `staff.yaml` disagree. The manifest is the
77
+ machine-readable half of the same document: handle, schedule, peers, surfaces, identities. If
78
+ the charter says it reviews pull requests on the product repo, `works_in` had better include it.
79
+
80
+ ## It is not finished
81
+
82
+ The charter is the file most worth revisiting. When a run does something you did not want, the
83
+ question is usually not "what went wrong in that run" but "what does the charter not say".
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@nanocollective/roster",
3
+ "version": "0.1.0-alpha.1",
4
+ "description": "An agent-run org, powered by GitHub. Scaffold AI staff members whose brain is a repo.",
5
+ "type": "module",
6
+ "bin": {
7
+ "roster": "./dist/cli.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "templates",
12
+ "docs",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "engines": {
17
+ "node": ">=20"
18
+ },
19
+ "scripts": {
20
+ "build": "tsup src/cli.ts --format esm --target node20 --clean",
21
+ "dev": "tsx src/cli.ts",
22
+ "test": "tsx --test test/*.test.ts",
23
+ "test:all": "pnpm test:format && pnpm test:lint && pnpm test:types && pnpm test:knip && pnpm test",
24
+ "test:ava:coverage": "c8 --reporter=text --reporter=json-summary tsx --test test/*.test.ts",
25
+ "test:format": "biome check --no-errors-on-unmatched .",
26
+ "test:lint": "biome lint .",
27
+ "test:lint:fix": "biome check --write .",
28
+ "test:types": "tsc --noEmit",
29
+ "test:knip": "knip",
30
+ "test:audit": "pnpm audit --audit-level=high",
31
+ "test:security": "semgrep scan --config auto --error",
32
+ "typecheck": "tsc --noEmit",
33
+ "format": "biome check --write .",
34
+ "prepublishOnly": "pnpm test:all && pnpm build"
35
+ },
36
+ "devDependencies": {
37
+ "@biomejs/biome": "^2.5.12",
38
+ "@types/node": "^22.10.2",
39
+ "c8": "^12.0.0",
40
+ "knip": "^6.34.0",
41
+ "tsup": "^8.3.5",
42
+ "tsx": "^4.19.2",
43
+ "typescript": "^5.7.2"
44
+ },
45
+ "license": "MIT",
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "git+https://github.com/Nano-Collective/roster.git"
49
+ },
50
+ "homepage": "https://github.com/Nano-Collective/roster#readme",
51
+ "bugs": {
52
+ "url": "https://github.com/Nano-Collective/roster/issues"
53
+ },
54
+ "author": "Nano Collective",
55
+ "packageManager": "pnpm@11.0.9",
56
+ "publishConfig": {
57
+ "access": "public",
58
+ "tag": "latest"
59
+ }
60
+ }