Skip to content

feat: Circle experimental capsules — decimals & stale_quote - #5

Merged
adrianhihi merged 1 commit into
mainfrom
feat/circle-experimental-capsules
May 26, 2026
Merged

feat: Circle experimental capsules — decimals & stale_quote#5
adrianhihi merged 1 commit into
mainfrom
feat/circle-experimental-capsules

Conversation

@adrianhihi

Copy link
Copy Markdown
Collaborator

Circle Experimental Capsules

Follow-up to #3. Adds 2 capsules whose q-values come from Helix's April 2026 Arc Testnet experiments, with end-to-end demo reproduction of both failure modes.


Why this PR

PR #3 (Circle Foundation) shipped 5 Circle capsules. Three were validated against live Circle Sandbox in the same PR. Two more capsules — decimals-metadata-mismatch and stale_quote — had pre-existing experimental data from scripts/circle-bench/ but weren't integrated into helix-core. This PR closes that gap.

Capsule Strategy q Source
decimals-metadata-mismatch override_api_decimals 0.95 Exp A — 1 real Arc tx
stale_quote observe (advisory) 0.96 Exp D — 932 real Arc tx, 96% E2E vs 0% bare

What it ships

Type system (types.ts)

  • ErrorCode += decimals-metadata-mismatch, stale_quote
  • FailureCategory += infrastructure (token metadata bugs, native-vs-ERC-20 confusion)
  • FailureClassification.chain optional field (e.g. 'arc-testnet', 'base-sepolia')

Strategy implementation (provider.ts)

override_api_decimals uses 3-priority resolution:

  1. On-chain — read ERC-20 decimals() if context.publicClient + tokenAddress provided
  2. Ground-truth table — for native USDC where no contract exists (Arc Testnet's case)
  3. Caller-supplied — fallback to context.expected_decimals

This handles Exp A's actual failure mode: Arc Testnet USDC is a native asset with no ERC-20 contract to query. Circle's API claims decimals=18; the real atomic resolution is 6. The repair must use ground-truth knowledge, not on-chain reads.

Two-tier perceive detection (circle/perceive.ts)

Tier 1 — Caller-emit (explicit context):

  • decimals-metadata-mismatch: when caller provides both expected_decimals and api_reported_decimals
  • stale_quote: when context.stale_quote=true OR response.data.code === 'STALE_QUOTE' OR message match

Tier 2 — Heuristic:

  • If requested_amount / available_balance > 10^9, suspect a decimals bug rather than insufficient funds (18-vs-6 ratio is 10^12, so 10^9 catches it comfortably)

Seed capsules (seed-genes.ts)

2 new entries with q-values from telemetry, not guesses. Both apply apiLayer: 'wallets-api' for correct Gene Map lookup matching.

Demo expansion (examples/circle-e2e.ts)

  • Scenario 4 — Naive agent uses API's wrong decimals=18 for Arc USDC, sends 0.001 × 10^12 raw amount, Circle returns insufficient funds. Perceive heuristic catches the suspicious ratio, ground-truth table provides correct decimals=6, parameterModifier scales the amount, retry succeeds on real Arc Testnet.
  • Scenario 5 — Synthetic STALE_QUOTE error matching x402 facilitator response shape. Perceive routes to stale_quote capsule. observe strategy records the failure for caller's preflight; does not auto-fix. Console surfaces 3 repair options with their applicability conditions.

Important caveat: stale_quote repair options

The bench Exp D demonstrated think → discover → estimate → pay → verify reorder yields 96% E2E. This reorder is only valid when the think step does not consume the quote (Exp D's controlled think-as-sleep design satisfied this).

Real-world agents typically have think that consumes the quote (e.g., "is $X worth my budget?"). For those, the repair options are:

  • (a) Reorder — valid only if think is quote-independent
  • (b) Split think — pre-quote selection + post-quote price-check
  • (c) Request longer TTL or refresh quote before pay

This nuance is encoded in:

  • seed-genes.ts params (machine-readable)
  • Scenario 5 console output (human-readable, visible during demo)
  • Commit 864587d message (historical record)

observe strategy is deliberate — Helix records the failure pattern and surfaces options; the caller's architecture decides which repair fits.


Validation

Two full demo runs (LEARN then IMMUNE rerun), all 5 scenarios pass cleanly.

Capsule deltas across two demo runs

Capsule RUN 0 (seed) RUN 1 RUN 2 Δ Final q
decimals-metadata-mismatch 1 3 5 +4 0.964
stale_quote 48 52 56 +8 0.976
wallets-api-rate-limit 9 21 31 +22 0.982
circle-param-invalid 3 7 11 +8 0.909
circle-insufficient-funds (new) 4 10 +6 0.758
gateway-rate-limit 7 7 7 0 (not exercised) 0.700
gateway-nonce-used 8 8 8 0 (not exercised) 0.920
cctp-attestation-pending 6 6 6 0 (not exercised) 0.980

All exercised capsules show q-value rising. Untouched capsules unchanged.

  • tsc --noEmit clean
  • ✅ 570/570 existing tests pass
  • ✅ Real Arc Testnet transactions submitted in Scenario 4
  • ✅ Demo Run 2 (IMMUNE) maintains correct classification

Notable findings during development

  1. Arc Testnet USDC is a native asset, not ERC-20. Circle's SDK returns { isNative: true, decimals: 18 } with no tokenAddress. Original Exp A repair worked by using ground-truth knowledge, not on-chain reads. Strategy now reflects this.

  2. stale_quote is advisory. The actual workflow reorder is the agent's responsibility, not the engine's. Helix records the failure pattern via observe strategy; agents preflight Gene Map audit logs to decide on a repair option (see caveat section above).

  3. stale_quote preserves underscore casing. Inconsistent with other ErrorCodes' hyphen convention, but matches the string already stored in bench audit logs and Cloudflare registry. Changing to hyphens would silently break cross-agent inheritance.


Roadmap (out of scope here)

  • Real e2e demos for CCTP V2 and Circle Gateway — capsules from PR feat: Circle Developer Wallets integration (foundation PR, 1 of 4) #3 are seeded, strategies implemented, but no live integration testing yet.
  • Automatic workflow reorder for stale_quote — implement as a wrap-layer feature alongside observe capsule. Currently advisory only.
  • Mainnet adapter and production tier — pending Circle KYC approval.

Verification by reviewers

  • All Arc Testnet transactions verifiable on testnet.arcscan.app
  • Cloudflare telemetry registry: https://helix-telemetry.haimobai-adrian.workers.dev/v1/stats
  • Bench scripts: scripts/circle-bench/ (Exp A through Exp D)
  • To reproduce demo locally:
  git checkout feat/circle-experimental-capsules
  npm install && npm run build -w packages/core
  set -a; source scripts/circle-bench/.env; set +a
  rm -f circle-demo-genes.db
  npx tsx examples/circle-e2e.ts

late_discover (reorder to think → discover → pay) is valid only when
the think step does not consume the quote. Bench Exp D used a
controlled think-as-sleep design that satisfied this constraint.

Real-world agents typically have think that consumes the quote
(e.g., 'is $X worth it?'). For those, options include:
  (a) split think into pre-quote selection and post-quote price-check
  (b) request longer quote TTL
  (c) refresh quote between think and pay

Updated stale_quote seed-gene params and Scenario 5 console output
to document all repair patterns.
@adrianhihi
adrianhihi merged commit 9a25f48 into main May 26, 2026
4 checks passed
@adrianhihi
adrianhihi deleted the feat/circle-experimental-capsules branch May 26, 2026 06:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant