Problem
run_registrations and runs are a 1:1 split keyed by the same run_id, and the
split exists mainly as a workaround: runs is clobberable, run_registrations is not.
register_run (control_plane/store.py) writes both: INSERT into
run_registrations (guarded — RunAlreadyRegisteredError / 409) and create_run
→ REPLACE INTO runs.
- Every example agent server (entry and downstream:
research, summarize,
writer, analyst, editor, triad *) also calls client.create_run(...) on
entry (PUT /v1/run-records → REPLACE INTO runs) and client.update_run(...) at
close.
Result for a 2-agent run sharing one run_id
| step |
writer of the single runs row |
effect |
researcher POST /v1/runs |
register_run → create_run(agent=intent, dims={user_id:alice}, task=intent) |
row created |
| researcher handler |
client.create_run(agent="researcher", task=<text>, started_at=...) — no dims |
REPLACE → dims wiped to {} |
| writer handler |
client.create_run(agent="writer", parent_span=span_1, started_at=...) |
REPLACE → agent="writer", started_at=writer clock |
writer finally |
update_run(status=completed, cost_micros, steps=<writer local>) |
UPDATE |
researcher finally |
update_run(status=completed, cost_micros, steps=<researcher local>) |
UPDATE (last wins) |
Final runs row: agent="writer" (the leaf, not the entry), dims={} — user
attribution lost from runs (survives only in run_registrations), started_at =
writer's clock, steps = one process's local count. Incoherent, last-writer-wins.
Desired
One runs table:
- write-once at registration, never in the update path:
run_id, intent/task,
user_dims, mode, parent_run, parent_span, registered_at, started_at.
- mutable, only via PATCH with an explicit allow-list (
update_run already takes
**fields — enumerate them): status, cost_micros, steps, halt_reason,
detector, ended_at, governance_events.
- keep the
INSERT-or-409 guard on that one table.
- remove
client.create_run(...) from every agent server. Registration creates the
row; agents only PATCH. Downstream agents create nothing (they already do not
register).
resolve_run = SELECT ... WHERE run_id=?; "no row" ⇒ not registered (or a
registered_at IS NOT NULL check).
- drop
create_run from the store protocol / HttpStore (or make it registration-only).
Out of scope
Per-agent participation rows. A single top-level run row is fine — per-agent spend is
already captured in ledger_spent (add an agent-dimension budget/accumulator if a
per-agent breakdown is wanted; segment_key_for already supports dimension="agent").
steps should come from the plane's own step accounting once RunState moves server-side
(plan Part 6), not from a client's local count.
Related: #55, #60, #113, plan Part 6 / Part 9b.
Problem
run_registrationsandrunsare a 1:1 split keyed by the samerun_id, and thesplit exists mainly as a workaround:
runsis clobberable,run_registrationsis not.register_run(control_plane/store.py) writes both:INSERTintorun_registrations(guarded —RunAlreadyRegisteredError/ 409) andcreate_run→
REPLACE INTO runs.research,summarize,writer,analyst,editor, triad*) also callsclient.create_run(...)onentry (
PUT /v1/run-records→REPLACE INTO runs) andclient.update_run(...)atclose.
Result for a 2-agent run sharing one
run_idrunsrowPOST /v1/runsregister_run→create_run(agent=intent, dims={user_id:alice}, task=intent)client.create_run(agent="researcher", task=<text>, started_at=...)— nodimsdimswiped to{}client.create_run(agent="writer", parent_span=span_1, started_at=...)agent="writer",started_at=writer clockfinallyupdate_run(status=completed, cost_micros, steps=<writer local>)finallyupdate_run(status=completed, cost_micros, steps=<researcher local>)Final
runsrow:agent="writer"(the leaf, not the entry),dims={}— userattribution lost from
runs(survives only inrun_registrations),started_at=writer's clock,
steps= one process's local count. Incoherent, last-writer-wins.Desired
One
runstable:run_id,intent/task,user_dims,mode,parent_run,parent_span,registered_at,started_at.update_runalready takes**fields— enumerate them):status,cost_micros,steps,halt_reason,detector,ended_at,governance_events.INSERT-or-409 guard on that one table.client.create_run(...)from every agent server. Registration creates therow; agents only
PATCH. Downstream agents create nothing (they already do notregister).
resolve_run=SELECT ... WHERE run_id=?; "no row" ⇒ not registered (or aregistered_at IS NOT NULLcheck).create_runfrom the store protocol /HttpStore(or make it registration-only).Out of scope
Per-agent participation rows. A single top-level run row is fine — per-agent spend is
already captured in
ledger_spent(add anagent-dimension budget/accumulator if aper-agent breakdown is wanted;
segment_key_foralready supportsdimension="agent").stepsshould come from the plane's own step accounting once RunState moves server-side(plan Part 6), not from a client's local count.
Related: #55, #60, #113, plan Part 6 / Part 9b.