コンテンツにスキップ

LT Scan-Cycle Runbook

DECOMMISSIONED 2026-04-27 (PR #41): aegis-lt-scan-cycle container は Saxo lane 撤退に伴い停止 + 撤去済み。compose / deploy workflow は repo から削除され、maintenance-decommission-lt-scan-cycle.yml が対象 container を Synology から取り除いた。本ランブックは historical reference として残置。再起動を試みる場合は Saxo 撤退の前提が変わったか確認すること。

CRITICAL (historical): --dry-run is the current observation mode for lt-scan-cycle. Do NOT switch to --live without explicit user approval + a WORK_LOG entry. The observation mode is a RUNTIME FLAG on the binary — not a separate service.

What It Is

lt-scan-cycle is the Rust-native scan cycle service, forward-looking successor to the Python paper_trading.py scan loop. It runs beside Python PT in --dry-run mode and writes a parity_log.jsonl that can be compared against the Python replay log with aegis_v3/scripts/parity_diff.py.

Design rule (2026-04-10): Rust is the source of truth going forward. Silent env var defaults are forbidden in the compose file — every required variable uses ${XXX:?message} so bringup fails loudly if the Synology .env file is missing anything.

Prerequisites

Central Synology .env at /volume1/aegis/.env MUST provide:

  • POLYGON_API_KEY — real runtime requirement. PolygonLiveProvider::new() calls env::var("POLYGON_API_KEY") and errors out if unset. The deploy workflow runs compose with --env-file /volume1/aegis/.env so this key is loaded from the central file.

Saxo credentials are NOT env-var-driven:

  • The binary reads Saxo tokens from the mounted token cache file at /data/tokens/saxobank_tokens_live.json (host: /volume1/aegis/tokens/saxobank_tokens_live.json), managed by the token-keeper container.
  • The legacy docker-compose.yml for aegis-lt-rust passes SAXOBANK_*_LIVE as a dead env-var passthrough (all empty in the running container). Cleaning that up is tracked as Track B work and does NOT block lt-scan-cycle bringup.

Paths that MUST exist on Synology:

  • /volume1/aegis/.env (must contain POLYGON_API_KEY=...)
  • /volume1/aegis/tokens/saxobank_tokens_live.json (managed by token-keeper container)
  • /volume1/aegis/wft_state/lt_rust/ (created by deploy.sh prepare_lt_rust_host_dirs)
  • /volume1/aegis/repo/aegis_v3/configs/scenarios/lt_wft.yaml (multi-scenario wrapper; includes LT_RC + WFT_A..I + PT_XW = 11 scenarios)
  • /volume1/aegis/repo/aegis_v3/configs/scenarios/lt_rc.yaml (individual scenario referenced by lt_wft.yaml; also used for single-scenario fallback via --config)

Multi-scenario vs single-scenario mode

As of 2026-04-11 the compose file runs lt-scan-cycle in multi-scenario mode via --scenario-multi-yaml=/config/scenarios/lt_wft.yaml. This matches the Python aegis-wft container running run_multi_scenario_pt.py --config pt_wft.yaml (11 scenarios: LT_RC priority + WFT_A..I + PT_XW).

To fall back to single-scenario LT_RC only (emergency rollback or isolated debugging), edit the compose command to:

command:
  - "--config=/config/scenarios/lt_rc.yaml"
  - "--state-dir=/data/state/lt_rust/lt_scan_cycle"
  - "--parity-log=/data/state/lt_rust/parity_log.jsonl"
  - "--dry-run"
  - "--scan-interval=120"

Multi-scenario mode additionally emits the comparison log at /data/state/lt_rust/lt_wft_comparison.jsonl every 20 WFT batches (Sprint 1b) and runs with --scan-interval=45 to match Python's shared_scan_interval_sec: 45.

How To Start

Preferred: trigger the dedicated workflow.

gh workflow run deploy-lt-scan-cycle.yml

Manual (operator on Synology, emergency only):

cd /volume1/aegis/repo/aegis_v3/lt-rust-docker
docker compose -f docker-compose.lt-scan-cycle.yml up -d --build aegis-lt-scan-cycle

How To Stop

cd /volume1/aegis/repo/aegis_v3/lt-rust-docker
docker compose -f docker-compose.lt-scan-cycle.yml stop aegis-lt-scan-cycle

Delete the container entirely:

cd /volume1/aegis/repo/aegis_v3/lt-rust-docker
docker compose -f docker-compose.lt-scan-cycle.yml rm -f aegis-lt-scan-cycle

How To Verify It Is Working

Container status (read-only SSH OK):

ssh fukutani.ryo@192.168.42.252 "sudo /usr/local/bin/docker ps --format 'table {{.Names}}\t{{.Status}}' | grep aegis-lt-scan-cycle"

Recent logs:

ssh fukutani.ryo@192.168.42.252 "sudo /usr/local/bin/docker logs aegis-lt-scan-cycle --tail 100 2>&1"

Parity log append confirmation:

ssh fukutani.ryo@192.168.42.252 "tail -n 5 /volume1/aegis/wft_state/lt_rust/parity_log.jsonl"

Recent scan summary:

ssh fukutani.ryo@192.168.42.252 "jq -c 'select(.record_kind==\"scan\") | {ts,scan_count,entries:(.entries_proposed|length),exits:(.exits_proposed|length),errors}' /volume1/aegis/wft_state/lt_rust/parity_log.jsonl | tail"

How To Read The Parity Log

Schema/version surface:

jq -c '{schema_version,record_kind,scan_count,ts}' /volume1/aegis/wft_state/lt_rust/parity_log.jsonl | tail

Entry details:

jq -c 'select(.record_kind=="scan") | .entries_proposed[]? | {underlying,short_strike,long_strike,expiry,status}' /volume1/aegis/wft_state/lt_rust/parity_log.jsonl | tail

Exit details:

jq -c 'select(.record_kind=="scan") | .exits_proposed[]? | {underlying,short_strike,long_strike,expiry,close_reason,status}' /volume1/aegis/wft_state/lt_rust/parity_log.jsonl | tail

What To Look For In Parity Comparison

  • schema_version is 1 on both sides
  • record_kind=="scan" count grows continuously
  • Rust/Python VIX within ±0.1
  • entries_proposed / exits_proposed counts do not drift significantly
  • Entry underlying, short_strike, long_strike, expiry match
  • errors are not continuously increasing

Comparison command:

python3 aegis_v3/scripts/parity_diff.py \
  --rust-log /volume1/aegis/wft_state/lt_rust/parity_log.jsonl \
  --python-log /volume1/aegis/wft_state/pt_replay_log \
  --tolerance-seconds 60

Rollback

lt-scan-cycle is completely independent from Python PT. Stopping it does not affect aegis-wft (Python PT).

ssh fukutani.ryo@192.168.42.252 \
  "cd /volume1/aegis/repo/aegis_v3/lt-rust-docker && \
   sudo /usr/local/bin/docker compose -f docker-compose.lt-scan-cycle.yml stop aegis-lt-scan-cycle"

Cutover Path (future, not active)

This is the G1 Day 10 cutover — replacing Python aegis-wft with Rust lt-scan-cycle --scenario-multi-yaml --live as the live LT_RC + WFT + PT_XW execution engine.

Pre-cutover checklist

  1. 5 business days clean parity observation
  2. /data/state/lt_rust/lt_wft_comparison.jsonl vs Python data/pt_wft_comparison.jsonl compared per 15-min interval
  3. Per-scenario equity delta ≤ ±$50
  4. Per-scenario trade count delta = 0
  5. LT_RC fill parity: Saxo precheck result identical (dry-run)
  6. No circuit breaker escalations during observation window
  7. User WORK_LOG entry explicitly approving cutover
  8. Python aegis-wft stopped BEFORE Rust switches to live (no dual-write)

Cutover sequence

# a. Stop Python aegis-wft FIRST
ssh fukutani.ryo@192.168.42.252 "sudo /usr/local/bin/docker stop aegis-wft"

# b. Wait 30s for any in-flight Saxo calls to flush
sleep 30

# c. Edit docker-compose.lt-scan-cycle.yml to swap --dry-run → --live
#    (change only that line; multi-scenario flags stay unchanged)
# d. Commit + push + let GHA deploy-lt-scan-cycle.yml redeploy the container
# e. Verify LT_RC scan loop starts submitting orders via `docker logs aegis-lt-scan-cycle`

Rollback (within 2 minutes)

# Stop Rust lt-scan-cycle (edits take effect via the ad-hoc docker stop,
# no git revert needed since the compose file still has both flag sets in
# source control and we only need to bring Python back up fast)
ssh fukutani.ryo@192.168.42.252 \
  "cd /volume1/aegis/repo/aegis_v3/lt-rust-docker && \
   sudo /usr/local/bin/docker compose -f docker-compose.lt-scan-cycle.yml stop aegis-lt-scan-cycle"

# Start Python aegis-wft back up (image still present)
ssh fukutani.ryo@192.168.42.252 "sudo /usr/local/bin/docker start aegis-wft"

Post-cutover monitoring (24h on-call)

  • Watch aegis-lt-scan-cycle logs for ENTRY/EXIT submissions
  • Verify Saxo trade confirmations arrive in the token-keeper-managed account
  • Watch lt_wft_comparison.jsonl for scenario-level drift
  • Retain Python aegis-wft container image for 1 week rollback safety