Skip to main content

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.
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.
max-queue and rate-limit are sidecar-side operator settings and are not available here. Set them with sinker policy set on the CLI.

PolicyConfig

Tip controls

policy.maxTipLamports
number
default:"500000"
Hard ceiling on tip size (lamports). The agent cannot recommend above this value regardless of what current network conditions suggest.
policy.maxTipRatioPct
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.
policy.tipPercentile
'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.
policy.staleAfterMs
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.

Bundle & retry controls

policy.maxRetries
number
default:"3"
Maximum number of re-submission attempts before the bundle is declared expired and dropped.
policy.expirySlots
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.
policy.escalationStepLamports
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.
policy.priority
'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.

Built-in defaults

KeyDefaultUnit
maxTipLamports500000lamports
maxTipRatioPct5.0%
tipPercentilep75
staleAfterMs2000ms
maxRetries3
expirySlots150slots
escalationStepLamports5000lamports
priorityauto

Constructor validation

The Sinker constructor validates the policy immediately on instantiation. Hard errors → throws SinkerPolicyError (construction fails):
ConditionError
escalationStepLamports > maxTipLamportsEvery retry would immediately exceed the tip ceiling
maxTipLamports === 0Jito requires a non-zero tip — all bundles will be rejected
maxTipRatioPct <= 0Invalid — would block all tips
expirySlots === 0Bundles expire immediately and can never be submitted
Soft warnings → logs to console.warn (construction continues):
ConditionWarning
staleAfterMs < 400Shorter than one slot — oracle may always appear stale
maxTipRatioPct > 100Tip can be larger than the transaction value
maxRetries === 0Failed bundles will not be retried

sinker.policy

The resolved policy — all defaults filled in — is available after construction as sinker.policy.
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.
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
errors
string[]
Hard problems that will cause incorrect operation.
warnings
string[]
Unusual values that may be intentional but are worth knowing about.
valid
boolean
true when errors is empty. Warnings do not affect valid.

SinkerPolicyError

Thrown by the constructor when the policy has hard errors.
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.
SettingScopeHow to set
max-queue, rate-limitSidecar-wideCLI only (sinker policy set)
Tip caps, retry limits, priorityPer applicationSDK policy config
Both setsSystem-wide persistentCLI + sinker.policy.json