feat(observability): WS-6a + WS-6c — D1's attribution stamp exists as a substrate - #333
Merged
Conversation
…-6a + WS-6c) First dispatchable slice of WS-6, after PR #319's remediation cleared the docs gate. Re-audit narrowed a-e down to a+c: the substrate is clear, the consumers (WS-6b/6d/6e) still fail contract point 3 and stay HELD. - instance joins _RUN_CONTEXT_KEYS / bind_run_context. Resolved once in run_agent_stream via a SECOND additive bind after load_agent, not by moving the first: the early bind is what correlates a failure DURING the load, so moving it would trade five fields for one - _emit_usage carries the full (run, member, agent, instance) tuple with ZERO call-site changes - it arrives by inheritance through activity._INHERIT, which already carried four of the five fields. Extended, not rebuilt - shared agents produce an ABSENT key, never the empty string - double-guarded and pinned, because '' and NULL are different values in the table WS-6d will build - refresh_run_presence() patches cc:activity:live:{run_id} after the late bind. Without it the presence key is written from the start event, which predates the bind, so /observability/active and /roster could never carry instance for any run. Presence patch over a second start event: a snapshot overwrite is invisible and idempotent; a duplicate stream entry is permanent and reads as a second activation - interim by_instance dimension on the Redis cost rollup, so the substrate answers a per-partition question before WS-6d lands - drift gate pinning set(_INHERIT) == set(_RUN_CONTEXT_KEYS); the invariant was prose-only and the two tuples must extend together or attribution silently half-lands Nothing durable is written. The stamp reaches logs and the Redis feed only. Two asymmetries recorded rather than papered over: the phase=start event predates the bind, and a delegated sub-run inherits the CALLER's partition while its blobs key to '' - so WS-6d must not treat instance as a foreign key onto agent_blob.instance. Fixing the second needs unbind+restore around the sub-run plus a product call on whether a delegated shared agent is billed to the caller's tenant. tests: 89 passed across the five observability/telemetry files (+17 new); repo-wide ruff select gate green; xenon clean (cost_summary's complexity went DOWN, 22 -> 20). Verified independently, not just as reported. Co-Authored-By: Claude Opus 5 <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.
WS-6 — Observability wiring + attribution. First dispatchable slice, after #319's remediation cleared the docs gate.
Why only a+c
The re-audit narrowed WS-6a–e down to a + c. The substrate is clear; the three consumers are not, and are HELD:
bind_run_contexthas exactly one call site (executor.py), contextvars don't cross the HTTP hop intov1_compat, andagent_runrows are written at the run boundary, so a mid-run join finds nothing. The one mechanism the code actually supports at request time is the presence keycc:activity:live:{run_id}, which for the orchestrator path carries a server-establisheduser. §7 must name that (or another) before this dispatches.build_run_trace_rowis pure over events + folded), so it sequences after WS-6b rather than independently.What shipped
instancejoins the run context. Resolved once inrun_agent_streamvia a second additive bind afterload_agent— deliberately not by moving the first. The early bind is what correlates a failure during the load; moving it trades five fields for one._emit_usagecarries the full four-tuple with zero call-site changes. It arrives by inheritance throughactivity._INHERIT, which already carried four of the five fields — so this extends an existing mechanism rather than rebuilding it. No production caller changed.''— double-guarded and pinned, because''and NULL are different values in the table WS-6d will build.refresh_run_presence()patchescc:activity:live:{run_id}after the late bind. Without it, the presence key is written from thestartevent — which predates the bind — so/observability/activeand/rostercould never carryinstancefor any run. Chose a presence patch over re-publishingstart: a snapshot overwrite is invisible and idempotent, a duplicate stream entry is permanent and reads as a second activation.by_instancecost dimension on the Redis rollup, so the substrate answers a per-partition question before WS-6d lands.set(_INHERIT) == set(_RUN_CONTEXT_KEYS)— the invariant was prose-only, and the two tuples must extend together or attribution silently half-lands.Nothing durable is written. The stamp reaches logs and the Redis feed only.
Verification
cost_summary's complexity went down (22 → 20) while gaining a dimension — the identicalsource/agent/instancearms collapsed into one branch. I ran the gates myself rather than accepting them as reported.Reviewer findings
ws-verifierPASSED (re-deriving acceptance for four instancing modes, and probing request-path safety with no run context, no event loop, in a bare thread, and with four malformed responses — no exception, response unmutated).diff-reviewerthen returned 2×P1 + 2×P2, no P0; all four were fixed in one repair round, including the presence-key gap above, which is why that fix is in this PR at all.Two asymmetries recorded rather than papered over:
phase="start"event predates the bind. The presence patch fixes the live view; a start↔end join still must not assume both ends carry the field.''. Soinstanceis not a foreign key ontoagent_blob.instance— WS-6d is warned explicitly. Fixing it needsunbind_contextvars+ restore around the sub-run, plus a product call on whether a delegated shared agent should be billed to the caller's tenant at all.Known coverage gap, disclosed
The single junction line
_bind_run_instance(_agent_instance, run_id)insiderun_agent_streamis covered by no executing test —test_run_agent_stream_e2e.pyhangs on this box. Both halves either side of it are tested, and the verifier reproduced the composition independently for personal / team / shared / cron and confirmed by reading that the bind is unconditional, correctly ordered, exception-proof, and ahead of every model call. Stating it rather than letting the green count imply otherwise.Deferred
Giving delegated sub-runs their own partition;
run_agent's missing run boundary (it resolves an instance but binds no context — a pre-existing gap, and adding one would newly correlate that path's logs); surfacingby_instancein the Control Plane cost view; everything in WS-6b/6d/6e and the owner-gated WS-6f–i.🤖 Generated with Claude Code