Skip to content

Track auxiliary model cost through namespaced conversation events #1170

Description

@dcramer

Problem

Junior runs auxiliary model calls outside the main agent turn, including Guardian action review, thinking routing, passive reply routing, and passive memory extraction. Their cost is operationally useful, but today only Guardian carries costUsd into durable System reporting.

We need one consistent pattern that can support more auxiliary operations without adding unrelated counters or relying on sampled telemetry.

Decision

Use the conversation event log as the durable source for conversation-bound auxiliary model cost.

  • Store cost on the namespaced domain event that owns the outcome.
  • Use the event's existing namespace + name identity as the reporting dimension.
  • Add optional costUsd at a uniform location in event content.
  • Treat missing cost as unknown, never zero.
  • Keep gen_ai.operation.name aligned with the provider operation (for these calls, chat).
  • Use gen_ai.prompt.name on spans to identify the stable auxiliary prompt.
  • Let transcript projection decide whether an event produces a row. Persistence does not own presentation visibility.
  • Derive System statistics from durable events rather than bespoke daily counters.

Conversation retention therefore also bounds these operational statistics. If permanent billing records become a requirement, that should be a separate accounting store.

MVP: passive memory extraction

Evolve the existing memory/memories_captured event so it is emitted for every completed passive extraction:

{
  memories: [], // valid when extraction found nothing worth storing
  costUsd?: number
}

Implementation requirements:

  • Expose structured-completion costUsd through PluginModel.completeObject.
  • Retain extraction memories and cost together in the task cache so retries do not rerun the model or lose cost.
  • Emit the operation-scoped, idempotent event for empty and non-empty results.
  • Allow a conversation event renderer to return undefined.
  • Have the Memory renderer return undefined when no memories were captured, so the durable event remains reportable without adding an empty transcript row.
  • Preserve existing rendering for one or more captured memories.
  • Add focused tests for cost propagation, empty-result persistence, retry/idempotency behavior, and transcript omission.

Follow-up scope

Apply the same event-cost pattern to:

  • Guardian review, including observable retry cost where provider usage is available.
  • Thinking routing via the existing turn_routed outcome.
  • Passive/subscribed reply routing, including model decisions that produce no agent turn.
  • Context compaction, vision summaries, conversation title generation, advisor calls, and image-prompt enrichment carried forward from superseded PR fix(reporting): Include auxiliary model costs #854.
  • A generic System aggregation grouped by event namespace, event name, and UTC day.
  • gen_ai.prompt.name labels for each auxiliary prompt.

Candidate prompt names:

  • junior.guardian_action_review
  • junior.thinking_route
  • junior.passive_reply_route
  • memory.session_extraction

Non-goals for the MVP

  • A permanent billing ledger.
  • A new stats table.
  • A second generic “model call completed” event beside the domain event.
  • Rendering zero-memory extraction events in conversation transcripts.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions