Skip to content

feat(tracing): emit ls_skill_name for skill invocations - #25

Open
Paarth Ahuja (paarth-a) wants to merge 5 commits into
mainfrom
paarth/ls-skill-name
Open

Paarth Ahuja (paarth-a) wants to merge 5 commits into
mainfrom
paarth/ls-skill-name

Conversation

@paarth-a

@paarth-a Paarth Ahuja (paarth-a) commented Jul 17, 2026

Copy link
Copy Markdown
Member

What

Emit an ls_skill_name run-metadata key for skill invocations, so skill usage is queryable in LangSmith via the run-stats endpoint. skillNameFromToolCall() (in metadata.ts) derives the skill name; trace.ts wires it onto the tool run.

Why

Today skill usage isn't aggregatable — the skill name isn't in any queryable metadata field or tag. With ls_skill_name you can run:

POST /api/v1/runs/stats
{ "session": ["<project>"], "group_by": { "attribute": "metadata", "path": "ls_skill_name" } }

for per-skill activation counts. It tags the exec_command that loads a skill — the activation — so it measures how often each skill is invoked, not the latency / tokens / cost of the skill's full downstream workflow. ls_skill_name is the shared coding-agent-v1 contract key, so once each coding-agent integration emits it, one query can span them.

How skills surface in Codex (confirmed)

Captured two fresh Codex 0.128.0 rollouts — one naming a skill explicitly (openai-docs), one where the model chose a skill implicitly (skill-creator). In both:

  • No dedicated skill-invocation span, no skill/invoke_skill tool, and no list_skills_response catalog event recorded in the rollout.
  • A skill invocation surfaces only as an exec_command reading the skill's definition file, e.g. cat …/skills/<name>/SKILL.md.

So the extractor keys on that read:

  • Gated to exec_command read commands (cat/sed/rg/…); writes, in-place edits, redirects, and deletes never match, so authoring or removing a skill isn't counted as using it.
  • Skill name = the directory holding SKILL.md, required under a skills/ path segment.
  • De-duplicated per skill per turn — one activation yields exactly one tag (Codex often reads a skill's SKILL.md several ways in one turn).
  • Inert for every other tool call, so it changes no existing trace. The remaining false positive — reading a SKILL.md for some non-use reason — is rare.

Changes

  • plugins/tracing/src/metadata.tsskillNameFromToolCall(): exec_command read → ls_skill_name
  • plugins/tracing/src/trace.ts — wire onto the tool run + per-turn dedup
  • plugins/tracing/test/fixtures/validator.jsonls_skill_name contract key (contextual, tool-scoped)
  • plugins/tracing/test/metadata.test.ts — unit coverage (reads vs writes/deletes, non-exec tools, verb-in-name)
  • plugins/tracing/test/skills.test.ts — end-to-end over the real rollouts
  • plugins/tracing/test/sessions/2026/07/23/rollout-skill-{explicit,implicit}.jsonl — sanitized fixtures
  • plugins/tracing/dist/index.mjs — regenerated bundle

Test plan

  • tsc --noEmit
  • vitest (metadata + skills + contract + trace suites)
  • npm run build (bundle regenerated; skill code present)
  • Confirmed against real Codex 0.128.0 skill-invocation rollouts (explicit + implicit)

Companion to the Claude Code plugin's skill-usage tracing.

Paarth Ahuja (paarth-a) and others added 3 commits July 17, 2026 14:02
Add skillNameFromToolCall() to extract an invoked skill's name from a
tool call and emit it as the ls_skill_name run metadata key, so skill
usage is queryable via RunQueryStats (group_by metadata
path=ls_skill_name). Same key name as the Claude Code and deepagents
integrations, so one query spans all coding agents.

PROVISIONAL: Codex does not yet emit a distinct skill-invocation span —
the rollout only carries skill *catalog* events (list_skills_response,
skills_update_available). The extractor is gated on a "skill"/
"invoke_skill" tool name and stays inert for ordinary tools until the
real shape is confirmed against a rollout (see the TODO in metadata.ts).
Regenerates dist/index.mjs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts:
#	plugins/tracing/dist/index.mjs
…ollouts)

Codex emits no skill-invocation span, no skill/invoke_skill tool, and records no
list_skills_response catalog event. Confirmed against two fresh 0.128.0 rollouts
(explicit "use openai-docs" + implicit skill-creator, model-chosen): a skill
invocation surfaces only as an exec_command reading .../skills/<name>/SKILL.md.

- skillNameFromToolCall: gate on exec_command + read commands only (reject
  writes/edits/deletes and non-exec tools); skill name = dir before /SKILL.md
  under a skills/ root
- trace.ts: dedup per skill per turn (one activation, one tag)
- test/sessions/2026/07/23: sanitized real rollouts (explicit + implicit)
- metadata.test.ts + skills.test.ts: unit + end-to-end coverage
- validator.json: describe the confirmed mechanism (drop PROVISIONAL)
- dist/index.mjs: rebuilt

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@paarth-a Paarth Ahuja (paarth-a) changed the title feat(tracing): emit ls_skill_name for skill invocations (provisional) feat(tracing): emit ls_skill_name for skill invocations Jul 23, 2026
@paarth-a
Paarth Ahuja (paarth-a) marked this pull request as ready for review July 23, 2026 20:12
Comment thread plugins/tracing/src/metadata.ts Outdated
Trim the verbose doc/inline comments on skillNameFromToolCall + the skill tests;
the investigation rationale now lives in the PR #25 description/discussion.
No behavior change (comment-only + regenerated bundle).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread plugins/tracing/src/metadata.ts Outdated
Comment thread plugins/tracing/src/metadata.ts Outdated
Comment thread plugins/tracing/src/trace.ts
- share isRecord via utils/isRecord.ts (used in metadata.ts + trace.ts), drop
  the trace.ts-local copy
- SKILL_MD_PATH accepts Windows path separators as well as POSIX
- clarify the per-skill-per-turn dedup comment (tag first read of each skill)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

3 participants