| name | build-executor |
| description | End-to-end orchestrator for building a trading executor from data collection through live deployment. Use when the user asks to build an executor, create a new strategy, implement a trading system, or says 'build me an executor for X'. Also use when resuming an in-progress executor build — the skill reads the executor directory to determine current stage and picks up where it left off. Covers the full lifecycle: data collection, scoring, strategy promotion, and live deployment. Cross-model `/verify` is the single gate at PROMOTE and LIVE. This skill should be used for ANY new executor work in this repo, even partial tasks like 'add scoring to my executor' or 'wire up live mode'. |
Build Executor
You are orchestrating the construction of a verified trading executor. Your job is to move through four stages — COLLECT, SCORE, PROMOTE, LIVE — building proof at each step and blocking progress when proof is missing.
First: determine where you are
Read the executor directory to figure out which stage you're in:
Has src/collector/ with real feed implementations?
No → You're in COLLECT (or haven't started)
Yes → Has `pnpm score` producing valid scoring contract JSON?
No → You're in SCORE
Yes → Has a tear sheet in research/evidence/?
No → You're in PROMOTE
Yes → You're in LIVE (or done)
If starting fresh, copy the execution template:
cp -r executors/execution-template executors/<your-executor>
The template includes experiment.md and research/evidence/ — these are part of the executor, not separate docs. Update package.json name/description, then fill in the experiment doc's thesis. The status starts as untested and progresses automatically as you produce evidence. See docs/reference/experiments.md for how evidence layers.
Before writing ANY code
- Load
/tdd-guide. All implementation follows red-green-refactor. Write the failing test first. Always.
- Load
/tradecore-packages. Check if an existing @recallnet/* package owns the concept you're about to build. Don't reinvent.
- Read
references/package-doctrine.md. It tells you which template package to use for every common task. Internalize the "I want to X → use Y" table.
- Load
/governed-research when you reach Stage 3 (PROMOTE). All config iteration and promotion attempts go through the governance script.
Reference loading schedule
Load only what you need for the current stage. Don't load everything up front.
| Stage | Load on entry | Apply while building | Cross-model gate |
|---|
| COLLECT | package-doctrine.md | adversarial-trading.md (Data Skeptic) | — (scaffolding) |
| SCORE | scoring-contract.md, error-classes.md, handicaps.md (§17), promotion-metrics.md (raw PnL bar) | adversarial-trading.md (Backtester); /strategy-verification as self-check | — (optional) |
| PROMOTE | handicaps.md (§18, all six attestations), promotion-metrics.md (Calmar + Sortino grade), bankroll-policy.md (if compounding) | adversarial-trading.md (Backtester + Live Operator); /strategy-verification | /verify --payload=strategy-verification --gate=promote |
| LIVE | parity-rules.md, pre-ship-checklist.md, operational-resilience.md, session-summary-integration.md | adversarial-trading.md (all personas); parity audit; /strategy-verification with live data | /verify --payload=adversarial-verifier --reviewers=2 --gate=live |
Trading personas and /strategy-verification are how you build well at each stage — not a review tier. The only gate-level requirement is the cross-model call via /verify.
agent-learnings.md — load the section for your current stage, not the whole file.
The packages at your disposal
These are already in the executor template's dependencies:
| Package | What it's for | Key entry point |
|---|
@recallnet/skunk-run-telemetry | ALL logging and event output; per-session summary at shutdown | createRunLogger(), writeSessionSummary() |
@recallnet/skunk-execution-ledger | ALL financial tracking | ExecutionLedger |
@recallnet/skunk-venue-budget | Pre-trade budget gates | VenueBudget.tryReserve() |
@recallnet/skunk-paper-trading | Simulated fills and positions | createPaperOms() |
@recallnet/skunk-run-report | Structured performance reports | createReportAccumulator() |
@recallnet/skunk-run-contract | Schemas, manifests, hashing (incl. variantHash) | RunFactSchema, stableJsonStringify, hashJsonValue |
@recallnet/skunk-strategy-report | Tear sheets and go-live verdicts | buildTearSheet() |
Cardinal rule: Use these packages. Do not use console.log (use run-telemetry). Do not query venue APIs for balance (use execution-ledger). Do not fabricate order book depth (paper-trading rejects empty books). See references/package-doctrine.md for the full doctrine.
Stage 1: COLLECT
Goal: Build a collector that records the superset of raw market observables without bias.
What to build (TDD — failing test first for each)
- Venue feed class per venue. Implement the
VenueFeed interface from the scaffold.
- Before writing: load
/tradecore-venues for venue-specific gotchas.
- If the venue isn't in TradeCore: load
/venue-integration. This will guide you through classifying the venue, designing branded types, building a @recallnet/skunk-{venue} package, and wiring credentials. Do not proceed with feed implementation until the venue package exists.
- Lifecycle tracking for detecting evaluation-cycle boundaries. For expiry markets this is settlements + rollovers; for continuous strategies this is window boundaries (hourly, per-block, per-epoch); for intent-based venues this is intent lifecycle completion events.
- NDJSON writer using
@recallnet/skunk-run-telemetry appendJsonlLine() for raw data writing (simple JSONL append). Use createRunLogger() separately for structured event logging (canonical RunFact envelopes).
- PID lockfile using
acquireLock/releaseLock from the scaffold's process-lock.ts. Second collector must fail fast.
pnpm collect command that runs via nohup for persistent background operation.
Rules
- Store raw observables ONLY. Never store model outputs — they get recomputed at score time.
- Zero REST in the collector hot path. One-time REST seed on cold start is acceptable.
- Every venue is independent. Never gate one venue's collection on another.
- Cold-start resilience: produce data immediately, not after the first window rolls.
data/ is gitignored. Raw data stays local. Only proof artifacts get committed.
Starting the collector
cd executors/<your-executor>
nohup pnpm collect > data/collector.log 2>&1 &
tail -f data/collector.log
Note: The collect script uses tsx for development. For production deployment, build first with pnpm build and use node dist/collector/collect.js instead.
Continue to Stage 2 work while the collector runs. You don't wait for Stage 1 gate to start building the scorer — but you DO need the gate to pass before promoting any scores.
Gate: COLLECT → SCORE
Run pnpm verify-data (runs scripts/verify-data.ts — the scaffold provides a stub, you implement the checks):
Then run adversarial review. Read references/adversarial-trading.md and run the Data Skeptic persona against your collector code and data sample.
Stage 2: SCORE
Goal: Build a scorer that replays collected data and produces a machine-readable objective function.
What to build (TDD)
- JSONL reader that streams collected data.
- Replay engine with temporal ordering — no future state leaks into the replay at any point. This is an event-sourcing pattern: the replay reconstructs state by processing events in order, and no event is visible before its observed time.
- For expiry markets: settlements are visible only AFTER
closeTime.
- For continuous markets (perps, DEX, lending): ensure no future funding rates, liquidations, pool state, or rate curves are readable ahead of the cursor.
- For intent-based venues: fulfillment/cancellation events visible only after confirmation.
Read
references/error-classes.md §5 (replay-time information leak) for why this matters.
- Model computation from raw observables. Recompute at score time, never read stored model outputs.
pnpm score <config> CLI outputting the scoring contract JSON. See references/scoring-contract.md for the format and baselines.
- Uses
@recallnet/skunk-paper-trading for fills — remember, empty book = rejected fill, no exceptions.
The scoring contract
Your pnpm score must output JSON matching ScoringContractSchema (in the scaffold at src/scorer/scoring-contract.ts). Minimum fields:
{
"config": "baseline",
"dataWindowHours": 24.5,
"settledWindows": 98,
"trades": 35,
"wins": 28,
"losses": 7,
"winRate": 0.8,
"expectancy": 0.42,
"worstLoss": -8.5,
"avgWin": 1.2,
"avgLoss": -4.5,
"breakEvenWinRate": 0.79,
"profitFactor": 2.13
}
Add executor-specific fields via .loose() — the schema allows extensions (Zod 4 syntax).
Default baselines
- Floor: doesn't lose money (expectancy ≥ 0)
- Target: makes more money per capital at risk than holding cash
Rules
total_trades <= 2 × settled_windows. If violated, your replay has a time-travel bug. Read references/error-classes.md §5. (settled_windows here means completed evaluation cycles — settlements, windows, fulfillments, whatever your venue's unit is.)
- Losses shrink replay capital immediately, even with zero reinvestment. Read
references/error-classes.md §13 (replay capital accounting).
- Bankroll caps must include ALL entry costs (premium + fees for expiry markets; notional + funding/borrow + fees for perps/lending; cost basis + gas + slippage for DEX/intent venues), not just the headline price.
- Determinism is mandatory: same data + same config = same score, every time.
While building in SCORE
Apply the Backtester persona from references/adversarial-trading.md to the replay engine and scoring pipeline as you develop. This is a same-session practice — not a gate step.
Run /strategy-verification on the scoring output as a self-check. Any FAIL is worth fixing before you try to promote.
Gate: SCORE → PROMOTE
Cross-model /verify is not required at this gate — it's called at PROMOTE → LIVE. If you're preparing to promote directly to shadow, run /verify --payload=strategy-verification --gate=promote now.
Stage 3: PROMOTE
Goal: Iterate on strategy configs under governance, then produce a formal go-live evaluation.
Load /governed-research
The promotion stage uses governed research to bound config exploration. Load the
/governed-research skill before starting any config iteration.
The governed iteration loop
Each config iteration costs Variant Equivalents (VE). This is bounded
exploration, not unlimited tuning.
- Score the baseline config — this is your floor.
- Create an alternative config (format is executor-specific — JSON, env vars, whatever).
- Write a governance candidate JSON artifact (
references/governance-input.md in the governed-research skill).
- Run the governance script:
pnpm tsx .agents/skills/governed-research/scripts/governed-research.ts \
evaluate research/governance-candidate.json
- If the decision permits (
PROMOTION_PROPOSAL or SHADOW), update only profiles.research.
- Run the governance script again with
--profiles-before and --profiles-after to verify no protected profile was touched.
- More data always beats more configs. Do not over-tune on thin data.
The governance script evaluates the full gate list:
data_quality, runtime_parity — red means QUARANTINE
liquidity_feasibility, ruin_probability, drawdown, segment_stability — red means CHALLENGER_ONLY
lockbox, shadow — evidence requirements
- VE budget (daily/weekly/monthly) — exhaustion means NO_CHANGE
$1 minimum tradable balance
- Protected profile diffs for
live and live-primary
These gates are the machine-enforced version of what was previously checklist items.
The tear sheet
After governance approves a candidate, generate the tear sheet for capital adequacy:
import {
buildTearSheet,
formatTextSummary,
} from "@recallnet/skunk-strategy-report";
const tearSheet = buildTearSheet({
variant: { name: configName, params: configParams },
events: tradeEvents,
sleeve: "your strategy description",
configsTried: numberOfConfigsScored,
});
console.log(formatTextSummary(tearSheet, { name: configName }));
Write the tear sheet and registry JSON to research/evidence/. These get committed — they're proof. The tear sheet comes AFTER governance approval, not before.
Gate: PROMOTE → LIVE
While building (not a gate step): apply the Backtester and Live Operator personas from references/adversarial-trading.md continuously; run /strategy-verification on the best config as a self-check. Verification status must be CAUTIOUSLY PROMOTABLE or VERIFIED before you reach for the gate.
Cross-model gate (required at PROMOTE):
/verify --payload=strategy-verification --gate=promote
/verify picks a reviewer from a different model family than the implementing agent, hands it .agents/skills/strategy-verification/SKILL.md as its checklist, writes the findings to research/evidence/prosecution-<slug>.md, and enforces the fix-and-re-review loop. If any attack verdict is LANDED, fix the code and re-invoke /verify until the gate returns SURVIVED or WEAKENED (acceptable, with rationale). Do not invoke oracles directly — that bypasses the prosecution doc and the loop.
Retrofitted executors that predate the gate system: run /trade-path-audit as a separate flow before re-entering this gate schedule. See .agents/skills/trade-path-audit/SKILL.md.
Self-report: Produce a structured assessment of what's verified, what's uncertain, and what the evidence grade means in plain English.
Stage 4: LIVE
Goal: Add live execution. Same binary, different env var. Live mode adds ONLY venue plumbing and unknown-order safety.
What to build (TDD)
LIVE_MODE env flag — no behavioral change, just a mode switch.
- Graceful shutdown — SIGTERM + stop file. Uses
acquireLock/releaseLock from the scaffold's process-lock.ts.
@recallnet/skunk-execution-ledger — double-entry for every dollar movement. This is your financial truth, not the venue API.
@recallnet/skunk-venue-budget — per-run USD cap. Reserve BEFORE submission, not after.
- OMS adapter — paper and live share the same lifecycle interface. This is the most important architectural decision in the executor. Define one
OrderManagementSystem interface with submitIntent(), positions(), pendingOrders(). Build PaperOms and LiveOms as separate implementations. The mode switch happens at construction time (createOmsForMode()), never at call sites. If you find yourself writing if (liveMode) in trade logic, you've broken the adapter — load /senior-architect and review the Ports & Adapters pattern.
- Credential validation — startup fails hard on 401.
- Timeout recovery — reconcile against open orders AND fill history.
The cardinal parity rule
Read references/parity-rules.md in full. The rule:
Live mode may ONLY add venue plumbing, unknown-order safety, and the configured run budget check. Everything else is a parity bug.
If you find yourself adding a startup gate, capital throttle, exit behavior, or guardrail that only exists in live mode — STOP. That's a parity bug. Either model it in dry-run first, or remove it.
Gate: PRE-SHIP
Read references/pre-ship-checklist.md. Every item must pass.
Run the parity audit: Search the codebase for every branch on LIVE_MODE / mode === "live". Each must be venue plumbing, unknown-order safety, or the run budget check.
While building (not a gate step): apply ALL THREE trading personas from references/adversarial-trading.md continuously, run the parity audit inline, and run /strategy-verification with live execution data if available. These are build-time practices, not a gate tier.
Cross-model gate (required at LIVE, two reviewers):
/verify --payload=adversarial-verifier --reviewers=2 --gate=live
/verify picks two reviewers in families different from the implementing agent AND different from each other. It hands .agents/skills/adversarial-verifier/SKILL.md (the Chain-of-Verification + Saboteur / New Hire / Security Auditor methodology) to each reviewer, writes findings to the ## LIVE gate section of the prosecution doc, and enforces the fix-and-re-review loop. Continue until the gate returns SURVIVED or WEAKENED (acceptable, with rationale).
At least one of the two reviewers should additionally run --payload=trading-attacks against the strategy (parity bugs, credential handling, timeout and reconciliation paths). /verify handles that routing — the caller just invokes it with --reviewers=2 and the two payloads are varied per the skill's LIVE-gate default.
When a gate fails
A gate failure is a diagnostic event, not a dead end.
- The skill generates a failure report with exactly which checks failed
- Read
references/error-classes.md for the most likely root cause of each failure
- Fix the root cause. Do not weaken the gate.
- Re-run the gate.
If the same gate fails 3 times with different root causes, generate a consolidated experiment report and present it to the operator. The approach may need rethinking.
Never: lower a coverage threshold, remove a check, add a try/catch to suppress a failure, or mark a check as "not applicable" to get past a gate.
Scripts
The skill includes utility scripts at scripts/:
scripts/parity-audit.sh
Searches the executor codebase for live-mode branches and classifies each as venue plumbing, safety, budget, or PARITY BUG.
bash .agents/skills/build-executor/scripts/parity-audit.sh executors/<name>/src/
scripts/gate-check.sh
Runs the machine-verifiable gate checks for a given stage and outputs pass/fail for each.
bash .agents/skills/build-executor/scripts/gate-check.sh collect executors/<name>
bash .agents/skills/build-executor/scripts/gate-check.sh score executors/<name>