# Postgres Is Missing Context for AI Agents

> Postgres already has a good policy engine: GRANT, row-level security, column privileges. What it doesn't have is trustworthy inputs to feed that engine when the caller is an AI agent. Four missing primitives, and why Oracle and SQL Server already solved pieces of this.

Tianzhou | 2026-08-06 | Source: https://www.bytebase.com/blog/postgres-missing-context-for-ai-agents/

---

At Bytebase we build the governance layer in front of Postgres, MySQL, SQL Server, Oracle, etc. Going engine by engine on "how do we authorize an agent," we see the same gap in the open-source duo, Postgres and MySQL. Below, I use Postgres as the example.

## 1. Delegation: who is this session for

Alice asks a support bot for her refund status; Postgres only ever sees `agent_svc`, never Alice. This is delegation, the on-behalf-of (OBO) pattern: the session needs to carry both who it's acting for and who's actually calling.

![Alice asks a support agent about her refund, which delegates the task to a refunds agent, which queries Postgres as agent_svc. Postgres's current_user only shows agent_svc, no matter how many agents the request passed through.](/content/blog/postgres-missing-context-for-ai-agents/identity-chain.svg)

The workarounds are advisory, not enforced:

- `SET ROLE` is reversible: `RESET ROLE` undoes it in one statement.
- Session variables are writable by the very session they're meant to constrain.

SQL Server has `EXECUTE AS`, Oracle has proxy authentication. Postgres has none of it, and agent chains need delegation to be _chainable_, something neither SQL Server nor Oracle has solved either.

## 2. Attenuation: how much can this session do

Attenuation means deriving a strictly weaker credential from a stronger one, and only weaker, never back up.

Postgres has nothing like it:

- `SET ROLE`, reversible, same as above.
- `default_transaction_read_only`, a GUC the client flips back off.
- `ALTER ROLE ... SET statement_timeout`, a default not a ceiling, resettable at will.

Attenuation composes with delegation: what you want is `Alice's permissions ∩ read-only ∩ this table ∩ 15 minutes`.

## 3. Immutable application context: where do delegation and attenuation get stored

Where does a session store `principal = alice@corp.com, boundary = read-only, task = refund-42` such that a policy can trust it?

- Custom GUCs, writable by anyone who can run SQL.
- SQL comments (sqlcommenter), a string convention, unparsed and unprotected.

Oracle's application contexts are writable only by a trusted package, which is why its VPD can trust them. SQL Server's `sp_set_session_context` takes a `@read_only = 1` flag that locks a key for the rest of the connection.

## 4. Tags, not comments: what the data means

`COMMENT ON` covers every object, but it's one unstructured blob: no keys, no namespaces, PII flag and business definition crammed into one string.

Postgres has nothing at the label layer. Snowflake's `TAG` is a schema object, and a masking policy binds to the tag, not to any one column:

```sql
CREATE MASKING POLICY mask_pii AS (val STRING) RETURNS STRING ->
  CASE WHEN CURRENT_ROLE() IN ('ANALYST') THEN val ELSE '**MASKED**' END;

CREATE TAG sensitivity;
ALTER TAG sensitivity SET MASKING POLICY mask_pii;

ALTER TABLE customers MODIFY COLUMN ssn SET TAG sensitivity = 'pii';
```

Tag `ssn` and it's masked, immediately, no per-column policy to write. Every future column tagged `sensitivity = 'pii'` inherits the same masking, no code change at all. That's enforcement, not documentation.

## What it looks like assembled

Once the four primitives exist, they feed one policy engine:

```sql
BEGIN SESSION AS agent_svc
  ON BEHALF OF 'alice@corp.com'                          -- delegation
  ATTENUATE TO (READ ONLY, TABLES (orders), TTL '15m')   -- attenuation
  WITH IMMUTABLE CONTEXT (task = 'refund-42',            -- application context
                          trace = '4bf92f35');
```

And a policy trusts its own predicates:

```sql
CREATE POLICY no_pii_for_agents ON customers
  AS RESTRICTIVE
  USING (
    NOT column_has_tag('sensitivity', 'pii')             -- data-side context
    OR session_principal() = 'alice@corp.com'             -- session-side context
       AND session_context('task') LIKE 'refund-%'
  );
```

Nothing in it evaluates over an input the client can forge.

## The audit log inherits the same chain

pgaudit can only write down `current_user`. With the four primitives in place, the audit entry is the whole chain:

```text
agent_svc ON BEHALF OF alice@corp.com
  task=refund-42 boundary=read-only,orders,ttl:15m
  SELECT ... FROM customers
  → blocked by policy no_pii_for_agents
```

---

Without native engine support, we built [Bytebase](/) as a proxy in front of Postgres that augments this missing context, then acts on it: access control, data masking, approval flow, audit logging.

## Related reading

- [On-Behalf-Of Database Access for AI Agents](https://www.bytebase.com/blog/on-behalf-of-database-access-for-ai-agents/)
- [How to Govern AI Agent Database Access](https://www.bytebase.com/blog/how-to-govern-ai-agent-access-to-enterprise-data/)
- [Postgres Row-Level Security (RLS) Limitations and Alternatives](https://www.bytebase.com/blog/postgres-row-level-security-limitations-and-alternatives/)