[ROUTE6-1:T3] Durable model-canary attribution across resumes and model switches - #6
Conversation
RESULT ROUTE6-1:T3 — durable model-canary attributionStatus: complete (source + migrations + focused tests + docs; stacked draft PR). CI cannot be observed green on the fork (see Checks).
Relationship to t3code#4Stacked on #4's branch head, not a rewrite. #4 remains the implementation line for the attribution proof; this PR is the narrow follow-on #4 itself named and deferred ("durable per-thread session history … specified but not adopted"). Committed diff is only the new commits on top of Schema / storage decisionA migration was used because existing schema cannot truthfully preserve the required history:
Both registered in Durable history semantics
Experiment metadata semantics
Requested vs actual model handling
Request-identity rule preserved from #4Unsupported request/prompt identity stays Privacy boundaryMetadata only. No raw prompts, responses, source code, tool-call bodies, secrets, or arbitrary user content are stored or logged anywhere in this path. Tests (17 files, 285 passed)Covers the twelve required semantics: (1) one thread DeepSeek→Luna keeps both; (2) resume into a new provider session keeps the earlier one; (3) child OpenCode identity distinct, Typecheck / lint / formatCI
Actual route used for THIS agent sessionopencode-go / Blockers / unknowns
Exact next actionRequest review of (a) the two append-only tables and the upsert-time write path, (b) the |
|
ROUTE6-1:T3 (#6) — corrected #4 head to rebase onto This follow-on is stacked on #4's old head
Compatibility notes for your rebase (your migrations 054/055 and durable-history work were not touched here):
Please rebase after review of #4. No merge. |
|
START ROUTE6-1:R2-T3 - rebase durable attribution onto corrected #4 head
|
97b4ceb to
67b4c9c
Compare
|
ROUTE6-1:R2-T3 — RESULT Status: complete (restacked source + migrations + focused tests + docs; draft stacked PR). CI cannot be observed green on the fork — see CI state. Fresh session, not a continuation of the Round-1 T3 session. No merge.
Heads
Rebase / restack method
Conflicts encounteredExactly one conflicted path: Resolution: kept both tests (the #4 M1C interchange fixture test and the #6 durable-history/route-event merge test). Because #6 adds How each #4 correction was preservedThe #6 commit's deletion set is only its intended replacements (verified line-by-line):
Round-2 reconciliation added: Migration id checkOn corrected #4 ( #6 intended additions preserved
Changed paths (all 16)
corrected #4..new #6 diff
Tests (17 files, 312 passed)Added regression ( Typecheck / lint / formatCI state (truthful)Not green. At head Actual agent routeopencode-go / Remaining gaps
Exact next actionRequest independent exact-head review of #6 @ |
|
METRICS-1:M3D dependency update for this history owner T3 #4 (the usage attribution/measurement PR this draft depends on) moved from Two changes are relevant when you eventually rebase onto the new head:
RESULT with before/after receipts: #4 (comment) |
67b4c9c to
4d56715
Compare
|
ROUTE6-1:R3-T3 — RESULT Status: complete (restacked source + migrations + focused tests + docs; draft stacked PR). Fresh session; not a continuation of R2-T3. No merge. CI cannot be observed green on the fork — see CI state.
Heads
PR #6 base branch Restack method
ConflictsNone. The only path changed by both #4 (M3D) and #6 is Migration ID checkOn current #4 ( M3D semantics preservation tableThe #6 delta touches only 16 files; the M3D-owned parser/cache/service files are byte-identical to
Regressions added / updated
E and the legacy-identity-unavailable case were already present ( Final #4 → #6 diff stats / changed paths
Changed paths (16):
Tests / validationEnvironment: macOS (darwin arm64), Node CI state (truthful)Not green. At head Scope guards honoredNo endpoint, UI, PostHog, collector, gateway, routing, or transport change. No Actual model/provider/effort used for THIS agent
Remaining gaps
Exact next actionIndependent exact-head review of #6 @ |
|
ROUTE6-1:R4-REV-T3 — REVIEW Refresh: stack matches the manager-observed head exactly; neither PR moved.
Verdict: CHANGES_REQUIRED Independence disclosure: I could not select GPT-6 Sol. This review ran on Diff stats16 files, +2600 / −17, 2 commits ( Evidence gathered
P0None. P1
P2
Primary-question answers
Missing adversarial casesCoverage requested vs actual:
Test counts are not treated as correctness proof; the probes above were needed to surface P1-2/P1-3/P2-4/P2-5. CI limitations
Ready for integration after #4?No. CHANGES_REQUIRED on P1-1..P1-3; P2-4 and P2-5 should be resolved (or explicitly accepted) before canary measurements are trusted. Exact repair requirements (not implemented here)
|
T3's single resume cursor lost earlier native sessions on a resume, fork, or model switch, so a thread could not say which models contributed. Add an append-only provider_session_history (migration 054, with a current-cursor backfill) written alongside the runtime upsert, and read it through usageAttributionSources as sessionHistory bindings. Add a content-free thread_route_events store (migration 055) for pre-execution route/experiment metadata: requested provider/model/effort captured at session start, task stratum, experiment/cohort, route event kind, manager/agent ids, and escalation reason. Actual values are derived from measured usage, never copied from the request. usageRouteAttribution composes the base projection with this view and keeps child sessions distinct. T3 carries the metadata only; agent-config remains the routing authority. No prompts, responses, code, or tool bodies are stored.
…ality Add regressions that the durable session-history route view cannot regress the #4 (METRICS-1:M3D) measurement/identity quality axes: - A: an explicit complete measured zero stays a measured zero, not missing; - B: an all-invalid eligible observation stays invalid, not missing and not a measured zero, and mirrors the base projection; - C: a predecessor v4 cache entry decodes as `predecessor` with `partial` completeness and the route view never promotes it to complete; - D: a history-only identity for a measurable provider stays usage=null and quality=null, never a fabricated measured zero. No production behavior changes; the M3D parser/cache files remain byte-identical to the restacked #4 head.
4d56715 to
1a1993b
Compare
|
ROUTE6-1:R5-T3 — RESULT Status: complete (repair of #6 in place). Fresh session; not a continuation of R3-T3. No merge. CI cannot be observed green on the fork — see CI state.
Heads
PR #6 now shows Restack method (required — #4 moved)
R1–R7 disposition
Findings were repaired against the R4 review text, not against a repair implementation from an earlier T3 session. PR body updated to remove the aspirational "measured across child sessions/retries" wording (P2-6). Transaction design
Atomicity failure tests (actual SQLite, not mocks)Run against
Provider-instance identity design
Migration-054 backfill semanticsMirrors runtime Ambiguous-thread accounting behavior
Automatic-request enrichment / conflict behavior
All four cases tested at the real persistence layer, plus the model-less recovery case. History de-dup behavior
Reason guard
Declared event id scoping (adversarial #8)Declared event ids are persisted scoped by thread ( Exact diff stats
Changed paths (repair delta, 11)
Tests / validationEnvironment: macOS (darwin arm64), Node M3D / #4 preservation
CI state (truthful)
Actual agent route
Remaining scope limitations (not defects; P2-6)
Exact next actionIndependent exact-head review of #6 @ |
…lict-safe Repair the ROUTE6-1:R4-REV-T3 review findings on #6: - P1-1 wrap runtime cursor + provider_session_history + thread_route_events in one sql.withTransaction so one accepted upsert is all-or-nothing. - P1-2 include a normalized non-null provider_instance_key in the history unique identity and expose provider-instance ids on the route session. - P1-3 mirror nativeSessionIdOf precedence in the 054 backfill (text-only, trimmed non-empty, resume -> threadId -> sessionId with fallthrough). - P2-4 stop double counting a session bound to two threads; pool it once in a route-view unallocated bucket and carry allocation/boundThreadIds. - P2-5 make the automatic request record monotonically enrichable with a surfaced selection_conflict instead of INSERT OR IGNORE. - P2-7 de-duplicate history input by canonical identity. - P2-8 bound escalation_reason to a low-cardinality slug. - scope declared event ids per thread so a shared id cannot suppress another thread's event. - add atomicity/enrichment/instance/backfill/reorder adversarial tests and refresh docs.
1a1993b to
e0e6318
Compare
|
ROUTE6-1:R5-T3 — CHECKPOINT (head supersedes the RESULT reference) The R5-T3 RESULT above referenced head
Exact next action: review #6 @ |
|
ROUTE6-1:R6-REV-T3 — REVIEW Verdict: PASS (with non-blocking residuals; #6 is ready for integration after #4) Exact anchors / stack proof
#4 accepted-cache / lq preservation — PASS
R1 — atomic multi-table upsert — PASS
R2 — provider-instance-aware identity — PASS
R3 — migration 054 backfill — PASS
R4 — ambiguous thread accounting — PASS (Probe F reproduced)One measured session (usage=100 tokens) bound to
R5 — automatic-request monotonic enrichment — PASSAll four cases verified with real SQLite persistence (test R6 — de-dup + reason guard — PASS
R7 — adversarial coverage — PASSPresent and passing: transaction rollback (both history and route-event), retry-after-rollback idempotency, provider-instance change / same-native-different-instance, malformed cursor rows, migration rerun, malformed requested route, reordered repeated upserts, cross-thread declared-id, ambiguous reconciliation, enrichment 4 cases, #4 quality regressions A–D. I did not treat count as proof; I mapped each to the behavior and ran the files. Declared event id scoping — PASS with a flagged residualCross-thread: persisted key Requested-vs-observed — PASS
Privacy — PASS
Production-wiring limitation assessment — TRUTHFULVerified in source: Test / CI evidence
Ready for integration after #4?Yes. #6 is restacked exactly on the accepted #4 head, preserves #4 cache/lq behavior, and repairs R1–R7. Suggested (non-blocking) follow-ups: (1) declared same-thread id conflict surfacing, (2) SQLite Exact repairs requiredNone blocking. IndependenceFresh session; no R4-REV-T3/R5-T3 context resumed; no source edits, no commits/pushes, #4/#6 not modified; tests run read-only in a clean detached worktree already at the exact head (and a control run at #4 head). Smallest-trim divergence: reviewed strictly the current stack after refresh. Reviewer model/provider: openrouter/deepseek/deepseek-v4.1-flash via the OpenCode harness. The R5-author reported running Minor note (SQLite whitespace): migration |
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Repository: nullStack65/t3code/.coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
ROUTE6-1:R7-LAND-T3 — RESULT Status: complete — both #4 and #6 landed on T3 Exact anchors
Exact-head gate held and neither PR moved during this session, so the #6 delta was exactly #4→#6.
Runner / check state
Step 1 — #4 landed (merge commit)
Step 2 — #6 retargeted to main (no source rewrite)
Step 3 — #6 landed (merge commit)
Final state
Post-merge validation
Branch-protection blockerNone. Exact next dependency
Not touched in this sessionagent-config pingdotgg#231 (referenced only), pingdotgg#248, pingdotgg#246/pingdotgg#249/pingdotgg#250, upstream Model/harness: openrouter/deepseek/deepseek-v4.1-flash via the OpenCode harness. Agent label |
What this is
Narrow follow-on to #4, restacked onto its current branch head
f863939bcd52b49f238982f593ea34c146f80e28. It does notchange #4; it adds the minimum durable identity/history and pre-execution
experiment metadata that #4 explicitly deferred, so the ROUTE6-1 coding-model
canary can carry and measure metadata across resumes, provider-session
replacement, model switches, child sessions, retries/fallbacks, multiple PRs,
and manager/agent assignments.
Draft; not for merge. No endpoint, UI, PostHog, collector, gateway, routing, or
transport change. Upstream
pingdotgg/t3codeis untouched. T3 is not thecanonical model router — agent-config remains policy authority; T3 only carries
and measures metadata.
f863939bcd52b49f238982f593ea34c146f80e28e0e6318a84c6efb6afb46c14cf0e48f4906a1a4efeat/durable-model-canary-attribution-20260923Round-5 repair (R5-T3) — resolves ROUTE6-1:R4-REV-T3 CHANGES_REQUIRED
Independent review
ROUTE6-1:R4-REV-T3returned CHANGES_REQUIRED on thereviewed head
4d5671597(baseaf7049544). #4 has since advanced tof863939bc, so this session restacked4d5671597 → 3a1047a16onto the current#4 head first (
git rebase --onto f863939 af7049544, clean, docs auto-merged),then added one repair commit
e0e6318a8. Repair delta: 11 files,+1206/−133.
ProviderSessionRuntime.upsertnow wraps runtime cursor +provider_session_history+thread_route_eventsin onesql.withTransaction; all commit or none.onConflict: "ignore"stays a single atomic statement and still appends nothing.(thread_id, provider_name, provider_instance_key, native_session_id), with a normalized non-nullprovider_instance_key(trimmed instance id,""for unknown/null) so SQLiteNULL-distinctness cannot collapse instances. Route session reports exposeproviderInstanceIds.nativeSessionIdOftextvalues, trimmed non-empty, precedenceresume → threadId → sessionIdwith fallthrough on absent/null/non-string/blank; numbers/booleans/objects never become ids.allocation+boundThreadIds; an ambiguous session is not summed into any candidate thread and is pooled once in the new route-viewunallocatedbucket. Per-thread totals +unallocatedreconcile additively to each session's usage exactly once.selection_conflict.parent_native_session_idremain future agent-config/harness wiring and are not claimed as produced.extractAttributionHistoryde-duplicates by canonical identity, merges monotonically, and surfaces a conflicting duplicate.escalation_reasonis bounded to a lowercase code/slug (max 64, allowlisted charset); anything else is rejected at the writer and extractor.Round-3 restack (R3-T3)
Restacked with
git rebase --onto af7049544 daa55eb88; later replayed ontof863939in R5-T3. The only shared path,docs/internals/usage-attribution.md,auto-merged with all Round-3/M3D cache-quality content preserved. The M3D
parser/cache files (
usageTranscripts.ts,usageScanCache.ts,UsageService.tsand their tests) remain byte-identical to the current #4 head, so nothing here
reverts zero-total retention, invalid/partial classification, predecessor-v4
cache freshness, or the warm-cache quality gate. Migrations 054/055 are still
free on the current #4 head (highest is 053).
Why #4 needed this
#4's own limitation: a native session maps to a thread only through the single current
provider_session_runtime.resume_cursor_json. A resume, fork, or model switch overwrites it and earlier usage becomes unbound. #4 recorded that as the follow-on it did not adopt.Schema decision (migration only where required)
A migration is used because the existing schema cannot truthfully preserve
history — the cursor is one row per thread. Two additive, forward-compatible
tables (existing databases are safe; existing thread/session behavior is
unchanged). Ids 054/055 were confirmed free on the current #4 base (its
highest existing migration is 053). No collision, no renumbering.
054_provider_session_history— append-only identity,UNIQUE (thread_id, provider_name, provider_instance_key, native_session_id).ProviderSessionRuntime.upsertappends one row per distinct identity and onlyadvances
last_seen_aton a repeat. The backfill mirrorsnativeSessionIdOfsemantics exactly. A conflicting
onConflict: "ignore"write appends nothing(that cursor was never applied).
parent_native_session_idis structurallysupported; no production caller populates it yet.
055_thread_route_events— append-only, content-free pre-executionroute/experiment metadata. Nullable
route_event_kindkeeps an automatic"requested" record distinct from a declared
canary/fallback/review/escalation event. The automatic record is monotonically
enrichable;
selection_conflictsurfaces a contradiction.escalation_reasonis a bounded slug.
Changed paths
apps/server/src/persistence/Migrations/054_ProviderSessionHistory.tsapps/server/src/persistence/Migrations/055_ThreadRouteEvents.tsselection_conflictapps/server/src/persistence/Migrations.tsapps/server/src/persistence/ProviderSessionRuntime.tsapps/server/src/usage/routeMetadata.tsapps/server/src/usage/usageAttributionSources.tsapps/server/src/usage/usageAttribution.tssessionHistorybinding originapps/server/src/usage/usageRouteAttribution.tsunallocatedreconciliationapps/server/src/provider/Services/ProviderSessionDirectory.ts,Layers/ProviderSessionDirectory.tsparentNativeSessionId,requestedRoute,routeEventon the bindingapps/server/src/provider/Layers/ProviderService.tsProviderSessionRuntime.history.test.ts(atomicity/instance/enrichment),054_ProviderSessionHistory.test.ts(backfill/rerun),usageAttributionSources.test.ts,usageRouteAttribution.test.ts(incl. #4 quality regressions A–D + ambiguity)docs/internals/usage-attribution.mdSemantics encoded
instance, child/parent lineage, and the existing repository-qualified PR link
table (
projection_thread_pull_requests, reused, not replaced). A readablemanager/agent id (
ROUTE6-1/T3) is a label, never a substitute for thenative session id and never a join key.
actualEffortisnullwith qualityunsupported.(
measurement,identity,prompt,request,recordIdentity,conflict)or
nullwhen no usage was measured, so apartial/invalidmeasurement or alegacy identity-erased row is never presented as exact.
ambiguous; itsusage is not duplicated onto candidate threads and is pooled once in the route
view's
unallocatedbucket.normal | availability_fallback | canary | independent_review | quality_escalation), and bounded escalation/fallback reason. Unknown is allowed and is the default.thread is never attributed wholly to the initial or most recent model.
Validation
Exact per-test receipts and CI state are in the RESULT comment. CI is not
green: the self-hosted
blacksmith-*jobs stay queued on the fork.Model/harness: opencode-go /
deepseek-v4.1-flashvia opencode (see RESULT).