actauth
v0.0.12
Published
A self-hosted policy gate for AI agent tool calls.
Downloads
1,099
Readme
ActAuth (TypeScript)
Part of LoopEngine — a runtime for defining and running AI agents through a transparent ReAct loop — handling rule-based permission gating (allow/ask/deny) with human-approval hooks. Works standalone too, no dependency on LoopEngine itself.
Same rule engine, scoped resolution, conditions, and audit log as the Python package at the repo root — ported line-for-line so both stay easy to keep in sync. See the root README for the product pitch and rule format; this file only covers what's specific to the TS build.
Install
npm installQuickstart
npm run quickstartimport { AuditLog, Gate, type Scope } from "./src/index.js";
const gate = Gate.fromConfig("examples/actauth.yml", {
auditLog: new AuditLog("actauth-audit.jsonl"),
});
const scope: Scope = { tenant: "beta-fintech", environment: "production", agent: "payments-agent" };
const result = await gate.evaluate("send_refund", { amount: 5000 }, scope);
console.log(result.decision, result.reason);Test / build
npm test # vitest
npm run build # tsc -> dist/Status
Same as the Python package: rule engine, scoping, conditions, audit log,
SlackChatApprover, and WebchatApprover are real and tested. No agent-SDK
adapter yet.
ConsoleApprover
The default Approver — a blocking terminal prompt, useful for local dev
and for exercising the full ask pipeline without standing up Slack:
import { Gate, ConsoleApprover } from "actauth";
const gate = Gate.fromConfig("actauth.yml", {
approver: new ConsoleApprover(), // this is also the default if you omit `approver`
});
const result = await gate.evaluate("send_refund", { amount: 900 }, scope);
// prints scope/tool/args/reason, then blocks on `approve? [y/N]`SlackChatApprover
import { SlackChatApprover } from "actauth";
const approver = new SlackChatApprover({
botToken: process.env.SLACK_BOT_TOKEN!,
channel: "#approvals",
signingSecret: process.env.SLACK_SIGNING_SECRET!,
});
// wire your own route for the Slack app's Interactivity Request URL:
app.post("/slack/interactions", async (req, res) => {
await approver.handleInteraction(req.rawBody, {
timestamp: req.headers["x-slack-request-timestamp"],
signature: req.headers["x-slack-signature"],
});
res.status(200).end();
});requestApproval() posts an interactive Approve/Deny message and resolves
when handleInteraction() is called with the matching click — verified
against Slack's request signature, timing out (deny) after timeoutMs
(default 5 minutes) if nobody responds.
WebchatApprover
For a web UI you own instead of Slack. Like SlackChatApprover, this class
only holds pending requests and resolves them — it doesn't serve HTTP
itself, so you wire two routes of your own: one that shows list(), and
one that calls decide(id, approved) when a human clicks Approve/Deny.
import { WebchatApprover } from "actauth";
const approver = new WebchatApprover();
app.get("/approvals", (req, res) => res.json(approver.list()));
app.post("/approvals/:id/:decision", (req, res) => {
const approved = req.params.decision === "approve";
const found = approver.decide(req.params.id, approved);
res.status(found ? 200 : 404).end();
});onPending/onSettled are for pushing an approval live instead of
requiring a poller to notice it in list() — e.g. writing it straight to
an SSE stream that's already open for the exact request that's blocked
waiting on it, the way LoopEngine's own playground does (webApprover.ts
creates one WebchatApprover per streamed chat turn, wired to onPending so
the popup appears inline in that conversation rather than on a separate
page):
const approver = new WebchatApprover({
onPending: (approval) => sseWrite(res, "approval:pending", approval),
onSettled: (id) => pendingById.delete(id),
});Same fail-closed-on-timeout behavior as SlackChatApprover — denies after
timeoutMs (default 5 minutes) if nobody decides.
