Agent SDK · Documentation

Integration Guide

Wrap your agent loop with three SDK calls. Budget ceilings, loop detection, and a kill switch — enforced atomically on every step.

Quick start

Three calls. Complete governance.

Drop into any agent loop — LangGraph, CrewAI, OpenAI Agents SDK, or your own.

1

Install the SDK

terminal
npm install @thsky-21/thskyshield
2

Add credentials

.env.local
THSKYSHIELD_SITE_ID=your_site_id_here
THSKYSHIELD_KEY=your_api_key_here
3

Wrap your agent loop

agent/run.ts
import { Thskyshield, ShieldKilledError } from '@thsky-21/thskyshield'

const shield = new Thskyshield({
  siteId: process.env.THSKYSHIELD_SITE_ID!,
  apiKey:  process.env.THSKYSHIELD_KEY!,
})

const run = await shield.beginRun({
  budgetLimitUsd: 2.00,   // hard ceiling — run dies here
  iterationLimit: 30,     // max steps
  timeoutSeconds: 300,    // wall-clock timeout
  loopThreshold:  5,      // kill after same prompt repeats N times
})

try {
  while (!done) {
    // Gate fires before the LLM call. Throws if a limit is hit.
    const { requestId, remainingUsd } = await run.beforeStep({
      stepType:        'llm',
      model:           'gpt-4o-mini',
      estimatedTokens: { input: 500, output: 200 },
      promptInput:     currentPrompt,   // used for loop detection
    })

    const result = await callYourLLM(currentPrompt)

    // Settle actual cost after the call resolves.
    await run.afterStep({
      requestId,
      actualTokens: result.usage,
      model:        'gpt-4o-mini',
    })
  }
} catch (e) {
  if (e instanceof ShieldKilledError) {
    // e.reason: 'killed_budget' | 'killed_loop' | 'killed_iterations' | 'killed_timeout'
    console.log(`Agent stopped: ${e.reason}. Spent: $${e.spent}`)
  }
} finally {
  const summary = await run.end()
  console.log(`Total: $${summary.totalCostUsd} over ${summary.iterationCount} steps`)
}

Always call end() in finally

If your agent throws before end() is called, the run stays open in Redis until TTL expiry. The finally block ensures cleanup on both success and error paths.

Prefer the auto-managed wrapper?

agent/run.ts
// withRun() manages beginRun / end automatically
const { result, summary } = await shield.withRun(
  { budgetLimitUsd: 1.00, iterationLimit: 20 },
  async (run) => {
    // your agent loop here — same beforeStep / afterStep pattern
    return finalResult
  }
)

withRun() calls beginRun() and end()for you — useful when you don't need to inspect the run handle directly.

SDK Reference

Methods

shield.beginRun({ budgetLimitUsd, iterationLimit?, timeoutSeconds?, loopThreshold?, externalRunId? })Init
Promise<Run>

Creates a governed agent run. Writes a run record to Supabase and initialises all Redis state atomically. budgetLimitUsd is required — the run is killed the moment accumulated spend would exceed it. All other limits have defaults (iterationLimit: 50, timeoutSeconds: 300, loopThreshold: 5). Returns a Run handle — keep this reference for the duration of the agent loop.

run.beforeStep({ stepType, model?, estimatedTokens?, promptInput?, toolName? })Before
Promise<{ requestId: string, remainingUsd: string }>

Single atomic Redis round-trip that checks all four limits — budget, iterations, timeout, and loop — before the LLM call fires. Throws ShieldKilledError immediately if any limit is hit. On success, reserves the estimated cost and returns a requestId — pass this to afterStep() to link the reservation to actual spend. promptInput is SHA-256 fingerprinted for loop detection; pass it every step for accurate loop tracking.

run.afterStep({ requestId, actualTokens, model? })After
Promise<{ success: boolean, actualCostUsd: string }>

Settles the cost reservation atomically. Releases the estimated reservation written by beforeStep() and applies the actual token cost. Must be awaited — fire-and-forget breaks the two-phase accounting and can let the next beforeStep() see a stale budget.

run.end()Close
Promise<{ runId, status, totalCostUsd, iterationCount, killedReason? }>

Closes the run. Reads final Redis state and writes a permanent record to Supabase. Always call in a finally {} block — if the agent throws before end() is called, the run remains open in Redis until TTL expiry. Returns the full run summary including total cost and kill reason if applicable.

shield.withRun(options, async (run) => T)Helper
Promise<{ result: T, summary: RunSummary }>

Convenience wrapper that calls beginRun(), runs your async function, then calls end() automatically — even on throw. Use this instead of the manual try/finally pattern when you don't need fine-grained control over the run lifecycle.

ShieldKilledError

Thrown by beforeStep() when a kill condition is met. Catch it to handle the kill gracefully — access partial results before the run ends.

PropertyTypeDescription
.reasonstringKill status — one of the kill codes below.
.runIdstringThe run ID that was killed.
.spentstringUSD spent before the kill, e.g. '1.84'.
.remainingstringUSD remaining at kill time.
HTTP API — any language

Any stack. Same enforcement.

All five routes accept Authorization: Bearer YOUR_API_KEY and return JSON. The SDK is a thin wrapper over these endpoints.

POST /v1/run/beginPOST /v1/run/{id}/before-stepPOST /v1/run/{id}/after-stepPOST /v1/run/{id}/endGET /v1/run/{id}
terminal
# 1 — Begin a run
curl -X POST https://thskyshield.com/v1/run/begin \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "budgetLimitUsd": 2.00,
    "iterationLimit": 30,
    "timeoutSeconds": 300,
    "loopThreshold":  5
  }'
# → { "runId": "run_abc123", "status": "running", "startedAt": "..." }

# 2 — Gate before each step
curl -X POST https://thskyshield.com/v1/run/run_abc123/before-step \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "stepType":        "llm",
    "model":           "gpt-4o-mini",
    "estimatedTokens": { "input": 500, "output": 200 },
    "promptInput":     "Summarize this document..."
  }'
# → { "allowed": true, "requestId": "req_xyz", "remainingUsd": "1.94" }

# 3 — Settle after the LLM responds
curl -X POST https://thskyshield.com/v1/run/run_abc123/after-step \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "requestId":    "req_xyz",
    "actualTokens": { "input": 487, "output": 193 },
    "model":        "gpt-4o-mini"
  }'

# 4 — Close the run
curl -X POST https://thskyshield.com/v1/run/run_abc123/end \
  -H "Authorization: Bearer YOUR_API_KEY"
# → { "status": "completed", "totalCostUsd": "0.06", "iterationCount": 8 }

# Read run state at any time
curl https://thskyshield.com/v1/run/run_abc123 \
  -H "Authorization: Bearer YOUR_API_KEY"
Kill Codes

Kill Codes

The value of ShieldKilledError.reason and the status field on the run record.

CodeTrigger
killed_budgetspent + reserved + estimated cost exceeds budgetLimitUsd
killed_iterationsiteration counter ≥ iterationLimit
killed_loopsame prompt fingerprint seen ≥ loopThreshold times within 600s
killed_timeoutelapsed time since beginRun() exceeds timeoutSeconds
SYSTEM_DEGRADEDcontrol plane error (Redis unreachable, etc.)
Design partners

Want help with your integration?

We wire the SDK into your agent loop with you — 30 minutes, free forever for the first five teams.

Become a design partner