feat(agents): bridge AgentStore to Vertz entities (RLS-aware sessions/messages) [#2847]#2966
Merged
viniciusdacal merged 1 commit intomainfrom Apr 22, 2026
Merged
Conversation
…/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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 throughsqliteStore/d1Store/memoryStore— atomic paths unchanged.Closes #2847.
What ships
New
@vertz/agents/entitiessubpath:agentSessionColumns,agentMessageColumns,agentSessionIndexes,agentMessageIndexes— plain constants that developers spread into their ownd.table().defineAgentEntities(db)factory — looks up the registered tables viadb._internals.models, wiresentity()definitions with user-scoped defaults, installs a column-awarebefore.createhook that injectsuserId/tenantIdfrom the request context (ctx wins — prevents silent impersonation).Breaking changes (pre-v1,
.claude/rules/policies.md):AgentStore.appendMessagesgains asession: AgentSessionparameter — matchesappendMessagesAtomic. All three in-repo store impls,run.ts,crash-harness.tsupdated.agent_messagesgainsuser_id/tenant_idcolumns (denormalized on every append — required for flatrules.where({ userId })). Fresh installs get them via the stores' updated DDL; existing DBs run the one-shot migration atpackages/agents/migrations/001-add-rls-columns.sql.Adversarial review
Spawned a review agent post-implementation. Three blockers surfaced and all fixed before push:
ctx.entities.agentSession.list()— default entity names are kebab (agent-session),ctx.entitiesdoes exact-key lookup. Updated docs to use bracket access + documented the lowercasesessionName: 'agentsession'escape hatch for dot-access.before.createhook unconditionally injectedtenantId— 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 inspectstable._columnsand only injects fields the table actually declares.Two should-fixes also landed:
sessionAccess, missingdbarg).workspaceIdcustom column, hook-through-entity verification, custom-field query viahandlers.list).user_id/tenant_idare 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).packages/agents/src/entities/__tests__/bridge.integration.test.tsexercises the full path end-to-end:sqliteStore.saveSession+store.appendMessages→ entity-sidesessionHandlers.list(ctx)→ RLS rejects cross-tenant and cross-user-same-tenant reads; extendedworkspaceIdcolumn round-trips throughhandlers.create+before.createhook.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
state/toolCallscolumns tod.jsonb<T>()with an opt-in flag.🤖 Generated with Claude Code