Agent Policy Language (APL)

APL is the policy syntax shipped by pg_policy. Documents are stored as text and compiled by pg_policy.parse_apl / pg_policy.upsert_policy.

PostgreSQL extensions cannot add keywords to core SQL. APL is the extension’s language, invoked from SQL.

Skeleton

permit | forbid | guide
  principal <type> "<id>"
  action <type> "<id>"
  resource <type> "<id>"          # optional
  when { <context predicates> }   # optional
  when temporal { <temporal> }    # optional
  reason "<text>"                 # optional
  advice "<text>"                 # guide/permit
  prefer_tool "<name>"            # guide/permit
  max_rows <int>                  # guide/permit

Effects

Effect Meaning
permit Contributes to allow when matched
forbid Deny overrides any permit (Cedar-like)
guide Allow with obligations (advice, preferences)

Principals & actions

  • Principal types: agent, user, role, service
  • Action types: tool, sql, data, admin
  • Resource types: table, schema, database, object
  • "*" wildcards match any id/type where supported by matcher

Context predicates (v0.1)

when {
  context.statement_type in ["DROP", "TRUNCATE", "ALTER"]
  and context.tenant_id == "acme"
}

Operators: ==, in [...]. Combine with and.

Temporal (v0.1)

when temporal {
  count(action == "export_csv") within interval '1 hour' >= 3
}

Requires a session_id on evaluate().

Examples

Hard guardrail

forbid
  principal agent "research_bot"
  action tool "execute_sql"
  when { context.statement_type in ["DROP", "TRUNCATE", "ALTER", "CREATE"] }
  reason "Research agents may not run DDL"

Soft guidance

guide
  principal agent "research_bot"
  action tool "execute_sql"
  advice "Prefer EXPLAIN ANALYZE before large scans"
  prefer_tool "sql_explain"
  max_rows 500

Session quota

forbid
  principal agent "research_bot"
  action tool "export_csv"
  when temporal {
    count(action == "export_csv") within interval '1 hour' >= 3
  }
  reason "Export budget exceeded"

Evaluation API

SELECT pg_policy.evaluate(
  p_principal_type := 'agent',
  p_principal_id   := 'research_bot',
  p_action_type    := 'tool',
  p_action_id      := 'execute_sql',
  p_context        := '{"statement_type":"DROP"}'::jsonb,
  p_session_id     := 'sess-1'
);

Returns JSON:

{
  "decision": "deny",
  "allowed": false,
  "matched_policies": ["block_ddl"],
  "obligations": [],
  "reasons": ["Research agents may not run DDL"],
  "mode": "enforce"
}