| name | compute-resell |
| description | Autonomous compute-reselling agent on Surplus Intelligence - resells free or low-cost provider compute across Bankr, AWS Bedrock, and Google Vertex (GCP) as one skill. Reads the live market + your cost + your usage, auto-lists your models per enabled provider, and reactively prices each offer to win routing within a cost floor, cap, and health guardrails. |
| metadata | {"title":"Compute Resell","category":"crypto","var":"","tags":["crypto","onchain"],"cron":"15 6,14,18 * * *","mode":"write","requires":["SURPLUS_SELLER_KEY?","SURPLUS_SELLER_AWS_KEY?","SURPLUS_SELLER_VERTEX_KEY?","BANKR_LLM_KEY?","BEDROCK_API_KEY?","VERTEX_SERVICE_ACCOUNT_JSON?","VERTEX_API_KEY?","COMPUTE_RESELL_CONFIG?","AWS_COMPUTE_RESELL_CONFIG?","VERTEX_COMPUTE_RESELL_CONFIG?"],"capabilities":["external_api","writes_external_host","sends_notifications"]} |
${var} - [<provider>:]<mode>. The optional <provider> prefix (bankr | aws | vertex) restricts the run to one provider; omit it to run every enabled provider (see Providers). <mode> selects the run mode - first match wins:
- empty → the reactive run (the scheduled default): validate the wallet, read the live market + your cost + your usage, auto-list any missing models, reprice every offer against the current market, notify on signal. No separate setup step.
monitor → read-only market + offers + usage digest, no writes.
reprice → same as empty (kept as an explicit alias).
pause → soft-delete every offer (stop serving); state retained.
resume → re-create offers from state at the current reactive price.
Examples: empty = all enabled providers, reactive. aws = only AWS, reactive. vertex:pause = pause only Vertex. monitor = read-only digest across all enabled providers.
(There's no setup mode - the empty run bootstraps itself: if a provider has no offers yet and its credential is set, it lists that provider's models on the first run.)
(You rarely need pause/resume by hand: the reactive run auto-reaps dead and orphaned offers and auto-relists healthy models - so removing a provider's credential secret already delists it cleanly, and adding a funded one back restores the lineup on the next run. Reach for pause/resume only for a deliberate manual hold.)
Providers
This skill is a single reselling engine run once per compute provider. All three providers sell on the same Surplus marketplace with the same create shape (POST /v1/seller/offers with {model, api_key, seller_base_url, ...}); they differ only in the compute source behind the offer - its credential, endpoint, cost basis, and per-account denylist. Everything downstream (market read, scoring, pricing, caps, adaptive discount, reaping, modes, notify) is provider-agnostic and identical for all three.
Provider adapter table - the only per-provider inputs:
| dim | bankr | aws | vertex |
|---|
| Surplus seller wallet (secret) | SURPLUS_SELLER_KEY | SURPLUS_SELLER_AWS_KEY | SURPLUS_SELLER_VERTEX_KEY |
provider credential → api_key (secret) | BANKR_LLM_KEY (+ _2/_3) | BEDROCK_API_KEY | VERTEX_SERVICE_ACCOUNT_JSON (or VERTEX_API_KEY) |
seller_base_url | https://llm.bankr.bot/v1 | BEDROCK_BASE_URL (repo var) | built from VERTEX_PROJECT_ID + VERTEX_LOCATION (repo vars) |
cost basis C (floor mode) | /v1/prices bankr entry | config bedrock_prices | config vertex_prices |
| config secret | COMPUTE_RESELL_CONFIG | AWS_COMPUTE_RESELL_CONFIG | VERTEX_COMPUTE_RESELL_CONFIG |
| state file | memory/state/compute-resell.json | memory/state/aws-compute-resell.json | memory/state/vertex-compute-resell.json |
| preflight auth | X-API-Key (Bankr gateway) | Authorization: Bearer (proxy) | SA→access-token, or AIza key |
A provider is "enabled" for a run iff its Surplus seller wallet secret and its provider credential secret are both set. Skip (don't error on) any provider missing either - a Bankr-only instance just sets SURPLUS_SELLER_KEY + BANKR_LLM_KEY and never touches AWS/Vertex.
The run loop. Resolve the provider set: [<provider>] from ${var} if it carries a prefix, else every enabled provider. Fetch the shared market reads once - GET /api/markets (roster) and GET /v1/prices (Surplus model catalog) are provider-agnostic, so read them a single time and reuse across providers. Then for each provider in the set, export RESELL_PROVIDER=<provider> (the helper scripts key their config secret + state file off it) and run the full engine below against that provider's adapter row - its own wallet, credential, seller_base_url, cost basis, config, and state file. Providers are independent: one provider failing (bad key, exhausted funds) never aborts the others; log its exit reason and continue. Aggregate one notify at the end (per-provider TLDR lines).
Never self-undercut across your own providers. Because all providers list on one order book, two of your own providers listing the same market id would undercut each other and drag your family's clearing price toward zero. The Cross-skill claim ledger (below) already prevents this: it globs memory/state/*compute-resell.json - which now includes all three provider state files - so each provider claims its models and a later provider in the loop skips any market id an earlier one already claimed this run. See Cross-skill claim ledger. The provider-specific credential, endpoint, cost basis, and denylist are detailed per adapter in Provider adapters at the end of this file; read that provider's row before its reactive run.
Why this design
Surplus routes each buyer to the cheapest healthy seller for a model, settling per-request in USDC on Base to the seller's wallet. Public endpoints make the market observable, so pricing is reactive, not blind: GET /api/markets/{model} is the live order book - every rival's real price, health, trust flag and remaining cap - and GET /v1/prices gives the per-provider cost basis (what Bankr charges you). The engine prices each offer against the reduced book (see Who actually competes) - undercutting to win routing when someone who can actually serve is cheaper, and probing upward only when you're already the cheapest.
The economics - free credit, so every sale is pure profit. In the default (undercut) mode the compute source is free - Bankr credit (token-launch fees / promo credits), free AWS credits, or free Google Cloud credits - so your_provider_cost ≈ $0 and profit per request = the whole buyer price. There is no cost to defend, no such thing as selling "below cost," and no "burn," "bleed," or "negative edge" - that framing does not apply to free inventory. The only axis is revenue = Σ (volume × price). Winning routing means being cheapest, which trades price for volume: a deeper discount wins more books but keeps fewer USDC per token; a shallower one keeps more per token but wins fewer books. The engine's whole job is to sit at the revenue-maximizing point of that curve. (floor still exists for the rare operator funding a real per-token cost - an out-of-credit Bedrock/Vertex account - who wants a hard price floor; unset by default, because free credit needs none. In floor mode the cost basis C comes from that provider's adapter row: /v1/prices for bankr, config bedrock_prices/vertex_prices for aws/vertex.)
Guardrails are load-bearing because every write is real money in two directions (buyers pay you, you owe the provider):
- Cost floor (
floor) - never list below your true cost.
cap_daily_usd on every offer - provider-spend protection against runaway routing.
- Idempotency - offer IDs live in state; the engine
GET /offers first and never creates a duplicate offer for a model it already lists.
- Fail-closed - offer writes are the final in-run action; a non-2xx aborts the run and reports the true reason.
Auth model
Runtime is Bearer-only. Each provider has its own si_seller_... seller-wallet key - minted once via SIWE and stored as a distinct repo secret (SURPLUS_SELLER_KEY for bankr, SURPLUS_SELLER_AWS_KEY for aws, SURPLUS_SELLER_VERTEX_KEY for vertex). Separate wallets are mandatory: two providers sharing one wallet would clobber each other's offers on the same Surplus account. No private key ever enters CI. Below, {SURPLUS_SELLER_<P>_KEY} denotes the running provider's wallet secret from the adapter table.
Mint it - the dashboard (recommended). Settings → Secrets → the provider's wallet secret → Connect wallet. Your browser wallet (MetaMask / Rabby / Coinbase) signs the SIWE challenge; the key is minted and written to the secret server-side - your private key never leaves the wallet and the key is never shown in the browser. Reconnect rotates it. (Routes: GET /api/surplus/challenge → wallet personal_sign → POST /api/surplus/mint → setSecret.)
Fallback - headless / no browser wallet. Run locally, never in Actions (pass the target wallet secret's label):
npm i viem
SELLER_WALLET_PRIVATE_KEY=0x... node skills/compute-resell/bootstrap-siwe.mjs compute-resell
All API calls below use ./secretcurl so the Bearer key stays off the command line (the running provider's wallet secret):
./secretcurl -sS -w '\nhttp=%{http_code}\n' --max-time 30 \
-H "Authorization: Bearer {SURPLUS_SELLER_KEY}" \
"https://api.surplusintelligence.ai/v1/seller/offers"
Host: https://api.surplusintelligence.ai (call directly; don't follow redirects). Print http=<code> and decide from it - only degrade on a real non-2xx, --max-time timeout, or a 200 with empty body, and log the true reason (http-<code> / timeout / empty). Never write "sandbox"/"expansion blocked".
Config
Provider-name convention (read once). The engine body below is written with the bankr adapter's names as the canonical stand-in: wherever it says COMPUTE_RESELL_CONFIG, memory/state/compute-resell.json, or SURPLUS_SELLER_KEY, read the running provider's equivalent from the Providers adapter table - i.e. AWS_COMPUTE_RESELL_CONFIG + memory/state/aws-compute-resell.json + SURPLUS_SELLER_AWS_KEY when RESELL_PROVIDER=aws, the VERTEX_/vertex- forms when RESELL_PROVIDER=vertex. The helper scripts already resolve this off RESELL_PROVIDER; you resolve it the same way when you run a command by hand. Two more per-provider paths follow the same rule: the topic file memory/topics/compute-resell.md → aws-compute-resell.md / vertex-compute-resell.md, and the earnings ledger memory/compute-earnings.csv → aws-compute-earnings.csv / vertex-compute-earnings.csv (bankr keeps the unprefixed names). The knobs, defaults, and mechanics are identical across providers - only the secret/file names and the provider wiring (endpoint, credential, cost basis, denylist in Provider adapters) change.
Zero config to start - each provider runs on safe defaults. To tune a provider, set its optional config secret (COMPUTE_RESELL_CONFIG / AWS_COMPUTE_RESELL_CONFIG / VERTEX_COMPUTE_RESELL_CONFIG) in the dashboard (Skill Keys) to a tiny JSON:
| Knob | Default | What it does |
|---|
daily_budget_usd | 10 | Total free credit to deploy per day across all this provider's offers. Split evenly across the listed models (n offers x budget/n). Lower it to concentrate on fewer models. Free credit, so it just bounds daily inventory, not loss. |
floor | (unset -> undercut) | Unset (default) -> undercut mode: list just below the cheapest competitor to win routing (pure profit on free credit). Set it only if you fund a real per-token cost: the lowest price you'll list as a multiple of the cost basis C - 1.0 = never below cost, 0.6 = down to 60% of it. |
max_discount | (unset -> no cap) | Discount-rate guardrail: the deepest discount off the direct sticker this provider will list at. 0.80 = never deeper than 80% off (d_in <= 0.80 and d_out <= 0.80), and skip any model whose market already clears deeper (unwinnable at your rate). Unset -> uncapped. See Maximum-discount cap. |
discount_adaptive | false | Self-tune max_discount for revenue. true -> treat max_discount as a starting point and nudge the effective cap <= discount_step per run toward the revenue-max point, within [discount_min, discount_max]: a sold-out offer -> tighten one step (shallower, more USDC/token); a cold run (revenue ~0 or <= 1 winnable book) -> loosen one step (deeper, open more books); else hold. Persists in state (adaptive_discount). Guarded so a tighten never prices out a live earner. See Adaptive discount. |
discount_min / discount_max / discount_step | 0.30 / 0.90 / 0.05 | Bounds and per-run step for discount_adaptive (ignored when off). discount_max is a real rate wall (never lists deeper). |
min_market_volume_usd | 1.0 | The selection liquidity gate (the only one): minimum 24h marketplace $-volume (volume_24h / 1e6) a model needs to be listed. Drops dead markets that trade ~$0 before scoring, so the budget concentrates on real liquidity. 0 disables it. |
cap_daily_usd | (derived) | Optional hard per-offer ceiling. Unset -> each offer's cap = the even split max(1, daily_budget_usd / n). Set it to cap any single offer below that share. Allocation is always even. |
min_credit_days | 3 | Low-credit warning (bankr only): when a real credit balance is read, warn if it is under this many days of runway. Warn-only - never throttles spend. 0 disables. |
payout_address | (unset) | Base address that receives USDC settlement for offers this provider creates. Unset -> the seller wallet. Point bankr at a Bankr wallet and earnings auto-top-up the credits that fund the next sales. Applied at create time only. |
denied_models | (unset) | Hard denylist - never list these (matched by Surplus market id or mapped provider id). Also auto-learned from create/probe access errors. Seed with models the provider account can't serve (see Provider adapters). |
model_map | (unset) | aws/vertex only: map the Surplus market id -> the provider's serve id (and, for vertex, a per-model region). Unmapped models fall back to a one-probe test-list. See Provider adapters. |
bedrock_prices / vertex_prices | (unset) | aws/vertex only, floor mode only: your per-token cost basis C for the model, $/1M tokens. Not read in the default undercut mode. See Provider adapters. |
Example secret value: {"daily_budget_usd":50,"max_discount":0.80} - or leave the whole secret unset to take defaults ($10 budget, undercut mode, uncapped discount). Concentrate credit on fewer models by lowering the budget: {"daily_budget_usd":20}. Point earnings back at your credit wallet: {"payout_address":"0xYourBankrWallet"}.
Everything else is automatic: models are discovered from the live market (/v1/prices), ranked by live demand (/api/markets) and listed most-active-first up to the auto-derived offer count, and the engine prices each offer against the current market price - down toward floor to win routing, up toward the reference to capture margin.
Reading COMPUTE_RESELL_CONFIG - read it by name, NEVER via $-expansion. It's injected as a secret, and the Bash permission layer blocks any command whose text contains $COMPUTE_RESELL_CONFIG / ${COMPUTE_RESELL_CONFIG} - so jq <<< "$COMPUTE_RESELL_CONFIG" or echo "$COMPUTE_RESELL_CONFIG" is refused, and the skill then silently falls back to defaults (this is exactly the bug that made a set config look unset). Read it by name through os.environ instead - the command line carries only the literal name, no $-expansion, so it's allowed:
python3 -c "import os,json