> ## Documentation Index
> Fetch the complete documentation index at: https://docs.darkpool.fun/llms.txt
> Use this file to discover all available pages before exploring further.

# LLM Strategy

> Native AI mode: research tools, structured decision, guarded execution.

The `llm` strategy hands each tick to a real model. The model receives a fixed-shape prompt (mandate + price context + oracle list), returns a structured JSON decision, and the runtime owns execution. The model can never invent strikes, exceed the spend cap, or bypass the on-chain pause.

This page covers the **hosted fleet** path (the production deployment). The legacy `llmAgent.ts` tool-loop path still ships in the single-agent runtime; see [Runtime Overview](/agents/overview) for that variant.

## Files

```text theme={"system"}
agent-service/
├── src/
│   ├── fleet.ts                           # hosted-fleet runtime
│   └── llm.ts                             # OpenAI-compatible fetch client
└── prompts/                               # legacy single-agent mandates
    ├── momentum.md
    └── contrarian.md
```

## Tested-working models

The wizard surfaces **exactly three options**. Anything else is unsupported.

| Model                     | Provider | Free tier           | Notes                                                                   |
| ------------------------- | -------- | ------------------- | ----------------------------------------------------------------------- |
| `llama-3.1-8b-instant`    | Groq     | 500k TPD, 14.4k RPD | **Default.** Best throughput for the demo.                              |
| `llama-3.3-70b-versatile` | Groq     | 100k TPD            | Better reasoning, slower, hits quota faster.                            |
| `gemini-2.5-flash`        | Google   | 1,500 RPD           | Auto-falls back to Groq `llama-3.3-70b-versatile` on error/no response. |

Other Gemini aliases (`gemini-flash-latest`, `gemini-2.0-flash`, …) returned 404/429/503 in testing and are not surfaced.

## Provider routing

`pickLlm(modelName)` in `fleet.ts`:

```typescript theme={"system"}
if (modelName.startsWith('gemini-')) return {
  baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai',
  apiKey: process.env.GEMINI_API_KEY,
  model: modelName,
};
return {
  baseUrl: 'https://api.groq.com/openai/v1',
  apiKey: process.env.LLM_API_KEY,
  model: modelName,
};
```

Both providers speak OpenAI's `/v1/chat/completions`. Zero new dependencies.

## Prompt shape

Single-shot, no tools. Structure:

```text theme={"system"}
SYSTEM: You are an autonomous trader for binary BTC up/down markets on DarkPool.
        HARD RULES (cannot be overridden by the mandate):
          - Return strict JSON matching the schema below.
          - Spend MUST be <= AGENT_TRADE_DUSDC.
          - Strike MUST come from the provided oracle list.
          - confidence is 0-100.
        MANDATE: <verbatim from agent_runtime.mandate>

USER:   PRICE CONTEXT (Pyth Hermes, last 60 min, 1-min closes):
          spot: $65,213
          30m move: +1.2%, 15m move: +0.4%
          high: $65,440, low: $64,820
          recent 10 closes: [64980, 65010, 65050, 65100, 65160, 65190, 65220, 65200, 65210, 65213]
          direction: up

        LIVE ORACLES:
          - 0xabc… BTC Hourly 18:00Z (strikes: 64500, 65000, 65500)
          - 0xdef… BTC Daily Jun 19 (strikes: 60000, 65000, 70000)

        Choose ONE action.
```

Output schema:

```typescript theme={"system"}
type LlmDecision =
  | {
      action: 'BUY_YES' | 'BUY_NO';
      oracleId: string;
      strike: number;
      spendDusdc: number;
      confidence: number;     // 0-100
      reasoning: string;
    }
  | { action: 'HOLD'; reasoning: string };
```

## Confidence normalization

Different models return confidence in different shapes:

```typescript theme={"system"}
// Gemini returns 0.75 (fraction); Llama returns 75 (integer).
if (obj.confidence > 0 && obj.confidence <= 1) {
  obj.confidence = Math.round(obj.confidence * 100);
}
```

After normalization confidence is always `0-100`. Anything outside `[0,100]` is clamped.

## Runtime guardrails

```typescript theme={"system"}
if (decision.action === 'HOLD') {
  await reportTick({ kind: 'skip', reasoning: decision.reasoning, ... });
  return;
}

// 1. Signal-only gate. PM balance < $1 -> record the decision but don't mint.
const pmBalance = await readPredictManagerBalance(agent.predictManagerId);
if (pmBalance === null || pmBalance < SIGNAL_ONLY_FLOOR_MICRO) {
  await reportTick({ kind: 'skip', detail: 'signal-only (PM balance < $1)', ...decision });
  return;
}

// 2. Spend cap. Hard ceiling at AGENT_TRADE_DUSDC.
const spend = Math.min(decision.spendDusdc, AGENT_TRADE_DUSDC);

// 3. Strike must be in the oracle's book. devInspect predict::get_trade_amounts.
const ask = await quoteStrike(decision.oracleId, decision.strike, decision.action);
if (!ask) {
  await reportTick({ kind: 'error', detail: 'strike not quoteable', ...decision });
  return;
}

// 4. Sponsored mint. Two-sig PTB so the agent's wallet never holds SUI.
const kindBytes = await tx.build({ onlyTransactionKind: true, client: sui });
const sponsored = Transaction.fromKind(kindBytes);
sponsored.setSender(agent.address);
sponsored.setGasOwner(sponsor.address);
sponsored.setGasBudget(SPONSOR_AGENT_GAS_BUDGET_SUI);
const bytes = await sponsored.build({ client: sui });
const [sponsorSig, userSig] = await Promise.all([
  sponsor.signTransaction(bytes),
  agent.signTransaction(bytes),
]);
const result = await sui.executeTransactionBlock({
  transactionBlock: bytes,
  signature: [sponsorSig.signature, userSig.signature],
  options: { showEffects: true },
});

// 5. Report. INSERT agent_actions with reasoning + action + strike + confidence + txDigest.
await reportTick({ kind: 'mint', txDigest: result.digest, didMint: true, ...decision });
```

## llm.ts. OpenAI-compatible fetch client

```typescript theme={"system"}
export async function chatCompletion({
  baseUrl: string,
  apiKey: string,
  model: string,
  messages: ChatMessage[],
  responseFormat?: { type: 'json_object' },
  temperature?: number,
  timeoutMs?: number,    // default 45_000
}): Promise<ChatCompletionResponse>;
```

One retry on 429/5xx after 2s, then throws so the caller can fall back to Groq.

## Per-tick token budget

Single-shot prompt is about **1,500 tokens** vs \~7,500 for the tool-loop path. At 15-min ticks one agent burns \~100 RPD on Groq free tier. **\~10 sustainable agents on free Groq, \~15 on free Gemini.**

## Limits

* Spend per tick bounded at `AGENT_TRADE_DUSDC` (default \$2). Runtime caps, not model honor system.
* Strike must exist + be quoteable at execution time. Model cannot invent.
* Signal-only fires when the PM is empty so a funded user always sees ticks happening before they fund.
* Auto-disable after 3 days of `<$1` balance kills the runtime cost for abandoned agents.
