The lab_mgmt repository (murmurent_lab_mgmt_<lab>)¶
The single most-confusing piece of Murmurent's filesystem layout is what
lab_mgmt is, who needs it, and how it differs from
~/.murmurent/lab_info/. This document is the short answer.
TL;DR¶
lab_mgmt is the per-group governance repo, one per PI. It is owned
by the lab and is not a Murmurent commons artifact. It holds the
canonical roster, project registry, inventory, training records,
audit log, and other governance records for a single research group.
Different labs each have their own lab_mgmt repo under their own
GitHub account/org. The canonical name is murmurent_lab_mgmt_<lab>
(see "Naming: read this before creating the repo" below): a given
lab's lives at <owner>/murmurent_lab_mgmt_<lab>; a bioinformatics
core's would be <owner>/murmurent_lab_mgmt_bioinformatics.
The centre-wide registry (labs, cores, common SEAs, join requests)
lives in a separate, distinct tree at ~/.murmurent/lab_info/. That
tree is owned by the registrar; it is not the property of any single lab.
Naming: read this before creating the repo¶
The repo is named murmurent_lab_mgmt_<lab>, where <lab> is your
group's registry slug (e.g. mh, bioinformatics). Both the GitHub
repo and the local clone directory use this exact name. The local clone sits
directly in your repositories folder (~/repos unless you chose another;
murmurent config repos-root shows it), not inside a project folder:
GitHub: <your-account-or-org>/murmurent_lab_mgmt_<lab> (private)
local clone: ~/repos/murmurent_lab_mgmt_<lab>
You normally never create it by hand: murmurent pi-init <lab>
scaffolds it at exactly this path (core.repo.lab_repo_path), and
pins the machine's lab_mgmt pointer to it. If you do create the GitHub
repo manually (e.g. to push an existing local scaffold), use the same
name:
gh repo create <you>/murmurent_lab_mgmt_<lab> --private \
--source ~/repos/murmurent_lab_mgmt_<lab> --remote origin --push
Why this shape and not a bare lab_mgmt? Field experience: two groups
independently created repos called lab_mgmt and they were
indistinguishable in clones, invitations, and conversation. The
murmurent_ prefix announces what kind of repo it is; the _<lab>
suffix says whose. Machines that host several labs' clones (a
registrar's laptop, a shared server) get collision-free directories
for free.
Deviant names still work (member-side resolution auto-discovers any
clone in your repositories folder that has the lab_mgmt shape (lab.md +
members/) and pins it), but stick to the canonical name; discovery
refuses to guess when two candidate clones both match.
The clean conceptual boundary is:
~/repos/murmurent/ ← Commons: agents, rules, skills, CLI
source. Shared across the centre.
Symlinked into ~/.claude/.
~/repos/murmurent_lab_mgmt_<lab>/ ← One lab's filing cabinet. PI-owned.
Members read; PI + delegates write.
~/.murmurent/lab_info/ ← Centre registry. Registrar-owned.
Lists every lab + core + common SEA.
What lives in lab_mgmt?¶
Canonical location is ~/repos/murmurent_lab_mgmt_<lab>/. The directory layout:
murmurent_lab_mgmt_<lab>/
├── lab.md PI handle, lab name, institution,
│ Slack workspace, GitHub org, lab-VM
│ base path. Single source of truth
│ for the lab's configuration.
├── compliance.md Group-level required training
│ (TCPS_2, WHMIS, etc.) — applied to
│ every member.
├── members/ One .md per member (active or inactive).
│ ├── the_pi.md Each carries handle, role, status,
│ ├── gary.md certifications, lab affiliation.
│ └── ...
├── inventory/ Reagents + equipment. Managed by the
│ inventory MCP. Lab-manager writes only.
├── oracle/ Curated group findings (promoted from
│ personal-vault drafts via the publish
│ flow). Read by every member; written by
│ the oracle MCP after PI review.
├── cert_projects/ THE authoritative project registry — one
│ .md per project (name, lab, lead,
│ sensitivity, certified members, repos,
│ Slack channel). This is what the
│ dashboard's Projects panel reads; it no
│ longer scans ~/repos for CHARTER.md files.
├── projects/ Legacy CHARTER-mirror registry that
│ cert_projects/ replaced (2026-07-15
│ split). Old entries may still be present
│ for history; new projects are recorded
│ in cert_projects/ only.
├── roles/ Role assignments (lab_manager,
│ oracle_curator, sysadmin, …) with
│ audit history.
├── keys/ Public age keys per member, for
│ encrypted artifacts.
├── audit/ Compliance + decommission records;
│ never deleted, only appended.
├── onboarding/ Reusable member profiles (student,
│ postdoc, pi-collab) the millwright
│ clones into a new member.md.
├── dashboards/ Auto-generated per-member dashboard
│ snapshots (markdown).
├── external_customers/ Industry / academic-external clients
│ that core facilities bill.
└── requests/ SEA request board (project↔project).
Who needs it?¶
| Person | Action | Where they get it |
|---|---|---|
| Lab PI | scaffolded by murmurent pi-init <lab> at ~/repos/murmurent_lab_mgmt_<lab>; push it to GitHub under the same name |
see "Naming" above |
| Lab member (postdoc, student) | clone, read-only | git clone git@github.com:<owner>/murmurent_lab_mgmt_<lab>.git ~/repos/murmurent_lab_mgmt_<lab> (auto-discovered + pinned on first dashboard load) |
| Core leader | clone OF THEIR CORE'S lab_mgmt (cores have their own) |
same |
| Registrar | reads multiple lab_mgmt repos via the centre registry |
each lab's path is recorded in ~/.murmurent/lab_info/_registry.yaml |
| Mayor (bootstrap) | no | uses ~/.murmurent/lab_info/ instead |
| External customer | no | only sees the dashboard surfaces, not the underlying repo |
If you're a new lab member, the millwright agent's PROVISION_MEMBER
flow will tell you to clone it during onboarding. If you're a brand-
new PI joining a centre, the registrar will tell you to create one,
populate lab.md, and push it to your lab's GitHub org during the
join-request approval flow.
How lab_mgmt is resolved at runtime¶
core.repo.lab_mgmt_repo_root() returns the active lab's repo path
using this order:
- Thread-local override: the dashboard sets this per-request so a
shared, multi-lab dashboard scopes each request to the acting viewer's
own lab (via
use_lab_mgmt_root(resolve_viewer_lab_mgmt(actor))). $MURMURENT_LAB_MGMT_REPOenv var: an explicit operator pin to one lab (single-lab installs, tests, scripted use). When set, it wins over the registry net below — and the dashboard's per-viewer scoping stands down, since a machine pinned to one lab is not multi-lab.- This machine's pinned pointer (
~/.murmurent/lab_mgmt_path): written bymurmurent pi-init <lab>, and by discovery (step 5). - Registry-authoritative (
_registry_lab_mgmt_for_owner): the machine owner's own group per the centre registry (_registry.yaml). A bare call acts for the owner, so their registered group is the canonical default — returned even if the clone isn't on disk yet (the honest answer; panels render empty until it's cloned). This is what replaced the old hardcoded~/repos/lab_mgmtfallback. - Discovery: scans your repositories folder for a directory with the lab_mgmt
shape (
lab.md+members/), preferring one whose roster contains your handle, and pins an unambiguous hit so this runs once. It matches on shape, not name, so a pre-convention~/repos/lab_mgmtclone is still found here — the name was dropped as a fallback, the folder still resolves. If two clones match, it refuses to guess and the panels stay empty; setMURMURENT_LAB_MGMT_REPOto break the tie. - Canonical-convention default (
~/repos/murmurent_lab_mgmt, no group suffix): the honest last resort when nothing above resolves. It never exists on disk, so every lookup misses cleanly and any resulting 404 names the canonical convention. Murmurent no longer falls back to~/repos/lab_mgmtby name (that pre-convention path used to outrank discovery and was returned even when absent — the root of the wrong-roster 404s in issues #31 / #33).
So the canonical clone path needs no configuration: pi-init pins it
for the PI; for members the registry net (step 4) resolves it the moment
the PI has pushed their roster record, and discovery (step 5) self-heals
the pre-registry case. The MURMURENT_LAB_MGMT_REPO env var remains the
knob for tests and unusual deployments (e.g. a multi-lab dev workstation
where discovery is ambiguous by design).
Backfilling access for pre-existing members¶
Members added before the automatic lab_mgmt grant existed (or via any path that skipped it) can be granted access a posteriori. The grant pass works from the whole roster, not just new additions:
murmurent group-reconcile <group> --apply
For every active roster member with a github: login this ensures
read-only collaborator access on the lab_mgmt repo (idempotent;
already-granted members and the repo owner are no-ops). Members whose
profile lacks a GitHub login are listed so the PI can collect them.
Prerequisite: the lab_mgmt repo must be on GitHub (git remote -v
shows an origin). The reconcile output says so if it isn't.
Each member then completes their side once: accept the GitHub
invitation e-mail, and clone the repo under its own name
(git clone git@github.com:<owner>/murmurent_lab_mgmt_<lab>.git
~/repos/murmurent_lab_mgmt_<lab>). Resolution auto-discovers the
clone and pins it on the next dashboard load; MURMURENT_LAB_MGMT_REPO
remains the explicit override. From then on their Lab Members panel
and daily reconcile track what the PI pushes.
Reading + writing: who's allowed?¶
Path under lab_mgmt/ |
Read | Write |
|---|---|---|
lab.md |
everyone | PI |
members/*.md |
everyone | PI (or millwright via PI delegation) |
inventory/*.md |
everyone | lab_manager role via the inventory MCP |
oracle/*.md |
everyone | oracle_curator role via the oracle publish flow |
cert_projects/*.md |
everyone | project lead + PI (written by the dashboard's New Project flow / core.cert_projects) |
projects/*.md (legacy) |
everyone | project lead + PI |
roles/*.md, audit/*.md |
everyone | PI |
compliance.md |
everyone | PI |
In practice the granular write permissions are enforced by the
MCP layer, not by git. Members CAN locally edit inventory/*.md,
but the inventory MCP refuses to commit-publish their changes unless
they hold the lab_manager role. The PI's review on incoming PRs is
the final gate.
How a member's profile edit reaches the roster (#34, #187)¶
Members hold the roster clone read-only by design: the PI is the only
writer, so a member's git pull --ff-only on the roster can never conflict.
So a member editing their own profile in the dashboard does not write to
members/<handle>.md. Instead:
- The edit is saved in the member's personal vault
(
profile/roster_profile.yaml) and pushed, so their other machines get it. Their dashboard shows it straight away on every machine with that vault. - The PI is sent the change as an issue on the lab management repo,
titled
Profile update: @<handle>. Members can open issues with read access. A later edit updates the same issue. - The PI applies it with
murmurent reconcile --applyon their own machine. The change is applied only if the GitHub account that opened the issue is the GitHub login already on that member's roster entry, so nobody can change someone else's details. A member's GitHub login is never changed from an issue. Each applied change is committed and pushed, and its issue closed. Without--apply, reconcile lists what is waiting.
If the member's machine has no personal vault, the change stays on that machine (step 1) but is still sent to the PI (step 2). If the lab management repo has no GitHub copy, the dashboard says the PI was not sent the change.
The PI's own profile edits go straight to members/<pi>.md, committed and
pushed. Every dashboard updates the roster and the vault when it starts and
every 15 minutes, so a change made elsewhere appears without pressing
"update".
Which settings follow you between machines and which stay on one machine is
recorded in src/murmurent/core/settings_scope.py; each settings window says
which at the top.
lab_mgmt vs ~/.murmurent/lab_info/: the comparison¶
| Aspect | ~/repos/lab_mgmt |
~/.murmurent/lab_info/ |
|---|---|---|
| Scope | one lab | the whole centre |
| Owner | the PI | the registrar |
| Members | one PI + lab members | one or more registrars (mayors become first registrar) |
| GitHub home | the lab's own org | a private centre org (or none, can stay local + git-push to a private remote) |
| First clone | each lab member runs git clone once during onboarding |
the mayor's machine creates it on murmurent centre-init |
| Cross-lab visibility | no: one lab's repo, period | yes: the registry lists every lab and core in the centre |
| What writes here | millwright (per-member onboarding), inventory MCP, oracle MCP, PI's hand-edits |
centre_millwright (lab/core onboarding), registrar.create_lab / create_core, join-request approvals, common-SEA submissions |
To decide where something belongs, ask whether it concerns one lab
specifically or how labs relate to each other. One-lab specifics
belong in lab_mgmt. Inter-lab relations belong in lab_info.
See also¶
docs/setup.md: first-time Murmurent install on a new machine, including the~/repos/murmurent_lab_mgmt_<lab>clone.docs/group_level.md: the broader design document for group-scope Murmurent operations.- A core's own
lab_mgmt(yes, cores have one too, parallel to labs) mounts inside~/.murmurent/lab_info/cores/<core>/lab-mgmt/. agents/millwright.md: the per-lab millwright that owns most of the read/write surface on this repo.