izawi · engineering · atlas architecture event → decision → order → record

Atlas: an event-driven trading platform, built by an agent fleet I designed and govern.

Two layers. The runtime is a set of small services on a durable event bus, built around one rule: when the system is unsure, it stops and says so. The build system is the fleet of AI agents that writes that runtime, under a review process whose strictest gate sits at the money boundary.

I don't hand-type most of this code. I designed the architecture, wrote the specifications, set the review rules, and I own what ships.

Every box below is drawn from the running system. No account data, no strategy parameters.

01 · Runtime

Small services, one durable bus, one source of truth

Market and chain data come in, get normalized, and flow as typed events through NATS JetStream. Each service owns one decision and records it in Postgres before anything downstream acts on it. Every step in the logical flow below is an event on the bus.

← scroll the diagram →

Atlas runtime topology Sources feed ingest, then strategy, risk, portfolio, and separate paper and live execution services, which reach broker venues. All services share a NATS JetStream bus. Postgres holds state; an outbox relay publishes committed events; a dead-letter consumer quarantines bad events; controls include the kill switch, control posture, and an independent reconciliation against the broker. A separate regime engine writes advisory classifications into Postgres. SOURCES exchange & broker market data derivatives: funding · OI own Bitcoin full node macro series ETF flow data INGEST normalize, stamp provenance missing ≠ zero STRATEGY market state → trade intent RISK allow · reject · resize; each decision durable PORTFOLIO order requests; position truth, single writer PAPER EXECUTION simulated fills LIVE EXECUTION separate service; ships disabled VENUES Alpaca paper + live simulated: Hyperliquid Coinbase · Kraken NATS JETSTREAM durable streams · per-consumer acks · replay · typed, versioned event envelopes REGIME ENGINE separate service daily full replay hysteresis state machine advisory (shadow) POSTGRES orders · fills · positions · balances risk decisions · kill-switch & control history an append-only record of every transition state change + outbox row: one transaction OUTBOX RELAY publishes only committed events: no dual write, no lost message DEAD LETTER invalid or undeliverable events are quarantined, never dropped CONTROLS kill switch: persisted, fail-closed control posture: halt · defensive reconciliation: our book vs the broker's, read separately monitoring → a human independent broker read
Arrows show the logical flow; every hop travels as an event on the bus. Paper and live execution are separate deployables by rule. A single service with a paper/live flag is forbidden.

Why a durable bus

JetStream gives durable streams, per-consumer acknowledgement and replay, with a much lighter operational footprint than Kafka on a small lab cluster. A consumer that crashes resumes from its last ack instead of losing the gap.

Transactional outbox

A service writes its state change and the event describing it in one database transaction; a relay publishes afterwards. So a crash can never leave the database and the bus disagreeing.

Structural isolation

Broker SDKs are imported only inside execution adapters. Strategy, risk and portfolio code cannot place an order even by mistake, and live execution is a separate service that ships disabled.

Contracts, not conventions

Events travel in versioned envelopes checked against a registry. An event that fails its contract is quarantined for a human, not silently dropped or half-applied.

02 · Failure handling

An order, failing safely

The hard question for any execution system: the process sends an order, then crashes or times out before it learns the result. Did the order go through? Atlas answers with a stable client order ID, a lookup at the broker before any retry, and a hard stop when the lookup can't settle it.

← scroll the diagram →

Live order submit: six fail-closed gates and the unknown-outcome path An order passes six gates in sequence: freshness, synchronous risk re-verification, kill switch, control posture, reduce-only check against broker positions, and submit with a stable client order ID. Any gate failure refuses before the broker, persists the refusal and alerts. If the submit outcome is unknown, the order is looked up at the broker by client order ID: if found it is adopted with no duplicate; if not found, the kill switch trips and a human is paged. 1 · FRESH AND WELL-FORMED stale command, wrong identity or environment 2 · RISK DECISION RE-VERIFIED the durable decision must match this exact order 3 · KILL SWITCH tripped, or unreadable → blocked 4 · CONTROL POSTURE halted, or posture unknown → fail closed 5 · REDUCE-ONLY vs BROKER TRUTH an exit is checked against the broker's own positions 6 · SUBMIT WITH A STABLE CLIENT ORDER ID derived deterministically from the order request REFUSED BEFORE THE BROKER the refusal is persisted with its reason an alert goes to a human no order leaves the building ONE EXCEPTION, GATE 3 ONLY a verified risk-reducing exit may pass a TRIPPED switch, never an UNREADABLE one. a switch that cannot be read is unknown, and unknown is not the same as tripped. getting out of risk should never be blocked by the switch meant to limit risk. the exemption is narrow and still passes gates 1, 2, 4, 5. ACCEPTED acceptance persisted fills recorded reconciled vs the broker OUTCOME UNKNOWN timeout · network error · accepted with no order id → never blindly retry not found / ambiguous FOUND AT THE BROKER looked up by client order id → adopted. no duplicate order is ever sent STOP THE WORLD trip the kill switch persist the evidence page a human
The live execution path, as built. When a fact can't be established, the system stops and pages a human rather than guessing.

Idempotency by construction

The client order ID is derived from the order request itself, so a retry or replay produces the same key. The broker can recognize a duplicate, and we can look the order up by that key.

Reconciliation is a separate witness

A separate service reads the broker's own account through a client whose whole surface is read-only calls, then compares it with our book to the cent and to the share. A discrepancy is recorded with its amount and sent to a human.

Where limits live

Per-trade limits are the risk service's durable decision, and execution re-verifies that decision synchronously at submit. The switch and control posture sit above both. Changing any of them is a gated action (see 04).

03 · One algorithm, in detail

The regime state machine, and why it doesn't flap

The regime engine classifies the Bitcoin market into one of six cycle states from five groups of signals. A naive classifier flips state every time an input crosses a threshold. This one uses hysteresis: entering a state is easy to justify, but leaving it takes sustained evidence.

Regime cycle: six states with an UNKNOWN centre Six states arranged clockwise: capitulation, accumulation, recovery, bull expansion, euphoria, bear decline, then back to capitulation. UNKNOWN sits in the centre and is used only when data is missing. CAPITULATION ACCUMULATION RECOVERY BULL EXPANSION EUPHORIA BEAR DECLINE UNKNOWN missing data only
States advance clockwise. Without a shock, a transition may move at most two steps forward.

Dwell

A state must hold for a minimum number of days before it can be left. The count lives in config, not in code.

Exit buffer

A held state counts as broken only when an input is beyond its cutoff by a margin. A value wobbling on the line can't cause a flip.

Edge-triggered shocks

A market shock may bypass dwell and the step limit, but only once per shock event. An earlier level-triggered version held the gate open for days after a single crash and produced one-to-four-day flips. Review caught that.

Regimes persist

If no rule fires on a given day, that's a gap in rule coverage, not a fact about the market, so the state holds. UNKNOWN is reserved for missing data. Before this change, 29% of days were UNKNOWN and 42% of spells re-entered the state they had just left.

Deterministic replay

Hysteresis makes the state path-dependent, and inputs arrive late. So each run replays the full history from the earliest data instead of appending to yesterday's answer. Writes are idempotent upserts.

No magic numbers

Every decision cutoff is loaded from one config file, and CI rejects numeric literals in the decision code. A cutoff still marked draft makes its rule unevaluable, so the rule can't fire on it.

04 · The build system

How an agent fleet ships code without being trusted

Every change starts as a written specification in the task tracker and ends as a reviewed merge. No agent approves its own work. The dispatcher never merges, and review comes from a different model family than the builder. Changes that touch money go through a stricter path with a human at the end.

← scroll the diagram →

Agent build pipeline and the money boundary Tracker task spec, dispatcher, builder in an isolated worktree, run report with failure classifier and verifier, independent review by a different model family, merge by an agent operations lead, and deploy verified by effect. A refused review loops back to the builder. Changes to live execution, routing, risk limits or arming go through the money boundary: hand-edited, a second key from an independent risk-auditor agent with its own tests and mutations, and explicit human approval recorded before the action. TRACKER task = spec: goal, constraints, acceptance tests DISPATCHER picks CLI + model isolated git worktree per task BUILDER Codex by default; Claude · Gemini · Kimi on demand RUN REPORT failure-mode classifier + verifier REVIEW a different model family reads the diff vs the spec MERGE an agent ops lead merges; the dispatcher never auto-merges DEPLOY verified by its effect inside the running service, not a commit hash refused → rework, with the reason THE MONEY BOUNDARY live execution · order routing · risk limits · arming → hand-edited by the ops lead, never dispatched to a builder → second key: an independent risk-auditor agent writes its own tests and mutates the change; reverting it must turn them red → explicit human approval, recorded before the action gate
The agent roles are real seats. An operations lead runs the build pipeline. A risk auditor sits outside it and holds exactly one veto, at the money boundary.

Specs over prompts

A task is a structured spec with acceptance tests, rendered into the builder's prompt once. Anything a builder needs has to be in the spec. A comment added later never reaches it.

Review that can say no

A reviewer that can only approve is decoration. Refusals are routine, carry a named defect and fix, and loop back to the builder.

The record precedes the action

Decisions at the money boundary are written to an append-only ledger before they take effect. A row written afterwards is a story about an action, not an authorization for it.

05 · Where the tools fall short

What agents get wrong, and the review that catches it

AI-written code fails in recognizable ways, and most of them look like success. These are real failures from this system, each now a standing review check.

A green suite that never ran the code

Over a thousand tests passed, and none of them called the function under review. Every sign-off now proves the suite reaches the change: mutate the change, and a named test must go red.

Each side correct, the join wrong

Two components, each correct against its own tests, were wrong together. Neither suite could see it. Composition defects need a test that spans the seam.

The near-neighbour question

A freshness check read a different table than the one it vouched for, and stayed green exactly when it was needed. Before trusting an instrument, state what it literally measures.

Unknown is not tripped

A draft of the exit exemption in 02 caught any kill-switch error, so an unreadable switch would have let orders through. Review narrowed it to a confirmed trip and added a test proving an unreadable switch still blocks.