Skip to main content

Module lease

Module lease 

Source
Expand description

One shared server per checkout: the lease that decides who it is. The first majordomus mcp (or serve) to create state/mcp/server.json under the checkout’s local half owns the server and publishes its URL there; every later process reads the file, checks that the server answers for this root, and attaches to it. A lease whose server does not answer is stale, and the next process takes it over; so is a file that is not a lease document, an empty one, or one whose owner never published a URL. A server that is slow to answer while its process is alive is busy, not stale: the election waits for it for BUSY_GRACE before it takes anything over. The file lives under .ai/local/ (never tracked, by the layer’s contract), and it is removed when the server stops — including when SIGTERM, SIGINT or SIGHUP asks it to — but only by the process that still holds it: every removal and every rewrite re-reads the file under the lock beside it (server.lock) and acts only on a lease that carries the actor’s own token (take_over_with says why it is a lock).

Per checkout, and this file said “per repository” until ADR 0044: a linked worktree is a checkout, so it has a manifest, a root, a lease and a server of its own, and on 2026-09-11 this repository had seven of them at once. What is repository-wide is the board, which peers.list gathers over these very leases; the election is unchanged.

Structs§

ExecutableIdentity
The identity of the file an executable was started from: where it is, and the mtime and size of the file at that path. A server outlives its own binary — a rebuild replaces the file under a process that keeps serving the code it loaded hours ago — and nothing about the process itself says so. This is what makes that visible.
Lease
The lease this process holds. Dropping it removes the file (when the file is still this process’s), so a failed start never leaves a stale lease behind.
LeaseDocument
The lease file, typed: what the process serving a checkout wrote down about itself. Every reader of the file — the election, a command asking which server is running, the environment snapshot, the server’s own status — reads it through LeaseFile::read, so that a field added here is added once and a file that is not a lease is refused in one place. A key this version does not know is ignored, so a lease written by a newer executable still reads; a key it needs that an older lease lacks takes its default.
Timings
The timings above are the defaults. What a process actually judges a lease contest by is declared in .ai/repo/policy.yaml’s server: block and read once, here.

Enums§

LeaseFile
What a lease file holds, read once. Every state of the file is a variant, so that a reader decides what to do about each rather than learning it from an error.
Role
What the election decided for this process.
VersionVerdict
How the version a server published in its lease stands to this executable’s.

Constants§

BIND_GRACE
How long a lease without a URL may be (the owner is still binding) before it counts as abandoned.
BUSY_GRACE
How long a live server that does not answer its probe is waited for before its lease is taken over.
IDLE
How long a server started for a checkout no client owns — by serve ensure or by a shell entering the checkout — outlives its last peer before it stops by itself: crate::cli::DEFAULT_IDLE_SECONDS. serve --idle on a command line overrides whatever is declared (I2701).
JOIN_TIMEOUT
How long a process keeps trying to acquire or join the lease before it gives up and says so: the bind grace with a margin for the probes. The caller then serves its client alone.
JUDGED_STALE_ENV
The environment variable a server is started with when the process that started it had already waited on the lease it found and judged it stale; its value is that lease’s token. The new server’s election then takes exactly that lease over without spending the same patience on it again. Any other lease, including one that changed in between, still gets probe_patiently’s wait.
LEASEHOLDER_KEY
The key the index answers was_lost’s converse under: whether the server answering still holds the lease of the checkout it serves. Named once, read by probe and written by the index route, so the two cannot drift apart.
LEASE_PATH
The lease file, relative to the checkout-local half (.ai/local/).
PROBE_TIMEOUT
How long the probe of a published URL waits.
READ_DEADLINE
How long the shared server lets a connection go without a request read in full on it before it closes the connection: waiting for a request head, kept alive between requests, or reading a body. A client that sends one byte a second would otherwise hold a request thread forever, and enough of them would leave the probe above nobody to answer it (I2156). Generous, because a person’s client on a slow link is a client too; far below anything that would let a handful of slow sockets outlast a BUSY_GRACE contest many times over.
SCHEMA
The lease file’s schema.

Functions§

declare_timings
Declare the timings for this process from a repository’s policy. The first call decides; later ones are ignored, which is what makes timings answer the same thing all the way through one contest.
elect
Decide whether this process serves the repository or attaches to the process that does.
executable_identity
What the running executable is, for the lease to record: the path it was started from and the identity of the file at that path. A server outlives its own binary — a rebuild replaces the file under a process that keeps serving the code it loaded hours ago — and nothing about the process itself says so. This is what makes that visible.
file_age
How long ago the file was last written; zero when that cannot be read, so that a file whose age is unknown is never taken for an old one.
held
The lease this process holds and has published: None in a process that serves nothing.
is_own
Is doc the lease this very process holds, published and not lost?
lease_file
Where the lease of the checkout at root lives, given the repository-relative path of its local half (.ai/local). The one composition of that path; every reader of a lease that is not this process’s own repository — another checkout’s, in the server’s status or the environment snapshot — goes through here.
lease_path
Where the lease of a repository lives.
lost
This process’s lease was taken over by another process: from now on nothing this process does on its way out touches the file, which is somebody else’s, and it stops claiming to be the checkout’s server — held answers None and was_lost answers true from here on. It serves the peers it has and ends with them; what it must not do is take on new ones, or answer a stranger’s probe as though it were still the one.
lost_for
How long ago this process lost the checkout’s lease, when it did.
probe
Does a Majordomus server answer at url for the repository at root, as that repository’s server?
probe_patiently
probe, with patience for an owner that is alive but slow.
probe_reply
probe, keeping what the server answered: its index document when it answers as the leaseholder of this checkout, None otherwise. One decision, so a caller that also wants the surfaces the server lists makes neither a second request nor a second judgement.
serving
Which server is serving this repository right now, if any.
serving_replaced_code
Why this process is serving code that is no longer on disk, or None while the file it was started from is still the file at that path. The same judgement the lease reader makes from outside (ExecutableIdentity::replaced), made by the process about itself, so that the endpoint a client asks for readiness can say so.
started_as
The executable this process was started from, as it stood on disk when first asked.
timings
What this process judges a lease contest by. The declaration when one was read, the constants otherwise.
version_verdict
Decide how served, the version in a lease, stands to mine.
was_lost
Did this process hold the checkout’s lease and lose it?