Signed plans and approvals for AI agent tool calls
Your agent declares a plan, Intentlock signs it, and every tool call is checked against it: allow, block, or hold for a human. Every decision lands in a tamper-evident audit log.
Works with any model that calls tools
- OpenAI
- Anthropic
- Gemini
- Groq
- Ollama
- Your own LLM
One plan per request, checked on every tool call
Declare
Before it reads anything untrusted, the agent lists the tools it will call, optionally with pinned arguments.
Sign
Intentlock signs the plan with the agent's active policy version. A refresh never widens it.
Check
Every tool call the model makes is checked against the signed plan and the policy, in your process.
Decide
Allowed calls run. Blocked calls return an error to the model. Held calls wait for a person.
Audit
Every decision is appended to a hash-chained log you can verify in the inbox or offline.
Built to fail closed and to show its work
Prompt injection can't widen the scope
The plan is fixed before untrusted text is read. Anything that text asks for outside the plan is blocked.
Policies per agent, people in the loop
YAML rules for scope, amounts, recipients and who may approve. Risky calls wait in the approval inbox.
A log you can verify
Each decision is hash-chained to the one before it. Verify the chain in the inbox or offline.
Fail closed
Server down, bad signature, expired token or a failed audit write: the call is blocked.
Immutable policy versions
Live validation, dry-runs against past calls, diff, publish and roll back.
Approval inbox
Holds show up live with the call, its arguments and the plan.
Hybrid gate
Decisions are made in your process; audit entries go to a local outbox first.
Provider-agnostic guard
One tool loop for OpenAI-compatible APIs, Anthropic, or your own provider.
Self-hosted
Server, inbox and Postgres in one deployment per team. Your data stays with you.
Wrap your tool loop, write a policy
import {
createGuard,
providerFromEnv,
toolset,
} from "@intentlock/guard";
import { HybridGate } from "@intentlock/sdk";
const gate = new HybridGate({
baseUrl: process.env.INTENTLOCK_URL!,
apiKey: process.env.INTENTLOCK_API_KEY!,
});
const guard = createGuard({
provider: providerFromEnv(),
gate,
...toolset([listIssues, postComment]),
});
await guard.run({
user: "alice@acme.dev",
task: "Triage open issues in acme/public-app",
});version: 1
default: block
rules:
- id: only-public-repo
match:
tool: "github_*"
args: { repo: { notGlob: acme/public-app } }
decision: block
reason: Only acme/public-app is in scope
- id: hold-comments
match: { tool: github_post_comment }
decision: hold
approvers: { roles: [approver, admin] }
- id: allow-in-plan
match: { inPlan: true }
decision: allowYour server, your audit log, running in a minute
BASE=https://raw.githubusercontent.com/AbdullahMukadam/intentlock/main/deploy
curl -fsSLO $BASE/docker-compose.selfhost.yml
curl -fsSL $BASE/selfhost.env.example -o .env
echo "POSTGRES_PASSWORD=$(openssl rand -hex 24)" >> .env
echo "SIGNING_KEY=\"$(openssl genpkey -algorithm ed25519)\"" >> .env
docker compose -f docker-compose.selfhost.yml up -dSelf-host bundle
Postgres, server, inbox and nginx on one origin with Docker Compose. The server prints a one-time setup code for the first admin.
Free hosting
Vercel for the inbox, Render for the server, Neon for Postgres. No credit card; the docs walk through every step.
MIT for the SDKs you embed
@intentlock/core, sdk, guard and anthropic. Use them anywhere, including closed-source products.
Elastic License 2.0 for the server and inbox
Run and modify it for your team; not to be offered as a hosted service. A missing license key only changes limits, never enforcement.
Put your agents on a plan.
Open source SDKs, a self-hosted server, and an audit log you can check yourself.