Appendix B - 2. Agent-orchestration model (developer journeys)

The Structure of Agent-orchestration model (developer journeys) — its shape at a glance:

Two typed state machines carry the developer journey. The agent lifecycle runs from dispatch to a clean teardown, with an abandoned/recovered branch; the orchestrator loop observes a completion, lands it, refills the freed slot, and banks state. A drift gate reconciles the declared states against the live registry's event vocabulary.

stateDiagram-v2
  [*] --> Dispatched
  Dispatched --> Working: prepare
  Working --> Landed: commit + verify
  Working --> Abandoned: stream-silence
  Abandoned --> Working: recover
  Landed --> Tombstoned: clean
  Tombstoned --> [*]

Accessible description: an agent moves from dispatched to working to landed to tombstoned, with a branch where a stalled agent is abandoned and later recovered back to working. The declared states are what a drift gate checks against the live registry.

Projected from the catalogue entry system-models / Agent-orchestration model (developer journeys).

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

Intent

Intent — Model the agent fleet and the orchestrator's own loop with the same MBSE method you point at the product: typed lifecycle states, typed seams, and invariants whose verification tier is derived (not hand-typed), all held to reality by a drift gate. The substrate that produces the software becomes as checkable as the software. It is the developer-journey view (dispatch→work→land→tombstone; the refill/bank loop), not the user-journey view (our instance: an agent_orch model that reuses the product state-machine's tier-derivation and drift machinery, and carries the reflection-facet registry as a first-class modeled node).

Motivation

The bridge models tie agents to the product, but the fleet that builds the product usually goes unmodeled. Its lifecycle (dispatch → prepare → sentinel → work → commit → land → tombstone → clean, with the abandoned/recovered branch) and the orchestrator's own loop (observe a completion → land + run the definition-of-done → refill the freed slot → bank state if the freshness window lapsed → rest) live implicitly, scattered across the dispatch tool, the registry, the tombstone tool, the worktree tool. Nothing names them as typed state machines, so the failures are all invisible: an agent reaches a state no transition allows; the orchestrator rests with ratified work still queued; a fleet invariant (exactly-one-live-marker per agent, upload-before-teardown, exactly-one-completer) is asserted nowhere; and the reflection substrate the fleet runs on itself drifts from what anyone modeled. The fleet is the one system the team operates every hour and the one system it never drew.

Applicability

Structure

The Structure diagram appears at the top of this page.

Sample Code

The lifecycle is a transition table, not scattered flags. A guard function accepts a move only if the table allows it, and a reconcile step fails when a declared state has no counterpart in the live event vocabulary — the check that keeps the model equal to the fleet it describes.

import sys

# The fleet lifecycle as an explicit transition table (the source of truth).
TRANSITIONS = {
    "dispatched": {"working"},
    "working":    {"landed", "abandoned"},
    "abandoned":  {"working"},          # recover branch
    "landed":     {"tombstoned"},
    "tombstoned": set(),
}

def legal(frm: str, to: str) -> bool:
    return to in TRANSITIONS.get(frm, set())

def reconcile(live_events: set[str]) -> list[str]:
    """Every declared state must appear in the live event vocabulary, and vice versa."""
    declared = set(TRANSITIONS)
    findings  = [f"declared state '{s}' has no live event" for s in declared - live_events]
    findings += [f"live event '{e}' has no declared state"  for e in live_events - declared]
    return findings

if __name__ == "__main__":
    # `read_registry_vocab` returns the state names the live registry actually emits.
    findings = reconcile(read_registry_vocab())
    for f in findings:
        print(f"MODEL-DRIFT: {f}")
    sys.exit(1 if findings else 0)

Consequences

Example use within DocAble

Contents
© James C. Davis, 2026–present