Files
trex-core/.claude/skills/run-trex-core-daemon/SKILL.md
T

7.0 KiB

name, description
name description
run-trex-core-daemon Launch the trex-core daemon against the real PostgreSQL + RabbitMQ infrastructure and drive it via the messaging surfaces (snapshot RPC, intent admission RPC, slot events). Use when asked to run trex-core, smoke-test the daemon, confirm a change works end-to-end, or reproduce the BAT-007 transport lifecycle.

Run trex-core (sync + thread daemon over RabbitMQ)

The daemon (BAT-007) has no HTTP surface. All strategy-facing traffic goes over RabbitMQ — snapshot RPC on trex.snapshot.req, intent admission RPC on trex.intent.req, slot lifecycle events on the trex.events topic exchange. "Run" means: boot the daemon in the background, drive it with messages, look at the responses + the events.

Prerequisites

The daemon needs three live services. Verify before launching:

.venv/bin/python - <<'PY'
import os, psycopg, pika
from dotenv import load_dotenv
load_dotenv(".env")
psycopg.connect(os.environ["TREX_POSTGRESQL_DSN"], connect_timeout=3).close()
print("postgres OK")
p = pika.URLParameters(os.environ["TREX_RABBITMQ_URL"]); p.socket_timeout = 3
pika.BlockingConnection(p).close()
print("rabbitmq OK")
PY

If either fails, fix the infra first — the daemon will not boot without TREX_POSTGRESQL_DSN and will boot silently degraded if TREX_RABBITMQ_URL is missing (stub InMemoryTransport — strategy modules can't reach it).

Required env (.env, gitignored)

Variable Required Notes
TREX_POSTGRESQL_DSN yes psycopg DSN
TREX_RABBITMQ_URL yes for real use e.g. amqp://admin:admin@localhost:5672/
TREX_BROKER no paper (default) or kis
TREX_TOTAL_SLOTS no default 10
TREX_EXECUTION_INTERVAL_SEC no default 1.0
TREX_LEDGER_INTERVAL_SEC no default 1.0
TREX_PORTFOLIO_HUB_BASE_URL no if set, real portfolio-hub ledger; else in-memory stub

The daemon preallocates slots for UTC today (datetime.now(tz=timezone.utc).date()). KST users near midnight: check date -u before driving the daemon — the trading_date you query must match what was preallocated.

Launch

Background, redirect log:

.venv/bin/trex-core-daemon &> /tmp/trex-core.log &
DAEMON_PID=$!

The daemon does not bind any TCP port; "ready" means four worker threads running and all RabbitMQ topology declared. Poll for the topology to appear:

for i in {1..15}; do
  .venv/bin/python - <<'PY' && break
import pika, sys
p = pika.URLParameters("amqp://admin:admin@localhost:5672/"); p.socket_timeout = 2
c = pika.BlockingConnection(p)
try:
    ch = c.channel()
    # passive=True: succeeds only if the daemon already declared it
    ch.queue_declare("trex.intent.req", durable=True, passive=True)
    ch.queue_declare("trex.snapshot.req", durable=True, passive=True)
    ch.exchange_declare("trex.events", exchange_type="topic", durable=True, passive=True)
    print("daemon ready")
    sys.exit(0)
except Exception as exc:
    print(f"not ready: {exc}"); sys.exit(1)
finally:
    c.close()
PY
  sleep 0.5
done

/tmp/trex-core.log has the pika INFO chatter — first connection lines mean boot succeeded; absence of ERROR/exception traceback means topology declare worked.

Drive

The canonical end-to-end smoke is in smoke.py — copy/adapt it. It exercises:

  1. snapshot RPC (view=all) — confirms the daemon answers.
  2. buy intent RPC with a fresh correlation_id — admitted, slot → pending_buy.
  3. duplicate buy with the same correlation_id — identical response, no double transition (intent_log dedup gate, REQ-023 AC-1).
  4. wait one execution loop tick — paper broker fills, slot → holding, slot_filled_buy.{strategy_id}.{symbol} event published.
  5. sell intent RPC on the holding slot — slot → closed, slot_filled_sell.{strategy_id}.{symbol} event published.

Run it:

.venv/bin/python .claude/skills/run-trex-core-daemon/smoke.py

Sample output (paper broker, strategy_id=STRAT-A):

=== 1) snapshot RPC ===
slots=10 capacity={idle=10, holding=0, pending=0}
=== 2) buy intent ===
admitted, slot_id=b2d60fa3-…
=== 3) duplicate buy ===
identical: True
=== 4) wait for fill ===
slot status=holding qty=10 avg_price=60000.0
=== 5) sell intent ===
admitted; event rk=slot_filled_sell.STRAT-A.005930 status=closed qty=10
DONE

Subscribing to events (timing caveat)

If you subscribe to trex.events after the publish happens, the message is gone — topic exchanges drop unroutable publishes. Production strategy modules have their per-strategy queue bound from boot, so they don't miss anything; ad-hoc subscribers need to declare + bind their queue before triggering the intent. smoke.py does this correctly (declares queue, then sleeps 0.6s, then runs the RPC).

Stop

kill -TERM $DAEMON_PID
# or if you've lost the PID:
pkill -f trex-core-daemon

The daemon installs SIGINT/SIGTERM handlers that set the stop event, join all worker threads, and close() every RabbitMQ connection — last log line should be Normal shutdown from pika. Exit code 0.

Inspecting state

The daemon's source of truth is PostgreSQL daily_slot:

.venv/bin/python - <<'PY'
import os, psycopg
from dotenv import load_dotenv
load_dotenv(".env")
with psycopg.connect(os.environ["TREX_POSTGRESQL_DSN"]) as c:
    with c.cursor() as cur:
        cur.execute("""
          SELECT trading_date, status, COUNT(*)
          FROM daily_slot GROUP BY trading_date, status ORDER BY trading_date DESC, status
        """)
        for r in cur.fetchall(): print(r)
PY

Ledger outbox state (BAT-006):

.venv/bin/python - <<'PY'
import os, psycopg
from dotenv import load_dotenv
load_dotenv(".env")
with psycopg.connect(os.environ["TREX_POSTGRESQL_DSN"]) as c:
    with c.cursor() as cur:
        cur.execute("SELECT status, last_outcome, COUNT(*) FROM ledger_outbox GROUP BY 1,2 ORDER BY 1,2")
        for r in cur.fetchall(): print(r)
PY

Troubleshooting

Symptom Cause Fix
slots returned: 0 from snapshot querying wrong trading_date use UTC today, see date -u
intent gets rejected: no_idle_slot no preallocated slots for that day OR all 10 in flight check daily_slot rows; restart daemon to repreallocate next UTC day
RpcTimeoutError on the client daemon not up, or consumer thread crashed check /tmp/trex-core.log for exception trace
TransportUnavailableError RabbitMQ down the daemon does NOT buffer locally (REQ-024) — fix the broker, then re-issue
fill event missed by ad-hoc subscriber queue bound after publish bind queue + sleep ≥0.5s before triggering the intent
import-time ModuleNotFoundError: pika dev deps not installed .venv/bin/pip install -e ".[dev]"
  • docs/interface-contract.md — full RabbitMQ topology + message payloads
  • docs/runtime-flows.md — flow diagrams (snapshot RPC, intent RPC, events, degrade branch)
  • docs/batches/bat-007_20260605_strategy-module-rabbitmq-transport/ — the batch that introduced this runtime shape