Skip to content

provider_used telemetry gap — always empty in sessions.jsonl for run_skill invocations #3289

@Trecek

Description

@Trecek

Summary

provider_used is always "" in sessions.jsonl for all run_skill invocations, even when a provider profile is active. The profile_name (which carries the resolved provider identity) and provider_name (which feeds ProviderOutcome.provider_used) are parallel data channels that never intersect in the run_skill code path.

This makes it impossible to distinguish intentional provider-profile routing from unexpected model drift in post-hoc diagnostics. All MiniMax-routed sessions appear identical to Anthropic-routed sessions in the session log.

Observed across all 8 sessions in kitchen 6de5cec6-c158-46f5-abb4-11c9f25a1435 (implementation pipeline, 2026-05-29).

Root Cause

Data flow trace showing the disconnect:

tools_execution.py:382-389
  _resolve_provider_profile() → profile_name_out = "minimax"
  provider_name is NEVER SET (defaults to "")
  executor.run(..., profile_name=profile_name_out)  # line 634
        ↓
run_headless_core()  [headless/__init__.py:100]
  Accepts both profile_name (line 128) and provider_name (line 129)
  profile_name="minimax" → SkillSessionConfig(profile_name=...) line 173 → env/command only
  provider_name=""       → _execute_claude_headless(..., provider_name="") line 228
  NOTE: profile_name is NOT forwarded to _execute_claude_headless
        ↓
_execute_claude_headless()  [_headless_execute.py:71]
  current_provider_name = provider_name = ""  # line 140
  ProviderOutcome(provider_used=current_provider_name)  # lines 541-543
        ↓
sessions.jsonl: "provider_used": ""   ← always blank

The disconnect in one sentence: profile_name carries the resolved provider identity and flows into SkillSessionConfig for subprocess command construction. provider_name is a separate parameter that flows into ProviderOutcome.provider_used for session logging. No code bridges the two in the run_skill path.

current_provider_name can only change via provider fallback (lines 452-458 of _headless_execute.py), which requires provider_name to be non-empty. Since it starts as "", even the fallback path can't update it.

Affected Components

File Line(s) Role
src/autoskillit/server/tools/tools_execution.py 634 Passes profile_name=profile_name_out but never provider_name=
src/autoskillit/execution/headless/__init__.py 127-229 Separate profile_name and provider_name params; only profile_name populated from run_skill
src/autoskillit/execution/headless/_headless_execute.py 140, 541-543 current_provider_name always "" from run_skill path
src/autoskillit/execution/session_log.py 367, 443, 507 Writes empty provider_used to summary.json, token_usage.json, sessions.jsonl
src/autoskillit/core/types/_type_backend.py 120-140 SkillSessionConfig has profile_name field, consumed only by backend command builders

Recommended Fix

  1. In run_headless_core() (headless/__init__.py), forward profile_name to _execute_claude_headless() as provider_name when provider_name is empty:

    effective_provider = provider_name or profile_name

    Then pass provider_name=effective_provider to _execute_claude_headless() at line 228.

  2. Add an integration test that calls through the run_skillrun_headless_core path with a non-empty profile_name and asserts provider_used is populated in the session log output. The existing tests in test_flush_provider_integration.py call _execute_claude_headless directly with provider_name="minimax", bypassing the run_skillrun_headless_core path where the gap lives.

Historical Context

This is the most deeply recurring issue family in the codebase — 10+ fix commits over 3 months, each addressing one structural escape hatch:

Commit Date What it fixed
202f51c3 2026-05-04 Added provider_used/provider_fallback fields to SkillResult (no wiring)
31f487d8 2026-05-04 Wired provider_used into _build_skill_result (empty by arrival)
10e26c4e 2026-05-04 Structural immunity via ProviderOutcome typed container (gap hidden by none_used() sentinel)
682390d9 2026-05-28 Threaded model_identifier from config resolution to session log flush
471ce42e 2026-05-29 Fixed argmax heuristic (output-token weighting)
be6cb848 2026-05-29 Fixed non-Anthropic provider turn instrumentation

Pattern: Each fix closes one escape route while the information-flow gap shifts one layer. The architectural root cause — no single authoritative "session intent record" (configured model + configured provider) written atomically at session launch — has never been addressed as a unit. This fix targets the "last mile" consumer: the run_skill call site not forwarding provider_name.

Additional Affected Paths (from adversarial validation)

The same gap exists in two other entry points:

  • report_bug (server/tools/tools_github.py:619-628): _run_report_session passes profile_name=report_profile_name but never provider_name=. Same disconnect.
  • Fleet dispatch (fleet/_api.py:127-146,607-636): execute_dispatch() has no provider_name parameter at all — it never reaches executor.dispatch_food_truck().

All three paths (run_skill, report_bug, fleet dispatch) should be fixed together since they share the same root cause.

Regression Safety (from adversarial validation)

Forwarding profile_name as provider_name is safe from fallback regression. The provider fallback loop in _headless_execute.py:445-458 requires BOTH provider_name to be truthy AND provider_fallback_env is not None. In the run_skill/report_bug paths, provider_fallback_env is never populated, so remaining_attempts is always 0 — the fallback loop will not activate spuriously.

Test Gap

  • Integration tests for provider_used call _execute_claude_headless directly, bypassing the run_skillrun_headless_core path
  • No test verifies provider_used population through the full run_skill entry point
  • report_bug and fleet dispatch paths also have no provider_used test coverage

Metadata

Metadata

Assignees

No one assigned

    Labels

    recipe:remediationRoute: investigate/decompose before implementationstagedImplementation staged and waiting for promotion to main

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions