Architecture
What happens during a run, and the constraint that decided the design.
What actually happens, and why it is shaped this way.
The constraint that decided everything
A reusable workflow in a private repo can only be called by repos in the same organisation.
So a tenant cannot call a workflow living in a private framework repo. Two ways out:
- Publish the framework early so it can be referenced. That forces the open-source decision before you are ready, and makes every tenant depend on an org they do not control.
- The framework never runs anything. It writes templates into the tenant's own repo.
The second is what roster does, and it is better independently of licensing. Every tenant is self-contained. Nothing breaks if the framework moves, goes private, or is deleted. An air-gapped install is a supported case rather than a special one.
The cost is that framework improvements do not arrive by themselves. roster upgrade is what
carries them, run by a human. See upgrading.
Three repos, three jobs
Nano-Collective/roster the framework. Never a runtime dependency of anything.
<org>/roster-ops the org layer and the machinery. Private.
<org>/<staff> one per staff member. The repo is the brain.Inside the ops repo there is a second split, enforced by convention so that extraction stays a directory copy:
org/is the tenant's business truth. Never published, always yours.- everything else came from the framework's templates and can be regenerated.
A day in the life of a run
07:00 UTC, cto-daily.yaml fires on cron.
- The caller passes nine inputs and five secrets to
roster-ops/.github/workflows/session.yamland does nothing else. It is forty lines because everything that could be shared, is. - The session mints a GitHub App token. The agent posts as
acme-cto[bot], not as you. - It clones the ops repo, which is the only thing it can clone without having read a manifest.
runner-plan.mjsreadsorg.yamland the staff member's manifest and says what else to clone: the brain with full history, each peer's brain, each product repo.compose.mjsassembles the prompt from six files: four org-level, the charter, and the fragment for this kind of run.agents.mjsresolves which coding agent to run and how.- The agent runs with a shell,
ghalready authenticated, and the whole checkout. - It works, commits, pushes, opens issues, comments, and rewrites its pinned status issue. The workflow does not commit on its behalf; the prompt tells it to and it does.
Nothing is stored outside the repos. There is no database and no service.
Boot, work, hand off
The prompt imposes a shape, and the shape is what makes an unattended run useful.
Boot is reconstituting a self that has no memory of yesterday. It reads memory/INDEX.md
in full, the pinned status issue, and its own charter. That is deliberately all: notes are read
only when a fact is in play, and the decision log is not boot context at all.
This is why memory is one line per fact. Boot context here went from about 52,000 words to about 6,000 by making that change, and the saving repeats on every run of every staff member forever.
Work is one thing done properly rather than four things started.
Hand off is the part that makes the next run possible: open the PR, rewrite the status issue (rewrite, not append), reconcile the tracker, update memory only if a fact changed, log real decisions, and write to peers if something touches their patch.
Why the memory is markdown
An agent writes markdown well and writes to a schema badly. A database would need the agent to
be careful about something it is not good at being careful about, and would put the brain
somewhere you cannot read with git log.
The cost is that the grammar is a convention rather than a constraint, so roster lint
enforces it and the portal parses it. See memory.
Identities and why there are two
A staff member has a private App for its own trackers, and shares a public App with everyone else for the product repo.
The private one is unique, so work on an internal board is attributable. The public one is shared and deliberately anonymous, because a bot opening a pull request on a public repo is unremarkable and a bot signing itself with a job title is a tell.
roster doctor treats these differently when attributing work: a solo identity names one staff
member, a shared one names only "one of them".
What cannot be automated, and why
- Creating a GitHub App has no API. Only the manifest flow, which needs a browser and a
human confirmation.
roster appdoes everything either side of that. - Installing an App grants access to specific repos and GitHub asks a human which. This is correct and should not be worked around.
- Pushing a workflow change is impossible with an App token, in any repo. So agents can never update their own workflows, and upgrades are human-run by design.
See manual steps.
Where the pieces live
| Piece | In | Why there |
|---|---|---|
compose.mjs |
the tenant | a run must not depend on npm or on the framework |
agents.mjs |
the tenant | same |
runner-plan.mjs |
the tenant | same |
session.yaml |
the tenant | private reusable workflows are same-org only |
| the CLI | the framework | runs on your machine, when you ask it to |
| the portal | the framework | reads the tenant's repos from disk |
| the docs | the framework | including the copy the portal serves |