board/board¶
Source: src/board/board.ts
A place several subagents can leave messages for each other.
Everything else here passes Results between subagents that never meet. A
board is the other arrangement: members that can see each other, and can
therefore divide work nobody assigned them. It is worth building only if it
can be watched, which is what this file is for - an append-only log, in
memory, with no pi and no disk, so a run that used one can be read back
afterwards exactly as it happened.
Three rules, each one a property the medium has to have rather than a preference:
Nothing is rewritten and nothing is deleted. A member can add to the record and that is all it can do to it. Agents given a record they could edit have been observed going looking for ways to edit it, and an investigation of a run is worth nothing if the run could rewrite it.
fromis stamped, never declared. The caller says who is posting; no field of the draft carries it. Identity being claimable is what makes a shared medium unauthenticated, and an unauthenticated medium is one where a member can speak as another. The same discipline as “only whoever raised an obligation may close it”.The caps ship with the board. All three have defaults, for the reason
maxIterationsdoes: a medium with no limit produces as many messages as the run has time for, and the only number nobody chose is “as many as it takes”.
And one refusal that is not a cap: a member does not say again exactly what it already said, same kind, same reader, same words. The copy tells nobody anything new and costs every reader a slot on its page. It was measured: a member told to post once a turn posted one vote six times in a single turn.
What it deliberately is not: a queue, a channel with delivery guarantees, or anything a member can read twice by accident. {@link Board.since} hands a reader what it has not been given yet and a cursor to ask again with, so the bookkeeping of “who has seen what” lives here and not in a workflow.
Board¶
type
export type Board = {
/**
* Posts as `from`, or refuses and says why.
*
* A refusal is an outcome the caller reports to the member, never a throw:
* a member that hits a cap has to be told which one, so it can do something
* else with the turn it has left.
*/
post(from: string, draft: Draft): PostOutcome;
/**
* What `reader` has not been given: everyone's broadcasts, plus its own mail.
*
* `limit` is a page: a board holds hundreds and a member's context holds one
* conversation, so a reader may ask for a few and be told how many wait. The
* cursor then moves past what was handed over, never past what was merely
* looked at, so a post left for the next page is still there when asked.
*/
since(reader: string, cursor?: string, limit?: number): Reading;
/** Every post, in the order they went up. The record an investigation reads. */
all(): readonly Post[];
};
The medium. Append, and read what you have not read.
BoardLimits¶
type
export type BoardLimits = {
/** Total posts before the board refuses. Default 200. */
maxPosts?: number;
/** Longest a single post may be. Default 2000 characters. */
maxPostChars?: number;
/** How many one member may post. Default 50. */
maxPostsPerMember?: number;
};
How much a board will hold.
Each default is a number somebody chose, which is the whole point of them being here: a board with no cap is one where a loop between two members is bounded by the deadline and nothing else.
BoardOptions¶
type
export type BoardOptions = {
/**
* The members, so a post addressed to nobody can be refused.
*
* Left out, the board does not know who is on it and accepts any `to`. That
* is the honest behaviour rather than a convenience: refusing an address it
* cannot check would be guessing.
*/
members?: readonly string[];
/** What it will hold. Left out, the defaults apply; there is no way to say "no cap". */
limits?: BoardLimits;
};
Who is on the board, and how much it will hold.
createBoard¶
function
export function createBoard(options: BoardOptions = {}): Board { /* … */ }
An empty board.
at is measured from here on a monotonic clock, so the record says how far
into the run each post went up. Wall-clock time is the operator’s business
and changes under a run; the distance between two posts does not.
Draft¶
type
export type Draft = {
/** What this post is for. */
kind: PostKind;
/** What it says. Empty is refused: a post with nothing in it is noise. */
text: string;
/** Who it is for. Left out, everyone reads it. */
to?: string;
/** The id of the post it answers. Left out, it answers nobody in particular. */
re?: string;
};
A post as a member writes it: what it says, and who for.
Post¶
type
export type Post = {
/** Assigned here, stable, never rewritten. */
readonly id: string;
/** The member that posted it. Stamped by the board, never taken from a draft. */
readonly from: string;
/** The member it is for. Absent means everyone. */
readonly to?: string;
/** What it is for, so a reader can sort traffic without reading it. */
readonly kind: PostKind;
/**
* The post it answers, and who wrote that one. The id comes from the draft,
* the author from the board's own record, so an answer names the member it
* answers without that member having to be claimed.
*/
readonly re?: { readonly id: string; readonly from: string };
/** What it says, trimmed. Never rewritten afterwards. */
readonly text: string;
/** Milliseconds since the board opened. Ours, and monotonic. */
readonly at: number;
};
One message, as it will be read back a year later.
PostKind¶
type
export type PostKind = "ask" | "tell" | "result" | "claim" | "release" | "hold";
What a post is for, so a reader can tell traffic apart without parsing prose.
Six and not more: each one is something a member does that another member has to react to differently. A seventh is added when a run needs it.
PostOutcome¶
type
export type PostOutcome = { ok: true; post: Post } | { ok: false; error: string };
A post that went up, or why it did not.
Reading¶
type
export type Reading = {
/** What came in since the cursor, for this reader only - up to the limit asked for. */
posts: readonly Post[];
/** Pass it back to {@link Board.since} to be given only what came after. */
cursor: string;
/** How many more were there for this reader, past the limit. `0` when it was handed everything. */
waiting: number;
};
What a reader has not been given yet, and what to ask with next time.