I already have an agent. Where does PRAE go?

Shipping

Right before your agent runs a tool. An agent built in Node or TypeScript with its own tool loop imports prae-gate from npm and asks it about each call. The verdict is one of three: run it, refuse it, or hold it for a person. Each decision is a ledger row.

Serves: Custom / homegrown agents →Deployed SaaS agents →

  1. 01 · Where the agent operates

    Node / TypeScript SDK

  2. 02 · What it can reach

    Whatever your agent’s tools reach: your APIs, databases, files, cloud services.

  3. 03 · Where PRAE intervenes

    In your tool loop, before each tool executes.

04 · What PRAE does: Evaluate → Enforce → Steer → Prove

  1. Evaluate

    judge() checks the call against your pack: tool, arguments, paths, hosts, commands, budgets.

  2. Enforce

    Your loop honours the verdict: a refused call never runs, a held call waits for a person.

  3. Steer

    Not in the SDK yet.

  4. Prove

    Append each decision’s record to the ledger: hash-chained, and signed when you give it a key.

05 · What you get, and how it plugs in

  1. Install npm install prae-gate
  2. Before each tool runs const { decision, record } = judge(pack, { name, arguments })
  3. Honour the verdict deny → do not run · ask → hold for a person · allow → run

Any tool your loop calls, with the same rules, verdicts and ledger rows as everywhere else PRAE runs.

Your loop must call the gate: a call it does not check is not seen. Python and other languages are not supported yet.

1. Install

prae-gate is on npm. Node 22.19 or later, or Node 24 and up.

npm install prae-gate

2. Load a pack, open a ledger

The pack is your policy: JSON, validated on load, with unknown fields rejected. The ledger is an append-only, hash-chained file.

import { readFileSync } from 'node:fs'
import { judge, Ledger, parsePack } from 'prae-gate'

const parsed = parsePack(readFileSync('policy.json', 'utf8'))
if (!parsed.ok) throw new Error(parsed.errors.map((e) => e.message).join('; '))
const ledger = Ledger.open('decisions.jsonl')

3. Intercept each action

Wrap the one place your loop runs tools. judge() returns the decision and the record to seal; it never runs the tool itself.

async function runTool(name, args) {
  const { decision, record } = judge(parsed.pack, { name, arguments: args })
  ledger.append(record)
  if (decision.kind === 'deny') return { refused: decision.reason }
  if (decision.kind === 'ask') return { held: decision.reason } // ask a person
  return tools[name](args)
}

4. Allow, refuse, hold

allow runs the tool. deny returns the reason to the model, and the tool never runs. ask holds the call for a person: your app decides how to ask, and records the answer.

5. Evidence

Each record names the call, the rule that decided it, the reason and the pack version. Check the chain at any time.

npx -p prae-cli prae verify decisions.jsonl

6. Local development

Validate a pack, explain one decision, and run security scenarios against it, before an agent ever runs.

prae check policy.json
prae explain policy.json bash command="rm -rf /"
prae test suite.json --pack policy.json

7. CI/CD

`prae test` exits non-zero when any scenario’s invariant no longer holds, so a pack change that would let an attack through fails the build.

prae test suite.json --pack policy.json --json