Skip to content

API reference

Every name below is exported from blackboard. Nothing else in the package is public, and a name outside this list may change without a deprecation.

blackboard

A skeletal blackboard system.

The library supplies the board, the shared structure through which independent agents contribute to one result, and the control component, which determines which agents are notified of a change, whether a proposed write is admitted, whether budgets hold, and when the run has finished. An application creates a model by supplying its regions, agents, seed, admission rule, termination predicate, and budgets. The public surface is the set of names in __all__; every other name is internal.

AdmissionRule module-attribute

AdmissionRule: TypeAlias = Callable[
    [ProposedWrite, "BoardReader"], Accept | Reject
]

The rule the control component calls on every proposed write.

AuditEvent module-attribute

Every kind of event the audit records.

NotificationId module-attribute

NotificationId = NewType('NotificationId', int)

The identifier an acknowledgment or an extension names.

ProposedWrite module-attribute

ProposedWrite: TypeAlias = (
    ProposedContribution | ProposedRegisterWrite
)

A proposed write of either kind, as the admission rule receives it.

RunOutcome module-attribute

RunOutcome: TypeAlias = (
    Complete
    | FinishedWithFailures
    | BudgetExhausted
    | Aborted
)

The four states a run closes in.

TerminationPredicate module-attribute

TerminationPredicate: TypeAlias = Callable[
    ["BoardReader"], TerminationDecision
]

The predicate the control component calls when no work is outstanding.

BlackboardError

Bases: Exception

The base of every error this library raises.

Board

Stores contributions in declared regions under one total order.

The board stores content without reading it and never combines two writes. Content comes back by identity: the board does not copy what it stores, so a caller that mutates a stored object changes what every later reader sees. Sequence assignment is the only point where two writes wait on each other, so writes from concurrent threads all succeed with distinct sequence numbers.

declare

declare(region: Level | Register) -> None

Creates a region. A name already declared, of either kind, is refused.

append

append(level: str, content: object) -> int

Adds one contribution to a level and returns its sequence number.

set

set(
    register: str, value: object, expected_version: int
) -> Written | Conflict

Replaces a register's value under the version the caller expects.

A write naming any version other than the register's current one fails: it returns a conflict carrying the current version, takes no sequence number, and leaves the register unchanged. A register never written has version 0.

read_level

read_level(
    level: str, from_sequence: int = 0
) -> list[Contribution]

Returns a level's contributions from the sequence bound, inclusive.

read_register

read_register(register: str) -> RegisterState

Returns a register's current value and version.

read_board

read_board(from_sequence: int = 0) -> list[BoardChange]

Returns every write to every region, in sequence order, from the bound.

BoardChange dataclass

One write to any region, at its position in the total order.

Conflict dataclass

A register write that failed because the version it named is not current.

Contribution dataclass

One unit stored in a level, at its position in the total order.

DuplicateRegionError

Bases: BlackboardError

A declaration named a region that already exists.

Level dataclass

A declaration of a region that accumulates contributions in arrival order.

RegionKindError

Bases: BlackboardError

An operation that takes a level named a register, or the reverse.

Register dataclass

A declaration of a region that holds one current value under a version.

The control component reads the batch window when a change to this register lands, to schedule its notification. The board ignores it.

RegisterState dataclass

The current value of a register and the version its write produced.

UndeclaredRegionError

Bases: BlackboardError

An operation named a region that no declaration created.

UnsetRegisterError

Bases: BlackboardError

A register was read before any write gave it a value.

Written dataclass

A register write the board sequenced, and the version it produced.

Clock

Bases: Protocol

Reads the current instant and arms calls to fire at chosen instants.

now

now() -> datetime

Returns the current instant, timezone-aware in UTC.

call_at

call_at(
    when: datetime, call: Callable[[], None]
) -> ScheduledCall

Arms a call to fire at an instant and returns its cancellation handle.

ManualClock

A deterministic clock for tests; time moves only through advance.

advance

advance(delta: timedelta) -> None

Moves time forward and fires every due call on the calling thread.

Calls fire in due order; ties fire in arming order. A call armed during the advance fires inside it when its instant falls at or before the target. A call armed at or before the current instant fires on the next advance, a zero advance included. A fired call may itself advance the clock; the outer advance never moves time back below where an inner one left it.

ScheduledCall

Bases: Protocol

A call armed on a clock, cancellable until it fires.

cancel

cancel() -> None

Prevents the call from firing. Cancelling a fired call changes nothing.

SystemClock

The default clock, backed by the operating system.

Aborted dataclass

The run closed because the application called abort.

Accept dataclass

The admission rule's verdict admitting a proposed write.

Accepted dataclass

A write the control component admitted, with the sequence it received.

Agent dataclass

An agent declaration: identity, deadline, wake cap, and delivery.

The control component invokes notify to deliver a notification, holding no lock, on the thread that closed the batch window or, when deliveries chain, on a thread already draining them; two notifications to one agent can therefore arrive on two threads at once. The callback may run the whole agent cycle inline or hand the notification to the application's own execution. A callback that blocks on wait_closed holds the run open, because its own unacknowledged notification counts as outstanding work.

BoardReader

Bases: Protocol

The three read operations, the handle the admission rule receives.

read_level

read_level(
    level: str, from_sequence: int = 0
) -> list[Contribution]

Returns a level's contributions from the sequence bound, inclusive.

read_register

read_register(register: str) -> RegisterState

Returns a register's current value and version.

read_board

read_board(from_sequence: int = 0) -> list[BoardChange]

Returns every write to every region, in sequence order, from the bound.

BudgetExhausted dataclass

The run closed because the named run-wide limit was reached.

BudgetKind

Bases: Enum

The names of the three run-wide limits.

BudgetReached dataclass

The audit record of a run-wide limit being reached.

Complete dataclass

The run closed with no agent presumed failed or capped, and the termination predicate, where one was supplied, returned complete.

Control

The control component's write path, over a board it owns.

reader property

reader: BoardReader

The board's read side. Reads bypass the control component entirely.

declare

declare(region: Level | Register) -> None

Creates a region on the board and records its kind.

register_agent

register_agent(agent: Agent) -> None

Registers an agent. Its cursor starts at the current sequence number.

write

write(
    agent: str, level: str, content: object
) -> Accepted | Rejected

Runs one level write through admission and, if admitted, the board.

set_register

set_register(
    writer: str,
    register: str,
    value: object,
    expected_version: int,
) -> Written | Conflict | Rejected

Runs one register write through admission and, if admitted, the board.

read_audit

read_audit() -> list[AuditEvent]

Returns every audit event in the order each occurred.

abort

abort(reason: str) -> None

Closes the run as aborted. A run already closed keeps its outcome.

outcome

outcome() -> RunOutcome | None

Returns the closed run's outcome, or nothing while the run is open.

wait_closed

wait_closed(
    timeout: timedelta | None = None,
) -> RunOutcome | None

Blocks until the run closes and returns its outcome.

With a timeout, returns nothing when the run is still open after it.

ack

ack(agent: str, notification_id: NotificationId) -> None

Records that the agent finished responding to a notification.

The cursor advances to the end of the range the notification covered. An acknowledgment of a notification no longer outstanding changes nothing; one naming a notification never issued to that agent raises.

extend

extend(
    agent: str, notification_id: NotificationId
) -> datetime | None

Grants the agent a new acknowledgment deadline for a notification.

Returns the new deadline, or nothing when the notification is no longer outstanding. An identifier never issued to that agent raises.

DeadlineExtended dataclass

The audit record of a new deadline granted before the old one passed.

DuplicateAgentError

Bases: BlackboardError

A registration named an agent that is already registered.

FinishedWithFailures dataclass

The run closed with agents presumed failed or capped, named here.

Notification dataclass

One wake: the range it covers, the changed registers, and its deadline.

It carries no values. The agent reads the registers itself, and reads whatever else on the board it wants.

NotificationAcknowledged dataclass

The audit record of an agent reporting that it stopped.

NotificationDispatched dataclass

The audit record of one notification leaving the control component.

PresumedFailed dataclass

The audit record of a deadline passing with no acknowledgment.

ProposedContribution dataclass

A level write as the admission rule sees it, before sequencing.

ProposedRegisterWrite dataclass

A register write as the admission rule sees it, before sequencing.

RegisterSeeded dataclass

The audit record of the seed writing one register when the run opened.

Reject dataclass

The admission rule's verdict refusing a proposed write, with its reason.

Rejected dataclass

A write the control component refused, with the cause and its reason.

RejectionCause

Bases: Enum

Why the control component refused a write.

ADMISSION: the admission rule rejected it. UNDECLARED_REGION: the named region was never declared. BUDGET_EXHAUSTED: a run-wide budget is exhausted. RUN_CLOSED: the run has closed.

RunBudgets dataclass

The run-wide limits. Each is required and positive.

RunClosed dataclass

The audit record of the run closing, with its outcome.

RunClosedError

Bases: BlackboardError

A declaration or registration reached a run that has closed.

SeedError

Bases: BlackboardError

The seed's names are not exactly the declared registers.

TerminationDecision

Bases: Enum

The termination predicate's answer when no work is outstanding.

UnknownNotificationError

Bases: BlackboardError

The named notification was never issued to the acknowledging agent.

WakeCapReached dataclass

The audit record of an agent receiving the last notification its cap allows.

WriteAccepted dataclass

The audit record of a write that reached the board.

WriteRejected dataclass

The audit record of a refused write; it never reached the board.

Model dataclass

A running shared solution model: its read handle and its control component.

Board reads go to reader directly, consume no control capacity, and cannot be refused. Writes, acknowledgments, and lifecycle calls go through control.

create_model

create_model(
    *,
    regions: Iterable[Level | Register],
    agents: Iterable[Agent],
    seed: Mapping[str, object],
    admission_rule: AdmissionRule | None = None,
    termination_predicate: TerminationPredicate
    | None = None,
    budgets: RunBudgets,
    clock: Clock | None = None,
) -> Model

Opens a run and returns the model.

The seed writes every declared register once, bypassing admission and the write budget, and the opening wakes follow, each counting against its agent's cap and the notification budget. With no admission rule a write is accepted subject to the region existing, the budgets holding, and the run being open; with no termination predicate the run closes as soon as no work is outstanding; with no clock the operating system clock serves.