Deterministic Authority for AI Agents: secure sensitive actions with sidecar-backed, pre-execution authorization.
@predicatesystems/authority is the TypeScript SDK for Predicate Authority. 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 sidecar/control-plane authority logic.
npm install @predicatesystems/authorityThis SDK requires the Predicate Authority Sidecar daemon to be running. The sidecar is a lightweight Rust binary that handles policy evaluation and mandate signing.
| Resource | Link |
|---|---|
| Sidecar Repository | predicate-authority-sidecar |
| Download Binaries | Latest Releases |
| npm Package | @predicatesystems/authorityd |
| License | MIT / Apache 2.0 |
Option A: Install via npm (recommended)
npm install @predicatesystems/authorityd
# The binary is automatically included for your platform
# Run with npx:
npx predicate-authorityd --helpOption B: Manual download
# Download from GitHub releases for your platform:
# https://github.com/PredicateSystems/predicate-authority-sidecar/releases
tar -xzf predicate-authorityd-darwin-arm64.tar.gz # or your platform
chmod +x predicate-authoritydThe Rust sidecar uses global CLI arguments (before the run subcommand) or a TOML config file.
Basic local mode:
./predicate-authorityd \
--host 127.0.0.1 \
--port 8787 \
--mode local_only \
--policy-file policy.json \
runUsing environment variables:
export PREDICATE_HOST=127.0.0.1
export PREDICATE_PORT=8787
export PREDICATE_MODE=local_only
export PREDICATE_POLICY_FILE=policy.json
./predicate-authorityd runUsing a config file:
# Generate example config
./predicate-authorityd init-config --output config.toml
# Run with config
./predicate-authorityd --config config.toml runGLOBAL OPTIONS (use before 'run'):
-c, --config <FILE> Path to TOML config file [env: PREDICATE_CONFIG]
--host <HOST> Host to bind to [env: PREDICATE_HOST] [default: 127.0.0.1]
--port <PORT> Port to bind to [env: PREDICATE_PORT] [default: 8787]
--mode <MODE> local_only or cloud_connected [env: PREDICATE_MODE]
--policy-file <PATH> Path to policy JSON [env: PREDICATE_POLICY_FILE]
--identity-file <PATH> Path to local identity registry [env: PREDICATE_IDENTITY_FILE]
--log-level <LEVEL> trace, debug, info, warn, error [env: PREDICATE_LOG_LEVEL]
--control-plane-url <URL> Control-plane URL [env: PREDICATE_CONTROL_PLANE_URL]
--tenant-id <ID> Tenant ID [env: PREDICATE_TENANT_ID]
--project-id <ID> Project ID [env: PREDICATE_PROJECT_ID]
--predicate-api-key <KEY> API key [env: PREDICATE_API_KEY]
--sync-enabled Enable control-plane sync [env: PREDICATE_SYNC_ENABLED]
--fail-open Fail open if control-plane unreachable [env: PREDICATE_FAIL_OPEN]
IDENTITY PROVIDER OPTIONS:
--identity-mode <MODE> local, local-idp, oidc, entra, or okta [env: PREDICATE_IDENTITY_MODE]
--allow-local-fallback Allow local/local-idp in cloud_connected mode
--idp-token-ttl-s <SECS> IdP token TTL seconds [default: 300]
--mandate-ttl-s <SECS> Mandate TTL seconds [default: 300]
LOCAL IDP OPTIONS (for identity-mode=local-idp):
--local-idp-issuer <URL> Issuer URL [env: LOCAL_IDP_ISSUER]
--local-idp-audience <AUD> Audience [env: LOCAL_IDP_AUDIENCE]
--local-idp-signing-key-env <VAR> Env var for signing key [default: LOCAL_IDP_SIGNING_KEY]
OIDC OPTIONS (for identity-mode=oidc):
--oidc-issuer <URL> Issuer URL [env: OIDC_ISSUER]
--oidc-client-id <ID> Client ID [env: OIDC_CLIENT_ID]
--oidc-audience <AUD> Audience [env: OIDC_AUDIENCE]
ENTRA OPTIONS (for identity-mode=entra):
--entra-tenant-id <ID> Tenant ID [env: ENTRA_TENANT_ID]
--entra-client-id <ID> Client ID [env: ENTRA_CLIENT_ID]
--entra-audience <AUD> Audience [env: ENTRA_AUDIENCE]
OKTA OPTIONS (for identity-mode=okta):
--okta-issuer <URL> Issuer URL [env: OKTA_ISSUER]
--okta-client-id <ID> Client ID [env: OKTA_CLIENT_ID]
--okta-audience <AUD> Audience [env: OKTA_AUDIENCE]
--okta-required-claims Required claims (comma-separated)
--okta-required-scopes Required scopes (comma-separated)
--okta-required-roles Required roles/groups (comma-separated)
--okta-allowed-tenants Allowed tenant IDs (comma-separated)
COMMANDS:
run Start the daemon (default)
init-config Generate example config file
check-config Validate config file
version Show version info
The sidecar supports multiple identity modes for token validation:
- local (default): No token validation. Suitable for development.
- local-idp: Self-issued JWT tokens for ephemeral task identities.
- oidc: Generic OIDC provider integration.
- entra: Microsoft Entra ID (Azure AD) integration.
- okta: Enterprise Okta integration with JWKS validation.
Safety notes:
idp-token-ttl-smust be >=mandate-ttl-s(enforced at startup)- In
cloud_connectedmode,localorlocal-idprequires--allow-local-fallback
export PREDICATE_API_KEY="your-api-key"
./predicate-authorityd \
--host 127.0.0.1 \
--port 8787 \
--mode cloud_connected \
--policy-file policy.json \
--control-plane-url https://api.predicatesystems.dev \
--tenant-id your-tenant \
--project-id your-project \
--predicate-api-key "$PREDICATE_API_KEY" \
--sync-enabled \
runimport { 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