Skip to content

Personal mode: Majordomus as one person's instrument

personal mode, the tool as one person's instrument with nothing left in git: who it is for, why it is ordinary practice, the measured way from personal to shared, the per-clone exclude file, a private policy with one projection and one enforcement, filesystem discovery for the server, how each part of the tool fits, and what was and was not measured

Rendered from docs/PERSONAL_MODE.md — the same Markdown GitHub shows.

Majordomus is built to be committed: the .ai/repo/ half of the layer travels with the repository so that every clone and every worker reads the same rules. Sometimes that is not yours to decide. You work in a repository a team owns, you want your own tasks to have a scope and a finish contract, you want a briefing at the start of a session and the UI over your own records, and the team's tree is not yours to add a commit, a line of .gitignore or a new file to.

Personal mode is the name for that arrangement: the layer lives beside the code, in your clone only, and the repository stays byte for byte what the team committed. Organisations have had a word for the hardware version of this for years: bring your own device. This is the same bargain one level up, bring your own discipline. The habits and the tool are yours, the repository and its rules are the organisation's, and neither changes the other.

There is a switch for it: majordomus init --personal, and --personal on the installer (ADR 0145, claims/personal-layer-changes-nothing-shared.md). The switch says what it does. The rest of this page is the measurement the switch was built from, taken by hand on 2026-10-12 in a scratch repository (one git init, a README, one source file, one commit) against the released build of that day, which had no switch. It stays because it shows what each piece is and why it is ordinary practice. Every step says whether it was run. Two were not, and they are named. Where the switch changes a statement below, the statement says so.

The switch

majordomus init --personal
majordomus update
majordomus doctor

init --personal creates the layer a plain init creates, covers it in a marked block of git's per-clone exclude file, and exits 10 unless git status reads the same after it as before. What it does that the hand-made arrangement below did not:

By hand, belowWith the switch
init writes two lines to .gitignore, restored with git checkout .gitignore.gitignore is not written
the exclude lines are typed, and nothing checks themone marked block, verified by init, by the installer and by every doctor
the policy is narrowed so that update writes one private fileinit --personal fits the seeded projections to the repository: a target the project already tracks moves to the file its provider reads for one person (personal_target in share/providers.yaml, CLAUDE.local.md for Claude Code), and a provider with no such file is left out. update covers each target before writing it, and still refuses, whole, a policy that names a tracked target
capture install was not run, because it writes under .claude/it covers what it writes the same way
the session record's commit and push are turned off in the policyunder a personal layer neither happens, whatever the policy says
the server needs --discovery filesystem, started before the clientdiscovery walks the work tree by itself when the layer is tracked nowhere
doctor keeps the failure about the READMEthe project's README is not asked to name AGENTS.md
converting means deleting the exclude lines and init --extendmajordomus init --extend --shared

The hook is still yours to add, and what the organisation's policy says about tools is still the organisation's to say. Where to add it is git's answer, and init prints it: under .git/hooks/ the two lines go straight in. Where core.hooksPath points at a directory the project tracks, the hook git calls is the project's; a dispatcher that runs <hook>.d/ leaves room for a file of your own, <hook>.d/majordomus-personal, which init --personal covers before it exists, and a tracked hook with no such directory has no place of your own, which init says instead of advising an edit to a shared file.

What it is for

The arrangement is non-invasive in a precise sense: the repository's diff is empty, nobody reviews anything, no teammate's checkout behaves differently, CI runs what it ran before, and removing it is deleting a directory. That is what makes it usable in the situations below, where the ordinary adoption is not available or not yet earned.

SituationWhat you get from it
Trying it before proposing it. You think the team would benefit and have nothing to show.Two weeks of your own tasks with a scope, a finish contract and handovers, on the real repository. You propose it with records instead of an opinion.
A contractor or consultant in a client's repository. The conventions are the client's and not yours to extend.Your own discipline travels with you: every engagement starts with a briefing and ends with a handover, and the client's tree is as you found it.
An audit, a review or due diligence. The brief is to read, and changing the repository is out of bounds.Findings, decisions and open questions as typed records, each tied to the commit it was made at, so a finding says by itself when the code moved under it.
Contributing to an open-source project. Upstream does not want your tool's files, rightly.The scope keeps a contribution to the paths it was about, and the finish contract asks for the verification command before you open the pull request.
Learning a codebase you did not write.What you work out is written down once, as knowledge candidates and decisions, and is in the next session's briefing instead of being worked out again.
A repository where adding a file is a change request. Regulated code, a frozen release branch, a vendor drop.The layer is beside the code and not in it, so nothing needs approving.
Working with an AI agent on your own.The agent reads your rules and your context from CLAUDE.local.md, and the task scope refuses a change outside the paths you named. The team's instruction files are untouched.
The same habits in every repository.One lifecycle everywhere, whatever each repository's own conventions are.

What it is not for: anything that needs the layer to be shared. Rules a team is held to, a board where colleagues see each other, a plan several people work from and evidence CI records all need the committed adoption in ADOPTION.md. This arrangement is one person's instrument.

Three ways to run it

Personal modeCommitted adoptionCommitted, with the mesh
where the layer livesthis clone, excluded from git.ai/repo/ in the repositorythe same, with runtimes linked across machines
who reads the rulesyou and your AI clientevery clone and every workerevery machine
who has to agreenobody, because nothing changes for anybodythe team, by reviewing a pull requestthe team, and whoever owns the machines
what refuses a changeyour own pre-push hookthe hooks and the CI gatesthe same
the peer boardyour own sessionsevery worker of the repositoryevery machine's workers
backupyours to makegitgit
leavingdeleting a directorya pull requesta pull request

The columns are a path as much as a choice. Personal mode is where one person finds out whether the lifecycle is worth having; From personal to shared is the measured way from the first column to the second, and MESH.md is the third.

Why this is ordinary practice

It uses each mechanism for what it was made for.

  • .git/info/exclude is git's own answer to "patterns specific to one person's workflow that should not be shared". It is the documented alternative to .gitignore for exactly this.
  • CLAUDE.local.md is the file an AI client reads for one person's instructions beside the project's.
  • Hooks under .git/hooks/ have never been shared by git; every clone has its own.
  • An editor's personal settings, a .envrc.local, a scratch directory and a private notes file are the same category of thing, and every developer already has some of them.

What stays exactly as it was is what matters to everybody else. Your commits are ordinary commits and are reviewed like any others. CI judges them with the gates the team chose. No check of the team's is bypassed, weakened or answered on its behalf, and nothing leaves the machine: Majordomus calls no model and sends nothing anywhere.

Three things the arrangement does not change, and that you still owe:

  • The organisation's tool policy. Where it decides which tools may read a repository, that covers a tool that lives only in your clone. Private is not the same as permitted.
  • The AI client's data policy. The layer is local; the client that reads it may not be. If AI tools are not allowed on this code, this does not make them allowed.
  • Honesty about your work. What stays local is a tool, never a change. If a colleague asks how you keep your tasks straight, the answer is one sentence, and when it has proved itself the next step is to propose the committed adoption.

Leaving

Removing it is as small as adding it, and leaves the repository bit for bit as it was.

rm -rf .ai CLAUDE.local.md .git/hooks/pre-push
git status --short --ignored

Then delete the lines you added to .git/info/exclude. The other way out, sharing the layer, is From personal to shared.

Where everything ends up

PlaceWhat is thereWho sees it
committed filesnothing new; git status stays cleanthe team
the work tree, excluded.ai/repo/, .ai/local/, CLAUDE.local.mdthis disk only
.git/info/exclude, hooks/pre-pushthis clone only; git never pushes it

.git/info/exclude has the syntax of .gitignore and none of its visibility: it is not a file of the work tree, so it is never committed. It lives in git's common directory, which means it holds for every linked worktree of the clone as well.

What Majordomus writes, and how each piece is kept out

What appearsWritten byKept out byMeasured
the .ai/ directoryinita line .ai/ in .git/info/excludeyes
two lines appended to .gitignoreinitgit checkout .gitignore; excluding .ai/ covers bothyes
AGENTS.md, CLAUDE.md, GEMINI.mdupdateone projection left in the private policy, into CLAUDE.local.md, excludedyes
git hooksyou, by hand.git/hooks/ is local by nature; pre-push is enoughyes
the Claude Code settings file and .claude/hooks/capture installexclude both; where the team tracks that settings file, do not run capture installno
.mcp.jsonyou, by handregister the MCP server for your user rather than for the projectno

The steps, by hand

This is the arrangement as it was assembled before the switch existed. It still works, and it is the one to read when the switch refuses something and you want to know why.

majordomus init
git checkout .gitignore
printf '.ai/\nCLAUDE.local.md\n' >> .git/info/exclude
git status --short

init appends .ai/local/ and .ai/repo/**/.tmp.* to .gitignore. Restoring the file loses nothing, because the exclude line covers the whole directory. The last command prints nothing.

The policy at .ai/repo/policy.yaml is yours alone now. Leave one enforcement and one projection in it:

enforcement:
  - name: finish-on-push
    path: majordomus
    args: [finish, --check]
    wired_by: git-hook:pre-push

projections:
  - provider: claude-code
    target: CLAUDE.local.md
    always_loaded: true

session:
  commit_record_on_end: false
  push_record_on_end: false

The two session keys are a precaution. The seeded policy (share/skeleton/policy.yaml) commits and pushes the session record when an episode closes. What those do with an excluded layer was not measured, so they are off here. For the same reason continuity publish and continuity sync are not for this arrangement: they push a ref to the shared remote, where everybody can read it.

doctor refuses a policy with no enforcement entry and one in which no projection is marked always_loaded, so one of each is the floor. CLAUDE.local.md is the file Claude Code reads beside the team's CLAUDE.md, which stays as it is.

majordomus update
printf '#!/bin/sh\nmajordomus finish --check || exit $?\n' > .git/hooks/pre-push
chmod +x .git/hooks/pre-push

Do not wire doctor into pre-commit here. In this arrangement it keeps one failure that cannot be removed (below), and a hook that always fails refuses every commit.

majordomus serve --discovery filesystem

This is the step that matters. Discovery defaults to vcs: the executable asks git's index which files exist, and an excluded layer is in no index. Measured, the default reads:

index built objects=1 diagnostics=4 state=Degraded
required class 'policy' (:(glob).ai/repo/policy.yaml) discovered nothing
required class 'rule' (:(glob).ai/repo/rules/**/*.md) discovered nothing

With --discovery filesystem the same tree reads state ok, with its policy, profiles, prompts and the vendored rules as objects, and the server answers at /ui and /cockpit. Start it before the AI client: a client attaches to the server that is already running and takes its options, and one that starts its own takes the default.

From there the task lifecycle is the ordinary one. In the scratch repository check counted the one edited source file as inside the scope, finish --check in the hook passed, and git status showed that file and nothing of the layer. finish also refused --verify-command "true": a command that does nothing verifies nothing.

How it fits the rest of the tool

Part of the toolIn this arrangementWhy
task lifecycle: start, check, finishworks, measuredcheck counted the edited file as inside the scope and finish --check passed
the task scopeworks, measuredgit status showed the source change and nothing of the layer
briefing and contextworks, measuredcontext printed git, the task and the profile
checkpoints, handovers, decisionsworksthey are local records in the committed adoption as well
instruction projectionworks, measuredupdate wrote CLAUDE.local.md and no other file
the pre-push hookworks, measuredit lives under this clone's .git/hooks/
the server, /ui and the Cockpitworks with --discovery filesystem, measuredthe default discovery asks git's index
the planworks locally/cockpit/plan answered
doctorone failure staysthe README does not name AGENTS.md
MCP tools for an AI clientstart the server firstreading the layer was measured; an attached client was not
the peer boardyour own sessions onlynobody else attaches to your server
knowledge recordslocalcandidates are derived for one reader; the derivation at an episode's close was not measured
the evidence ledgerlocalit supports no guarantee anybody else reads
Claude Code session hooksnot measuredcapture install writes under .claude/ in the work tree
the session record's commit and pushturn offthe two policy keys above
continuity publish and syncdo not usethey push a ref to the shared remote
the meshdo not useit shares session and claim metadata with other machines
CI gatesnothing to plana private layer declares no CI model; the team's CI is untouched

What each statement rests on

StatementWhere it is decided
init writes .ai/ and two lines of .gitignore, and installs no hooklib/init.sh, test/cases/01_init.sh, claims/local-state-ignored.md, claims/ai-layer-manifest.md
instruction files are projections of the policy, and a projection's target is one linelib/update.sh, claims/projection-generation.md, claims/region-projection.md
a touched file outside the scope is a failure, and finish evaluates its contract line by lineclaims/scope-enforcement.md, claims/finish-contract.md
a verification command that does nothing is refusedverification-integrity
nothing performs a network callclaims/no-network.md, no-network-no-eval
a personal layer changes nothing shared, and the command that says so checks itlib/common.sh, test/cases/2979_a_personal_layer_changes_nothing_shared.sh, claims/personal-layer-changes-nothing-shared.md
discovery can walk the work tree instead of asking the indexapps/majordomus-cli/src/cli.rs, MCP.md, the-server-sees-the-current-tree
what a worker never reads is declared onceclaims/scope-declared.md, SCOPE.md

What does not fit

  • By hand, doctor keeps one failure. Of the six a fresh adoption reports, five go with the policy above and the hook. The one left is bootstrap README.md — does not name AGENTS.md, and removing it means editing the README. With the switch that check is not asked of a personal layer.
  • The released build has no ui command. The /ui surface is served; open it in a browser.
  • Nothing is shared, so nothing is backed up. ADRs, knowledge records, session records and the plan exist on one disk. git clean -fdx deletes them, because -x removes excluded files, and git stash -a hides them. Copy .ai/ somewhere else.
  • The view of the team is git's. The board shows your own sessions. What you know of anybody else is what their branches and commits say. Whether /ui reads another repository's pull requests was not measured.
  • Two rows of the table above were not run. The session hooks of Claude Code and an MCP registration outside the repository are described from how they are built, not from a run.

From personal to shared

Personal mode has done its job when you can show what it gave you. The proposal to a team is then a pull request and a set of records, not an opinion: the tasks you ran with a scope, the handovers a session started from, the times finish refused. The conversion was measured on 2026-10-12 in a scratch repository whose layer still carried the seeded policy.

With the switch this is one command, majordomus init --extend --shared: it removes the cover, adds the two .gitignore lines, prints what is now the repository's to commit, and commits nothing. By hand it was this.

Delete the two lines you added to .git/info/exclude. git status --short then shows the layer as one untracked directory. After that:

majordomus init --extend
majordomus update
majordomus doctor

What each did there:

StepMeasured result
removing the exclude linesgit status --short shows ?? .ai/ and nothing else
init --extendadds .ai/local/ and .ai/repo/**/.tmp.* back to .gitignore, and nothing else
git add -A -nnames 96 paths under .ai/ and .gitignore; none of them is under .ai/local/
updatewith the seeded policy, creates AGENTS.md, CLAUDE.md and GEMINI.md
doctortwo failures, both wiring: the pre-commit and pre-push hooks it names are not installed

A policy you narrowed for personal mode does not widen itself. Before update, return its projections and enforcement blocks to what the team wants, starting from share/skeleton/policy.yaml; that edit was not part of the measurement. Your own records under .ai/local/ stay local in every arrangement. From here the path is ADOPTION.md.

How to say it

Nothing about personal mode needs a careful answer. What each asker needs is short and is decided somewhere that can be read.

Who asksWhat is trueWhere it is decided
a colleaguemy tasks, their scope and my handovers are in a local directory git ignores; nothing of it is in the repositorygit status --short --ignored in my clone
a team leadthe repository, its review and its CI are exactly what they were; if it proves useful I will propose it as a pull requestFrom personal to shared
securitythe tool performs no network call and calls no model; the AI client I use is the one the organisation already allows, under the same data policyclaims/no-network.md, no-network-no-eval
an architectthe same lifecycle the committed adoption runs, with a private policy; no rule of the team's is restated or overriddenADOPTION.md
whoever owns tooling policyit is a local tool in the category of an editor configuration, and it stays subject to the policy on which tools may read this codethe organisation's policy, which this does not change

What stays visible

Personal is not hidden. These are the places where the arrangement shows, and each is worth knowing before a screen is shared.

  • git add -f adds an excluded path. Read git status before a commit.
  • Commit trailers, the shape of commit messages and branch names are visible to everybody.
  • The banner drawn on entering the directory is visible on a shared screen; MAJORDOMUS_BANNER=off turns it off.
  • git check-ignore -v .ai/manifest.yaml names the line of .git/info/exclude that excludes the layer, and git status --ignored lists everything excluded.

Private is not the same as permitted. Where an organisation decides which tools may read a repository, that decision covers a tool that lives only in your clone. Majordomus sends nothing anywhere and calls no model; the AI client using it does.

  • ADOPTION.md for the ordinary, committed adoption.
  • INSTALL.md for the installer.
  • MCP.md for the shared server, its discovery modes and what a client inherits.
  • SCOPE.md for what the executable reads of a repository at all.

Searches the titles and descriptions of every page this site publishes.