Appendix A - 20. Orchestrator-as-reactor over an event bus

The Structure of Orchestrator-as-reactor over an event bus — its shape at a glance:

Emitters publish topics drawn from a closed registry; the orchestrator polls the queryable surface, matches each event to its playbook entry, and takes the prescribed action — the playbook is the active half that turns a signal into a reaction.

flowchart LR
  Sub[Substrate emitters] -->|typed topic| Bus[(Event bus)]
  Reg[(Const-string topic registry)] -.->|topics must be declared| Bus
  Bus --> Orch{Orchestrator polls}
  Orch -->|match topic → playbook| React([Prescribed action])

Accessible description: substrate emitters publish typed topics, drawn from a closed const-string registry, onto the event bus; the orchestrator polls the queryable surface, matches each event to its playbook entry, and takes the prescribed action.

Projected from the catalogue entry lifecycle-and-observability / Orchestrator-as-reactor over an event bus.

On this page: Intent · Motivation · Applicability · Structure · Sample Code · Consequences · Example use within DocAble · Related Patterns

Intent

Intent — A typed event bus with a closed, const-string topic registry and a companion playbook, over which substrate emits lifecycle/health events. The bus turns the orchestrator into a reactor over the fleet: it reads health from a queryable, self-documenting signal surface and reacts to each event with a playbook-prescribed response, which keeps a fleet of agents productive over long-running sessions instead of drifting into silent breakage.

Motivation

A fleet's health — is cron running, is the merge-train yielding, are tombstones stuck — is invisible without a signal surface, so degradation accretes silently (cron broken for hours before anyone notices). Worse, without a reaction loop the orchestrator is a passive observer: it can only steer the fleet if it reacts to what the substrate reports. The failure is silent substrate degradation and an un-reacting orchestrator, and it recurs continuously across a long session. The fleet slowly stops being productive while each dispatch still looks locally fine.

Applicability

Structure

The Structure diagram appears at the top of this page.

Sample Code

Topics are a closed const-string namespace, so a typo can't silently create a dead topic that disables a signal; every topic must carry a playbook entry, so a raw signal becomes an actionable reaction. The reactor loop is: poll, match topic to playbook, act.

TOPICS = {"cron.tick", "merge_train.yield", "tombstone.stuck"}   # closed registry — emit off-list = fail loud

PLAYBOOK = {
    "merge_train.yield": {"healthy": "0-1 per session", "wrong": "repeated yields",
                          "do": "open the merge-train recovery playbook"},
    "tombstone.stuck":   {"healthy": "none queued", "wrong": "queued with no progress",
                          "do": "inspect the dedup event, clear the stuck closer"},
}

def emit(topic: str, bus: list) -> None:
    assert topic in TOPICS, f"undeclared topic '{topic}'"        # typo-proof by construction
    bus.append(topic)

def react(bus: list) -> list[str]:
    actions = []
    for topic in bus:
        entry = PLAYBOOK.get(topic)                              # a topic with no entry is un-actionable noise
        if entry:
            actions.append(f"{topic}: {entry['do']}")
    return actions

Consequences

Example use within DocAble

Contents
© James C. Davis, 2026–present