feat!: Add per-execution runId, at-most-once tracking, and cross-process tracker resumption#29
Merged
Merged
Conversation
- Each tracker now carries a run_id (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>
fc11d5d to
e2d74cf
Compare
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…n token Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Per AICONF spec 1.2.7.1, create_tracker always returns a new tracker instance. The evaluation path always sets a factory, so the safe navigator is unnecessary. Updated test to verify disabled configs from evaluation still produce a tracker. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Each ask_agent invocation now creates its own tracker via create_tracker, returns a resumption token alongside the response, and deferred feedback is correlated back to the specific invocation by reconstructing the tracker from the token at the call site. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Disabled configs are an internal client concern. AIConfig.disabled had no tracker factory, which could cause errors if create_tracker was called on it. The client now uses a private DISABLED_AI_CONFIG_DEFAULT hash constant as its internal fallback. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Split AIConfig into two types to match the js-core pattern: - AIConfigDefault: user-facing fallback type with enabled, model, messages, provider (no tracker factory). Used as default: parameter. - AIConfig: SDK-returned type with tracker_factory as a required kwarg, ensuring create_tracker is always available. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…t.disabled The constant was only referenced once — inline AIConfigDefault.disabled in completion_config and remove the private hash constant entirely. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixes Style/KeywordParametersOrder rubocop offense by placing the required keyword parameter first in AIConfig#initialize. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
AIConfigDefault serves as the base class holding the shared attr_readers (enabled, messages, model, provider) and to_h. AIConfig extends it with tracker_factory and create_tracker only. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reword the six at-most-once warning messages to lead with the method
name ("Skipping <method>:") and tell the user how to recover ("Call
create_tracker on the AI Config for a new run"). Matches the wording
applied to the Go SDK in PR #363.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Expand the AIConfigTracker class doc and AIConfig#create_tracker doc to explain runId, how all events emitted by a tracker share a runId so they correlate in metrics views, and that a resumption token preserves the runId across processes. Add per-method "Records at most once per Tracker" paragraphs to track_duration, track_time_to_first_token, track_feedback, and track_tokens. Note shared-state behavior on track_success and track_error. Note that track_openai_metrics and track_bedrock_converse_metrics will re-run the inner block but emit no additional metric events on repeat calls. Sweep remaining "execution" wording in doc-comments and replace with "AI run". Public method names and on-the-wire event names are unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The reworded warning exceeded the project's 180-character line limit. Split it into two adjacent string literals; the resulting string is unchanged at runtime. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rephrase the two doc sentences that placed "run" and "runId" next to each other. The AIConfigTracker class doc now says a reconstructed tracker "shares the original runId" instead of "correlates with the original run". The AIConfig#create_tracker doc now describes the runId as correlating "the tracker's events" rather than "the run's events". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A tracker built from _completion_config defaults variation_key to '' when _ldMeta.variationKey is absent. resumption_token correctly omits empty strings from the encoded payload, but from_resumption_token previously defaulted the missing key to nil, so the reconstructed tracker exposed variation_key as nil instead of ''. The wire payload was unaffected because flag_data guards on both nil and empty, but the asymmetry was visible via the public attr_reader and brittle: any future simplification of either guard would diverge the two paths. Default the decode to '' to match how fresh trackers are constructed in client.rb, and to mirror the existing empty-string defaults for model_name and provider_name in the same method. Add a spec covering the empty-variation_key round-trip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AIConfig and AIConfigDefault are now parallel hierarchies rather than
parent/child. The two types serve different roles -- AIConfigDefault is
an application-supplied fallback value passed into the SDK, AIConfig is
what the SDK returns and always carries a working tracker factory --
and inheritance was producing footguns at the boundary:
* AIConfig inherited AIConfigDefault.disabled, which dispatched to
AIConfig.new(enabled: false) and raised ArgumentError because
tracker_factory is required.
* AIConfigDefault.disabled.create_tracker raised NoMethodError because
AIConfigDefault has no create_tracker.
Both crashes are now impossible by construction: AIConfig has no
disabled class method, and AIConfigDefault is never expected to mint
trackers. The shared fields (enabled, model, messages, provider) and
to_h are intentionally duplicated -- the type separation is the point.
Update the commented fallback examples in hello_bedrock.rb and
hello_openai.rb to use AIConfigDefault.new(...) (the public way to
build a fallback). Add three specs locking in the new invariants:
AIConfig does not respond to .disabled, AIConfig.new requires
tracker_factory, and AIConfig is not in AIConfigDefault.ancestors.
The inheritance was only introduced earlier on this branch (commit
8d1a66b) and never shipped, so no released API is broken.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
keelerm84
approved these changes
May 14, 2026
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default mode and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 822734b. Configure here.
The previous wording on track_openai_metrics and track_bedrock_converse_metrics claimed a second call produces "no additional metric events". That is wrong for partial-failure cases: if the first call recorded duration and success but the inner block had no usage data, a second call where the block returns usage can still emit token metrics, because track_tokens has not yet recorded on this Tracker. Rephrase both doc paragraphs to describe the actual behavior -- only metrics not already recorded are emitted -- and point users at create_tracker for a clean run, matching the call-to-action in the at-most-once warnings. Co-Authored-By: Claude Opus 4.7 (1M context) <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.

BEGIN_COMMIT_OVERRIDE
feat!: Add per-execution runId, at-most-once tracking, and cross-process tracker resumption
feat!: Replace tracker tuple from completion_config with AIConfig#create_tracker factory
feat!: Track each AIConfigTracker metric at most once per tracker
feat: Add per-execution runId to correlate AIConfigTracker events
feat: Add Client#create_tracker(token:, context:) to resume a tracker across processes
END_COMMIT_OVERRIDE
Summary
runId: Every tracker event now includes a uniquerunId(UUID v4) for billing isolation and deduplicationAIConfig#create_trackerfactory: The completion config object returned byClient#completion_configexposes acreate_trackermethod that returns a freshAIConfigTrackerwith a newrunIdon each call, replacing the old single-tracker pattern. The SDK always supplies a working tracker factory, even for disabled flag evaluations.AIConfigDefault/AIConfigare independent classes:AIConfigDefaultis the application-supplied fallback type (passed asdefault:toClient#completion_config);AIConfigis the SDK-returned type that carries a tracker factory. Direct construction ofAIConfigis not supported.AIConfigTracker#resumption_tokenreturns a URL-safe Base64-encoded JSON token ({ runId, configKey, variationKey, version }) for cross-process tracker reconstructionClient#create_tracker(token:, context:): Reconstructs a tracker from a resumption token for deferred tracking (e.g. feedback from a different process).modelNameandproviderNameare set to empty strings on reconstruction.base64gem dependency: Added as a runtime dependency (removed from Ruby 3.4 default gems)Test plan
AIConfig#create_trackerreturns new tracker instances with distinctrunIdsAIConfig#create_trackerpreserves flag metadata (configKey, variationKey, version, modelName, providerName)AIConfigDefault.disabledreturns a disabled fallback configAIConfigis not a subclass ofAIConfigDefaultand does not expose adisabledclass methodAIConfig.newrequires atracker_factory:keyword argumentresumption_tokenencodes exactly{ runId, configKey, variationKey, version }(no modelName/providerName)from_resumption_tokenround-trips correctly and sets modelName/providerName to""runIdClient#create_trackerround-trips through resumption token🤖 Generated with Claude Code
Note
High Risk
High risk because it changes the public tracking API (
AIConfig/default handling) and modifies emitted analytics event payloads by introducing per-runrunIdand at-most-once semantics, which could affect downstream metrics/billing expectations.Overview
Introduces per-execution tracking by minting a UUIDv4
runIdfor each AI run and attaching it to all tracker events, with a newAIConfig#create_trackerfactory replacing the previous single-tracker pattern.Adds at-most-once semantics to
AIConfigTrackermetric methods (duration, TTFT, tokens, success/error, feedback), dropping subsequent calls with warnings, and implements cross-process tracker resumption viaAIConfigTracker#resumption_tokenplusClient#create_tracker(token:, context:).Splits fallback vs returned config types by adding
AIConfigDefaultforcompletion_config(default:), removesAIConfig.disabled, updates examples/tests accordingly, and adds a runtimebase64dependency.Reviewed by Cursor Bugbot for commit 86bcf1f. Bugbot is set up for automated code reviews on this repo. Configure here.