The whole system in one pass
T3 Trade extends the T3 Code agent environment with supervised, budgeted trading on Hyperliquid testnet. This page shows a new engineer what the fork keeps, what it adds, which safety rules are fixed, and how a trade moves from server startup to a UI refresh.
apps/server/src/trading/HyperliquidExecutionService.ts:4
apps/server/src/provider/builtInDrivers.ts:47-53
apps/server/src/trading/TradingControlService.ts:26-27
apps/server/src/trading/MissionTransitions.ts:19
A coding environment extended for supervised trading
T3 Trade starts from T3 Code, an agentic coding environment, and makes trading the
point. The architecture doc says it in one line: the project is "a fork of T3 Code
with supervised, budgeted trading on Hyperliquid testnet as its center"
docs/internals/overview.md:5-6. The repository's own
instructions open the same way: keep the coding-agent workspace, add supervised
trading, and treat the trading system, not the upstream product, as the center
AGENTS.md:3.
The fork keeps the inherited runtime: agent sessions, workspaces, version control, and
clients that talk to the server over one authenticated Effect RPC WebSocket
docs/internals/overview.md:7-8. The server is the execution
boundary. Every provider process, terminal, git operation, and filesystem read happens
there, never in the client docs/internals/overview.md:8-10.
The fork adds a trading runtime that rides the same loop as coding: services in
apps/server/src/trading, with shared types and pure policy
in packages/trading-contracts
docs/internals/overview.md:119-122. Agents reach it through
the typed t3-trade MCP toolkit
docs/internals/overview.md:121-123. Exactly one service can
submit orders that spend testnet capital: "This is the only code path that spends
testnet capital"
apps/server/src/trading/HyperliquidExecutionService.ts:4.
The Execution page walks it.
The venue is pinned in the type system:
TradingEnvironment is the single literal
"hyperliquid_testnet"
packages/trading-contracts/src/account.ts:13. There is no
mainnet path to introduce by accident AGENTS.md:15.
The boundaries the repository will not cross
The stated priorities are correctness, clarity, and restraint, in that order
AGENTS.md:7-9. One rule takes precedence over all three:
"Trading safety outranks convenience." Execution guards, loss budgets, protection
requirements, authority checks, idempotency boundaries, and reconciliation rules are
never weakened to make an agent or UI flow pass
AGENTS.md:11. The
Safety invariants page enumerates them.
AGENTS.md:15. The environment is a single type literal
packages/trading-contracts/src/account.ts:13, and the one
spending path resolves its signer fail-closed as its first step
apps/server/src/trading/HyperliquidExecutionService.ts:7.
apps/server/src/trading/HyperliquidReconciler.ts:5-7.
Local state records T3 Trade's decisions; the exchange records what actually
happened AGENTS.md:16. The
Reconciliation page shows the convergence path.
apps/server/src/trading/TradingProtectionService.ts:4-8.
Take-profit works differently: it wakes the agent instead of placing a resting order
apps/server/src/trading/TradingProtectionService.ts:34-36.
See Risk control.
apps/server/src/trading/InterimSignerConfig.ts:29-32. The
only signature point is a serialized nonce lane
apps/server/src/trading/HyperliquidExecutionService.ts:12,527. Under vitest the key-file source is disabled, so no test inherits the developer's
real key
apps/server/src/trading/InterimSignerConfig.ts:26-27.
apps/web/src/components/trading/tradeHomePresentation.ts:87-89. The wiring proves the claim: backtest and validation services get dependency sets
that cannot reach an order
apps/server/src/trading/runtimeLayer.ts:221-235. Covered
in Research mode.
apps/server/src/trading/TradingControlService.ts:4-8,26-27. They bypass the harness checklist but never the signer, the nonce lane, or
reconciliation
apps/server/src/trading/TradingControlService.ts:19-24.
apps/web/src/routes/trade.tsx:3-6; the desktop app is an
Electron shell that loads that same web app
apps/desktop/src/window/DesktopWindow.ts:623. Mobile is
inherited upstream code: nothing under
apps/mobile/src references trading. Tour in
Web & desktop.
apps/server/src/trading/HyperliquidExecutionService.ts:17-23. So the execution record and its risk reservation are persisted before signing,
and a retry reads that record back and refuses
apps/server/src/trading/HyperliquidExecutionService.ts:11,17-19. Failure stories records how this rule was earned.
How the parts stack
Clients send typed WebSocket requests. The server converts each request into a command, and a pure function decides which events to record. One transaction stores those events and updates the read models. Queued workers perform external work, trading services call the exchange, the archive records history, and the relay links environments.
The command spine
Clients never mutate state directly; they dispatch typed commands
docs/internals/overview.md:63-66. The engine serializes
them through one in-process queue and fans committed events out to every subscriber
apps/server/src/orchestration/Layers/OrchestrationEngine.ts:101-102. A pure decider turns each command into events, and one SQLite transaction appends
the events, folds the read models, and writes the receipt together
docs/internals/overview.md:73-78.
Providers behind adapters
Five drivers ship built in: Codex, Claude, Cursor, Grok, OpenCode
apps/server/src/provider/builtInDrivers.ts:47-53. The
routing layer resolves turns "without knowing which agent is behind them"
docs/internals/overview.md:111-115. Provider-specific
complexity stays at the adapter boundary.
Trading services
Execution, guards, protection, missions, watches, and reconciliation live under
apps/server/src/trading; pure policy and accounting live
in packages/trading-contracts
docs/internals/overview.md:119-122. Reactors answer
requested events only after the domain write: "The UI never sees a status the domain
refused"
apps/server/src/trading/TradingMissionReactor.ts:10-12.
The exchange
Writes leave as signed actions to POST /exchange on
testnet
apps/server/src/trading/HyperliquidExecutionService.ts:13.
Reads come back canonical and converge the local tables
apps/server/src/trading/HyperliquidReconciler.ts:2-7.
The archive
A separate always-on process records candles because the info API is "a window, not
a history": about 5000 bars per interval, and nothing older is served again
apps/server/src/trading/archive/config.ts:4-9. "The
archiver never authenticates, never sees a key, and never touches an order
endpoint." apps/server/src/trading/archive/config.ts:11-12
The relay
infra/relay is the hosted T3 Connect control plane. It
links environments to a cloud account, provisions managed endpoints, issues
short-lived credentials, and reports diagnostics
infra/relay/README.md:15-24. After connection, normal API
and WebSocket traffic goes directly between client and environment; "the relay is
not the steady-state data path" AGENTS.md:54.
Clients
Web and desktop share packages/client-runtime: connection
lifecycle, authentication, RPC session, cached environment data, and domain state as
atom factories docs/internals/overview.md:55-61. React
components never construct transports, retry loops, or RPC clients
docs/internals/overview.md:59-61.
How the system comes to life
One trade, end to end, with the code that owns each step. Monospaced labels show the names used in code, and every claim cites its source.
The trading runtime boots by taking a single-writer lease: a lock file
<dbPath>.trading.lock holding pid, host, and
heartbeat, created atomically
apps/server/src/trading/TradingRuntimeLease.ts:12-14. The
lease exists because a second process once ran the same maintenance against the same
database and terminated a long-running test. It turns that collision into an
explicit startup failure
apps/server/src/trading/TradingRuntimeLease.ts:4-10. While
the lease is refused, the rest of the server boots normally; only the destructive
trading runtime stays down
apps/server/src/trading/TradingRuntimeLease.ts:28-30.
A supervised archiver starts recording market history, because the exchange serves
only a recent window of candles and "nothing older is ever served again"
apps/server/src/trading/archive/config.ts:4-9. It makes
public reads only and never touches an order endpoint
apps/server/src/trading/archive/config.ts:11-12.
In a chat, the agent binds its thread to a market.
bindThreadToMarket creates a mission and freezes the
provider binding for the mission's life
apps/server/src/trading/TradingAuthorityBinding.ts:279,306-309. A mission is the persistent record of authority delegated to an agent. It moves
through ten statuses, and tests verify every allowed transition against the
specification
apps/server/src/trading/MissionTransitions.ts:4-5,40-42.
The Missions watches wakes page owns this story.
The mission arms typed predicates: price crossings, candle closes, scheduled
reassessments. On a match, the evaluator flips the watch from
active to triggered atomically
and persists a dedup-keyed inbox event, so a watch fires exactly once even across
restarts apps/server/src/trading/WatchEvaluator.ts:11-20.
A firing is a nudge, not permission: "A watch firing does not authorize a position"
apps/server/src/trading/WatchEvaluator.ts:22-23.
TradingTurnCoordinator.requestRun runs the pre-run checks,
acquires the one-per-mission decision lease (a concurrent second run becomes
queued_behind_active_run), composes a bounded wakeup
snapshot, and dispatches thread.turn.start
apps/server/src/trading/TradingTurnCoordinator.ts:5-18.
The coordinator never calls the provider layer directly; dispatch is the only
sanctioned way
apps/server/src/trading/TradingTurnCoordinator.ts:15-18.
The resumed agent calls trading_enter. The server derives
what a language model cannot know and raises the typed command
trading.execution.requested on the spine
packages/contracts/src/trading.ts:1492. The submit
sequence is fixed and ordered: resolve the signer, resolve market metadata and fresh
pricing, run the preview checklist, map the order, then persist the execution record
and risk reservation before any signature
apps/server/src/trading/HyperliquidExecutionService.ts:4-15.
Signing happens once, inside the serialized nonce lane
apps/server/src/trading/HyperliquidExecutionService.ts:12,527, then the order goes to POST /exchange
apps/server/src/trading/HyperliquidExecutionService.ts:13
and every per-order status is inspected on the way back. A grouped response is not
proof: coverage is read back from the exchange's open orders
apps/server/src/trading/TradingProtectionService.ts:20-22.
Canonical truth flows back, and local tables converge to it: local records are hints
until canonical state confirms them
apps/server/src/trading/HyperliquidReconciler.ts:5-7.
Every confirmed increase gets its exchange-native reduce-only protection confirmed
against the canonical position
apps/server/src/trading/TradingProtectionService.ts:4-8.
The Reconciliation page maps every trigger.
invalidate() bumps a revision counter and publishes an
event that deliberately carries no data, only the revision
apps/server/src/trading/TradingAccountProjection.ts:250-260: the fetched state is authoritative, while the event only tells clients to fetch
again. The web re-fetches and renders projector output, and "There is no client-side
mission state to stale"
apps/web/src/lib/tradingMissionsState.ts:41-47.
The map on disk
One line each. Start in the server; read the trading contracts before changing any math.
AGENTS.md:33. The trading runtime, including the archive
runtime, lives under apps/server/src/trading
AGENTS.md:31.
apps/web/src/routes/trade.tsx:3-6.
apps/desktop/src/window/DesktopWindow.ts:623 and adds
desktop behavior AGENTS.md:34.
AGENTS.md:35. Tour on the
Contracts page.
AGENTS.md:32.
AGENTS.md:36.
packages/hyperliquid/src).
AGENTS.md:37, responsibilities at
infra/relay/README.md:15-24. Not the steady-state data
path AGENTS.md:54.
docs/internals/overview.md and the trading product
constitution docs/internals/trading-final-form.md
AGENTS.md:40.
AGENTS.md:40.
docs/user
AGENTS.md:40.
AGENTS.md:38.
Three ways through the atlas
Pick the path that matches your question. Every page cites the repository the way this one does.
The new engineer
You want to change code without breaking the bank.
- 1Big picture (this page)
- 2Event spine, because every write travels it
- 3Contracts, the shared types
- 4Missions watches wakes, the agent loop
- 5Web & desktop, where it renders
The safety auditor
You want to know what stops the bad day.
- 1Safety invariants
- 2Risk control, budgets and gates
- 3Execution, the spending path
- 4Reconciliation, canonical truth
- 5Signer & authority, keys and ceilings
- 6Failure stories, how the rules were earned
The product viewer
You want to see what the user actually gets.
- 1Research mode, the product without a key
- 2Missions watches wakes
- 3Provider adapters, which agent answers
- 4Relay & environments, remote access
- 5Glossary, when a term bites
Go deeper
Read first
-
docs/internals/overview.md: the repository's own architecture page, source of the stack diagram and the spine description. -
docs/internals/trading-final-form.md: the settled trading product constitution. -
AGENTS.md: priorities, product boundaries, and key locations (lines 7 through 40).
The spine
-
apps/server/src/orchestration/Layers/OrchestrationEngine.ts:90-102: the queue, the pubsub, the engine. -
apps/server/src/trading/TradingMissionReactor.ts:1-18: requested events are questions; reactors answer.
The money path
-
apps/server/src/trading/HyperliquidExecutionService.ts:1-26: the nine-step submit sequence in its own header. -
apps/server/src/trading/TradingProtectionService.ts:1-32: the protection invariant and its repairs. -
apps/server/src/trading/HyperliquidReconciler.ts:1-7: canonical truth, local hints. -
apps/server/src/trading/TradingControlService.ts:1-29: the seven controls and the rule that shapes them.
The fork's edges
-
packages/shared/src/forkPaths.ts:1-26: why the names fork and nothing else. -
apps/server/src/trading/InterimSignerConfig.ts:1-43: the key, the gate, the fail-closed rule. -
apps/server/src/trading/archive/config.ts:1-19: the archive's reason and its limits. -
apps/server/src/provider/builtInDrivers.ts:47-53: the five drivers.