Skip to content

dscanlan/agent-handoff

Repository files navigation

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 agent-handoff

Quick start

import { Pipeline, MemoryStore } from 'agent-handoff'
import type { AgentFn } from '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

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors