board/claims¶
Source: src/board/claims.ts
One owner per thing, decided here rather than agreed between members.
A board lets a member announce what it is taking. Measured, that is not enough and cannot be made enough: three members read the board within 130ms of each other, were each handed nothing because nobody had posted yet, and all three then claimed the same file. Announcing into a medium that was empty when you looked is a race whatever the prompt says.
So a claim is granted, never declared. First to ask holds it, everyone
else is refused and told who holds it, and the refusal is the useful part: it
turns contention into somebody to talk to (“ask scout#3, it holds
src/parser.ts”) instead of two members doing one job.
The keys are a list, not free text. The same run posted console.ts,
src/reporters/console.ts and I will handle src/reporters/console.ts for
one file. A lease keyed on what a model writes would have granted all three
and arbitrated nothing, so a caller that knows what there is to claim says
so, and a key that is not on the list is refused with the list - the same
discipline as an agent name that is not on the roster.
Pure data, like ledger.ts next door: no pi, no disk, no bus. Whoever hands
the mechanism to a member announces it.
Claims¶
type
export type Claims = {
/**
* Grants `key` to `member`, or refuses and says who has it.
*
* Taking what you already hold is granted: it is not contention, and a
* member told "you cannot have it, you have it" learns nothing.
*/
take(member: string, key: string): TakeOutcome;
/** Gives `key` up. Only its holder can, and a key nobody holds is `false`. */
release(member: string, key: string): boolean;
/**
* Everything `member` was holding, given up at once, and what that was.
*
* For the workflow above, when a member dies or is stopped: claims left
* hanging by a member that is gone are work nobody will do and nobody can
* take. The keys come back so the run can say which ones, rather than
* leaving a reader to notice the gap.
*/
releaseAll(member: string): readonly string[];
/** Who holds `key`, if anyone. */
owner(key: string): string | undefined;
/**
* What is left to take, when the caller said what there was.
*
* `undefined` when it did not: a list of what is free cannot be derived from
* a list of what is held, and inventing one would be this file guessing at
* the size of the work. It is what turns a refusal into a next move.
*/
free(): readonly string[] | undefined;
/** What is held right now, in the order it was taken. */
open(): readonly Holding[];
};
The leases, and the rules that govern them.
ClaimsOptions¶
type
export type ClaimsOptions = {
/**
* The things that may be claimed.
*
* Left out, any key may be taken - which is honest for a caller that cannot
* enumerate the work, and weaker: two spellings of one thing are then two
* things, and both are granted.
*/
keys?: readonly string[];
/**
* How much one member may hold at once. Unbounded by default.
*
* Measured, on three members and six keys: with no bound one of them took
* everything and the other two spent their turns being refused, and telling
* them in the prompt to take one at a time changed nothing - four held at
* once in both arms, the same as with no rule. A bound of one held it to one,
* and the work still went round: more grants, not fewer.
*
* It is not the default because it is not free. The same measurement put the
* bounded arm 29% slower and 64% heavier in input tokens, since taking,
* releasing and being refused are all calls. Reach for it when there is
* contention, which is when it pays for itself.
*/
maxPerMember?: number;
};
What there is to claim, when the caller knows.
createClaims¶
function
export function createClaims(options: ClaimsOptions = {}): Claims { /* … */ }
An empty set of claims.
Keys are compared exactly, after trimming. Nothing else is normalised: a key
is whatever the caller decided it is, and folding case or stripping a ./
would be this file guessing at what two strings have in common.
heldList¶
function
export function heldList(holdings: readonly Holding[]): string { /* … */ }
What is held, as a member reads it: one line each, in the order taken.
Holding¶
type
export type Holding = {
/** What is held, exactly as it was taken. */
key: string;
/** The member holding it. */
heldBy: string;
};
Something held, and who holds it.
TakeOutcome¶
type
export type TakeOutcome = { ok: true } | { ok: false; error: string; heldBy?: string };
What a take did, or why it did nothing.
heldBy is set only when the refusal is contention, because that is the one
a member can act on: it names somebody to ask.