Skip to content

feat(telemetry): label subagent metrics with root session - #113

Open
sigkillsdottir wants to merge 4 commits into
DEVtheOPS:mainfrom
sigkillsdottir:feat/root-session-label
Open

feat(telemetry): label subagent metrics with root session#113
sigkillsdottir wants to merge 4 commits into
DEVtheOPS:mainfrom
sigkillsdottir:feat/root-session-label

Conversation

@sigkillsdottir

Copy link
Copy Markdown

Description

Labels subagent telemetry with root session IDs, enabling backend query-time aggregation.

Type of change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes)
  • Chore (dependency updates, etc.)

Checklist

  • I have read the CONTRIBUTING.md document
  • My code follows the style guidelines of this project
  • bun run lint passes with no errors
  • bun run check:jsdoc-coverage passes with no errors
  • bun run typecheck passes with no errors
  • bun test passes with no errors
  • I have added tests that prove my fix is effective or that my feature works
  • I have updated the documentation accordingly
  • My commits follow the Conventional Commits specification

Related issues

Additional context

Copilot AI lite review requested due to automatic review settings August 3, 2026 16:12

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds session hierarchy tracking so subagent telemetry can be labeled with a root session identifier, enabling query-time aggregation across a root session and its spawned sub-sessions.

Changes:

  • Introduces sessionMetadata and helpers (sessionAttrs, metricAttrs, setSessionMetadata) to propagate root.session.id / parent.* attributes into metrics, logs, and spans.
  • Updates message + session handlers to apply the new hierarchy-aware attributes and adds subtask de-duplication via seenSubtasks.
  • Expands test coverage for hierarchy propagation and subtask de-duplication behavior, and updates README metric labeling guidance.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/helpers.ts Extends test HandlerContext with sessionMetadata and seenSubtasks.
tests/handlers/session.test.ts Adds tests for agentType derivation and root session propagation through session lifecycle.
tests/handlers/message.test.ts Adds tests for subtask attribution/dedupe and subagent root attribution in token/cost metrics.
src/util.ts Adds hierarchy-aware attribute builders and setSessionMetadata.
src/types.ts Introduces SessionMetadata and new HandlerContext fields.
src/index.ts Initializes new maps and adds prepareSessionForMessage to preserve hierarchy/agent metadata across message rebuilds.
src/handlers/session.ts Stores hierarchy metadata on session.created and applies hierarchy-aware metric/log/span attributes.
src/handlers/message.ts Adds hierarchy-aware metric/log/span attributes and subtask update de-duplication.
README.md Documents how root.session.id should be used for aggregating session-scoped metrics.

Comment thread src/handlers/message.ts
Comment on lines +249 to +252
const key = subtask.id
? `${subtask.sessionID}:${subtask.id}`
: `${subtask.sessionID}:${subtask.messageID}:${subtask.agent}:${subtask.description}:${subtask.prompt}`
if (ctx.seenSubtasks.has(key)) return
Comment thread README.md Outdated
| `opencode.model.usage` | Counter | Messages per model and provider |
| `opencode.retry.count` | Counter | API retries observed via `session.status` events |

All session-scoped metrics include `session.id`. Sessions created by subagents also include `root.session.id`, which identifies the top-level session, plus `is_subagent=true`. Costs and tokens remain attributed to their emitting `session.id`; aggregate by `root.session.id` in your metrics backend to include subagent usage in a root-session total.
Copilot AI review requested due to automatic review settings August 4, 2026 06:43

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/util.ts:166

  • setBoundedMap does not refresh insertion order when updating an existing key, so long-lived sessions can have their sessionMetadata evicted once MAX_PENDING is exceeded even if they're still active. That would silently drop root.session.id/parent attribution from subsequent metrics/logs. Consider refreshing the entry before re-inserting so eviction behaves closer to LRU for session hierarchy metadata.
/** Stores session hierarchy identity, evicting the oldest entry first when the map is at capacity. */
export function setSessionMetadata(sessionID: string, value: SessionMetadata, ctx: HandlerContext) {
  setBoundedMap(ctx.sessionMetadata, sessionID, value)
}

src/index.ts:380

  • prepareSessionForMessage currently sets rootSessionID to sessionID even when no hierarchy metadata exists yet. If the session later turns out to be a subagent (once session.created arrives with parentID), any metrics emitted in-between would be labeled with an incorrect root.session.id (the child session), fragmenting aggregation. Leaving rootSessionID unset until it is known (or previously recorded) aligns with metricAttrs's behavior of only adding root.session.id when the chain is known.
  setSessionMetadata(sessionID, {
    sessionID,
    parentSessionID: priorMetadata?.parentSessionID,
    parentMessageID: priorMetadata?.parentMessageID,
    rootSessionID: priorMetadata?.rootSessionID ?? sessionID,
    agentType,
  }, ctx)

Copilot AI review requested due to automatic review settings August 4, 2026 07:16

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/handlers/message.ts:256

  • ctx.seenSubtasks is bounded by insertion order, but repeated subtask updates currently short-circuit on has(key) without refreshing recency. That means a frequently-updated subtask can still be evicted as “old”, and later duplicates will be counted again, inflating subtask.count. Refresh the key (delete + reinsert) before returning so active subtasks stay protected from bounded eviction.
    if (ctx.seenSubtasks.has(key)) return
    setBoundedMap(ctx.seenSubtasks, key, Date.now())
    const { agentName, agentType } = getSessionAgentMeta(subtask.sessionID, ctx)

src/index.ts:233

  • Session spans are now created with agentAttrs(...), which includes both agent and agent.name. This update path only sets AGENT_NAME/agent.type, leaving the agent attribute stuck at its initial value (often "unknown") and making span agent labeling inconsistent. Set all agent attributes together via agentAttrs when refreshing the span.
      const { agentType } = getSessionAgentMeta(input.sessionID, ctx)
      const sessionSpan = sessionSpans.get(input.sessionID)
      if (sessionSpan) sessionSpan.setAttributes({ [AGENT_NAME]: agent, "agent.type": agentType })

Copilot AI review requested due to automatic review settings August 4, 2026 14:51

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/index.ts:365

  • prepareSessionForMessage derives agentType from priorMetadata.agentType, which can be "unknown" per SessionMetadata. That value then gets written into sessionTotals (typed as SessionAgentType), which should stay limited to "primary" | "subagent".

Consider mapping unknown → primary when populating sessionTotals to keep types and downstream metric labels consistent.

  const agentType = existingTotals?.agentType ?? priorMetadata?.agentType ?? "primary"

README.md:53

  • The metrics table doesn’t list opencode.subtask.count, but the new paragraph below references subtask.count. Add the missing metric entry so the docs remain self-consistent.
`session.count`, `token.usage`, `cost.usage`, `cache.count`, `message.count`, `model.usage`, `subtask.count`, `session.duration`, `session.token.total`, and `session.cost.total` include hierarchy attributes when session metadata is available. Root sessions then have `root.session.id` equal to `session.id`; subagent metrics use their top-level session ID and include `is_subagent=true`. Cost and token measurements remain attributed to their emitting `session.id`; aggregate `token.usage` and `cost.usage` by `root.session.id` in your metrics backend to include subagent usage in root-session totals.

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.

2 participants