Autonomous build decisions, and why. Where the PRD left a choice open (or where the environment forced one), the reasoning is recorded here rather than lost.
D1 — Python 3.14, not 3.11. The build machine ships only /usr/bin/python3.14.
Everything in packages/strategy-runtime is stdlib-only and version-agnostic, so
this costs nothing. pnpm demo invokes python3.
D2 — Zero third-party Python dependencies. No numpy, no pandas, no FastAPI.
The runtime is pure stdlib (http.server, ast, resource, subprocess). Two
reasons: (a) pnpm install alone must be enough to run the demo — a pip install
step is one more thing to fail on stage; (b) the sandbox child is spawned with
-S (no site-packages), so anything the strategy runtime needs must be stdlib
anyway. Statistics that would have been numpy are ~40 lines of math.
D3 — Default chain is local anvil, not Arc testnet. CHAIN_MODE=anvil is the
default so pnpm demo works with no funded key and no network. CHAIN_MODE=arc
switches to the real testnet and the real USDC/EURC/USYC predeploys. Both paths
run identical agent code; only address resolution differs.
D4 — OQ1: both instruments, capped at two. USDC/EURC (the purest Arc story)
and ETH/USD (strategy variety, and something for trend families to find).
The PRD allowed three; two is enough for family diversity and keeps the Pyth
cache small.
D5 — OQ2: flat per-run backtest pricing. $0.05/run through the same x402 meter the agent pays. Metering by compute would be more honest but adds a pricing surface nobody will read during a five-minute demo, and the symmetry that matters — humans and the agent pay the same meter — is preserved either way.
D6 — OQ3: post-mortems published on-chain. StrategyDemoted(codeHash, reason)
carries the full reason string. It is cheap, it is the most informative event
the factory emits, and a registry that only records successes is exactly the
kind of selective disclosure this project exists to argue against.
D7 — OQ4: signal-sales endpoint cut. F-7.4 was explicitly a stretch. The agent-to-agent loop is already demonstrated by the meter (the agent pays for inference per call); a scripted buyer-agent adds a demo surface without adding a new claim. The Nanopayments contract would support it unchanged.
D8 — Synthetic fallback data carries deliberate, documented structure. A pure
random walk has no edge, so every strategy would score ~0 minus costs and the
factory would select on noise — the pipeline would run but prove nothing.
The offline generator therefore gives USDC/EURC an Ornstein-Uhlenbeck pull and
ETH/USD AR(1) momentum, both documented in data.py. Costs, slippage, gas and
out-of-sample-only evaluation are all still enforced in full, so badly
parameterised strategies still fail — see the fitness spread across the twelve
template families. Every surface labels this data SIMULATED.
D9 — Signal at bar i fills at bar i+1. Structural anti-lookahead, not a
convention. Asserted by test_signal_fills_at_next_bar_not_current.
D10 — Walk-forward windows are non-overlapping and train is never scored. The reported equity curve is the concatenation of test segments only.
D11 — Three independent layers, and the third is the real one. AST allowlist
(cheap, catches ~everything), rlimit'd subprocess (catches what compiles), and
PolicyGuard on-chain (catches what escapes). Layers 1–2 contain code; layer 3
contains capital, which is the only guarantee that survives a novel escape.
D12 — LLM code and user code use one security model. Not two pipelines with a "trusted" branch. A user paste and a Forge output hit the identical validator, the identical sandbox, and the identical graduation gate.
D13 — __build_class__ is in the sandbox builtins allowlist. class Strategy:
compiles to a call to it, so the interface cannot exist without it. It grants no
capability the allowlist otherwise withholds.
D14 — Decimals are always read, never assumed. Arc's native gas asset is USDC
at 18dp while the USDC ERC-20 interface at 0x3600…0000 reports 6dp — a silent
1e12 error. test/Decimals.t.sol and apps/agent/src/lib/decimals.test.ts both
assert the discipline; local TestToken takes decimals as a constructor argument
specifically so anvil mode reproduces the trap.
D15 — Local-key signing by default, Circle Wallets scaffolded behind it.
CircleWalletSigner implements the same Signer interface and activates when
CIRCLE_API_KEY is present. PolicyGuard mirrors Agent Wallet policies on-chain
so the containment guarantee holds identically in both modes.
D16 — Venue liquidity is synthetic and labelled, not disguised. SpotVenue is
a constant-product stub with operator-seeded reserves. The claim Nectar makes is
about the pipeline — code hash → registry → capped wallet → signed receipt —
and that chain is real end to end. Pretending the depth was real would undercut
the only thing here worth trusting.
D17 — Drizzle over node:sqlite, not better-sqlite3. better-sqlite3 is a
native addon with no Node 24 prebuilds, so pnpm install would have required a
working node-gyp toolchain. The definition of done says the demo boots from a
cold pnpm install; making it also depend on a C++ compiler is the kind of
thing that fails on exactly one laptop, on stage. node:sqlite ships with Node,
and Drizzle's sqlite-proxy driver gives the same schema and query builder.
D18 — A parametric variant is not a duplicate. The diversity guard blanks
numeric literals before comparing structure, which is right for catching an LLM
that was asked for a new mechanism and returned the old one with a constant
nudged. Applied absolutely it also killed template-mode evolution outright:
every parametric mutation read as a 100% structural match and was rejected, so
the loop kept running and silently stopped exploring. Now, when structure
matches, the parameters decide — near-identical is a duplicate, materially
different is an admitted variant. Asserted in diversity.test.ts.
D19 — Species guard on parent selection. Rank-weighted sampling alone collapsed the population into one family within a few generations. Parents are now drawn from a pool built one-per-family first, and the seed/mutate ratio tilts toward seeding as concentration rises.
D20 — Feature windows are primed, not cold. Paper and live sessions seed their history from recent cached bars. A strategy needing a 50-bar average would otherwise sit inert for its first fifty ticks — technically correct, and useless as a confirmation gate.
D21 — Resolved parameters are baked into the source. The code hash is
keccak256 over the source bytes and it is what goes on-chain, so a module
declaring size_frac: 0.4 while the harness runs 0.3 would mean the published
code is not the code that traded. bakeParams keeps source, behaviour and hash
identical by construction — and it is also what the user reads in the /submit
read-back.
D22 — demo:security asserts which revert it got. The first version
selected any strategy with a wallet, picked a demoted one, and got
NotProvisioned for all three probes — printing three green ticks for entirely
the wrong reason. It now requires an active, unexpired policy, includes a
positive control (the allowlisted venue must be accepted), and fails if the
revert name does not match the check being made. A security demo that can pass
for the wrong reason is worse than none.
D23 — Live concurrency cap (MAX_LIVE_STRATEGIES, default 8). Not a safety
limit — PolicyGuard is that — but forty simultaneous live strategies make the
factory floor unreadable and prove nothing the eighth did not.
D24 — The paper gate does not compare incomparable Sharpes. The first
version annualised a Sharpe computed on a few dozen sub-minute paper
observations and compared it against a backtest Sharpe computed on hourly bars.
That produced figures like "Paper Sharpe -184.21" and demoted strategies on what
was effectively a unit error. realised_sharpe() now returns the raw per-step
figure, clearly labelled, and the divergence check makes only the claim it is
entitled to: a strategy with a positive out-of-sample record that is losing on
more than 60% of live steps is not reproducing. Drawdown breach — which needs no
cross-frequency comparison — remains the primary gate.
D25 — The live harness records a price only when the feed moves. The harness ticks faster than the oracle updates, so appending on every tick packed the feature window with duplicates and dragged every moving average toward spot. Strategies saw a market that never moved and never traded. Deduplicating by value restored live execution.
D26 — pnpm demo refuses a busy agent port rather than reusing it. Unlike
the stateless web server (which it does reuse), the agent owns the SQLite
population database and caches contract addresses at boot. A leftover process
may be writing to a deleted database file or pointing at contracts from a
previous chain, and the demo would look perfectly alive while doing neither.
This was caught in testing, having produced exactly that failure.