Concepts¶
The thesis¶
Detecting a threat should not just block one request. It should change what the agent may do for the rest of the session. In an interactive session that means narrowing permissions. For a registered autonomous system it means throttling and human approval instead, so the business process keeps running while risky actions wait for a person.
Two identities on every call¶
An agent acts on behalf of a person, but must not inherit all of that person's rights. Every
request carries a signed token with both: sub, the person (or svc:<agent> for an
autonomous job), and act.sub, the agent. The decision is made on the intersection:
base_allowed =
principal grants match (action, resource) # U: the user's roles
AND agent grants match (action, resource) # A: the agent's allow list
AND task scope matches (action, resource) # T: the token's scope
AND no explicit deny matches
Session risk can only add restrictions and obligations (approval, throttling, redaction)
on top of that; it never widens it. An approval never grants an operation outside
base_allowed.
Permissions are action:resource strings, for example read:db:sales.payments,
write:fs:reports/* or generate:model:qwen3:8b. The gateway also forwards a signed principal
assertion to the database, so Postgres row-level security decides which rows a person sees.
Same question, two people
Anna (analyst) and Bartek (intern) both ask "What is the total amount of all payments?".
Anna gets the total. Bartek's role has no read:db:sales.payments, so the query never
reaches the database.
Credentials stay in the gateway¶
Real keys to databases, APIs and models live only in the gateway. The agent knows one URL. The upstreams sit on internal networks shared only with the gateway, so the agent cannot bypass the layer. See Architecture.
Hybrid controls¶
Fifteen controls in two kinds: 11 deterministic rules (secrets, PII, SQL cost, egress, signatures, budgets, ...) and 4 AI-based checks (prompt injection, tool poisoning, intent, output). Cheap first, the LLM only when needed, and every judge fails closed. See The 15 controls.
Session risk and taint¶
Every verdict adds risk; injection-type detections also taint the session. Interactive
sessions lose write, delete and egress; autonomous ones are throttled and routed to the
approval queue. The table is on The 15 controls.
Human in the loop¶
When an action needs approval, the tool call returns an approval_id. An approval authorizes
one exact pending operation: it is bound to the principal, agent, session, tool, a digest of
the arguments and the policy version, and it expires. Approvers see a digest, never the
arguments, and can never approve their own session. Replaying a used approval is refused
(approval_already_used). See Operations.
One policy, live¶
Everything above is configured in one config/policy.yaml, validated on load and hot-reloaded
without a restart. See Policy file.
Budgets¶
Tokens, cost, tool calls and GPU time are budgeted per user, agent and session. Budget is reserved atomically in Redis before each call and settled on actual usage, so concurrent calls cannot overspend. If Redis is unavailable, budget-limited calls fail closed.