Skip to content

feat!: Add per-execution runId, at-most-once tracking, and cross-process tracker resumption#133

Draft
jsonbailey wants to merge 13 commits intomainfrom
jb/aic-2207/update-ai-sdks-billing-spec
Draft

feat!: Add per-execution runId, at-most-once tracking, and cross-process tracker resumption#133
jsonbailey wants to merge 13 commits intomainfrom
jb/aic-2207/update-ai-sdks-billing-spec

Conversation

@jsonbailey
Copy link
Copy Markdown
Contributor

@jsonbailey jsonbailey commented Apr 15, 2026

Summary

  • Per-execution runId: Every tracker now includes a unique runId (UUID) in all track event payloads, enabling billing isolation per execution
  • At-most-once semantics: Each metric type (duration, tokens, success/error, feedback, time-to-first-token) can only be tracked once per tracker instance — subsequent calls are silently dropped with a log warning
  • create_tracker() factory on config objects: AICompletionConfig, AIAgentConfig, and AIJudgeConfig now carry an optional create_tracker callable that returns a fresh LDAIConfigTracker with a new runId each time it's called. Set to None when the config is disabled.
  • Per-invocation trackers in managed classes: ManagedModel.invoke(), ManagedAgent.run(), and Judge.evaluate() now call create_tracker() at the start of each invocation to get a fresh tracker, fixing the multi-turn tracking issue where at-most-once guards blocked metrics from second+ invocations
  • resumption_token property on tracker: URL-safe Base64-encoded (no padding) JSON string containing {runId, configKey, variationKey, version} for cross-process tracker reconstruction
  • LDAIClient.create_tracker(token, context): Reconstructs a tracker from a resumption token for deferred feedback scenarios. Validates required fields and raises ValueError for invalid tokens.

Test plan

  • Enabled config has create_tracker callable; disabled config has None
  • Each create_tracker() call returns a new tracker with a distinct runId
  • Factory closure captures correct flag metadata (configKey, variationKey, version, modelName, providerName)
  • ManagedAgent.run() uses create_tracker() when available, falls back to stored tracker
  • Resumption token round-trip encode/decode preserves all fields
  • Resumption token has no base64 padding characters
  • create_tracker(token, context) reconstructs tracker with original runId and empty model/provider
  • Invalid base64, invalid JSON, and missing required fields all raise ValueError
  • All 137 existing + new tests pass with no regressions

🤖 Generated with Claude Code

@jsonbailey jsonbailey changed the title feat!: Add per-execution runId and at-most-once event tracking feat!: Add per-execution runId, at-most-once tracking, and cross-process tracker resumption Apr 15, 2026
jsonbailey and others added 2 commits April 16, 2026 11:03
- Each tracker now carries a runId (UUIDv4) included in all emitted
  events, scoping every metric to a single execution
- At-most-once semantics: duplicate calls to track_duration,
  track_tokens, track_success/track_error, track_feedback, and
  track_time_to_first_token on the same tracker are dropped with a
  warning

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ess tracker resumption

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jsonbailey jsonbailey force-pushed the jb/aic-2207/update-ai-sdks-billing-spec branch from bdf7384 to 211ead4 Compare April 16, 2026 16:48
jsonbailey and others added 11 commits April 16, 2026 12:46
…osure

The run_id parameter on LDAIConfigTracker is now required (no default).
UUID generation happens in the tracker_factory closure in client.py,
keeping the tracker itself a plain data holder.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Break long tuple lines in client.py to stay under 120 char limit
- Add required run_id parameter to LDAIConfigTracker calls in
  openai and langchain provider tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove the redundant _tracked dict from LDAIConfigTracker. The summary
already stores each metric with None as the unset sentinel, so the
nil-check on summary properties serves as the at-most-once guard.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New order: ld_client, run_id, config_key, variation_key, version,
model_name, provider_name, context, graph_key. All call sites
converted to keyword arguments for resilience against future reorders.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…oken

Reorder LDAIConfigTracker.__init__ to match updated spec: context now
comes before model_name and provider_name.

Also fix resumption_token to omit variationKey from the JSON when it is
empty, and handle the absent key when reconstructing from a token.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All six at-most-once guard warnings in tracker.py now log the track data
dict (runId, configKey, etc.) to aid debugging duplicate-track scenarios.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move the resumption token decoding logic from LDAIClient.create_tracker
into a classmethod on LDAIConfigTracker per spec 1.1.20.2. The client
method now delegates to LDAIConfigTracker.from_resumption_token.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Match the resumption token behavior: only include variationKey in the
track data dict when it has a non-empty value.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The create_tracker field on AIConfig is now always a callable that
returns a working tracker, even when the config is disabled. The
factory is always set to tracker_factory — callers use the enabled
flag to decide whether to proceed, not the factory result.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
BREAKING CHANGE: The `tracker` field has been removed from all config
dataclasses (AICompletionConfig, AIJudgeConfig, AIAgentConfig). Users
must now call `config.create_tracker()` to obtain a tracker instance.

ManagedModel and ManagedAgent no longer accept a tracker constructor
parameter — they call `create_tracker()` from the config on each
invocation. The `__evaluate` return tuple no longer includes a
pre-created tracker.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add graphKey to the resumption token following the spec key order:
runId, configKey, variationKey (if set), version, graphKey (if set).
The from_resumption_token classmethod now decodes and passes graphKey
to the tracker constructor.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant