Where it started
I was working on my sysadmin_assistant, which watches my daily driver and raises alerts when something triggers a threshold or something in the journal is erroring.
Some of my projects run local inference on my GPU. When I was testing venture-assistant a run could take an hour of compute, so I would step away from my desk. While I was away I had no visibility of when it finished. So I had the idea for sysadmin_assistant to alert me when the test was complete and the GPU was free again, for some gaming time. There was the added bonus that I could wire up the same function to notify me when something went wrong (and eventually be able to sort it from my phone!).
I already had the functionality set up on my Alfred via MQTT. It was running and being published to.
The catch was that Alfred was the owner of this implementation. All the identities, roles,
and ACLs were all defined in Alfred’s backend and written to the broker at startup.
The only credentials that existed were Alfred’s. I found the real problem in dynsec.py rather than by anything breaking.
The reconcile() function deleted every client that was not Alfred’s admin, publisher or a live device token.
On a private bus that would be correct. On a shared one it meant that any other app’s credentials were deleted the next time Alfred started. The delete was best effort, logged at info and raised no alert.
A pain in the arse but that’s what a lack of planning gets you!
With sysadmin provisioning its own credential, it would be deleted by Alfred.
If Alfred was to administer it, it would be stretching outwith its domain and
creating identities for a monitoring app it had nothing to do with.
Both roles were scoped to alfred/events/# anyway, with the broker itself enforcing the scope.
Alfred’s main role is a personal assistant. Running the estate’s broker identities is not part of its remit.
After some thinking, I decided that neither app could own the MQTT broker.
The drift had already crept in from elsewhere, and I didn’t notice it. Four of
the five files in sysadmin_assistant/docs/guides/ were about other
repositories, and only one was sysadmin’s own.
The decision
Following on from this I ended up giving anything spanning more than one project a repo of its own. It took the concerns from sysadmin_assistant and shifted it to the new estate-manager because a repo holding only the new ones would be a document hygiene nightmare.
The main migration of MQTT came over in two jobs; Alfred’s bootstrap() was defining a shared schema and provisioning Alfred’s own credentials with the former being estate-manager’s concern. The ADR decided on an estate-manager owned schema to describe the split, with applications writing their own rows.
Three decisions, all taken on the first day:
- [ADR-0001] Shared infrastructure gets an owner that is not an application.
Written as
sysadmin_assistant’s ADR-0002 and moved here, with a pointer left at the old path. - [ADR-0002] The estate delegates. Its writes into other repositories are documents and pointers only, never code and never config a runtime reads.
- [ADR-0003]
sysadminmonitors, the estate audits. The project scanner never moves, because two scanners is the copy-drift defect the repository was founded on. Although, there is always an exception to the rule; shortly after writing the ADR the scanner did end up moving as theestate-managerowns the document standard, so it should own the parse too!
ADR-0002 exists because the first session broke the rule before anyone had
written it, editing CLAUDE.md and tasks.md inside sysadmin_assistant from
an estate-manager session.
What it buys
Every repository that declares itself active carries the same five documents
(README.md, CLAUDE.md, HANDOFF.md, tasks.md and snag_list.md), and the
handoff carries its date. That started as a habit of mine. It is estate rule 2
now, so when one repo hands something to another the structure is already the
same at both ends.
Two mechanisms do the enforcing.
- Eleven checks that ask whether a document is telling the truth
([ADR-0003], [ADR-0009]). Health stays
sysadmin’s question. The estate publishes its numbers andsysadminjudges them, so nothing on the box grades itself. - Rule 2 stops being something I assert ([ADR-0012], [ADR-0013]). The
docscheck reads every active repository, and a missing document floors the estate board’s grade.
The repository records a decision about every other commit, where Alfred
records one every five or six. Alfred produces features and this produces
rules.
Where it’s going
On 2026-08-16 the estate filed a row into Alfred’s tasks.md, and a live
Alfred session’s git add -A swept it up and filed it into a phase archive as
completed. Nothing raised anything, and detecting it is still unsolved.
With that there had to be a change as cross-repo concerns are hard to track down.
If a row in the working tree task list can be swept away by staging it’s no use but a row in a database can’t be moved so easily. So, now cross-repo messages go into a register that estate-manager has on :8400 ([ADR-0041]).
The automation of the cross-repo messaging is still being built. Currently, sessions have a SessionStart hook to GET the register to list open messages addressed to the working repo with a printed command to close them and one at the end (runs on Stop) to ensure that HANDOFF.md is current with today’s date. Closing a message is still something I do.
Sessions will have autonomy to start working on messages and act on anything that can be settled itself. Anything else is escalated to my phone for me to sign off on. That line is [ADR-0011] §1’s test,
written 11 days prior for a different problem. Would answering this differently change what another repo must do?
The register is built. The notification path is designed but blocked. The design assumes two types of notification; in 29 payloads over 26 sessions the box has produced one, a permission prompt. The publisher has nothing to write against until a second type appears, or until I collapse the design to the one.