@vyr-e/latch
v0.2.2
Published
A permission system for AI agents, declared in one YAML file.
Maintainers
Readme
Agents get tool calls. latch decides which of them run. You declare what an agent may do in a
latch.yaml — allow, deny, amount limits, human-approval gates, protected paths — and every runtime
that reads the file enforces the same rules. Unlisted actions are denied by default.
agent: support-agent
allow:
stripe.customers.read: true
stripe.refunds.create:
max_amount: 50
approval: required
github.issues.create: true
deny:
github.repositories.delete: true
filesystem:
paths:
- ~/.ssh
- ~/.awsHow agents consume it
latch gives the same policy three surfaces, so any agent — framework-wired or plain LLM — can follow it:
Runtime gate. Wrap a tool; the policy decides whether
executeruns at all.import { createGate } from "@vyr-e/latch"; const gate = createGate({ policy: "latch.yaml" }); export const refund = gate.wrap("stripe.refunds.create", { async execute(input) { return chargeApi.refund(input); }, }); // denied → throws LatchDeniedError with the reason // approval: required → throws LatchApprovalRequiredError (or asks your onApproval handler)Action names autocomplete from
latch.yaml; unknown names fail to compile.A prompt section.
renderPrompt(policy)turns the YAML into markdown a model can read and follow: what is allowed, what needs a human, what never happens, and what to do when a call is denied. Paste it intoinstructions.mdor a system prompt.A CLI. Validate files, evaluate single calls in CI, print the prompt section, and generate action-name types.
bunx @vyr-e/latch validate latch check stripe.refunds.create --input '{"amount": 80}' # exit 1: exceeds max_amount 50 latch prompt > instructions-appendix.md latch types # writes latch-env.d.tsA contextual layer (optional). Rules say what an agent may do; an interchangeable classifier judges whether an authorized call is appropriate now — so an agent allowed to open GitHub issues doesn't open one for "good morning". Denials, constraints, and approvals always win; the classifier only restricts.
const gate = createGate({ policy: "latch.yaml", classifier: createLLMJudge({ model: "typesafe-ai/jev" }), situation: (input) => ({ agent, task, context }), });See Contextual gating.
Rules that hold everywhere
- Deny beats allow. A matching deny rule wins, even against a more specific allow.
- Default deny. Anything not allowed is denied;
default: allowloosens only the fallback, never the deny rules. - Constraints are fail-closed. A
max_amountrule with no numericamountin the input is a denial, not a pass. - Approval is a decision.
approval: requireddowngrades an allow to a pause-for-a-human decision — the sameapproved / denied / user-approvalshape eve-style runtimes already use.
Install
bun add @vyr-e/latchThe CLI ships with the package: bunx @vyr-e/latch --help (or npx @vyr-e/latch).
