Your agent can only do what you said it could.

Thskyshield gives every agent run a declared boundary — how much it may spend, how many steps it may take, which tools it may call, and whether it may write. The check happens before the call fires. Three SDK calls. Any loop.

$ npx @thsky-21/thskyshield-demo

No signup. No API key. No network. About four seconds.

Works with LangGraph · CrewAI · OpenAI Agents SDK · any framework

$ npx @thsky-21/thskyshield-demo
Thskyshield — runaway agent demo
An agent gets stuck in a retry loop. Watch it get killed.
Budget ceiling $0.05
Loop gate 20 identical prompts
Model gpt-4o (500 in / 200 out per step)
step 01 allowed cost $0.003250 total $0.003250 █░░░░░░░░░░░ 7%
step 02 allowed cost $0.003250 total $0.006500 ██░░░░░░░░░░ 13%
↳ loop detector: identical prompt ×2 of 20
⋯ steps 03–14 elided
step 15 allowed cost $0.003250 total $0.048750 ████████████ 98%
↳ loop detector: identical prompt ×15 of 20
KILLED step 16budget ceiling reached
Total spend $0.048750 of $0.05
Steps allowed 15

Real output, verbatim — this is what the command above prints.

Why a boundary

A stuck agent doesn't crash, and an off-track agent doesn't ask.

The stuck shape

A stuck agent doesn't crash.

It retries. It re-asks. Every individual call returns a clean 200 — your monitoring shows a healthy service, because nothing ever throws. The loop is the failure, and a loop has no stack trace. By the time a human notices the bill, the run has been repeating itself for however long nobody was watching.

The off-track shape

An off-track agent doesn't ask.

It hits an obstacle — a permission it doesn't have, a file it can't write, a step that keeps failing — and it does what an agent does: it looks for a way through. Sometimes that way through is reasonable. Sometimes it's a tool call nobody scoped it for, reaching a system nobody meant it to reach. It doesn't pause to check, because nothing told it where the edge was.

The boundary

Six things every run declares.

Three are contained the moment you switch enforce on. Three are measured from day one, and enforced when you decide they should be.

Three things it cannot do

Tools it may call

A run declares its tool allowlist up front. A call to anything outside it is denied before it fires — killed_scope_tool.

What it may write

Every step declares how destructive it is — read-only, reversible, or destructive. A step over the run's ceiling is denied — killed_scope_mutation.

The same prompt again

The same prompt fingerprint repeated past the loop threshold is denied before the next call — killed_loop.

Three things you'll see first

Dollars

A hard budget ceiling in USD. Reserved before the call, settled to the actual cost after — killed_budget.

Wall clock

A run that's been open too long is denied on its next step — killed_timeout.

Step count

A hard ceiling on how many steps a run may take, independent of what each one costs — killed_iterations.

We don't kill a run mid-task unless you tell us to. You'll see what would have happened first.

How it works

Three calls. That's the integration.

Wrap your agent loop with beginRun, beforeStep, and afterStep. One atomic round-trip, under 10ms, prompts hashed in your own process, fails open at every layer. There is no agent to run and nothing to host.

01
shield.beginRun()
Declare the boundary

Set a dollar ceiling, a step cap, a timeout, a loop threshold, which tools may be called, and how destructive a write may be.

02
run.beforeStep()
The check, before the call fires

One round-trip checks every boundary — tools, writes, loop, budget, steps, time — before your API call fires.

03
run.afterStep() + run.end()
Settle the real cost

afterStep settles the real token cost against the reservation. Nothing on the record is an estimate.

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

const shield = new Thskyshield({ siteId, apiKey })

const run = await shield.beginRun({
  budgetLimitUsd:   2.00,
  iterationLimit:   30,
  loopThreshold:    5,
  allowedTools:     ['search', 'read_file'],   // the only tools this run may call
  maxMutationClass: 'reversible',              // no destructive writes
})

try {
  while (!done) {
    const { requestId } = await run.beforeStep({
      stepType:        'tool',
      toolName:        'search',
      mutationClass:   'read_only',
      estimatedTokens: { input: 500, output: 200 },
      promptInput:     currentPrompt,
    })

    const result = await callYourTool(currentPrompt)

    await run.afterStep({
      requestId,
      actualTokens: result.usage,
      model:        'gpt-4o-mini',
    })
  }
} catch (e) {
  if (e instanceof ShieldKilledError) {
    // e.reason: 'killed_scope_tool' | 'killed_scope_mutation' | 'killed_loop'
    //         | 'killed_budget' | 'killed_iterations' | 'killed_timeout'
    console.log(`Agent stopped: ${e.reason}`)
  }
} finally {
  await run.end()
}
Watch first. Then enforce.

It watches before it touches anything.

Handing a kill switch to something you installed this morning is a lot to ask, so we don't ask for it. Every new project starts in observe mode.

You get the report either way. Enforcement is the part you turn on once you have read it — and there is nothing to undo if you never do.

  • Observe mode runs every check and stops nothing. Your agents behave exactly as they do today.
  • Each run records the decision it would have made — which boundary, and where. No card, nothing blocked.
  • Flip a project to enforce when you're ready. The switch applies to the next run, never one already in flight.
What enforce stops — a run ends on whichever limit trips first
killed_scope_tool
Tool not on the list
toolName ∉ allowedTools
killed_scope_mutation
Write too destructive
mutationClass > maxMutationClass
killed_loop
Loop detected
same prompt ≥ loop_threshold
killed_timeout
Timed out
elapsed > timeout_seconds
killed_iterations
Max steps hit
iter ≥ iteration_limit
killed_budget
Budget exceeded
spent + reserved > limit
Honesty

We'd rather tell you what we don't know.

A boundary that fails silently isn't a boundary — it's a guess you stopped checking. Every number and every verdict on this product is built to be wrong in only one direction: understated, never inflated.

  • When we can't reach a verdict, your agent runs anyway — and we say so. Every step carries a reason: a real decision, or exactly which part of our system was down when it went through unchecked.
  • A model we don't have a price for is priced at the floor of what we know, never a guess above it. A total can come in too low. It never comes in too high.
  • What you're billed against is never an estimate held over. The number before the call is a reservation; the number after it is replaced by what the call actually cost.
Where the boundary lives

Policy that outlives the process.

The boundary isn't a constant in your code. It's a setting on your project, read fresh at the start of every run. Change it and the next run picks it up — no redeploy, no restart, no code review.

Every agent your team points at that project inherits the same boundary. One place declares it. Every run enforces the one that was declared, not whatever the code happened to ship with.

Your code
shield.beginRun({ ... })
Read at run-begin

Mode, budget, allowlist, and mutation ceiling — resolved from your project's current settings, not baked into a deploy.

A change you make today

Applies to the next run your agent starts. Runs already in flight finish under the boundary they started with.

Pricing

Watch for free. Talk to us to enforce.

Free
$0
Forever
  • Every decision recorded — the full counterfactual
  • Nothing blocked
  • Unlimited history — nothing expires
  • No card required
  • Enforce mode
Get started
Enforce
Enforce
Everything in Free, plus:
  • All six boundaries enforced — tools, writes, loop, budget, steps, time
  • Denies the call before it fires, not after
  • Priority support, direct to the founder
Talk to us →

Enforce is priced per team while we're early — tell us what you're running and we'll walk you through it.

Things people ask us.

Give it a boundary it can't cross.

Free to watch, no card. Or watch it stop an agent at its boundary first — one command, nothing to install.

$ npx @thsky-21/thskyshield-demo