run_origin nullable and kept two individual agent names
(vega_matter_frame, atlas_dock_chat) inside agent_run CHECK predicates as
a transition tolerance. A predicate that names an agent is a coupling every new agent must edit. This step
closes the window: origins are derived for every existing row, the column goes NOT NULL, and no predicate
names an agent ever again.
Every live INSERT path was read before choosing the rule:
| Writer | Run kind | Origin |
|---|---|---|
workers/framer_trigger/processor.py |
Worker-minted from the domain-event outbox; the ONLY writer that sets trigger_event_id
(its delivery-dedupe key) and the only one that may leave user_id NULL. |
system — even when the waking event had a user actor; the user acted on the matter,
the worker initiated the run. |
clerk_worker/runner.py claim, app_api/router_ws.py and
agent_launch_core.py blocked-run inserts |
Launch-edge runs; user_id is always bound. A kind='compact' claim is broker
maintenance, never a user turn. |
user (system for compact claims) |
UPDATE agents.agent_run
SET run_origin = CASE
WHEN trigger_event_id IS NOT NULL THEN 'system' -- worker-minted
ELSE 'user' END -- launch-edge-minted
WHERE run_origin IS NULL
All four writers now set run_origin on INSERT, so NOT NULL lands with every path already
honouring it.
user_id IS NOT NULL OR run_origin = 'system'.
agent_run_scope_present keeps the chat/form shapes and replaces both named-agent clauses:
a matter-anchored system run (the framer shape) and a scope-free user run (the dock shape — dock_chat
launches are contractually forbidden from carrying matter/session/form scope).
| 0214 (transition) | 0216 (contracted) |
|---|---|
OR agent_key = 'vega_matter_frame' (user + scope) |
run_origin = 'system' — every framer row is worker-minted, so the backfill makes the name redundant. |
OR agent_key = 'atlas_dock_chat' (scope) |
OR (matter_id IS NULL AND chat_session_id IS NULL AND form_id IS NULL AND run_origin = 'user') — the dock shape named by origin. |
context_judge STAYS — the Story-2873 compaction rail claims real rows through the clerk
worker. record_registrar and indigo are SHED from the run/approval CHECKs: no
INSERT path anywhere writes an agent_run row with those keys (the registrar consumer mints a
bare run_id for provenance, never a row). EPIC-2834 re-admits its artifact seats with the
broker writers that use them. chat_session's key set stays decoupled and untouched.
user_id IS NULL AND run_origin IS NULL) inserted cleanly against
0214's nullable expand and now dies on the run_origin NOT NULL.agent_run_scope_present.alembic check) passes on a
fresh database; the downgrade restores 0214's expand state verbatim and keeps the backfilled data.chat_session_agent_key_valid is never touched.Step 1 widened and kept names as tolerance; step 2 derives origins from who mints the run and lets the contract speak in shapes, so the next agent is a key, not a predicate edit.