Class: ClaudeMemory::OTel::PromptScope

Inherits:
Object
  • Object
show all
Defined in:
lib/claude_memory/otel/prompt_scope.rb

Overview

Back-tags activity_events with the OTel prompt.id after telemetry events arrive. Hook events (hook_ingest, hook_context, …) fire ~immediately as the user’s turn closes, but Claude Code batches OTel exports on the OTEL_METRIC_EXPORT_INTERVAL (default 60s), so by the time we see a prompt.id on the receiver, the activity_events for that turn already exist with prompt_id = NULL.

Tagging strategy per (prompt_id, session_id) group:

1. session_id-match path — for activity_events carrying the same
   session_id, update those that fall in the prompt's time window.
   Hook events (hook_ingest, hook_context, hook_sweep, hook_publish,
   roi_nudge) reliably carry session_id from the Claude Code hook
   payload.
2. time-window path — for activity_events with NULL session_id
   (recall, store_extraction — MCP-originated; Claude Code doesn't
   thread session_id into plugin MCP calls per reference_mcp_session
   _id_gap), tag by occurred_at falling in the prompt window only.

Operates on both project_store and global_store when available. Cross-project tagging (projects other than the dashboard’s loaded one) is out of scope — dashboard is per-project and other project DBs aren’t in the manager.

Constant Summary collapse

MAX_WINDOW_SECONDS =

Bound the prompt window so a long-running turn doesn’t sweep up later activity events that belong to the NEXT prompt. The OTel spec only emits prompt.id between user_prompt and the next user_prompt, so the natural max is implicit; we add a safety ceiling.

600
POST_WINDOW_BUFFER_SECONDS =

Buffer added after the latest OTel event because hook_ingest fires AFTER the Stop event, which can be a few seconds after the last api_request.

30

Instance Method Summary collapse

Constructor Details

#initialize(manager) ⇒ PromptScope

Returns a new instance of PromptScope.



39
40
41
# File 'lib/claude_memory/otel/prompt_scope.rb', line 39

def initialize(manager)
  @manager = manager
end

Instance Method Details

#tag(events) ⇒ Hash

Returns count, groups: count.

Parameters:

  • events (Array<Hash>)

    just-persisted OTel event rows (each carrying :prompt_id, :session_id, :occurred_at) — the same shape OtlpJsonEnvelope.parse_logs returns.

Returns:

  • (Hash)

    count, groups: count



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/claude_memory/otel/prompt_scope.rb', line 47

def tag(events)
  return {tagged: 0, groups: 0} if events.nil? || events.empty?

  groups = group_by_prompt(events)
  return {tagged: 0, groups: 0} if groups.empty?

  tagged = 0
  groups.each do |key, range|
    prompt_id, session_id = key
    [@manager.project_store, @manager.global_store].compact.each do |store|
      tagged += tag_in_store(store, prompt_id, session_id, range)
    end
  end
  {tagged: tagged, groups: groups.size}
rescue Sequel::DatabaseError => e
  ClaudeMemory.logger.debug("prompt_scope tag failed: #{e.message}")
  {tagged: 0, groups: 0, error: e.message}
end