Early Access · beta. Help test and shape the 1.0 release. Where the project stands →

Architecture

How MessageFoundry fits together

Explore the components, process boundaries, message flow, and interface wiring below. For the detailed reference, read ARCHITECTURE.md. The engine repository also lists every architecture diagram.

MessageFoundry started in May 2026 and is in Early Access. Read the project status →

1 · The whole system

Top-level components

The system includes operator tools, configuration, service commands, test tools, and a migration tee relay. Operator tools connect to the engine through its local API.

See the whole toolkit, up close →

Top-level components — the whole system A central headless engine sits behind an API that binds 127.0.0.1 by default and is served over TLS. Independent components surround it. They are the operator tools (admin console, VS Code extension), the version-controlled author-time config, and the CLI and Windows service that run it. They also include dev and test tooling, live message traffic over any Connection type, a standalone migration tee relay, and the CI-to-PyPI release path. HTTPS / WSS loads config runs · MLLP any Connection type shadow Operator tools Admin console · VS Code extension API — 127.0.0.1 by default · TLS · auth · RBAC Engine — headless asyncio pipeline · transports · parsing store · config · auth Store SQLite · PostgreSQL · SQL Server Author-time config Connections · Routers · Handlers + environments · version-controlled Run it · dev / test Windows service · CLI generators · test harness Live message traffic Upstream senders ↕ downstream receivers Build / release CI: tests · SAST · SBOM · sign → PyPI · Trusted Publishing Migration — standalone tee relay parallel-run parity vs a legacy engine · own SQLite egress-suppressed shadow — imports no engine code
2 · Components & boundaries

System topology

The engine runs as a service. Separate client processes connect only through its localhost API. The API depends on the engine; the engine never imports the API or a client.

System topology The admin console in the browser, the VS Code extension, and the test harness are clients of the API. The API binds 127.0.0.1 by default and is served over HTTPS and WebSocket, with auth and RBAC. The test harness also sends and receives MLLP as a test partner. The headless engine's pipeline depends on transports, parsing, the store (SQLite, PostgreSQL, SQL Server), and config. depends on over the API / wire Admin console in the browser · /ui VS Code extension authoring + ops Test harness API client · MLLP test partner LOCALHOST BY DEFAULT · ONLY CLIENT API API — HTTPS + WebSocket auth · RBAC · hash-chained audit ENGINE — HEADLESS ASYNCIO SERVICE Pipeline listener · router · transform · delivery Transports MLLP · TCP · HTTP SOAP · DB · files (SFTP/FTP/FTPS) Parsing HL7 v2 parser, strict validation; X12, DICOM, FHIR and XML codecs Store SQLite · PostgreSQL · SQL Server AES-256-GCM Config Connections · Routers · Handlers
3 · Runtime message flow

The durable staged queue

The engine acknowledges receipt after it commits the message to the ingress stage, before routing, transforms, or delivery. Each handoff uses one committed transaction. The stored stages support retries and replay without a separate broker.

Runtime message flow through the staged queue An inbound message is decoded and parsed by the listener, committed to the ingress stage, and ACKed on receipt. It then moves through routed and outbound stages via committed transactions — router, transform, and delivery workers — before reaching the outbound connection. Each message earns a disposition finalized by the store. listener router transform deliver ACK (AA) — on receipt Inbound connection ingress raw committed routed one row / handler outbound one row / dest. Outbound idempotent send DISPOSITION · count-and-log, finalized by the store decode/validate failures NAK synchronously, pre-ingress RECEIVED → ROUTED / UNROUTED → PROCESSED / FILTERED / NOT_DEPLOYED / ERROR

The engine commits each message before acknowledgment and each stage handoff in one transaction. After a crash, it can repeat the last step without skipping it. Normal operation delivers each message once. A crash after a downstream send, but before recording success, can cause that message to be sent again. This is at-least-once delivery. A repeated HL7 message keeps the same MSH-10 control ID, so the downstream system can detect and remove duplicates.

On anonymized, representative traffic, one inbound interface measured ~50 messages/second on commodity hardware. That is about 180,000 in a peak hour, or roughly 1.6 million a day under the traffic pattern below. The engine stores each message before acknowledgment and delivers in strict first-in, first-out order per interface.

Daily estimates account for uneven traffic. In de-identified ADT traffic from a live hospital feed, the busiest hour was roughly 2.7× the daily average. Messages averaged 11.5 KB, giving about 18 GB/day at that estimated volume. Message sizes vary by feed. Base64-encoded documents or images can greatly increase storage and bandwidth needs. Scale by splitting feeds at the source across interfaces, with an optional multi-node tier. Total capacity does not grow as a simple sum of single-interface rates. Measure your transforms, storage, and hardware. Read the throughput measurements →  ·  Throughput & capacity (PDF) ↓

4 · Config wiring

Connections, Routers, and Handlers — wired by name

Configuration links components by name. An inbound Connection names a Router, which selects Handlers. Each Handler sends to outbound Connections. There is no enclosing channel object. Use guided wizards or write the configuration in Python.

Config wiring graph An inbound connection IB_ACME_ADT (MLLP) names a router. The router forwards by name to two handlers — to_EHR and to_archive — which Send to outbound connections OB_EHR_ADT (MLLP) and OB_ARCHIVE (File). names by name Send Send IB_ACME_ADT inbound · MLLP @router filters · forwards by name @handler: to_EHR filter → transform @handler: to_archive filter → transform OB_EHR_ADT outbound · MLLP OB_ARCHIVE outbound · File
Why wire it this way

Reuse components by name

Define each component once, then reference its name wherever you need it.

  • Reuse a named destination, transform, or router across interfaces without copying its definition.
  • Add a destination with another Send, or reroute a feed by changing a name. Review those changes in version control.
  • Configuration checks reject unknown routers, missing handlers, duplicate names, and duplicate ports.
  • Test each Router or Handler as a separate function. Teams or AI agents can work on independent parts in parallel.

See it run for yourself

Follow the quickstart to route a test message, or read the feature list and engine documentation.