§11.1 Mission state
The main loop runs initializing → analysing → waiting → executing → position_open → waiting, and any active state may exit to a terminal.
11–13 · State machines, lifecycle & market data
A harness turn is temporary; a trading mission is durable. Four state machines pin how a mission, a harness run, a watch, and an execution record progress; an event-driven lifecycle wakes the bound harness on a typed wake-up payload rather than raw streams; and a Hyperliquid market-data gateway supplies ETH candles, BBO, asset context, and account state under fixed freshness thresholds.
§11 · State machines
These machines are persisted, versioned, and the only legal paths through trading state. A harness run may not invent transitions; the deterministic authority and execution services may only move a mission to a blocked terminal for a fixed reason. Hyperliquid exchange state, not a local cache, decides whether an execution record rests, fills, or fails.
The main loop runs initializing → analysing → waiting → executing → position_open → waiting, and any active state may exit to a terminal.
One TradingHarnessRun at a time owns the decision lease; events persist and coalesce behind an active run.
A watch moves active → triggered → consumed, or to cancelled / expired / superseded, bound to a strategy version.
A record flows planned → validated → queued → signing → submitted → acknowledged → live → terminal.
§11.1 · Mission state machine
The active loop expresses the continuing-mandate shape: analyse, publish strategy and watches, end the turn, wake on event, reassess, enter or decline, manage, close, and publish new watches. Any active state may exit to a terminal when the user, the provider, or a deterministic safety condition requires it.
Blocks new discretionary entries and scale-ins, cancels mission-owned non-reduce-only entry orders, and preserves confirmed reduce-only stop and take-profit orders. It does not close the current position unless the user separately requests a reduction or close.
Blocks new discretionary actions. Existing exchange-native protection remains active and market and account monitoring may continue. No new discretionary entry is permitted; the user may retry the same provider, pause, close, or revoke.
A deterministic safety condition prevents further autonomous exposure. The reason is persisted and visible: cumulative_loss_limit, protection_failure, account_unavailable, or reconciliation_failure.
revoked permanently ends mission authority and cancels mission-owned position-increasing orders while preserving valid protection if a position remains. completed means the harness declared the objective complete.
paused blocks new entries and scale-ins but does not strip confirmed reduce-only stop and take-profit orders. blocked is the deterministic safety counterpart — reached only for a persisted reason — and likewise preserves valid protection while blocking further exposure.
§11.2 · Harness run state machine
Each wake-up produces at most one TradingHarnessRun. Only one run may own the mission decision lease at a time, so a second provider turn is never started while another is active. New events are still persisted and equivalent events coalesced; a follow-up run is considered only after the current run completes.
type TradingHarnessRun = {
id: string;
missionId: string;
cause:
| "mission_created"
| "market_watch_triggered"
| "scheduled_reassessment"
| "order_updated"
| "position_updated"
| "user_message"
| "mission_resumed";
status:
| "queued"
| "starting"
| "running"
| "waiting_for_tool"
| "completed"
| "failed";
startedAt?: number;
completedAt?: number;
};
Only one run may own the mission decision lease. A wake-up that arrives while a run is active is persisted and coalesced; it does not start a second provider turn.
While a run is active, new events are persisted to the inbox and equivalent events are coalesced, so the follow-up run begins with the merged authoritative set rather than a re-evaluation per event.
A follow-up run is considered only after the current run completes. The cause field records why each run started — watch, timer, exchange update, user message, or mission resume.
§11.3 · Watch state machine
A watch moves active → triggered → consumed when a wake-up consumes it, or to cancelled, expired, or superseded from the active state. Watches are bound to a strategy version, so publishing a new strategy supersedes the watches of the prior version.
A triggered watch is consumed by exactly one run; a duplicate firing is dropped by the inbox deduplication key. Supersession is automatic when a new strategy version is published, so an obsolete watch can never wake a harness against stale intent.
§11.4 · Execution state machine
Every typed action becomes an execution record that moves planned → validated → queued → signing → submitted → acknowledged, then to a live state of resting, partially_filled, or filled, and finally to a terminal of canceled, rejected, expired, or failed.
cloid)acknowledged is not filled. A grouped TP/SL response, a cancellation reason, or a locally cached fill is a hint until canonical Hyperliquid order, fill, and position state confirms it. The record's terminal state is set by reconciliation, not by submission response.
§12 · Event-driven harness lifecycle
The harness ends a turn; the mission does not end. T3 evaluates simple typed watch predicates, persists and coalesces events, acquires a single decision lease, resumes the bound provider through the existing ProviderService, and hands the harness an authoritative snapshot. Complex interpretation stays a harness responsibility.
A discriminated union of simple, deterministic, typed, inspectable predicates.
An authoritative mission snapshot, not a prompt from unbounded raw streams.
Acquires the lease and resumes the session through existing provider APIs.
Exchange-native orders keep working while the harness sleeps.
§12.1 · Watch types
A MarketWatch is a simple condition the runtime can evaluate without judgment: a price crossing, a finalized candle close, an order update, a position update, or a scheduled reassessment. Anything that requires weighing evidence — regime, volume quality, structural invalidation — is harness responsibility and does not belong in a watch predicate.
type MarketWatch =
| {
type: "price_cross";
market: "ETH";
priceSource: "mark" | "mid";
direction: "above" | "below";
price: number;
}
| {
type: "candle_close";
market: "ETH";
interval: TradingTimeframe;
direction: "above" | "below";
price: number;
}
| {
type: "order_update";
cloid: string;
}
| {
type: "position_update";
market: "ETH";
}
| {
type: "scheduled_reassessment";
runAt: number;
};
A watch is a single typed comparison or a fixed schedule. It does not encode strategy logic.
The same market state and the same watch produce the same firing decision, so reconnects and replays can be deduplicated.
Every watch is a persisted record bound to a strategy version, visible in the workspace, and cancelable by harness or user.
§12.2 · Wake-up payload
When a run starts, the harness does not receive unbounded market data or a free-form prompt. It receives a TradingHarnessWakeup: the cause, the triggering watch or user message, and a fresh, versioned snapshot of market, account, active strategy, authority, and pending events. The runtime assembles the bounded context; the harness supplies the judgment.
type TradingHarnessWakeup = {
missionId: string;
harnessRunId: string;
cause:
| "market_watch_triggered"
| "scheduled_reassessment"
| "order_updated"
| "position_updated"
| "user_message"
| "mission_resumed";
occurredAt: number;
triggeringWatch?: PersistedWatch;
userMessage?: string;
marketSnapshot: AgentMarketSnapshot;
accountSnapshot: AgentAccountSnapshot;
activeStrategy: MomentumStrategyState;
authority: TradingAuthority;
pendingEvents: TradingDomainEventSummary[];
instruction: string;
};
The payload is an authoritative mission snapshot, not a prompt assembled from unbounded raw market streams. Market and account state are bounded, freshness-stamped views produced by the gateway and reconciler.
activeStrategy and authority carry their current versions, and pendingEvents is the coalesced set the run was started with — so a stale wake-up cannot silently overwrite a newer published conclusion.
§12.3 · Turn coordinator
The TradingTurnCoordinator is the single gate between an observed event and a started run. It verifies mission, provider, account, lease, and version state before doing anything, and it returns one of three outcomes so a caller can tell whether a run actually started, was queued behind an active one, or was blocked.
interface TradingTurnCoordinator {
requestRun(input: HarnessRunRequest): Promise<
| { status: "started"; harnessRunId: string }
| { status: "queued_behind_active_run" }
| { status: "blocked"; reason: string }
>;
}
ProviderService owns session start and resume.
§12.4 · What happens between turns
Between harness turns, T3 may observe and route facts. It may not generate new discretionary intent. A watch firing is routed through the evaluator, the inbox, the coordinator, and ProviderService before the harness sees it — and the harness may still decline.
TradingHarnessWakeup, not a raw market stream.§12.5 · Exchange-native activity while sleeping
Because immediate downside protection lives at the exchange, the mission is not idle merely because the harness is asleep. The following continue without an active harness turn, because they were already placed or authorized, and T3 reconciles them and wakes the harness when interpretation is required.
T3 subscribes to order, fill, and clearinghouse updates and reconciles each against canonical Hyperliquid state. When interpretation is required — a partial fill that needs independent protection, a triggered stop, a funding swing — T3 wakes the bound harness with a fresh snapshot. Routine exchange-native bookkeeping does not require a turn.
§13 · Market data
The market-data gateway subscribes directly to every POC ETH candle interval plus BBO, active asset context, and the account streams that drive execution and reconciliation. Hyperliquid documents direct candle snapshot and WebSocket support for every POC interval — 1m, 3m, 5m, 15m, and 1h — so the gateway subscribes to those intervals directly rather than synthesizing longer candles locally.
§13 · Gateway requirements
The gateway is the only market-data path into trading state. It must distinguish snapshots from incremental updates, survive disconnects without duplicating records, and refresh canonical state through the Info API after reconnect rather than trusting replayed updates.
| Stream | Freshness threshold | Stale handling |
|---|---|---|
| BBO | stale after 2 seconds | blocks marketable IOC pricing |
| Asset context | stale after 5 seconds | refresh via Info API |
| Account state during execution | stale after 5 seconds | execution-critical stale data blocks submission |
| Closed candles | valid only after final close time and single processing | a finalized candle triggers a watch at most once |
§13 · Bounded context and computed features
Hyperliquid supports all five POC intervals directly, so the gateway subscribes to each rather than deriving 3m or 1h candles locally. One tool response is bounded to 500 bars; the gateway may paginate historical requests within Hyperliquid's documented recent-candle availability, but it must not place an unbounded history into a harness turn. Computed features are derived from the bounded view, not shipped as raw streams.
Subscribe directly to 1m, 3m, 5m, 15m, and 1h. Hyperliquid supports them; the POC does not synthesize 3m or 1h candles locally.
One tool response is bounded to 500 bars. Historical requests may paginate within Hyperliquid's documented recent-candle availability, but no unbounded history enters a harness turn.