OpenScript
Software
2026
Python 3.11+, Java 17, asyncio, Pydantic v2, LLM Agent Security, Tool-Call Policy Enforcement, Pyodide / WebAssembly, Next.js 16 (Static Export), TypeScript, FastAPI, Chrome Extension (MV3), AgentDojo, pytest & mypy --strict, JUnit 5,
Python SDK that puts hard limits behind LLM agents, so the limits still hold when the model is fooled or simply wrong. It wraps the agent and every tool it calls in a policy pipeline: a tool-call firewall, approvals bound to the exact call, a session-taint rule, PII and secret redaction, and an audit trail. Ten local policies, no network. The live demo runs the real package in your browser via WebAssembly — nothing leaves the tab. A dependency-free Java 17 SDK puts Java agents and their tools under the same Python policies.
- Role
- Engineering
- Year
- 2026
- Type
- Software
Challenge
LLM agents read untrusted text and can call real tools, and no detector catches every instruction planted in that text. A model that is fooled, or simply wrong, can leak credentials, expose customer PII or take an action nobody approved. Most guardrails are hosted services or extra LLM calls that try to spot the attack, which sends your prompts off your infrastructure and leaves the agent unguarded whenever they miss. I wanted limits that hold whether or not the attack is spotted, and that anyone could check in one click on a phone, with nothing sent off the device.
Approach
I built a pipeline that wraps any agent exposing invoke or stream (LangChain and LangGraph agents among them) and every tool it calls. Ten swappable policies run around each call, and between them they can allow, redact, deny or hold a call for human approval. An approval for a tool call is bound to the exact call, tool name plus exact arguments, is single use and expires after an hour. Once the agent has called a tool that reads outside content, a session rule holds each later call to a sending or writing tool for approval. Every policy is local code with no network or LLM calls, and a CI import audit fails the build if any policy can reach the network. Because of that, the real package runs under Pyodide inside a static Next.js site with no backend.
For Java agents I added a /v1/guard endpoint and a Java 17 SDK with no runtime dependencies, so the Python policies are reused rather than reimplemented. It fails closed: a transport error, a non-200 response, arguments it cannot serialize or a decision it does not recognise all throw before the agent or tool runs, and output is withheld on any decision but allow. It streams in the same three modes as the Python SDK, and in guarded mode it holds back a window and checks it at a streaming endpoint, so a secret split across chunks is redacted, or the stream refused, before any of it is released. Every call also has a CompletableFuture form, and a pluggable recorder writes Prometheus-format metrics without being able to change a decision.
Outcome
The full pipeline takes 5.3 ms (median) under WebAssembly, measured on a CI runner, and the live demo loads in about 5-6 s on a phone. More than 870 tests back it, 67 of them JUnit tests that CI runs against the real policy server on Postgres, failing if any are skipped. The tool policies also ship as a Claude Code hook (144 ms median) that can only tighten and fails closed, and in an MV3 Chrome extension, and there is an optional FastAPI server. The prompt-injection detector missed 4 of AgentDojo's 5 attack wordings and catches 4% of held-out attacks, and I publish those numbers because they show detection is a speed bump. The limits do the work: in a replay of AgentDojo, the session rule stopped 570 of 617 attacks when the person approving refused the attacker's calls. Over localhost on one laptop with one server worker, across two runs, a guarded check from Java took 3.4 to 5.0 ms p50, and holding back 256 more characters cut guarded streaming of a 1,020-character answer from 196 to 247 ms p50 to under 10 ms. Work is ongoing.