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

@dscanlan/agent-handoff

v0.1.0

Published

Structured handoff protocol for multi-agent AI pipelines

Readme

agent-handoff

Structured handoff protocol for multi-agent AI pipelines.

Handles passing context between agents, saving checkpoints, and rolling back to any prior state. Works with any LLM provider — agents are plain async functions.


Install

npm install @dscanlan/agent-handoff

Quick start

import { Pipeline, MemoryStore } from '@dscanlan/agent-handoff'
import type { AgentFn } from '@dscanlan/agent-handoff'

// 1. Define your agents
const researchAgent: AgentFn = async (handoff) => {
  const { topic } = handoff.input as { topic: string }
  const findings = await fetchResearch(topic) // your logic here

  return {
    result: { findings },
    summary: `Researched "${topic}"`,
    artifacts: { research: findings },
  }
}

const writerAgent: AgentFn = async (handoff) => {
  const research = handoff.context.artifacts.research
  const draft = await writeDraft(research) // your logic here

  return {
    result: { draft },
    summary: 'Wrote draft from research',
  }
}

// 2. Build and run the pipeline
const pipeline = new Pipeline({ id: 'content-pipeline' })

pipeline
  .agent('researcher', researchAgent)
  .checkpoint('research-complete')
  .agent('writer', writerAgent)

const result = await pipeline.run({ topic: 'AI in healthcare' })

console.log(result.output)            // writer's output
console.log(result.context.history)   // step-by-step audit trail
console.log(result.context.artifacts) // named outputs from all agents

Core concepts

Pipeline

An ordered sequence of agents with optional checkpoints between them.

const pipeline = new Pipeline({
  id: 'my-pipeline',
  store: new MemoryStore(), // optional, defaults to MemoryStore
})

pipeline
  .agent('step-one', agentOneFn)
  .checkpoint('after-step-one')
  .agent('step-two', agentTwoFn)

Agent function

Every agent is an async function that receives a Handoff and returns a HandoffOutput:

const myAgent: AgentFn = async (handoff) => {
  // What you receive:
  // handoff.input                  — output from the previous agent (or pipeline.run() input)
  // handoff.context.history        — summaries of all prior steps
  // handoff.context.artifacts      — named outputs from prior agents
  // handoff.context.annotations    — messages left by prior agents for downstream agents

  return {
    result: { /* your output — must be JSON-serialisable */ },
    summary: 'What this agent did (appears in audit trail)',
    key_decisions: ['Decision 1', 'Decision 2'],    // optional, for provenance
    artifacts: { my_output: data },                 // optional, named for later steps
    annotations: [{                                 // optional, messages for downstream agents
      from_step: 'my-step',
      to_step: 'next-step',  // or '*' for all downstream
      content: 'Something the next agent should know',
      type: 'instruction',   // 'instruction' | 'warning' | 'context'
    }],
  }
}

Checkpoint

Saves pipeline state at a point in the pipeline. Enables rollback.

pipeline
  .agent('researcher', researchAgent)
  .checkpoint('research-complete')  // state saved here
  .agent('writer', writerAgent)

Rollback

Re-run from any saved checkpoint, optionally with different input:

// Re-run from checkpoint with same input
await pipeline.rollback(result.run_id, 'research-complete')

// Re-run from checkpoint with new input
await pipeline.rollback(result.run_id, 'research-complete', {
  override_input: { topic: 'Different topic' },
})

Rollback creates a new run — the original is preserved.


Events

pipeline
  .on('step:start',        ({ run_id, step }) => console.log(`Starting ${step}`))
  .on('step:complete',     ({ run_id, step, handoff }) => console.log(`Done: ${step}`))
  .on('checkpoint:saved',  ({ run_id, checkpoint }) => console.log(`Checkpoint: ${checkpoint}`))
  .on('pipeline:complete', ({ run_id, handoff }) => console.log(`Pipeline done`))
  .on('pipeline:error',    ({ run_id, step, error }) => console.error(`${step} failed`, error))

Error handling

| Error | When | |-------|------| | PipelineStepError | An agent throws. Wraps the original error with step name and run ID. | | HandoffSerializationError | An agent returns a non-JSON-serialisable value. | | CheckpointNotFoundError | rollback() called with a checkpoint name that doesn't exist. | | HandoffError | Base class for all library errors. |

import { PipelineStepError, CheckpointNotFoundError } from 'agent-handoff'

try {
  await pipeline.run(input)
} catch (err) {
  if (err instanceof PipelineStepError) {
    console.error(`Step "${err.step}" failed in run "${err.run_id}":`, err.cause)
  }
}

Constraints

  • Agent outputs must be JSON-serialisable. No functions, class instances, or circular references in result or artifacts. The library enforces this at runtime.
  • Not an LLM framework. Agents are plain async functions — wire in Claude, OpenAI, or any other provider yourself.
  • Sequential execution only in v1. Parallel fan-out/fan-in is planned for v2.
  • Human approval gates are planned for v2.

Roadmap

  • v1 (current): Sequential pipeline, in-memory store, checkpoints, rollback
  • v2: Human-in-the-loop approval gates, SQLite persistent store
  • v3: Full audit trail API, per-step observability hooks
  • v4: Parallel agent execution, Redis store, Python port