Skip to content
All posts /

Skillpulse: Your AI Skills Are Flying Blind Without Telemetry

A PostToolUse hook that logs every skill activation to local JSONL. No existing tool tracks whether the model actually follows a skill's instructions.

#context-engineering #claude-code #agents #telemetry #local-first

You install 16 skills. You see them fire. But here’s the question nobody asks: did the model actually follow them?

I reviewed every telemetry tool in the Claude Code ecosystem — built-in OTel, claude_telemetry, claude-code-otel, even the skills.sh platform metrics. None of them track skill adherence. They tell you a skill was loaded, but not whether the model executed its instructions.

So I built skillpulse.

The gap

Claude Code’s built-in OpenTelemetry (via CLAUDE_CODE_ENABLE_TELEMETRY=1) captures general metrics: session duration, tokens, cost, tool calls. With OTEL_LOG_TOOL_DETAILS=1, it even records skill_name in tool result events. But that’s a load signal, not a follow signal.

The difference matters. A skill can load successfully and be completely ignored by the model. Without tracking adherence, you’re optimizing in the dark.

ToolTracks loadingTracks following
Built-in OTelYesNo
claude_telemetryNoNo
claude-code-otelNoNo
skills.shInstall count onlyNo
skillpulseYesYes (planned)

How it works

Skillpulse is a PostToolUse hook that fires on every Skill tool call. It writes one JSONL line per activation:

{
  "skill_id": "signum:signum",
  "timestamp": "2026-03-11T08:48:18Z",
  "session_id": "20260311_114818_93074",
  "loaded": true,
  "followed": null,
  "plugin_name": "skillpulse"
}

The implementation is 60 lines of bash. Some design choices:

2-second watchdog. PostToolUse hooks run synchronously — a hung hook blocks the entire session. Skillpulse spawns a self-kill watchdog that sends SIGKILL after 2 seconds. In practice, the hook finishes in <50ms.

Skill-only filter. PostToolUse fires for every tool call — Read, Write, Bash, everything. Skillpulse checks tool_name == "Skill" and exits immediately for anything else. Zero overhead on non-skill calls.

Append-only JSONL. No database, no rotation, no config. One file at ~/.local/share/emporium/activation.jsonl. Survives crashes, easy to inspect, trivial to back up.

What I learned from 4 entries

Yes, four. Skillpulse was created on March 4th and then… not installed. Classic. I fixed that today.

But even 4 entries told me something useful:

Skill                       Acts  Sess  Load%  Age
-----------------------------------------------------
herald:news-digest             2     2  100%   7d
arbiter                        1     1  100%   7d
signum:signum                  1     1  100%   7d

Three skills account for all activations. I have 16 installed. That’s an 81% dormancy rate. Most of my skills are dead weight consuming context tokens.

The aggregator

A 90-line Python script reads the JSONL and produces per-skill stats:

python3 scripts/aggregate.py          # table output
python3 scripts/aggregate.py --json   # for pipelines

No dependencies. Reads timestamps, groups by skill_id, computes frequency, unique sessions, loaded rate, and days since last activation.

Where this is going

Skillpulse is Phase 1 of a larger pipeline I’m calling EvoSkill — skills that improve themselves based on usage data.

The pipeline:

skillpulse (log)
    |
aggregator (stats)
    |
bench (test against tasks)
    |
evolver (propose improvements)

The followed field is currently null — it needs tool-pattern fingerprinting to determine if the model executed a skill’s expected behavior. That’s the hard part, and I’m deliberately deferring it until I have enough activation data to validate the approach.

Some things I’m explicitly skipping at my current scale (<20 sessions/week, 16 skills):

  • Hotelling T-squared drift detection — need 50+ trajectories per skill
  • Bayesian calibration — need labeled outcome data
  • Hierarchical routing — relevant at 50+ skills
  • Automated gates — human review is my gate for now

The research backing this is in EvoSkill, AutoSkill, and the ASI drift framework.

Try it

claude mcp add-json skillpulse '{"source": {"source": "url", "url": "https://github.com/heurema/skillpulse.git"}}'

Source: github.com/heurema/skillpulse

All data stays local. MIT licensed. Zero dependencies beyond bash and jq.