npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

davepi-plugin-skill-extractor

v0.1.0

Published

The learning loop for @davepi/agent: resolved conversations are queued to a fresh extraction agent that proposes reusable runbooks as governed draft skills (a human approves before they reach the prompt). Subscribes to conversation.resolved on the record

Readme

davepi-plugin-skill-extractor

The learning loop for @davepi/agent — the last workstream of the Hermes-style learning layer (RFC §7). Good outcomes become draft skills automatically: when a conversation is resolved, a fresh extraction agent reads the transcript and, only when the approach was non-trivial and the outcome positive, proposes a reusable runbook. The runbook lands as a draft skill scoped to the originating account; a human approves it (the #131 state machine) before it can ever reach a live customer.

conversation resolved ──► conversation.resolved (record bus)
        │
        ▼
  this plugin ──► davepi-plugin-queue (off-thread) ──► fresh extraction agent
                                                              │
                                          non-trivial + positive?
                                                              │ yes
                                                              ▼
                                                   draft skill (account-scoped)

Why off-thread

Extraction runs an LLM over the whole transcript — slow, and pure upside if it never happens (a missed extraction just means no draft skill, never a failed request). So it's best-effort and asynchronous: resolving a conversation emits an event and returns immediately; the queue worker does the extraction later. Resolving a conversation never blocks the response.

The queue job carries only identifiers + tenancy (userId, accountId, agentKey, recordId) — never the transcript itself. The conversation history is the full JSON transcript, re-serialized every turn, so it grows without bound; copying it into every Redis job would bloat the queue. The worker re-reads history from the (already persisted) conversation record by id instead. If the record is gone by the time the job runs (deleted between resolution and extraction), the worker logs and skips — best-effort, as everywhere else.

Requirements

  • The skill schema (#131) and the conversation schema with its status state machine must be present in the backend.
  • davepi-plugin-queue must be installed and enabled (QUEUE_REDIS_URL set). If the queue is dormant, this plugin logs a warning at boot and stays dormant too — events are simply not acted on, never an error.
  • An LLM. By default the extraction agent uses the AI SDK + Anthropic (ANTHROPIC_API_KEY); inject your own runExtraction to use a different stack.

Install

npm install davepi-plugin-skill-extractor davepi-plugin-queue

List both plugins in your project's package.json, queue first:

{
  "davepi": {
    "plugins": ["davepi-plugin-queue", "davepi-plugin-skill-extractor"]
  }
}

How resolution works

A conversation is closed by transitioning its status field — the same governed, davepi-native path on REST, GraphQL, and MCP:

open ──► resolved     # reached a good outcome → triggers extraction
open ──► abandoned    # petered out / dropped  → no extraction

The agent (or an operator) sets status: 'resolved' via the normal update tool. There's no field ACL on status, so the agent's service role can close its own conversations. Arriving at resolved emits the conversation.resolved event this plugin consumes.

Configuration

The default export is configured for the common case. For tests or a non-Anthropic LLM, build an instance with createPlugin:

const { createPlugin } = require('davepi-plugin-skill-extractor');

module.exports = createPlugin({
  // ({ system, transcript, messages, agentKey }) => Promise<string>
  runExtraction: myLlmCall,
  minMessages: 4,          // skip transcripts shorter than this
  modelId: 'claude-sonnet-4-5',
});

| Option | Default | Notes | | --------------- | ---------------------------------------- | ------------------------------------------------- | | queue | require('davepi-plugin-queue') | The queue instance to enqueue on / register with. | | runExtraction | fresh Anthropic call (lib/agent.js) | The LLM call. Inject to swap providers / in tests.| | getSkillModel | looks up skill off schemaLoader | Override the model source. | | getConversationModel | looks up conversation off schemaLoader | Source for re-reading the transcript by id. | | jobName | skill.extract | Queue job name. | | minMessages | 4 | Pre-filter: shorter chats never spend an LLM call.| | modelId | SKILL_EXTRACT_MODEL or claude-sonnet-4-5 | Model for the default agent. |

The extraction verdict

The fresh agent (no tools, no data access — it only reads the transcript) replies with strict JSON:

{ "skill": null }

for the common, trivial case, or:

{
  "skill": {
    "name": "Reset a locked account",
    "description": "Steps to safely unlock an account after repeated failed logins.",
    "body": "1. Verify identity...\n2. ..."
  }
}

The plugin validates the shape, then persists it as a draft skill with userId/accountId stamped from the originating conversation. Skill names are unique per (account, agentKey), so re-resolving a conversation (or a similar one) never duplicates or overwrites an existing skill — including one an operator has already approved.

Governance

The worker writes the skill directly via the model, so it forces status: 'draft' itself (matching stampInitialStates). The draft is invisible to the L0 prompt index until an operator promotes it draft → approved. An extracted runbook can never reach a customer unreviewed — the safeguard Hermes's auto-reuse lacks on a customer-facing surface.