Deterministic Authority for AI Agents: secure sensitive actions with sidecar-backed, pre-execution authorization.
@predicatesystems/authority is the TypeScript SDK companion to the Python
predicate-authorityd sidecar from predicate-authority (Python). It keeps authority
decisions in the sidecar and gives Node/TS runtimes a thin, typed client for
fail-closed pre-execution checks.
Most agent security failures come from over-broad delegated credentials and lack of per-action runtime checks. Predicate Authority introduces short-lived mandates bound to policy, identity, and evidence-backed state/intent checks.
- Bridge, do not replace: keep enterprise identity stacks (Entra/Okta/OIDC).
- Fail-closed by default: deny before execution when checks fail.
- Deterministic binding: decisions are tied to runtime evidence.
- Provable controls: reason codes and mandate IDs propagate to audit systems.
This TS repository currently focuses on:
- typed sidecar transport for
POST /v1/authorize, - request/response contracts for authorization flows,
- runtime wrapper primitives (incremental),
- CI/release scaffolding for npm package delivery.
Out of scope for this package:
- re-implementing policy engine or mandate logic in TypeScript,
- replacing Python sidecar/control-plane authority logic.
This package targets compatibility with the current Python authority baseline in predicate-authority (Python):
- sidecar authorize route:
POST /v1/authorize(/authorizecompat alias), - mandate/token baseline: ES256-default signing + standard JWT claim envelope,
- revocation baseline: explicit cascade semantics and global kill-switch runtime behavior,
- control-plane baseline: long-poll policy/revocation sync (runtime baseline),
- control-plane write hardening: replay freshness headers/signature support on Python client paths.
The TS SDK should preserve compatibility with these runtime behaviors before adding TS-specific extensions.
npm install @predicatesystems/authorityThis SDK requires the Predicate Authority sidecar running locally. Install and start it:
# Install via pip (requires Python 3.11+)
pip install predicate-authority
# Start the sidecar
predicate-authorityd --port 8787import { AuthorityClient, type AuthorizationRequest } from "@predicatesystems/authority";
const client = new AuthorityClient({
baseUrl: "http://127.0.0.1:8787",
});
const request: AuthorizationRequest = {
principal: "agent:payments",
action: "http.post",
resource: "https://finance.example.com/transfers",
intent_hash: "intent-hash-placeholder",
labels: ["verified:user_presence"],
};
const decision = await client.authorize(request);
if (!decision.allowed) {
throw new Error(`Authority denied: ${decision.reason}`);
}import { AuthorityClient } from "@predicatesystems/authority";
const client = new AuthorityClient({
baseUrl: "http://127.0.0.1:8787",
timeoutMs: 2000, // per-attempt timeout
maxRetries: 2, // retry budget on network/5xx failures
backoffInitialMs: 200, // linear backoff base (attempt * base)
});- Sidecar client mode (recommended): use
AuthorityClientto callpredicate-authorityd(/v1/authorize) as the authority source of truth. - Local guard mode (optional): use
PolicyEngine + ActionGuardfor local evaluation in TS runtime flows (useful for tests/dev or controlled deployments).
import {
ActionGuard,
PolicyEngine,
type ActionRequest,
type PolicyRule,
} from "@predicatesystems/authority";
const rules: PolicyRule[] = [
{
name: "allow-transfer-submit",
effect: "allow",
principals: ["agent:payments"],
actions: ["http.post"],
resources: ["https://finance.example.com/transfers"],
required_labels: ["verified:user_presence"],
},
];
const guard = new ActionGuard({
policyEngine: new PolicyEngine(rules),
});
const request: ActionRequest = {
principal: { principal_id: "agent:payments" },
action_spec: {
action: "http.post",
resource: "https://finance.example.com/transfers",
intent: "submit transfer #123",
},
state_evidence: { source: "browser", state_hash: "state_abc" },
verification_evidence: {
signals: [{ label: "verified:user_presence", status: "passed" }],
},
};
const decision = guard.authorize(request);
if (!decision.allowed) {
throw new Error(`Local guard denied: ${decision.reason}`);
}See docs/runtime-adapters.md for copy-paste adapter patterns that map common
agent/tool runtime operations to authority checks in both:
- sidecar-first mode (
AuthorityClient), and - local wrapper mode (
ActionGuard+guardedShell/guardedFile*/guardedHttp).
Use buildWebStateEvidence(...) to map browser snapshot artifacts into canonical
state_evidence:
import { buildWebStateEvidence } from "@predicatesystems/authority";
const stateEvidence = buildWebStateEvidence({
snapshot: {
url: "https://app.example.com/transfer",
title: "Transfer Funds",
dom_hash: "dom_hash_here",
visible_text_hash: "text_hash_here",
observed_at: new Date().toISOString(),
},
});If your runtime already produces sdk-ts snapshots, use
buildWebStateEvidenceFromRuntimeSnapshot(...) to map timestamp,
dominant_group_key, and diagnostics confidence directly.
Non-web evidence contracts are also available for Phase 4 adapter work:
TerminalEvidenceProvider, DesktopAccessibilityEvidenceProvider, and
VerificationSignalProvider (see docs/runtime-adapters.md).
npm install
npm run typecheck
npm test
npm run buildRun integration tests against a live predicate-authorityd:
export SIDECAR_BASE_URL="http://127.0.0.1:8787"
npm run test:integrationGitHub Actions test.yml also supports optional integration execution via
manual workflow_dispatch inputs (run_integration, sidecar_base_url).
For explicit allow/deny integration assertions, you can pass request fixtures:
export RUN_SIDECAR_INTEGRATION_TESTS=true
export SIDECAR_BASE_URL="http://127.0.0.1:8787"
export SIDECAR_ALLOW_REQUEST_JSON='{"principal":"agent:allow","action":"http.get","resource":"https://example.com","intent_hash":"ih_allow"}'
export SIDECAR_DENY_REQUEST_JSON='{"principal":"agent:deny","action":"http.post","resource":"https://example.com/admin","intent_hash":"ih_deny"}'
export SIDECAR_EXPECTED_DENY_REASON="missing_required_verification"
export SIDECAR_REQUIRE_MANDATE_ON_ALLOW=false
npm run test:integrationGitHub Actions workflows are included for:
- test/build checks on push/PR:
.github/workflows/test.yml - npm release on
v*tags or manual dispatch:.github/workflows/release.yml- prerelease path:
rc-v*tags publish to npmnextdist-tag
- prerelease path:
- manual post-publish smoke evidence:
.github/workflows/post-publish-smoke.yml
Required GitHub secret:
NPM_TOKENwith publish access for@predicatesystems.
Release docs:
CHANGELOG.mddocs/release-checklist.md
Post-publish smoke:
npm run smoke:npm -- latest
# optional live sidecar authorize check
SIDECAR_BASE_URL=http://127.0.0.1:8787 npm run smoke:npm -- latestSee CONTRIBUTING.md for branch, test, integration, and release conventions.
Common failure modes and first checks:
AuthorityClientError: timeout- sidecar may be unreachable or overloaded; verify sidecar health and increase
timeoutMsfor slow environments.
- sidecar may be unreachable or overloaded; verify sidecar health and increase
AuthorityClientError: network_error- check
baseUrl, local networking, and whetherpredicate-authoritydis listening on expected host/port.
- check
AuthorityClientError: protocol_error- sidecar returned non-JSON or unexpected payload shape; verify sidecar version compatibility.
AuthorityClientError: bad_request- request payload is invalid for
/v1/authorize; compare fields with the fixture examples intests/fixtures/.
- request payload is invalid for
- Frequent retries before success
- tune
maxRetriesandbackoffInitialMs; investigate sidecar/host resource pressure.
- tune
Dual-licensed under MIT and Apache 2.0:
LICENSE-MITLICENSE-APACHE
Copyright (c) 2026 Predicate Systems Contributors