Recipes

Patterns that earn their place

A cookbook of agent flows we've watched succeed in production. None require special API capability — every recipe is reachable with the public endpoints.

Drift correction

Read the mandate, see if the crypto sleeve has drifted past a threshold, propose a trim toward target.

ts
async function rebalanceIfDrifted(ac: AcClient) {
  const m = await ac.getMandate();
  if (!m.utilization) return;

  // 70% of the way through the band ≈ ~28% in a 0.10–0.35 sleeve.
  if (m.utilization.cryptoUsedPct < 0.7) return;

  const result = await ac.propose({
    action: 'sell',
    assetSymbol: 'BTC',
    assetType: 'crypto',
    amountUsd: 8500,
    rationale: 'Rebalance crypto sleeve toward 20% target.',
  });
  if (result.proposed) {
    console.log('Pending attestation:', result.serviceRequestId);
  } else {
    console.warn('Proposal blocked:', result.decision?.violations);
  }
}

Drawdown response

A signal fires. Probe smaller and smaller sizes via validate until you find one the IPS accepts, then propose with rationale.

ts
async function defensiveTrim(ac: AcClient) {
  let amount = 50_000;
  let last;
  while (amount >= 5_000) {
    last = await ac.validate({
      action: 'sell',
      assetSymbol: 'BTC',
      assetType: 'crypto',
      amountUsd: amount,
    });
    if (last.allowed) break;
    amount = Math.floor(amount * 0.75);
  }
  if (!last?.allowed) return;
  return ac.propose({
    action: 'sell',
    assetSymbol: 'BTC',
    assetType: 'crypto',
    amountUsd: amount,
    rationale: 'Drawdown trigger — defensive trim.',
  });
}

Opportunity sizing

A research signal arrives. Use the IPS’s drawdown headroom as the sizing dial, not the prompt.

ts
async function sizedBuy(ac: AcClient, ticker: string, baseUsd: number) {
  const m = await ac.getMandate();
  const headroom = m.utilization?.drawdownHeadroomPct ?? 0;
  // Cap aggressiveness at 100% even if headroom is huge.
  const aggressiveness = Math.min(headroom / 0.15, 1);
  const amountUsd = Math.round(baseUsd * aggressiveness);

  const v = await ac.validate({
    action: 'buy', assetSymbol: ticker,
    assetType: 'tradfi', amountUsd,
  });
  if (!v.allowed) return null;

  return ac.propose({
    action: 'buy', assetSymbol: ticker,
    assetType: 'tradfi', amountUsd,
    rationale: `Conviction buy sized to current drawdown headroom (${(headroom * 100).toFixed(1)}%).`,
  });
}

Watching for attestation outcomes

You proposed. Now you want to know if the user accepted. Two ways:

Webhooks (recommended)

Subscribe to attestation.accepted and attestation.rejected. You get a push within seconds — no polling.

Polling get_audit

Read get_audit every 30–60 seconds for the relevant serviceRequestId. Slower and more rate-limit-noisy, but works in environments where you can’t expose a webhook URL.

ts
async function watchOutcome(ac: AcClient, serviceRequestId: number) {
  for (let i = 0; i < 60; i++) {
    const audit = await ac.getAudit(100);
    const row = audit.find((r) =>
      JSON.stringify(r.payload).includes(String(serviceRequestId))
    );
    if (row) return row;
    await new Promise((r) => setTimeout(r, 30_000));
  }
}

Self-correcting agent

Before retrying a previously-blocked proposal, fetch the audit and inspect what tripped. Shape the next try.

ts
async function retryWithContext(ac: AcClient, baseAmount: number) {
  const audit = await ac.getAudit(5);
  const lastBlocked = audit.find((r) => !r.allowed);
  if (lastBlocked?.violations.some((v) => v.rule === 'crypto_max_allocation')) {
    baseAmount = Math.floor(baseAmount * 0.5);
  }
  return ac.validate({
    action: 'buy',
    assetSymbol: 'BTC',
    assetType: 'crypto',
    amountUsd: baseAmount,
  });
}

Anti-patterns

  • Don’t poll /mandate per trade. Read once at session start. The bands don’t change on the timescales an agent operates on.
  • Don’t catch and ignore violations. They’re structured — switch on rule and reshape, don’t retry blind.
  • Don’t tell the user the trade executed. A 200 from propose means the policy accepted the proposal. The trade is still pending attestation.
Last updated 2026-06-15