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

# Building an agent

> The operating brief for an LLM driving Knockout: the loop, the refusals, the units, orders and closes, and the five things an autonomous client must never do.

This page assumes the reader is a capable model with no prior context, building a client that
decides and sends without a human watching each action. It is short, dense and imperative. Every
section links to the page that explains it properly — read this first, then follow the links you
need.

<Warning>
  Knockout is a **capped barrier product**. A position can be knocked out by a small move and
  end at zero. An autonomous client is a client that can lose money quickly and without anyone
  noticing. Build the read path and the refusal path before you build the send path.
</Warning>

## The loop

Five steps, in this order. Skipping step 2 or 3 is how a client ends up learning the protocol from
reverted transactions.

<Steps>
  <Step title="Read the protocol">
    Config, vault and market, decoded from the chain — [Read the protocol](/sdk/reading). Addresses are
    derived locally with `configPda`, `vaultPda`, `marketPda`, so you never trust an address printed
    on a page. **Never cache `config.params`**: every field in it is governed and can move without a
    program upgrade.
  </Step>

  <Step title="Price it">
    `openPreview` / `costToOpen` tell you what the trader is actually charged and the gross payout at
    the target, which is before the exit fee and the share of the gain the terms withhold. Do not recompute protocol arithmetic yourself — a second derivation of a number is how
    a client and a chain come to disagree.
  </Step>

  <Step title="Ask what would refuse it">
    `openGates` and `settleGates` return every gate with the measured value and the threshold —
    [Reading gates](/sdk/gates). Branch on the first failing gate.

    **Order is a per-function property.** `openGates` follows the program's order for an open.
    `settleGates` emits its own sequence and does not reproduce the program's settle order: the
    program checks the reason before the price, and `settleGates` appends the reason at the end.
    Neither report reads the guardian flags; read `config.oracleFrozen` and
    `config.settlesDisabled` yourself. An agent that sends to find out is paying for information it
    could have had for free.
  </Step>

  <Step title="Send, then wait for the step to end">
    **An open, an add and a close request carry no quote.** Build an open with
    `ix.open(accounts, args, priceAccountsFor(market, config, 'open'))`, an add with
    `addCollateralForMode` and a close request with `ix.requestClose`, sign, send, and then wait for
    the event that ends each step: `EntryFilled` or `OpenVoided` for an order, `CollateralAdded` or
    `AddReversed` for an add, `SettlementPriced` for a close. A landed transaction is an order or a
    request, never the result. [Orders and closes](/sdk/orders) is the contract, and
    [Opening](/sdk/opening) has the code.

    A quote matters only if you send an instruction that carries one yourself: a `settle`, a
    `stamp_book` or a `set_take_profit`. Those age out in **slots, not seconds**, judged when the
    program runs rather than when you obtained the quote. **Obtain a new quote immediately before
    signing**, sign locally, and never put a human approval between the two. See
    [Settling](/sdk/settling) and [Quotes and freshness](/sdk/quotes), and
    [Getting prices](/sdk/prices) to obtain one.
  </Step>

  <Step title="Reconcile">
    Compare what you predicted against what the chain moved. A client that never checks is a client
    that will be wrong quietly. Decode the events rather than inferring from balances: the SDK
    exports `decodeSettlementPriced`, `decodePositionOpened`, `decodePositionStamped`,
    `decodeEntryFilled`, `decodeOpenVoided`, `decodeAddReversed` and `decodeSettlementReceipt`, and
    `readTradeHistory` reads them back off a cluster. Join every event on `openerTag`.
  </Step>
</Steps>

## Refusals: branch on `retryable`, never on the name

The error catalogue is generated from the SDK's client IDL, so it cannot drift from what the chain
raises — [Error handling](/sdk/errors). The one field that matters to an autonomous client is
`retryable`:

| `retryable` | Do |
| - | - |
| `never` | Fix the request. Retrying is an infinite loop. |
| `after-state-changes` | Re-evaluate when the world moves on its own — a settle, a new slot. Back off. |
| `after-governance` | Stop retrying on a timer. Only an instruction signed by the venue's config authority clears it; poll the flag the `fix` names and alert an operator. |
| `immediately` | Retry on the next slot. These are almost all price freshness. |

Resolve with `errorForCode` / `errorForName`. **Do not pattern-match error strings.** The names are
an ABI and the codes are stable; your regex is neither. For the order and close codes, 6203 to 6238,
bind to `retryClassOf(code)`: it says "done", "send the other instruction" and "retry with the right
quote", which `retryable` cannot.

Two refusals that mean something different from what they sound like:

* **`PositionSlotsFull`** — the *trader's book* is full, not the venue. A book holds a fixed number
  of positions, chosen when it was created and changeable afterwards.
* **`SideStakeCapReached`** — a market's limit on one side's stake is set and that side is full. It
  clears as other positions settle. Back off; do not spin. A pool that cannot cover an order is not
  a refusal: the order is returned with the whole stake.

## What constrains you, whatever you are trying to do

These hold on chain and no client can talk its way past them:

* **The cover rule.** A fill is admitted only if the pool can cover it. The number of concurrent
  positions the venue admits depends on the pool and the queue. An order that does not fit is returned
  with the whole stake and no fee: treat it as a state, not a fault.
* **A win may be paid later.** A win is paid in full, but part of it may be paid later, in order, and
  `SettlementPriced.trader` is only what was paid now. Never present it as the amount won: read
  `SettlementQueued`.
* **Money in = money out on every path.** The program never mints and never burns.
* **The pauses close the entry, not the exit.** A pause of the venue or of one market stops opens,
  adds and take-profit retargets; `settle` and LP exits continue. An order already placed is still
  filled or returned.
* **An order is not a position, and a request is not a close.** The entry is the venue's next price
  after a short delay, and the exit is the first price after the close's target, by the window rule.
  Never show a price from the tap as the entry or the exit.
* **A stop can freeze both prices and closes.** An oracle freeze refuses opens, adds and `lastResort`,
  and starves every priced close of a price. A settle stop refuses every close reason. Only the config
  authority lifts either. Read `config.oracleFrozen` and `config.settlesDisabled`, and treat either as
  a stop, not a retry.
* **A market with trading hours only accepts opens and adds during its own session.** The calendar is on
  the market account (`market.sessions`).
  Read it, or call `availability()` for the state and the next open, and do not assume that a market
  is always open. See [markets](/markets).

## Units

* USDC in **base units**, 6 decimals. `250000` is \$0.25.
* A payout can be large relative to collateral. Format money as dollars in anything a human reads; a
  figure printed as `25007500` will be misread as twenty-five million.
* Rates and limits are in basis points or micro-basis-points on chain. Read them from the account,
  never from a constant you copied.

## Who signs

An open, an add and a close request carry no quote, so a person can approve each one in a wallet,
and an autonomous client signs locally. The instructions that carry a quote are `set_take_profit`
and any `settle`, `stamp_book` or `prove_price` you send yourself. A quote ages out in slots, so a
human approval between the quote and the send cannot land: sign those locally.

If you are trading a book that is not your own, one option is for the owner to authorise a
**session-key delegate** your agent signs with; if you are trading your own, you are already a local
signer. How you sign is your decision. Put any human approval on the **authorisation**, which
carries no price and races nothing.

If you choose the program's session-key delegate, it is four moves — [session keys](/sdk/sessions)
has working code for each:

1. **Authorise.** `authorizeSession` — one instruction the owner signs, setting the expiry, the
   spend window and the SPL token allowance together.
2. **Fund it.** The fee payer must sign, so on this route it is not the human — the session key
   itself, or a separate sponsoring fee payer you add. Give whichever pays its own lamports and
   watch the balance.
3. **Sign every trade with it.** `authority` is the session key, `delegate` is
   `delegatePda(owner).address`, and the book, the token account and the money all stay the
   owner's.
4. **Revoke when the mandate ends.** `endSession` closes the account and drops the SPL allowance in
   one instruction, in the same slot, and returns the rent.

Gate before you send: `delegateGates` covers the four refusals `openGates` does not model, and
`sessionAllowanceGates` covers the SPL allowance — a **total that draws down and never refills**,
which is what actually ends most long sessions, arriving from the token program as
`InsufficientFunds` in none of this program's error shapes.

<Note>
  **A session key can open AND request a close**, unless its grant carries the no-close
  restriction. The program authorises a `request_close` for the owner or for an unexpired session key
  bound to them, and the request saves a wallet prompt and races nothing. The settle that completes
  it is sent by anyone, and the other five close reasons are permissionless and need no signature
  from either.
</Note>

## Five things an autonomous client must never do

1. **Never route around a refusal.** A guard that refuses you knows something you do not. Read the
   gate, fix the request.
2. **Never cache governed state.** `config.params`, market parameters and the session calendar all
   move on chain. Re-read them, and show how old your copy is.
3. **Never put a credentialled RPC endpoint in a command line, a log, a commit or a screenshot.**
   Reference it by variable name — [Installation](/sdk/installation).
4. **Never publish a model output as a fact.** Implied odds, projections and annualisations are
   yours to compute and yours to label. What the chain settled is a fact; what your model expects is
   not.
5. **Never sign a second order for the same intent after a timeout.** Inside one blockhash life, send
   the same signed bytes again. A second signature is a second order, and both can land.

## Where to go next

| You want | Read |
| - | - |
| a bot that quotes, opens and closes | [Quickstart](/sdk/quickstart) |
| a working read path in one file | [Read the protocol](/sdk/reading) |
| the docs as an MCP server, and a skill file | [Build with an AI agent](/sdk/ai) |
| **orders, fills, closes and what never to assume** | **[Orders and closes](/sdk/orders)** |
| **to sign with a session key** | **[Session keys](/sdk/sessions)** |
| to understand `6007` and what to do about it | [Quotes and freshness](/sdk/quotes) |
| every refusal, before you send | [Reading gates](/sdk/gates) |
| to open a position | [Opening](/sdk/opening) |
| to read and value a book | [Positions](/sdk/positions) |
| to close a position | [Settling](/sdk/settling) |
| to decode accounts, events and errors | [Reference](/sdk/reference), [Error handling](/sdk/errors) |
| what the product actually is | [How it works](/how-it-works), [Risk](/risk) |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.