@darkpool/agent-service package is the hosted-fleet runtime. One process polls /v1/agents/fleet every 30s, maintains an in-memory FleetAgent per row, and ticks each on staggered offsets. Agent keys are AES-GCM encrypted at rest in Postgres (agent_runtime table) and only decrypted into memory inside this process.
This is a v0.3 pivot. Pre-pivot the package was one-process-per-agent with AGENT_KEY in .env; that path still exists for self-hosters but the production deployment runs the hosted fleet exclusively.
Architecture
![Hosted-fleet architecture. Step 1: Wizard (frontend) calls buildCreateAgentChallenge. you sign once with your wallet. Step 2: POST /v1/agents/create verifies the signature, mints an ephemeral keypair, AES-GCM encrypts the runtime secret, signs 2 sponsored PTBs, INSERTs into agent_runtime, and transfers the AgentOwnerCap back to you. Step 3: fleet.ts (this package) keeps in-memory FleetAgent[N], polling GET /v1/agents/fleet (Bearer) every 30s for decrypted rows. Each tick: Pyth candles to summarizeTrend to LLM (Groq or Gemini) to sponsored mint via [sponsor, agent] dual signatures. Step 4: POST tick reports the decision and counters back to the api.](https://mintcdn.com/dp-550c5f99/z9sBhG7Nbc_h18_Q/images/hosted-fleet-architecture.png?fit=max&auto=format&n=z9sBhG7Nbc_h18_Q&q=85&s=efb61802e4bb22fb1b11956282a8c7ad)
One signed message creates the agent; the hosted fleet runtime ticks it on staggered offsets with AES-GCM-encrypted keys.
Required env (hosted fleet)
Tick loop
For eachFleetAgent whose stagger offset is due:
- Read on-chain pause.
sui.getObject(tradeAgentId). Ifis_active=false, record onepaused on-chain by owneraction and return. - Read PredictManager balance.
predict_manager::balance<DUSDC>via devInspect. - Fetch price context. Pyth Hermes 1-min BTC candles for the last 60 min:
- Summarize trend. Derive 30-min move, 15-min move, direction, high/low, last 10 closes.
- Discover oracles. REST
/v1/oraclesdeduped viaoracles.ts(freshest per (asset, expiry); 1hlastUpdateMsbucket primary,fillCounttiebreak). - Decide via LLM. Single-shot prompt (~1,500 tokens) with mandate + Pyth context + oracle list. Model returns structured JSON:
{action, side, oracleId, strike, spendDusdc, confidence, reasoning}. - Signal-only gate. If PM balance is
nullor< $1micro, recordkind=skipwith the full decision (reasoning,action,confidence) and return. The agent still ticks, still reasons, just never mints. - Sponsored mint.
tx.build({onlyTransactionKind:true})toTransaction.fromKindtosetGasOwner(sponsor)to both sponsor + agent sign the same canonical bytes toexecuteTransactionBlockwithsignature: [sponsorSig, userSig]. - Report.
POST /v1/agents/fleet/:id/tickwith{tickCount: sql\$+1`, reasoning, action, strike, confidence, txDigest, didMint}. Inserts a row intoagent_actions` so the UI can replay every decision.
Per-agent LLM routing
pickLlm(modelName) routes based on model prefix:
These are the only models surfaced in the Create wizard because they’re the only ones tested working end-to-end against the runtime’s JSON schema.
Auto-disable
GET /v1/agents/fleet runs inline before returning rows: if a predict_manager::balance<DUSDC> < $1 micro and now - max(created_ms, last_funded_ms) > 3 days, sets enabled=false. Prevents idle agents from burning sponsor SUI on signal-only ticks forever.
Self-host single agent (legacy)
The pre-v0.3 path still works:pnpm --filter @darkpool/agent-service dev. See Heuristic Strategies for the strategy options.
Status URLs
The legacy/status HTTP server still ships in the single-agent path. The hosted fleet does not expose /status. agent state is queryable via /v1/agents, /v1/agents/:id/actions, and /v1/agents/:id/operator.
Logs
Health hardening
- Consecutive identical actions collapse into one row with
×Ncounter. - Common errors humanized:
out of gas(sponsor SUI < 0.02),manager balance too low,api unreachable. - Fleet card derives a
degradedstate (amber pulse + warn banner) whenhumanizeAgentErrormatches. - 429/5xx from the LLM: one retry after 2s, then fall back to Groq
llama-3.3-70b-versatileif the failing call was Gemini.

