Integration Guide
Wrap your agent loop with three SDK calls. Budget ceilings, loop detection, and a kill switch — enforced atomically on every step.
Three calls. Complete governance.
Drop into any agent loop — LangGraph, CrewAI, OpenAI Agents SDK, or your own.
Install the SDK
npm install @thsky-21/thskyshieldAdd credentials
THSKYSHIELD_SITE_ID=your_site_id_here
THSKYSHIELD_KEY=your_api_key_hereWrap your agent loop
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?
// 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.
Methods
shield.beginRun({ budgetLimitUsd, iterationLimit?, timeoutSeconds?, loopThreshold?, externalRunId? })InitCreates 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? })BeforeSingle 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? })AfterSettles 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()CloseCloses 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)HelperConvenience 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.
| Property | Type | Description |
|---|---|---|
.reason | string | Kill status — one of the kill codes below. |
.runId | string | The run ID that was killed. |
.spent | string | USD spent before the kill, e.g. '1.84'. |
.remaining | string | USD remaining at kill time. |
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.
# 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
The value of ShieldKilledError.reason and the status field on the run record.
| Code | Trigger |
|---|---|
killed_budget | spent + reserved + estimated cost exceeds budgetLimitUsd |
killed_iterations | iteration counter ≥ iterationLimit |
killed_loop | same prompt fingerprint seen ≥ loopThreshold times within 600s |
killed_timeout | elapsed time since beginRun() exceeds timeoutSeconds |
SYSTEM_DEGRADED | control plane error (Redis unreachable, etc.) |
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