Skip to content

Latest commit

 

History

History
166 lines (133 loc) · 9.21 KB

File metadata and controls

166 lines (133 loc) · 9.21 KB

Decisions

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.

Environment

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.

Product

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.

Backtesting

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.

Containment

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.

Chain

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.

Build-time corrections

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.