Skip to main content
The Halyrd WebSocket stream is the primary channel for real-time agent data. Every equity snapshot, executed trade, journal entry, risk veto, and lifecycle transition is pushed to connected clients the moment it occurs — no polling required. Connect once and your application stays in sync with the agent automatically for as long as the session is open.

Endpoint

ws://localhost:8000/ws/agents/{id}
Use current as the {id} to connect to the single running agent:
ws://localhost:8000/ws/agents/current
No authentication is required. The WebSocket endpoint is open on localhost for the single-user setup, consistent with the REST API. If you ever proxy the backend beyond localhost, add authentication at that boundary.

Message format

Every message is a JSON object with an event field that identifies its type, plus a ts field (ISO 8601 UTC timestamp) present on all events. Parse the event field first and branch on its value to handle each message type.

Events

snapshot

Emitted every ~15–30 minutes when the agent records a new equity snapshot. Use this to drive equity charts and drawdown indicators.
{
  "event": "snapshot",
  "ts": "2025-06-22T14:00:00Z",
  "equity": 1042.50,
  "phase": "paper",
  "dd_from_peak": 0.012,
  "daily_dd_used": 0.031
}
equity
number
Current portfolio equity in USDT.
phase
"paper" | "live"
The agent’s current execution phase.
dd_from_peak
number
Drawdown from the all-time equity peak, expressed as a decimal fraction (e.g. 0.012 = 1.2%).
daily_dd_used
number
Fraction of the daily drawdown budget consumed so far today (e.g. 0.031 = 3.1%).

trade

Emitted immediately after every trade execution — paper or live. The journal_ref field links to the corresponding journal entry.
{
  "event": "trade",
  "ts": "2025-06-22T14:05:30Z",
  "pair": "ETH/USDT",
  "side": "buy",
  "size": 100.00,
  "price": 3421.50,
  "pnl": null,
  "fees": 0.42,
  "phase": "paper",
  "journal_ref": "jrn_abc123"
}
pair
string
Trading pair in BASE/QUOTE format.
side
"buy" | "sell"
Direction of the trade.
size
number
Trade size in USDT (quote currency).
price
number
Executed fill price from the TWAK-verified quote.
pnl
number | null
Realised PnL in USDT. null for opening trades where the position is not yet closed.
fees
number
Total fees paid in USDT, including swap fee and gas.
journal_ref
string
ID of the corresponding journal entry. Use this to correlate trade events with journal detail.

journal_entry

Emitted for every event the agent records in its append-only journal — trade executions, skips, risk decisions, configuration changes, and lifecycle transitions.
{
  "event": "journal_entry",
  "ts": "2025-06-22T14:05:30Z",
  "type": "EXECUTED",
  "tags": ["EXECUTED", "ETH"],
  "body": "Bought ETH/USDT: price 3421.50, size 100 USDT. RSI 23, price 5.2% below 48h SMA."
}
type
string
High-level event classifier (e.g. EXECUTED, SKIPPED, RISK_VETO, STATUS_CHANGE).
tags
string[]
Array of searchable tags for filtering and grouping journal entries in your UI.
body
string
Human-readable description of the event, including the agent’s reasoning where applicable.

risk_veto

Emitted when the Risk Engine’s policy layer blocks a trade before it reaches the executor. The trade is not executed; the veto is also recorded in the journal.
{
  "event": "risk_veto",
  "ts": "2025-06-22T15:00:00Z",
  "rule": "daily_drawdown",
  "reason": "Trade would use 2.1% of daily drawdown budget; only 1.9% remaining.",
  "order_ref": "ord_xyz789"
}
rule
string
The risk rule that fired (e.g. daily_drawdown, max_drawdown, streak_limit).
reason
string
Plain-language explanation of why the rule blocked this specific trade.
order_ref
string
Reference ID of the blocked order.

status_change

Emitted whenever the agent transitions between lifecycle states. Use this to update status indicators and unlock or disable UI actions.
{
  "event": "status_change",
  "ts": "2025-06-22T18:00:00Z",
  "from": "PAPER",
  "to": "ELIGIBLE"
}
from
"PAPER" | "ELIGIBLE" | "LIVE" | "DEMOTED"
The previous agent status.
to
"PAPER" | "ELIGIBLE" | "LIVE" | "DEMOTED"
The new agent status.

twak_block

Emitted in live mode when the TWAK signing layer (the physics layer) rejects a transaction after the policy layer has already approved it. This is distinct from a risk_veto — the trade passed risk checks but was refused at the signing boundary.
{
  "event": "twak_block",
  "ts": "2025-06-22T18:05:00Z",
  "rule": "per_trade_cap",
  "reason": "Order size 520 USDT exceeds per-trade cap of 500 USDT.",
  "order_ref": "ord_abc456"
}
twak_block events only occur in live mode. In paper mode, the signing layer is not involved. If you see a twak_block, review your TWAK allowlist, per-trade cap, and daily cap settings.
rule
string
The TWAK policy rule that refused the transaction (e.g. per_trade_cap, daily_cap, allowlist).
reason
string
Plain-language explanation of the refusal.
order_ref
string
Reference ID of the blocked order.

quote_rejected

Emitted when the QuoteVerifier discards a price quote before the trade reaches the risk engine. The skip is journaled automatically — no action is required on your part.
{
  "event": "quote_rejected",
  "ts": "2025-06-22T14:10:00Z",
  "pair": "CAKE/USDT",
  "reason": "divergence",
  "detail": "TWAK quote 0.82% above CMC mid; threshold 0.75%."
}
pair
string
The trading pair for which the quote was rejected.
reason
"divergence" | "stale" | "liquidity"
The category of rejection. Maps to the QUOTE_DIVERGENCE, QUOTE_STALE, and INSUFFICIENT_LIQUIDITY error codes respectively.
detail
string
Quantified explanation of the rejection — useful for tuning quote thresholds.

promotion / demotion

Emitted in live mode when the agent is promoted to LIVE or demoted back to PAPER. Both events are also echoed as a status_change event on the same connection.
{ "event": "promotion", "ts": "2025-06-22T19:00:00Z", "first_tx_hash": "0xabc..." }
{ "event": "demotion", "ts": "2025-06-22T20:00:00Z", "rule": "max_drawdown", "scope": "max", "relock_until": null }
first_tx_hash
string
(promotion only) The on-chain transaction hash of the agent’s first live trade.
rule
string
(demotion only) The risk rule that triggered the demotion.
scope
"daily" | "max"
(demotion only) Whether the breach was against the daily drawdown limit or the maximum drawdown limit.
relock_until
string | null
(demotion only) ISO 8601 timestamp until which promotion is locked, or null if no time-lock applies.

Connection example

Connect to the stream and handle each event type in a switch statement. Unrecognised event types should be silently ignored to stay forward-compatible as new events are added.
const ws = new WebSocket('ws://localhost:8000/ws/agents/current');

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);

  switch (msg.event) {
    case 'snapshot':
      console.log('Equity:', msg.equity, 'DD from peak:', msg.dd_from_peak);
      break;
    case 'trade':
      console.log('Trade:', msg.side, msg.pair, '@', msg.price, '— fees:', msg.fees);
      break;
    case 'journal_entry':
      console.log('[Journal]', msg.type, msg.body);
      break;
    case 'risk_veto':
      console.warn('Risk veto:', msg.rule, '—', msg.reason);
      break;
    case 'status_change':
      console.log('Status:', msg.from, '->', msg.to);
      break;
    case 'twak_block':
      console.error('TWAK block:', msg.rule, '—', msg.reason);
      break;
    case 'quote_rejected':
      console.warn('Quote rejected for', msg.pair, '—', msg.reason, msg.detail);
      break;
    case 'promotion':
      console.log('Agent promoted to LIVE. First tx:', msg.first_tx_hash);
      break;
    case 'demotion':
      console.warn('Agent demoted. Rule:', msg.rule, 'Scope:', msg.scope);
      break;
    default:
      // Ignore unknown events — new event types may be added in future releases.
      break;
  }
};

ws.onerror = (err) => {
  console.error('WebSocket error:', err);
};

ws.onclose = () => {
  console.log('WebSocket connection closed. Reconnect to resume the stream.');
};
If WebSocket is unavailable in your environment — for example, in a server-side polling script — you can poll the REST endpoints on a 30-second interval as a fallback. Use GET /agents/{id}/equity, /trades, /journal, and /risk/blocks to approximate the same data. Event ordering and latency will differ from the push stream.
The Halyrd dashboard connects to the same WebSocket stream your integration uses. Open your browser’s DevTools, go to the Network tab, filter by WS, and select the ws/agents/current connection. You’ll see every live event arrive in real time — a fast way to verify your parsing logic or inspect the exact payload shapes the agent is emitting.

Event reference summary

EventTierWhen emitted
snapshotCoreEvery ~15–30 min on a new equity snapshot
tradeCoreOn every trade execution (paper or live)
journal_entryCoreOn every journal write
risk_vetoCoreWhen the policy layer blocks a trade
status_changeCoreOn every agent status transition
quote_rejectedCoreWhen a price quote is discarded
twak_blockLive onlyWhen the TWAK signing layer rejects a transaction
promotionLive onlyWhen the agent transitions to LIVE
demotionLive onlyWhen the agent is demoted from LIVE