agents — Module Spec
1. Purpose
agents (module #28) is the agent runtime — the module that actually claims work, makes decisions, executes tools, and can be killed. It is Phase 5 of 6 of the agents-v2/v3 build authorization (Phase 1 reopened platform for the module registry + AI-capacity layer; Phase 2 reopened ai for the model-registry/deployment/prompt/routing layer; Phase 3 built semantics, the shared business ontology; Phase 4 built signals, the bitemporal feature/forecast/outcome store — all 4 already schema-locked before this phase started). Where semantics defines the vocabulary an agent decision reasons about and signals stores the facts and outcomes it reads and writes, agents is the module that actually does something: it is this codebase's first genuinely new orchestration layer, and every prior autonomy mechanism this codebase already had (identity.agent_identity, identity.agent_duty_grant, ai.agent_execution) is a dependency this module consumes, not a competitor it replaces.
agents is not a new design — it is a full merge of 3 documents into one coherent build, not merely the latest delta: v1's own 21-table orchestration baseline (agent_task, agent_thread, agent_event_log, agent_schedule, agent_trigger, agent_eval_suite/_run/_case, agent_performance_profile, agent_shadow_run/_decision, agent_autonomy_profile, agent_action, rollback_recipe/_execution, tool_catalog, agent_tool_grant, agent_catalog_entry/_required_tool, tenant_agent_deployment, agent_incident — never previously built, confirmed live: zero tables existed anywhere in this schema before this phase), merged with v2's amendments (A1–A8) and v3's corrections (BLOCKER 1–8, I1–I8, v3 wins on any conflict). Design of record: vrida-agents-module-design-proposal-2026-07-11.md (v1), vrida-agents-v2-design-amendment-2026-07-11.md (A1–A8), vrida-agents-v3-correction-pass-2026-07-12.md (BLOCKER 1–8, I1–I8).
47 logical tables / 475 columns. Migration: packages/db/migrations/20260716000000_agents_module_new_schema.sql (~1650 lines). Full build record: docs/decisions/PROJECT_DECISIONS.md, Phase 5 entry.
2. Ownership
Owns — 47 tables, 475 columns, grouped by concern (matching the 12 Drizzle source files under packages/db/src/schema/agents/):
| Family | File | Tables | Cols | Role |
|---|---|---|---|---|
| Tool/toolset catalog | tool.ts |
tool_definition, tool_version, tool_version_module, toolset_definition, toolset_version, toolset_version_member |
6 / 44 | A2b version-row catalog pattern. Supersedes v1's flat, unversioned tool_catalog (see DR-1). |
| Skill catalog + assignment | skill.ts |
skill_definition, skill_version, skill_version_toolset, skill_version_required_duty, skill_version_module, agent_skill_assignment |
6 / 36 | A2b version-row pattern for skills; agent_skill_assignment is the moved-from-identity activation point (see DR-3, item 8). |
| Marketplace / deployment | marketplace.ts |
agent_catalog_entry, agent_catalog_entry_required_tool, tenant_agent_deployment, agent_tool_grant |
4 / 43 | v1's marketplace/entitlement surface — which agents a tenant can deploy, and which tools a deployed agent identity actually holds. |
| Task orchestration | task.ts |
agent_schedule, agent_trigger, agent_task, agent_thread, agent_event_log |
5 / 89 | The unit-of-work core (v1 Gap 1, amended A1/A5/E3/BLOCKER4) — atomic claim/fencing/lease, runaway-ceiling caps, kill-switch gate, saga-gate anchor. |
| Evaluation | eval.ts |
agent_eval_suite, agent_eval_suite_version, agent_eval_run, agent_eval_case, agent_performance_profile |
5 / 56 | Pre-deployment and ongoing agent quality measurement, version-pinned (see DR-4). |
| Shadow / autonomy | shadow.ts |
agent_shadow_run, agent_shadow_decision, agent_autonomy_profile |
3 / 31 | Shadow-mode dry-runs and the tenant's own per-domain autonomy-mode recommendation surface. |
| Certification | certification.ts |
skill_certification |
1 / 13 | A3's gate — the pass/fail record validate_skill_activation() requires before a skill can be enabled for an agent. |
| Decision context / evidence | decision_context.ts |
evidence_retention_policy, decision_context_snapshot, decision_context_manifest, decision_context_feature, decision_context_forecast, decision_context_metric, decision_context_policy, decision_context_knowledge_source |
8 / 64 | A4/BLOCKER6's evidence envelope — partitioned, append-only, the reconstructible "what did the agent know" record. |
| Action / rollback / incident | action.ts |
agent_decision, agent_action, rollback_recipe, rollback_execution, agent_incident |
5 / 67 | The actual write path (BLOCKER2/I1/BLOCKER4) plus the safety-net rollback and incident-tracking tables. |
| Kill switch | kill_switch.ts |
kill_switch_event, kill_switch_scope_state |
2 / 14 | BLOCKER7's history/state split. |
| Policy / capacity | policy.ts |
skill_execution_policy, workload_class |
2 / 18 | Tenant-bound spend/concurrency ceilings and the AI-capacity workload classification ai.routing_policy now reads. |
| Total | 47 | 475 |
Does NOT own:
- Agent identity/authority itself —
identity.agent_identity,identity.agent_duty_grant(already built and locked).agentsreads both constantly (every kill-switch check, everyvalidate_skill_activation()call) but introduces no competing authority mechanism (see §9). - The append-only execution ledger —
ai.agent_execution(already built, Phase 2). This phase amends it (+8 columns, see §10) but the table itself, and its ownership, stays withai. - The shared business ontology —
semantics.metric_definition/goal_definition/attribution_model_definition(Phase 3).decision_context_snapshotreadssemantics.metric_versionbut does not duplicate it. - The feature/forecast/outcome store —
signals.feature_value/forecast/experiment_assignment/outcome_observation(Phase 4).decision_context_snapshotreadssignals.feature_value/forecast/experiment_assignment;agent_actionis now the real FK targetsignals.outcome_observation.agent_action_idpoints at (a forward-ref closed by this phase, not owned by it). - The
agent_readerrole and its dedicated connection helper — deferred to Phase 6 (see §11).
3. Layer & Dependencies
Foundation layer (per SCHEMA_CONVENTIONS.md's foundation/business/consumer taxonomy) — same positioning as ai, approvals, semantics, signals. agents is the runtime that sits on top of all 4.
Depends on:
platform—tenant.id,module_catalog.id(A1'sagent_task.domain/tool_definition.domain/skill_definition.domainretype),polymorphic_target_registry(I6, this phase's own companion table, see §6/§10).identity—actor.id(attribution columns throughout),agent_identity.id/.status(the kill-switch fail-closed check reads this directly),agent_duty_grant(bothcheck_agent_execution_not_blocked()andvalidate_skill_activation()query it).ai—model_version.id,prompt_version.id(both read bydecision_context_snapshotand by the retirement-check triggers),agent_execution(amended this phase, not owned by it).semantics—metric_version.id(real FK fromdecision_context_snapshot).signals—feature_value.id,forecast.id,experiment_assignment.id(real FKs fromdecision_context_snapshot); this phase also closessignals.outcome_observation.agent_action_id/outcome_authority.agent_action_idas real composite FKs intoagents.agent_action(a Phase-4-disclosed forward-ref, now wired — see §10).files—document_chunk.id(real FK fromdecision_context_knowledge_source; this phase adds the prerequisiteUNIQUE(id, tenant_id)onfiles.document_chunkas a companion migration step, since it didn't exist before).approvals—approval_request.id(bothai.agent_execution.approval_request_idand the saga-gate exemption path reference it); this phase widensapproval_request's ownsource_moduleCHECK to add'agents'.
Depends on NO nursery-vertical product module — same reverse-dependency discipline every foundation-layer module in this codebase holds itself to.
Depended on by: every future consumer of the agent runtime — agent_reader (Phase 6, not yet built) is the intended tenant-facing read role; no module currently queries agents.* in production code, since no service layer exists yet (see §7).
4. Tables — grouped
See the ownership table in §2 for the 12-family grouping and per-family column counts. Full column-level detail (types, nullability, defaults, CHECK constraints, indexes, triggers, partition mechanics) lives in docs/database/schema_docs/agents.md — not restated here.
Partitioned tables (3, monthly BY RANGE(created_at), per E1): decision_context_snapshot, decision_context_manifest, agent_decision, agent_action — all append-only (REVOKE UPDATE, DELETE + platform.reject_append_only_mutation() on the parent and every partition, the same per-partition DO-loop pattern Phase 2/Phase 4 already established).
5. Capabilities — AI Capability Plane application
agents is not a module that applies the AI Capability Plane to some other business surface (the way crm or pos do) — this module IS the AI Capability Plane's own runtime substrate. Every control the plane names in the abstract (authority level, autonomy mode, spend/step ceilings, kill-switch, evidence trail, shadow-mode evaluation) has a concrete table here:
- Authority —
draft_only/needs_approval/may_act_alone, the same 3-value vocabularyidentity.agent_duty_grantalready established, now also onagent_task.autonomy_mode,skill_version_required_duty.minimum_authority_level, andagent_performance_profile.recommended_autonomy_mode— one vocabulary, reused everywhere, never re-invented per table. - State —
agent_task.status(pending/in_progress/awaiting_human/completed/failed/canceled), with an atomicUPDATE ... WHERE status='pending' AND (lease_expires_at IS NULL OR lease_expires_at < now()) RETURNING fencing_tokenclaim (E3) — no two workers can claim the same task, and a stale lease is reclaimable. - Spend/step ceilings —
agent_task.max_steps/max_cost_centsvs.step_count/cost_cents_accrued, enforced BLOCK-not-flag (v1's own explicit decision: an agent runaway is a safety property, not a business-judgment tolerance call like receiving's over-shipment) byenforce_task_runaway_ceiling(). - Kill-switch — a 5-scope precedence chain (
global>tenant>agent_identity>skill/tool/model) resolved throughkill_switch_event(history) +kill_switch_scope_state(current state, native-upsert-maintained) — see §6 GUARD 2. - Evidence —
decision_context_snapshot+ its 6 typed child tables (_feature,_forecast,_metric,_policy,_knowledge_source, plus_manifest) is the reconstructible "what did the agent know, and from where" record BLOCKER6 requires — typed FKs intosignals/semantics/files/ai, not a single opaque JSONB blob. - Shadow-mode / graceful degradation —
agent_shadow_run/agent_shadow_decisionlet a skill run in observe-only mode before it is trusted with real autonomy;agent_autonomy_profileis the tenant's own per-domain recommendation surface, fed byagent_performance_profile's measured reliability scores. Every AI-OFF path in this codebase (identity'sagent_duty_grant, ai'sagent_execution) still works withagentsabsent entirely — nothing here is a hard runtime dependency for non-agent flows.
⚠ PROHIBITION — read before building the agent runtime.
agent_autonomy_profile.current_modeabove is a label with no structural enforcement — seedocs/open-items/OPEN_ITEMS.md("Autonomy level is advisory — PROHIBITION until structurally bound"). Structurally bindingcurrent_modeto a real enforcement path, live-reproduced, is the first task of the agent-runtime /AgentsServicebuild — not a later hardening pass. Do not ship an executor without it.
6. Enforcement Mechanisms — Trigger-Backed Guards
Unlike semantics/signals (mostly CHECK/EXCLUDE-enforced), agents is trigger-heavy — its own runtime correctness (claim-once, don't-exceed-ceiling, don't-act-while-killed, don't-drift-across-modules) cannot be expressed as a static constraint. 7 critical guards were named up front (matching v3's own BLOCKER numbering) and a genuine 8th (T1) was added during this build's own Section 4 self-audit — disclosed honestly as 8, not forced to fit the original count of 7:
| # | Guard | Trigger function(s) | Table | Fires on |
|---|---|---|---|---|
| 1 | E3 — atomic task claim + fencing | (no dedicated trigger — the claim itself is the proven UPDATE ... WHERE ... RETURNING idiom; lease renewal is a deliberately separate statement that does NOT bump fencing_token, closing v2's own self-fencing bug, CC-2) |
agent_task |
claim vs. renewal (2 different statements) |
| 2 | BLOCKER7 — kill-switch history/state split + resume-safe propagation | resolve_kill_switch_state(), propagate_kill_switch_fencing() |
kill_switch_event → kill_switch_scope_state, agent_task |
AFTER INSERT on kill_switch_event |
| 3 | A5, corrected — kill-switch check fires on task CLAIM, not just creation | check_agent_task_not_blocked(), check_agent_execution_not_blocked(), is_agent_blocked() (shared resolver) |
agent_task, ai.agent_execution |
BEFORE INSERT OR UPDATE OF status on agent_task (not INSERT-only); BEFORE INSERT on agent_execution |
| 4 | runaway-ceiling (v1 Gap 1) — BLOCK not flag | enforce_task_runaway_ceiling() |
ai.agent_execution |
BEFORE INSERT |
| 5 | BLOCKER4 + I1 — saga gate against real writes, plus decision/action consistency | enforce_single_module_autonomy(), validate_action_decision_execution_consistency() |
agent_action |
BEFORE INSERT (both) |
| 6 | A3 + BLOCKER5 + A8 Rule 4 — skill activation: certification + duty-grant authority + spend-ceiling | validate_skill_activation() |
agent_skill_assignment |
BEFORE INSERT OR UPDATE OF is_enabled |
| 7 | A2b retirement rule 1 — retirement blocks new executions | ai.reject_retired_tool_version(), agents.reject_retired_decision_context_versions() |
ai.agent_execution, decision_context_snapshot |
BEFORE INSERT (both) |
| 8 (T1) | Section-4-audit-added — the same retirement-rule split re-tested as its own guard, closing a coverage gap the original 7 didn't explicitly exercise | reject_retired_decision_context_versions() (same function as #7, additional test coverage) |
decision_context_snapshot |
BEFORE INSERT |
Two more trigger functions round out the enforcement surface but weren't part of the "named" count: ai.require_idempotency_key_for_write_tools() (a write-tool execution without an idempotency_key is rejected outright) and ai.validate_agent_memory_source_polymorphic_ref() (GUARD 7/I6 — the real validator for ai.agent_memory_source.source_ref, resolved through the new platform.polymorphic_target_registry + platform.validate_polymorphic_reference(), see §10).
Two genuine PL/pgSQL bugs were found and fixed during this build's own live guard-reproduction (not the read-only Section 4 audit):
- A
record-typed PL/pgSQL variable'sIS NOT NULLcheck is unreliable when used directly as anIFcondition — empirically confirmed via a bareDOblock (a genuinely non-null, fully-populated record still evaluated as if NULL in that boolean context). This silently swallowed the entire A8 Rule 4 spend-ceiling check insidevalidate_skill_activation(). Fixed by rewriting to scalar variables, checking each scalar's own NULL-ness directly. SELECT MIN(...) ... FOR UPDATEis syntactically rejected by Postgres ("FOR UPDATE is not allowed with aggregate functions"), also insidevalidate_skill_activation()'s Point 3. Fixed by splitting into a plainPERFORM ... FOR UPDATE(lock only) followed by a separateSELECT MIN(...) INTO ...(aggregate over the now-locked rows).
One genuine security gap was found and fixed during the Section 4 self-audit itself (item B): kill_switch_event had tenant_id + GRANT SELECT, INSERT TO authenticated but zero RLS policies — any tenant session could read every other tenant's kill-switch history and forge a kill/suspend/resume event against another tenant's agents. Fixed with the same 2-policy mixed-scope RLS pattern already established by agent_eval_suite/rollback_recipe in this same build; live-reproduced clean (cross-tenant SELECT returns 0 rows, forged cross-tenant/global INSERT rejected, legitimate own-tenant INSERT succeeds).
7. Service Contract — AgentsService
No AgentsService exists yet — schema-only this phase, matching every phase of the agents-v2/v3 build so far (Phase 1's platform reopen, Phase 2's ai reopen, Phase 3's semantics build, Phase 4's signals build were all schema-only too). Downstream code (once it exists) would query agents.* directly via Drizzle, and — once agent_reader (Phase 6) is granted — through that dedicated read role rather than the tenant-scoped authenticated connection ordinary application code uses.
The mechanisms that matter most for correctness — the atomic claim/fencing, the kill-switch precedence chain, the runaway ceiling, the saga gate, the skill-activation checks — are NOT waiting on that service layer to be correct: they are already live, atomic, and enforced directly by the schema (see §6) regardless of what future application code does or doesn't do.
8. Data-Flow / Population Model
agents has no first-party UI in this phase and is not populated by a human filling out a form directly, with 2 partial exceptions:
agent_schedule/agent_triggerare expected to be tenant-admin-configured (via a futureAgentsService/UI), defining when/why a task gets auto-created.agent_taskis the entry point — created either by a human (created_by_actor_idset,automation_source='human') or by another agent decision (decision_provenanceJSONB traces the originatingagent_decision_id,automation_source='agent'). Once created, an elevated worker process claims it via the atomicUPDATE ... WHERE status='pending' ...idiom (E3) — neverauthenticateddirectly.decision_context_snapshot→agent_decision→agent_actionis the append-only chain a claimed task produces: a snapshot of what the agent knew, the decision it made, and (if it wrote anything) the action it took — each partitioned monthly, each write-once.skill_certificationis populated by an evaluation pipeline (viaagent_eval_run/agent_eval_case), not by a tenant directly —agent_skill_assignmentcannot be enabled without a passing, unexpired, unrevoked certification (validate_skill_activation()Point 1).kill_switch_eventis the one table any authorized human or system process can write to directly today (GRANT SELECT, INSERT TO authenticated, RLS-scoped) — an emergency-stop path deliberately kept simple and always-available, not routed through the task/decision/action pipeline.
agent_reader (Phase 6, not yet built) is the expected first dedicated read consumer for tenant-facing surfaces; until then, agents.* can be populated and queried by an elevated service connection, but nothing in the live system actually consumes it yet — the same deliberate "build the substrate before the speaker" sequencing semantics/signals established for themselves.
9. Agent Authority Mapping
agents introduces no competing authority mechanism — every authority check in this module's own triggers resolves against identity.agent_duty_grant (already built and locked), never a parallel table:
check_agent_execution_not_blocked()requires anactiveagent_duty_grantrow for the specificpermission_idreferenced on theai.agent_executionrow, and anactiveagent_tool_grantrow for the specifictool_version_id.validate_skill_activation()(GUARD 6) requires a sufficientagent_duty_grant(ordinal-CASE-compared, not a raw text comparison — see the trigger's own inline comment on why'may_act_alone' >= 'needs_approval'as plain text would silently invert) for every permissionskill_version_required_dutynames, and bindsskill_execution_policy.max_cost_centsagainst the LOWEST reachable duty-grantspend_limit_cents(A8 Rule 4) — a skill can never grant itself more spend authority than its own weakest required duty grant allows.- The kill-switch fail-closed check (
check_agent_task_not_blocked()/check_agent_execution_not_blocked()) readsidentity.agent_identity.statusdirectly — akilled/suspendedagent identity (or one that doesn't resolve at all) is rejected beforekill_switch_scope_stateis even consulted.
agents is where identity.agent_duty_grant's authority actually gets exercised — this module is the first real consumer proving that mechanism end-to-end (certification, duty sufficiency, spend-ceiling binding, kill-switch precedence) rather than just holding the grant rows.
10. Cross-Module Seams
Cataloged in docs/modules/CROSS_MODULE_CONTRACTS.md (referenced, not restated here). Key relationships built or closed by this phase:
| Direction | Contract |
|---|---|
| Agents → Platform | tenant.id (every tenant-scoped table), module_catalog.id (A1's agent_task.domain/tool_definition.domain/skill_definition.domain retype — see DR-2), the new platform.polymorphic_target_registry + platform.validate_polymorphic_reference() (I6, built THIS phase as a companion table — the real validator for ai.agent_memory_source.source_ref, a genuinely-unavoidable 4-target polymorphic pointer; split target_schema/target_table columns, not a single free-text string, closing a live SQL-injection vector v3's own independent review found in the single-string draft). |
| Agents → Identity | agent_identity.id/.status, agent_duty_grant (authority checks, see §9), actor.id (attribution throughout). |
| Agents ↔ AI | ai.agent_execution amended, +8 columns: agent_task_id, sequence_index, tool_version_id, approval_request_id, retry_count, blocked_by_policy, confidence_threshold, escalation_reason — the execution ledger now links back to the task that produced it and the tool version it invoked. ai.model_version/ai.prompt_version are read by decision_context_snapshot and retirement-checked by reject_retired_decision_context_versions(). ai.routing_policy.workload_class_id → agents.workload_class(id) — a forward-ref CLOSED this phase (disclosed open item since Phase 2, wired same-day, 0 orphans). |
| Agents ↔ Semantics | decision_context_snapshot.metric_version_id → semantics.metric_version.id (real FK) — the decision context's own resolved-metric snapshot. |
| Agents ↔ Signals | decision_context_snapshot reads signals.feature_value/forecast/experiment_assignment (real FKs). signals.outcome_observation.agent_action_id/outcome_authority.agent_action_id → agents.agent_action(id, tenant_id) — 2 forward-refs CLOSED this phase (disclosed open since Phase 4, wired same-day, 0 orphans; Phase 4's own pre-existing signals-schema.spec.ts fixtures were retrofitted with a real minimal fixture chain rather than left on placeholder UUIDs). agents.decision_context_snapshot's own AFTER INSERT trigger calls signals.lock_experiment_causal_basis() — the trigger statement Phase 4 deferred until this table existed. |
| Agents → Files | decision_context_knowledge_source.document_chunk_id → files.document_chunk(id, tenant_id) (real composite FK) — files.document_chunk gained the prerequisite UNIQUE(id, tenant_id) as a companion migration step this phase, since it didn't carry one before. |
| Agents ↔ Approvals | approvals.approval_request.source_module CHECK widened to add 'agents' (now 11 values total: purchasing, pos, orders, identity, crm, inventory, ai, offers, returns, agents, other) — the saga-gate exemption path (enforce_single_module_autonomy()) and A3's autonomy-promotion path both route through approval_request_id. A pre-existing Drizzle-file/live-DB drift ('offers'/'returns' already live but missing from the Drizzle source) was also closed in the same statement. |
| Agents (internal, disclosed transitional) | identity.agentSkillAssignment (v1, still used by production IdentityService) and agents.agentSkillAssignment (this phase's new destination table, I5) coexist until Phase 6 drops the old one — resolved via explicit named re-exports in packages/db/src/schema/index.ts (see DR-3). |
11. Design Rationale
- DR-1 —
tool_catalog(v1) is SUPERSEDED bytool_definition/tool_version, not built at all. v1's flat, unversioned catalog already carried exactly the columns A2b's version-row pattern requires — rather than build a table with zero live rows anywhere and immediately deprecate it, the module is built directly ontool_definition/tool_versionfrom day one. Documented inpackages/db/src/schema/agents/tool.ts's own header comment. - DR-2 — A1's
domain → module_idretype is scoped to EXACTLY 3 columns, per A1's own explicit scope-limiting text:agent_task.domain,tool_definition.domain,skill_definition.domain. It is deliberately NOT extended toagent_performance_profile.domain/agent_autonomy_profile.domain, which stay free-text — a faithful port of v1's literal spec rather than an unstated 4th/5th retarget invented during the build. Documented inline inpackages/db/src/schema/agents/eval.tsandshadow.ts. - DR-3 —
agent_skill_assignmentMOVED fromidentitytoagents(I5), the 3rd instance of this pattern codebase-wide (after approvals-out-of-admin, receiving-out-of-purchasing). This resolves a real foundation-layer dependency-direction cycle:identity.agent_skill_assignment → agents.skill_versionwould be an outboundidentity → agentsedge, whileagentsalready carries the deep, unavoidableagents → identityedge (agent_identity, actor attribution) — both directions simultaneously violatesSCHEMA_CONVENTIONS.md's one-way dependency rule within the foundation layer. Zero live rows existed inidentity.agent_skill_assignmentat the time of this build (confirmed live), so the new table is built here empty;identity's ownDROPof the old table is Phase 6's job, a deliberate build-order exception matching v2 Section 10's own disclosed sequencing. Transitional barrel collision:identity.agentSkillAssignment(still read by productionIdentityService) andagents.agentSkillAssignmentcoexist inpackages/db/src/schema/index.tsvia explicit named re-exports until Phase 6 drops the old table. - DR-4 —
agent_eval_run.eval_suite_version_idretargets toagent_eval_suite_version.id, a disclosed judgment call neither v2 nor v3 explicitly addresses. Now that the suite payload has moved to a version row (the same A2b pattern used everywhere else in this build), an eval run's own "which exact configuration was evaluated" concern is identical to A3 certification's ownevaluation_suite_version_id— the same version-pinning is applied here for consistency, rather than leaving a run pointing at the identity row alone. Stays a bare FK: a composite FK cannot match a NULL-tenant global suite version's own parent. - DR-5 —
agent_actionhas NOagent_task_idcolumn.enforce_single_module_autonomy()(BLOCKER4's saga gate) derives it by joining throughagent_execution_idinstead — a genuine, disclosed gap in v3's own literal SQL, which referencedNEW.agent_task_idon this table without ever declaring that column existed. Fixed structurally (the join), not patched by silently adding an unstated column. - DR-6 — A3's "trigger on
tenant_agent_deployment" resolved instead as a trigger onagents.agent_skill_assignment.tenant_agent_deployment(v1's own marketplace table) has noskill_version_idcolumn at all, making the literally-described trigger structurally impossible to build;agent_skill_assignmentis the schema's own actual "this skill is active for this agent" instantiation point and is used for all 3 checks instead (A3 certification validity, BLOCKER5 duty sufficiency, A8 Rule 4 spend-ceiling binding), folded into one function,validate_skill_activation(), rather than 3 separate triggers. - DR-7 — A2b's retirement-rule-1 checks are split by actual column location, since A2b's own prose names
skill_version_id/model_version_id/prompt_version_id/toolset_version_idgenerically without pinning which table carries which reference (a mapping only settled later by I1/BLOCKER6):ai.agent_executioncarries onlytool_version_idand is checked byai.reject_retired_tool_version();agents.decision_context_snapshotcarries the other 4 and is checked byagents.reject_retired_decision_context_versions().
12. Deferred / Future Items
All items tracked in docs/open-items/OPEN_ITEMS.md, attributed to agents. Summary for context:
| Item | Status | Trigger |
|---|---|---|
12 tenant-scoped tables have no tenant_id-leading index (agent_eval_case, agent_event_log, agent_shadow_decision, agent_thread, all 6 decision_context_* child tables, evidence_retention_policy, kill_switch_event, rollback_execution) |
open, low severity | RLS correctness is unaffected; each table's natural query path is via its own FK-indexed parent. Add the supporting index if a real direct tenant-wide scan need materializes. |
agent_reader role + agentReaderDB() connection helper |
open | Phase 6 of this same build authorization. |
ESLint rule banning adminDb/tenantDB/set_config from agent code paths |
open | Phase 6. |
identity.agentSkillAssignment (old table) DROP |
open, disclosed transitional | Phase 6, after production IdentityService is retargeted to the new agents.agentSkillAssignment. |
AgentsService |
open | No service layer exists yet — schema-only this phase, matching every phase of this build so far. |