Reference Implementation Architecture
This page explains how the Vultron reference implementation is structured. It is written for readers who want to understand the code before reading it: software developers assessing the implementation, and contributors orienting to the layout. It answers three questions: what the hexagonal boundary means, how an incoming message flows from Hyper Text Transfer Protocol (HTTP) delivery to behavior-tree execution, and what the reference implementation supplies versus what an adopter must build.
Reference implementation, not the protocol
Vultron is a reference implementation of the CVD coordination protocol. The structure described here — hexagonal layering, an ActivityStreams Vocabulary 2.0 (AS2) inbox pipeline, behavior-tree orchestration — is one valid way to realize the protocol. The Concept Taxonomy separates the abstract protocol (vultron-core, vultron-wire, vultron-transport) from any implementation of it. Other implementations may make different choices and still conform.
The core, wire, and adapter boundary
The reference implementation follows Hexagonal Architecture (Ports and Adapters), adopted in ADR-0009. The domain logic sits in an inner circle that knows nothing about how messages arrive, how they are formatted, or where state is stored. Everything external reaches the domain only through ports — typed interface contracts — and each port is fulfilled by an adapter at the edge.
The codebase splits into three concentric responsibilities.
| Layer | Python package | Owns | Must not import |
|---|---|---|---|
| Core | vultron/core/ |
Domain models, state machines, use cases, ports, behavior trees | vultron/wire/, vultron/adapters/, web frameworks |
| Wire | vultron/wire/as2/ |
AS2 vocabulary, structural parser, semantic extractor, activity factories | Core domain logic, web frameworks |
| Adapters | vultron/adapters/ |
HTTP inbox, CLI, persistence, outbound delivery, connectors | Core domain rules |
The wire format is a separate concern from the domain, not a subset of it.
AS2 was chosen because it maps cleanly to Vultron semantics (ADR-0005), but that alignment does not make it a domain dependency.
Core domain objects and wire classes form two structurally distinct branches with no shared base: a strict core branch and a lenient wire branch (ADR-0017, ADR-0099).
The core branch is authoritative, and AS2 is one of its serializations: a core object dumped with its AS2 aliases is the wire document.
The wire branch tolerates the loose, optional shapes that arrive over the network.
A domain type such as a case has no separate wire class: its as_ name is an alias of the core class.
We keep this boundary for two reasons.
Wire formats change, and the domain logic should not have to change with them.
Parsing and shape validation are edge concerns, so pushing them out of the core keeps the state-machine logic small and testable.
The rule that core carries no wire or framework imports (ARCH-01-001) is enforced by ratchet tests under test/architecture/, so a violation fails continuous integration rather than merely drawing a review comment.
vultron-core the concept versus vultron/core/ the package
vultron-core names the abstract protocol.
vultron/core/ names the Python package that implements it here.
The package is the reference realization of the concept, not the concept itself.
From HTTP delivery to behavior tree: the inbox pipeline
An incoming message crosses every layer of the hexagon in one pass. It arrives as AS2 JavaScript Object Notation (JSON) at an HTTP endpoint and leaves as a committed state change with any resulting messages queued for delivery. The stages below each do one job and hand off to the next.
---
title: Inbound message pipeline
---
flowchart TD
http["HTTP POST /inbox<br/>(AS2 JSON)"]
parse["AS2 parser<br/>structural validity"]
rehydrate["rehydrate<br/>resolve referenced objects"]
extract["semantic extractor<br/>AS2 pattern → MessageSemantics"]
dispatch["behavior dispatcher<br/>semantics → use case"]
bt["use case runs a behavior tree<br/>intake → guards → commit → effects"]
persist["persist via DataLayer<br/>and queue outbound messages"]
http --> parse
parse --> rehydrate
rehydrate --> extract
extract --> dispatch
dispatch --> bt
bt --> persist
A driving adapter receives the request.
The HTTP inbox lives in vultron/adapters/driving/fastapi/, returns 202 Accepted immediately, and runs the pipeline as a background task so the sender is never blocked.
The wire layer turns bytes into meaning.
The AS2 parser checks structural validity, rehydrate() resolves referenced objects into full inline objects, and the semantic extractor (vultron/wire/as2/extractor/) matches the activity against an ordered pattern registry to produce a domain-level MessageSemantics value.
The parser also decides what happens to every unrecognized key, at every depth (MV-11, ADR-0099).
A misspelling of a field the receiver reads refuses the whole activity, and any other unknown key is set aside and logged.
The semantic extractor is the single place where AS2 structure becomes domain intent (ARCH-03-001), so the rest of the core never inspects wire shapes.
The core layer decides what to do.
The behavior dispatcher (vultron/core/dispatcher.py) maps the extracted semantics to a use case through a table lookup, and the use case runs the appropriate behavior tree.
Routing all inbound case activity through the CASE_MANAGER — in the prototype, the Case Actor — is what lets a single behavior-tree execution produce a canonically ordered ledger entry (ADR-0021, ADR-0022).
The design that moves this orchestration into a core module behind a typed process_payload seam is recorded in ADR-0020.
For the protocol-level view of what these messages mean to a participant, see Protocol Event Flow.
Sending messages: the outbox
Outbound messages follow the mirror path.
A use case constructs a domain object, an activity factory in vultron/wire/as2/factories/ renders it to a conformant AS2 activity, and a driven delivery adapter puts it on the wire.
Every actor communicates with every other actor over HTTP. The reference implementation retired an earlier in-process shortcut so that co-located actors talk to each other exactly as remote actors would (ADR-0042). This keeps the transport uniform: the same JSON payload is deliverable whether the recipient is in the same process or across the internet.
Delivery is treated as unreliable and bounded.
The outbox retries with backoff, classifies 4xx responses as terminal, and moves an activity to a dead-letter store once its per-activity attempt budget is exhausted rather than retrying forever (ADR-0066).
When the exhausted activity is an Announce(CaseLedgerEntry), the dead-letter record also carries the URI of the canonical ledger entry being replicated, so replica divergence is observable without log access (OX-14-001, OX-14-003).
Delivery is also ordered per recipient: one drain runs per actor outbox at a time, rows addressed to the same recipient are delivered in the order they were enqueued with at most one in flight, and a row that fails is re-queued ahead of the rows behind it for that recipient, while rows for other recipients proceed (ADR-0112).
This is what keeps a ledger fan-out from reaching a replica out of order and turning the receiver's gap recovery into the normal path.
An outbox holds at most one pending row per activity id, so queuing an id that is already waiting adds nothing (OX-06-002).
A retry that rebuilds an activity under the id its first attempt used therefore sends it once.
An outbound activity always carries a non-empty to: field (OX-08-001).
It also carries full inline objects, because a recipient can only act on what it has received or can fetch, and the reference implementation cannot yet fetch an object by reference (AKM-03-003; see the Actor Knowledge Model).
Behavior trees drive the state transitions
Inside a use case, protocol-significant work runs as a behavior tree (BT). A behavior tree is a hierarchy of nodes ticked from the root; each node returns Success, Failure, or Running, and the control-flow nodes above them compose those results into higher-level behavior. Vultron uses this structure because CVD activities compose the same way: validate a report, propose an embargo, publish an advisory — each is a small tree that slots into a larger one (ADR-0002).
On the received side, a tree first stores what arrived (intake), then runs guards, commits the ledger entry, and fires effects, in that order (CLP-10-006, ADR-0111). A read or write of Report Management (RM), Embargo Management (EM), or Case State (CS) goes through a dedicated state node rather than touching state inline, which keeps every transition auditable.
This page stays at the summary level by design. The Behavior Logic section develops the trees in depth: the canonical tree structure, the per-domain subtrees for RM, EM, and the Do Work behaviors, and the node notation. The behavior trees illustrate one conformant implementation; the protocol does not require an adopter to use them.
Call-out points: where implementers plug in
A behavior tree can reach a step it cannot decide on its own: whether a report is credible, whether to accept an embargo proposal, or whether it is time to publish. These are call-out points — locations where automated execution pauses and waits for an answer from an external service.
During development and simulation, a call-out point is filled by a Fuzzer Node that returns a probabilistic success or failure. In production, an adopter replaces that stub with a real service that satisfies one of four capability shapes — Evaluator, Retriever, Composer, or Actuator (ADR-0097, BT-18-013). In the hexagonal layout, a capability shape is a port and a concrete capability is the adapter that fulfills it.
Not every external question is a call-out point. A question that needs a decision from another actor — for example, whether the Case Owner approves a change — cannot be answered while the tree is running, so the actor sends a request and finishes instead of pausing (see when an actor must ask permission).
The Capability Model page is the full taxonomy: the two integration surfaces, the four shapes, the Sentinel call-in pattern, and the catalog of known call-out points by domain.
What the reference implementation provides, and what you supply
The extension boundary is the set of ports. The reference implementation provides the protocol machinery: the five state machines, the wire vocabulary and its semantic mapping, the inbox and outbox pipelines, the behavior trees that orchestrate transitions, and the persistence and delivery adapters that make a local actor run. What an adopter supplies are the capabilities behind the call-out points — the judgment and the connections to outside systems that the protocol deliberately leaves open.
This division also shows in how conformance is tested.
The specification organizes conformance tests in four layers, L1 Syntax to L4 Process (§12.5 Conformance Testing Approach).
A layer says what a test checks, and is a separate question from the capability sets an implementation claims, which say what it provides (§12.2 Capability Sets).
Correct message syntax and correct state transitions (L1 and L2) come from using a conformant serializer and implementing the state machines.
Correct observable behavior (L3) — the right messages emitted and the right states reached — is stated by the behavioral conformance specifications.
Correct internal decision structure — precondition checks before state writes before effects — is L4, which is only enforceable against a reference implementation, and the vultron/core/behaviors/ behavior-tree layer is that demonstration.
An adopter therefore has a spectrum of choices. A minimal participant reuses the protocol machinery and supplies only the capabilities it needs. A different-language implementation reuses the concepts — the boundary, the pipeline stages, the port seam — without reusing this code at all.
Further reading
- The Case Model — the domain objects the pipeline reads and writes
- Protocol Event Flow — what the messages mean to a participant
- Behavior Logic — the behavior trees in depth
- Capability Model — the call-out point taxonomy and how to build a capability
- Concept Taxonomy — vultron-core, vultron-wire, and vultron-transport as distinct concepts
- Process Implementation Notes — integrating the protocol into an existing workflow system
- Federation — future work on a deployment in which each organization runs its own coordination service
- ADR-0009 — Adopt Hexagonal Architecture
- ADR-0017 — Domain/Wire Object Separation
Reading path: You want to work on the Vultron reference implementation
This page is part of Understand how it is built.
Before this page:
After this page: