Skip to main content
The @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.

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 each FleetAgent whose stagger offset is due:
  1. Read on-chain pause. sui.getObject(tradeAgentId). If is_active=false, record one paused on-chain by owner action and return.
  2. Read PredictManager balance. predict_manager::balance<DUSDC> via devInspect.
  3. Fetch price context. Pyth Hermes 1-min BTC candles for the last 60 min:
  4. Summarize trend. Derive 30-min move, 15-min move, direction, high/low, last 10 closes.
  5. Discover oracles. REST /v1/oracles deduped via oracles.ts (freshest per (asset, expiry); 1h lastUpdateMs bucket primary, fillCount tiebreak).
  6. 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}.
  7. Signal-only gate. If PM balance is null or < $1 micro, record kind=skip with the full decision (reasoning, action, confidence) and return. The agent still ticks, still reasons, just never mints.
  8. Sponsored mint. tx.build({onlyTransactionKind:true}) to Transaction.fromKind to setGasOwner(sponsor) to both sponsor + agent sign the same canonical bytes to executeTransactionBlock with signature: [sponsorSig, userSig].
  9. Report. POST /v1/agents/fleet/:id/tick with {tickCount: sql\$+1`, reasoning, action, strike, confidence, txDigest, didMint}. Inserts a row into agent_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:
Then 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 ×N counter.
  • Common errors humanized: out of gas (sponsor SUI < 0.02), manager balance too low, api unreachable.
  • Fleet card derives a degraded state (amber pulse + warn banner) when humanizeAgentError matches.
  • 429/5xx from the LLM: one retry after 2s, then fall back to Groq llama-3.3-70b-versatile if the failing call was Gemini.