Skip to content

feat(agents): bridge AgentStore to Vertz entities (RLS-aware sessions/messages) [#2847]#2966

Merged
viniciusdacal merged 1 commit intomainfrom
feat/agent-store-entity-bridge-impl
Apr 22, 2026
Merged

feat(agents): bridge AgentStore to Vertz entities (RLS-aware sessions/messages) [#2847]#2966
viniciusdacal merged 1 commit intomainfrom
feat/agent-store-entity-bridge-impl

Conversation

@viniciusdacal
Copy link
Copy Markdown
Contributor

Summary

Implements the agent-store ↔ entity bridge designed in #2959 (merged). Agent sessions and messages become first-class Vertz entities, queryable from app code with full rules.* enforcement. Writes still flow through sqliteStore / d1Store / memoryStore — atomic paths unchanged.

Closes #2847.

What ships

New @vertz/agents/entities subpath:

  • agentSessionColumns, agentMessageColumns, agentSessionIndexes, agentMessageIndexes — plain constants that developers spread into their own d.table().
  • defineAgentEntities(db) factory — looks up the registered tables via db._internals.models, wires entity() definitions with user-scoped defaults, installs a column-aware before.create hook that injects userId / tenantId from the request context (ctx wins — prevents silent impersonation).

Breaking changes (pre-v1, .claude/rules/policies.md):

  • AgentStore.appendMessages gains a session: AgentSession parameter — matches appendMessagesAtomic. All three in-repo store impls, run.ts, crash-harness.ts updated.
  • agent_messages gains user_id / tenant_id columns (denormalized on every append — required for flat rules.where({ userId })). Fresh installs get them via the stores' updated DDL; existing DBs run the one-shot migration at packages/agents/migrations/001-add-rls-columns.sql.

Adversarial review

Spawned a review agent post-implementation. Three blockers surfaced and all fixed before push:

  1. Migration SQL not idempotent — the header comment claimed "safe to run more than once" while the body crashes on a second run. Removed the false claim; documented the need for a migration runner.
  2. Docs showed ctx.entities.agentSession.list() — default entity names are kebab (agent-session), ctx.entities does exact-key lookup. Updated docs to use bracket access + documented the lowercase sessionName: 'agentsession' escape hatch for dot-access.
  3. before.create hook unconditionally injected tenantId — broke extended schemas that drop the column (the docs themselves recommend this) and could conflict with the CRUD pipeline's multi-level tenancy auto-set. Hook now inspects table._columns and only injects fields the table actually declares.

Two should-fixes also landed:

  • Added the missing negative type tests (string-value inside sessionAccess, missing db arg).
  • Beefed up the integration test with an extended-schema path (workspaceId custom column, hook-through-entity verification, custom-field query via handlers.list).
  • Added a direct store-level assertion that user_id / tenant_id are denormalized onto rows.

Test plan

  • vtz run ci:affected — all 254 tests pass, 0 failures. Build + typecheck + lint green.
  • vtz run format + format:fix — clean.
  • vtz run lint — 0 errors (pre-existing warnings only).
  • Integration test in packages/agents/src/entities/__tests__/bridge.integration.test.ts exercises the full path end-to-end: sqliteStore.saveSession + store.appendMessages → entity-side sessionHandlers.list(ctx) → RLS rejects cross-tenant and cross-user-same-tenant reads; extended workspaceId column round-trips through handlers.create + before.create hook.

Design

Merged at #2959 (plans/agent-store-entity-bridge.md). Six review rounds (DX, Product, Technical × 2) settled the architecture, API surface, breaking-change scope, and all API references against real code.

Follow-ups

🤖 Generated with Claude Code

…/messages) [#2847]

Agent sessions and messages become first-class Vertz entities, queryable
from app code with full `rules.*` enforcement (authenticated, tenant-scoped,
row-level `where`). Writes still flow through the existing sqliteStore /
d1Store / memoryStore implementations — atomic paths unchanged.

New `@vertz/agents/entities` subpath exports:
- `agentSessionColumns`, `agentMessageColumns`, `agentSessionIndexes`,
  `agentMessageIndexes` — spread into your own `d.table()`.
- `defineAgentEntities(db)` — looks up the registered tables via
  `db._internals.models` and returns `{ session, message }` entities
  pre-wired with user-scoped access rules and a `before.create` hook that
  injects `userId` / `tenantId` from the request context (ctx wins over
  input — prevents impersonation). Column-aware: only injects fields the
  extended table declares.

Breaking changes (pre-v1, per policies.md):
- `AgentStore.appendMessages(sessionId, messages)` gains a third
  `session: AgentSession` parameter, matching `appendMessagesAtomic`.
  All three in-repo store impls + run.ts + crash-harness updated.
- `agent_messages` table gains `user_id` / `tenant_id` columns
  (denormalized from the session row on every append; required for flat
  `rules.where({ userId: rules.user.id })` since access-enforcer evaluates
  conditions against the row directly). Fresh installs get the columns
  via the stores' updated DDL. Existing DBs run the one-shot migration in
  `packages/agents/migrations/001-add-rls-columns.sql`.

Tests: 254 passing. Integration test proves cross-tenant + cross-user RLS
isolation end-to-end via a shared DB file between sqliteStore writes and
entity reads. Direct store-level assertion confirms denormalization.
Three negative type tests for the factory options.

Docs: new mint-docs page `guides/agents/entity-bridge` covers setup,
default access, reading agent data (HTTP + ctx.entities + "do not use
db.*.list() with RLS"), schema extension, and the upgrade migration.

Design: `plans/agent-store-entity-bridge.md` (merged as #2959).
Follow-ups: #2957 (reject entity hook registration on factory entities),
#2958 (migrate state/toolCalls to `d.jsonb<T>()`).

Closes #2847.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@viniciusdacal viniciusdacal merged commit 6181b03 into main Apr 22, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(agents,server): bridge AgentStore to Vertz entities (RLS-aware sessions/messages)

1 participant