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

# Policy

> In-memory policy config for the AI agent — tip caps, retry limits, escalation controls.

## Overview

The `policy` field on `SinkerConfig` lets you set guardrails on what the AI agent is allowed to do, directly in your application code. No config file, no restart — just pass it at construction time.

```ts theme={null}
const sinker = new Sinker({
  model: { provider: 'anthropic', apiKey: process.env.ANTHROPIC_API_KEY! },
  policy: {
    maxTipLamports: 200_000,
    tipPercentile: 'p75',
    escalationStepLamports: 5_000,
    maxRetries: 4,
    priority: 'auto',
  },
});
```

All keys are optional — any key you omit resolves to its built-in default. An empty `policy: {}` is the same as not providing `policy` at all.

<Note>
  `max-queue` and `rate-limit` are sidecar-side operator settings and are not available here.
  Set them with [`sinker policy set`](/cli/policy) on the CLI.
</Note>

***

## PolicyConfig

### Tip controls

<ParamField path="policy.maxTipLamports" type="number" default="500000">
  Hard ceiling on tip size (lamports). The agent cannot recommend above this value
  regardless of what current network conditions suggest.
</ParamField>

<ParamField path="policy.maxTipRatioPct" type="number" default="5.0">
  Tip cannot exceed N% of the transaction's transfer value.
  For example, `3` means the tip is capped at 3% of the amount being transferred.
</ParamField>

<ParamField path="policy.tipPercentile" type="'p50' | 'p75' | 'p95' | 'p99'" default="'p75'">
  The percentile the agent targets when the network is calm.
  `p75` is a good balance between landing rate and cost for most use cases.
  Use `p95` or `p99` for time-sensitive transactions.
</ParamField>

<ParamField path="policy.staleAfterMs" type="number" default="2000">
  Maximum age of tip oracle data in milliseconds before the agent refuses to proceed.
  The oracle updates every \~10 seconds; setting this below 400ms (one slot) will cause the
  agent to always see the data as stale.
</ParamField>

### Bundle & retry controls

<ParamField path="policy.maxRetries" type="number" default="3">
  Maximum number of re-submission attempts before the bundle is declared expired and dropped.
</ParamField>

<ParamField path="policy.expirySlots" type="number" default="150">
  How many slots past the enqueue slot the agent keeps retrying before dropping the bundle.
  At \~400ms per slot, 150 slots is about 60 seconds.
</ParamField>

<ParamField path="policy.escalationStepLamports" type="number" default="5000">
  Maximum tip increase per retry cycle (lamports). Prevents the agent from doubling or
  tripling the tip in a single escalation step.

  Must be less than or equal to `maxTipLamports` — the constructor throws `SinkerPolicyError`
  if `escalationStepLamports > maxTipLamports`.
</ParamField>

<ParamField path="policy.priority" type="'normal' | 'high' | 'urgent' | 'auto'" default="'auto'">
  Priority tier for all bundles submitted through this instance.
  `auto` lets the agent choose based on network conditions.
  Pin to `high` or `urgent` when every transaction is time-sensitive.
</ParamField>

***

## Built-in defaults

| Key                      | Default  | Unit     |
| ------------------------ | -------- | -------- |
| `maxTipLamports`         | `500000` | lamports |
| `maxTipRatioPct`         | `5.0`    | %        |
| `tipPercentile`          | `p75`    | —        |
| `staleAfterMs`           | `2000`   | ms       |
| `maxRetries`             | `3`      | —        |
| `expirySlots`            | `150`    | slots    |
| `escalationStepLamports` | `5000`   | lamports |
| `priority`               | `auto`   | —        |

***

## Constructor validation

The `Sinker` constructor validates the policy immediately on instantiation.

**Hard errors** → throws `SinkerPolicyError` (construction fails):

| Condition                                 | Error                                                       |
| ----------------------------------------- | ----------------------------------------------------------- |
| `escalationStepLamports > maxTipLamports` | Every retry would immediately exceed the tip ceiling        |
| `maxTipLamports === 0`                    | Jito requires a non-zero tip — all bundles will be rejected |
| `maxTipRatioPct <= 0`                     | Invalid — would block all tips                              |
| `expirySlots === 0`                       | Bundles expire immediately and can never be submitted       |

**Soft warnings** → logs to `console.warn` (construction continues):

| Condition              | Warning                                                |
| ---------------------- | ------------------------------------------------------ |
| `staleAfterMs < 400`   | Shorter than one slot — oracle may always appear stale |
| `maxTipRatioPct > 100` | Tip can be larger than the transaction value           |
| `maxRetries === 0`     | Failed bundles will not be retried                     |

***

## sinker.policy

The resolved policy — all defaults filled in — is available after construction as `sinker.policy`.

```ts theme={null}
const sinker = new Sinker({
  model: { provider: 'openai', apiKey: process.env.OPENAI_API_KEY! },
  policy: { maxTipLamports: 100_000 },
});

console.log(sinker.policy.maxTipLamports);         // 100000  (your value)
console.log(sinker.policy.tipPercentile);           // 'p75'   (default)
console.log(sinker.policy.escalationStepLamports);  // 5000    (default)
```

**Type:** `ResolvedPolicy` — all 8 fields present, no optionals.

***

## sinker.validatePolicy()

Returns `{ errors, warnings, valid }` without throwing. Use this if you want to inspect
the policy programmatically after construction without relying on try/catch.

```ts theme={null}
const result = sinker.validatePolicy();

if (!result.valid) {
  for (const e of result.errors) {
    console.error('policy error:', e);
  }
}
for (const w of result.warnings) {
  console.warn('policy warning:', w);
}
```

**Returns:** `PolicyValidationResult`

<ResponseField name="errors" type="string[]">
  Hard problems that will cause incorrect operation.
</ResponseField>

<ResponseField name="warnings" type="string[]">
  Unusual values that may be intentional but are worth knowing about.
</ResponseField>

<ResponseField name="valid" type="boolean">
  `true` when `errors` is empty. Warnings do not affect `valid`.
</ResponseField>

***

## SinkerPolicyError

Thrown by the constructor when the policy has hard errors.

```ts theme={null}
import { Sinker, SinkerPolicyError } from 'sinker-sdk';

try {
  const sinker = new Sinker({
    model: { provider: 'anthropic', apiKey: '...' },
    policy: { maxTipLamports: 0 },  // ← hard error
  });
} catch (err) {
  if (err instanceof SinkerPolicyError) {
    console.error(err.message);
    // "Sinker policy has 1 error(s):
    //   • maxTipLamports is 0: Jito requires a non-zero tip — all bundles will be rejected"

    console.log(err.validation.errors);    // string[]
    console.log(err.validation.warnings);  // string[]
    console.log(err.validation.valid);     // false
  }
}
```

***

## Relationship to sinker.policy.json

The CLI's `sinker.policy.json` and the SDK's `policy` config are **independent**. The CLI file controls what the sidecar and agent enforce at the process level. The SDK `policy` is in-memory config on the client side.

In practice: set your hard system-wide limits in `sinker.policy.json` via the CLI, and set per-application constraints in the SDK `policy` config. The agent will respect whichever is more restrictive.

| Setting                          | Scope                  | How to set                     |
| -------------------------------- | ---------------------- | ------------------------------ |
| `max-queue`, `rate-limit`        | Sidecar-wide           | CLI only (`sinker policy set`) |
| Tip caps, retry limits, priority | Per application        | SDK `policy` config            |
| Both sets                        | System-wide persistent | CLI + `sinker.policy.json`     |
