Platform Architecture

TL;DR

Aurum splits a trading application into four product surfaces — Strategy, CLI, Forge, and Terminal — connected by two protocols (SSH for deployment, REST/WebSocket for operations). Each surface can fail independently. This page explains why the boundaries exist, how data flows between them, and what happens when a boundary fails.

Why this architecture?

Before Aurum, a trading system was typically built as a single process that did everything: connected to exchanges, ran strategy logic, recorded results, and served a dashboard. This worked until it didn’t. When the dashboard crashed, it took the strategy with it. When the exchange connection flapped, there was no record of what happened. When you wanted to deploy a new version, you had to stop everything.

Tip

If you’re new to Aurum, read this page once to build your mental model, then return to it when you’re debugging a production issue. The failure-boundaries section at the bottom is the most practically useful part.

Aurum separates these concerns into four surfaces with explicit failure contracts. The guiding principle is: a process can fail without taking its neighbors with it, and every failure leaves evidence.

Follow the data

A BTC/USDT one-minute bar arrives at the OKX WebSocket. Here is the journey it takes through Aurum:

Exchange WebSocket
  |
  v
venue::okx adapter          ← decodes exchange protocol into Aurum schema
  |
  v
Engine MessageBus            ← orders events by logical time
  |
  +--> on(TOPIC<"bar://okx/BTCUSDT@1m">)   ← your strategy handler
  |
  +--> Runtime WebSocket     ← live data for Terminal and API clients
  |
  +--> Kronos journal drain  ← writes to ClickHouse for durable history
           |
           v
      ClickHouse
           |
           v
      Forge history API      ← Terminal queries history through Forge

Each arrow in this diagram is a boundary. At each boundary, Aurum makes a deliberate choice about what can fail and how that failure is surfaced.

The four surfaces

.at definitions --Athanor--> generated C++
                                    |
venue packages --> C++23 strategy + SDK --> aurum CLI
                                    |             |
                              local runtime       +--SSH--> Forge
                                                      |
                                    Terminal <-- REST / WebSocket

Strategy and SDK

A strategy is your executable. It derives from sdk::Application, binds venue or paper devices, reacts to typed topics, and submits typed effects. The SDK supplies the runtime, scheduling, resource catalog, query model, telemetry, and live WebSocket surface inside that process.

Note

A strategy is an active program that declares its own event sources and effects. It is not a collection of framework-owned callbacks. Aurum calls your handlers when data arrives; your code owns the control flow.

The SDK is designed around a core insight: trading strategies are event-driven, not request-driven. Market data arrives on the exchange’s schedule, not yours. An order fill can arrive seconds after submission. A timer fires at a configured interval. The SDK’s job is to order these events correctly and deliver them to your handlers with minimal overhead between the network and your logic.

Athanor is optional and runs before the C++ compile step. Its generated factor plugins execute inside the same strategy process as handwritten code — there is no Athanor service in production.

CLI and Forge

The aurum CLI has two independent roles:

  • Local development: it creates, compiles, and runs an installed SDK project on your machine.

  • Remote delivery: it delivers immutable project versions to a named Forge target over SSH and invokes lifecycle operations there.

These roles share a binary but not a network path. A local aurum run . does not touch SSH. A remote aurum push does not need a local compiler.

Tip

The CLI and Terminal use different Forge addresses. The CLI uses an SSH target (user@host:port). Terminal uses an HTTPS URL (https://host/api/v1). They are separate by design: deployment access and browser access should use different authentication and network policies.

Forge is the public server boundary. It has three internal data planes:

Plane

Interface

Purpose

Control

REST instance endpoints

Inventory, health, lifecycle, and logs.

Live

Runtime REST and WebSocket

Capabilities, resource discovery, current values, and subscriptions.

History

Forge history REST

Run-scoped summaries and durable resource rows.

These are related signals, not one shared state flag. Forge health can be good while aurumd is unreachable; live subscriptions can work while history is unavailable. The product surfaces expose these distinctions so clients do not invent system state from one convenient signal.

Note

Forge uses aurumd for inventory and lifecycle, the runtime process for live data, and ClickHouse for durable history. Terminal — and any external integration — connects only to the Forge API. It does not connect directly to aurumd, a runtime process, or ClickHouse.

Terminal

Terminal is a browser API client. It receives one public Forge API base URL and derives runtime WebSocket URLs from the same address. It does not need the CLI’s SSH target, a daemon socket, a strategy port, or a database address.

This separation enables a critical operational pattern: deployment access, browser access, and internal service networking can use different policies. A developer pushing code over SSH does not need the same network path as an operator viewing a dashboard over HTTPS.

Terminal has three views for each project, each answering a different question:

Question

View

Primary source

How did the strategy perform?

Summary

Historical aggregation

What is the runtime publishing now?

Inspect

Catalog + WebSocket

Why is data missing or an order stuck?

Debug

Runtime queries, events, logs

Failure boundaries

This is the most important section on this page. Each surface boundary can fail independently. Understanding these failure modes is the difference between diagnosing an incident in 30 seconds and 30 minutes.

Failure

What it means

Forge API is healthy, aurumd is unreachable

The API process is running but the control-plane daemon is down. Lifecycle commands will fail; inventory may be stale; running strategies are unaffected.

Process is running, runtime endpoint is degraded

The OS process is alive but the Engine WebSocket is not accepting connections. Live data and queries are unavailable; logs and process state are still reachable.

Live subscriptions work, history is unavailable

The runtime is publishing; ClickHouse or the history query path is failing. Terminal charts show live data but no backfill.

Project is stopped, historical runs remain

A stopped project retains all previous run_id data in ClickHouse. Terminal can still query history for past runs.

One capability is absent, the rest of the project is healthy

For example, a factor server has no ledger. This is the correct contract, not a failure. Terminal shows capabilityErrors as evidence rather than inferring a health classification.

Warning

Do not treat one signal as proof of another. A running process does not imply a healthy runtime. A successful /health response does not imply aurumd is reachable. A capability set to false can be the correct contract for that project — inspect capabilityErrors before treating it as a failure.

Operator interfaces expose these distinctions so clients do not invent state from one convenient signal. When debugging, start at the boundary closest to the symptom and work inward:

1. Terminal shows no data → check Forge /health and /doctor
2. Forge API is healthy → check instance status
3. Instance process is running → check runtime endpoint
4. Runtime is available → check the specific capability
5. Capability is true → check the specific resource key

Design principles

These principles explain why the architecture looks this way. They are not rules to memorize — they are the reasoning behind decisions you will encounter throughout the documentation.

Compile-time over runtime. Push errors to build time wherever possible. Topic keys are validated at compile time. Schema mismatches are compile-time errors. The cost of a build failure is paid once by the developer; the cost of a runtime data error is paid by every operator who touches the system.

Separate signals, don’t synthesize state. Each data plane reports its own truth. The API does not guess whether a project is “healthy” — it reports process state, runtime state, and capability flags separately. Clients combine them according to their own operational model.

Immutable releases. A published name@version is permanently registered on Forge. You cannot overwrite it. Increment the version and push a new release. This means every deployment is auditable and every version is rollback-capable.

The narrowest interface that works. Forge exposes one public API. Terminal uses it. External integrations use it. No one connects directly to aurumd, a runtime port, or ClickHouse. This keeps the integration surface small enough to version, secure, and test.

What Aurum is not

Aurum is not an exchange gateway SDK. It does not normalize every venue into one abstraction — it adapts venue protocols into Aurum schemas and gives you the typed result.

Aurum is not a backtesting framework with a built-in data feed. It replays your own recorded data through your own strategy process. There is no shared historical market database.

Aurum is not a low-code platform. Strategies are C++23. Factors are Athanor .at definitions that compile to C++. The abstractions are in the SDK, not in a configuration language.

Next steps