docs(agents): design doc for agent-store ↔ entity bridge [#2847]#2959
Merged
viniciusdacal merged 1 commit intomainfrom Apr 22, 2026
Merged
docs(agents): design doc for agent-store ↔ entity bridge [#2847]#2959viniciusdacal merged 1 commit intomainfrom
viniciusdacal merged 1 commit intomainfrom
Conversation
Design doc at plans/agent-store-entity-bridge.md for bridging AgentStore with Vertz entities so agent sessions/messages become queryable with RLS from app code. Resolves Gap #4 of plans/open-agents-clone.md. Rev 6 after three rounds of adversarial review (DX, Product, Technical) per .claude/rules/design-and-planning.md. All reviewers approved. Awaits human final sign-off. Key design decisions settled: - Entities are a READ-VIEW over the store's tables. Writes stay on memoryStore/sqliteStore/d1Store; entity RLS applies to app-side reads. - defineAgentEntities(db) factory + column packs (no extend API, no sugar helpers). Custom fields via normal d.table() spread. - AgentStore.appendMessages gains session parameter (matches appendMessagesAtomic). Pre-v1 breaking change. - agent_messages denormalizes userId/tenantId for flat rules.where(). Non-adopter breaking change acknowledged explicitly. - before.create hook injects ctx.userId/ctx.tenantId (ctx wins over input to prevent silent impersonation). - Hook-bypass on agent-loop writes tracked as follow-up #2957. - state/toolCalls → d.jsonb<T>() migration tracked as #2958. Closes: none yet (design-only PR). Refs: #2847, #2957, #2958, plans/open-agents-clone.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Merged
4 tasks
viniciusdacal
added a commit
that referenced
this pull request
Apr 22, 2026
…/messages) [#2847] (#2966) 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
Design doc for bridging
AgentStorewith Vertz entities so agent sessions and messages become queryable from app code withrules.*RLS applied — without changing the agent loop's atomic write path. Resolves Gap #4 ofplans/open-agents-clone.md.This PR is design-only. No implementation. Awaits human sign-off on the settled design at
plans/agent-store-entity-bridge.md(Rev 6) before Task A/B/C implementation begins in a follow-up PR.The design, in three sentences
agentSessionColumns/agentMessageColumnsinto their ownd.table()calls, register viacreateDb({ models }), then calldefineAgentEntities(db)to get{ session, message }entities wired intocreateServer({ entities }). Tenant scoping auto-detected; access defaults arerules.where({ userId: rules.user.id })+ abefore.createhook that injectsuserId/tenantIdfrom the request context (ctx wins over input).memoryStore/sqliteStore/d1Storeimplementations — atomictransaction()/batch()semantics preserved, D1 semantics unchanged, durable resume unaffected.AgentStore.appendMessagesgains asessionparameter (matches the existingappendMessagesAtomicshape) so stores can denormalizeuserId/tenantIdontoagent_messagesrows — enabling flatrules.where({ userId })onMessagewithout framework changes./api/agent-sessionauto-routes,ctx.entities.agentSession.list()) get full RLS. Reads inside the agent loop (store.loadSession) stay on the existingrun.ts:222-252ownership check. Honest scoping.Public API changes
Additions (
@vertz/agents/entitiessubpath):agentSessionColumns,agentMessageColumns— plainColumnRecordconstantsagentSessionIndexes,agentMessageIndexes— index arraysdefineAgentEntities(db, opts?)— factory returning{ session, message }Breaking change (pre-v1,
.claude/rules/policies.mdpermits):AgentStore.appendMessages(sessionId, messages)→appendMessages(sessionId, messages, session). All three in-repo store impls update. One call site inrun.ts(~1 line). No known external consumers.agent_messagestable gainsuser_id/tenant_idcolumns. Every@vertz/agentsuser runs an additiveALTER TABLE agent_messages ADD COLUMN …migration on upgrade. Migration SQL provided.Review process
Three full rounds of adversarial review (DX, Product, Technical) per
.claude/rules/design-and-planning.md:Architecture settled at Rev 3; Rev 4–6 were editorial passes fixing API-shape drift in the E2E acceptance test (verified against real file:line evidence).
Follow-up issues filed
state/toolCallscolumns tod.jsonb<T>()with opt-in flagDecision requested
One scope call flagged for explicit sign-off:
@vertz/agentsuser must run a two-statementALTER TABLEmigration on upgrade, even if they never adopt entities. Rationale + alternative rejected in the design. If this scope is unacceptable, say so and I'll re-work the denormalization into an opt-in flag.Test plan
plans/agent-store-entity-bridge.mdend-to-endapps/demo/e2e/agent-store-entity-bridge.test.tspassing against realcreateDb+sqliteStore+createCrudHandlers(not the stubbed snippet in the doc)🤖 Generated with Claude Code