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.

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, reached only through a localhost API, surrounded by independent components: operator tools (admin console, VS Code extension), version-controlled author-time config, the CLI and Windows service that run it, dev and test tooling, live HL7 traffic, a standalone migration tee relay, and the CI-to-PyPI release path. HTTP / WS loads config runs · MLLP MLLP / file shadow Operator tools Admin console · VS Code extension API — 127.0.0.1 auth · RBAC · only external surface 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 HL7 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, VS Code extension, and test harness reach the engine only through a localhost API with auth and RBAC. 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 separate process VS Code extension authoring + ops Test harness synthetic traffic LOCALHOST · THE ONLY EXTERNAL SURFACE API — HTTP + 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 deep parse; JSON / XML / X12 routed 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 / 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.