Agent Handoff Protocols logo

Agent Handoff Protocols

Organization
cosmicstack-labs
agent-handoff-protocols

Design and implement agent-to-agent handoff protocols for multi-agent systems. Covers context passing, escalation patterns, handshake mechanisms, conversation continuity, and routing between specialized agents in production workflows.

Overview

Publishercosmicstack-labs
Repositorymercury-agent-skills
Skill nameagent-handoff-protocols
Stars
485
Forks
63
Bundled files
Instructions only
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by cosmicstack-labs on GitHub. Read the source before you install it.

Installation

Install the Agent Handoff Protocols AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/cosmicstack-labs/mercury-agent-skills/tree/main/categories/ai-ml/agent-handoff-protocols
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/cosmicstack-labs/mercury-agent-skills.git /tmp/mercury-agent-skills
mkdir -p .claude/skills
cp -r /tmp/mercury-agent-skills/categories/ai-ml/agent-handoff-protocols .claude/skills/agent-handoff-protocols
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Agent Handoff Protocols in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Agent Handoff Protocols on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Agent Handoff Protocols is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Agent-to-Agent Handoff Protocols

Overview

In a multi-agent system, agents need to hand off tasks — and context — to each other seamlessly. A broken handoff means lost context, frustrated users, and failed workflows. This skill covers structured protocols for passing control between agents, handling escalations, and maintaining continuity across agent boundaries.


Core Concepts

When Handoffs Happen

ScenarioFromToWhy
EscalationTier-1 agentTier-2 specialistTask exceeds capability
SpecializationRouter agentDomain expertTask matches expertise
SupervisionSub-agentSupervisorNeeds approval or guidance
RecoveryFailed agentFallback agentPrimary agent broken
Load sheddingOverloaded agentIdle agentBalance workload

Handoff Types

TypeDescriptionLatencyRisk
Warm HandoffFull context + current state passed explicitlyMediumLow — all state transferred
Cold HandoffOnly task description passed, receiving agent starts freshLowHigh — context loss
Supervised HandoffSupervisor mediates, validates, then transfersHighVery Low — human/LLM checks
Broadcast HandoffAll agents notified, first capable claimsMediumMedium — race conditions
Delegation HandoffSender waits for resultHighLow — synchronous, traceable

Step-by-Step Implementation

Step 1: Define the Handoff Contract

python
from dataclasses import dataclass, field
from typing import Any, Optional
from enum import Enum
import json
import time

class HandoffReason(Enum):
    ESCALATION = "escalation"
    SPECIALIZATION = "specialization"
    RECOVERY = "recovery"
    LOAD_SHEDDING = "load_shedding"
    SUPERVISION = "supervision"

@dataclass
class HandoffContext:
    """Complete context transferred between agents."""
    
    # Identity
    source_agent: str
    target_agent: str
    handoff_id: str
    
    # The task
    task_id: str
    original_task: str
    current_state: str  # What has been done so far
    
    # Conversation history (condensed)
    conversation_summary: str
    key_facts: list[str] = field(default_factory=list)
    decisions_made: list[str] = field(default_factory=list)
    
    # State
    collected_data: dict[str, Any] = field(default_factory=dict)
    confidence: float = 1.0  # How confident source was in resolution
    reason: HandoffReason = HandoffReason.SPECIALIZATION
    
    # Metadata
    created_at: float = None
    expires_at: Optional[float] = None
    
    def __post_init__(self):
        if self.created_at is None:
            self.created_at = time.time()
    
    def serialize(self) -> str:
        """Serialize to JSON for transport."""
        return json.dumps({
            "source_agent": self.source_agent,
            "target_agent": self.target_agent,
            "handoff_id": self.handoff_id,
            "task_id": self.task_id,
            "original_task": self.original_task,
            "current_state": self.current_state,
            "conversation_summary": self.conversation_summary,
            "key_facts": self.key_facts,
            "decisions_made": self.decisions_made,
            "collected_data": self.collected_data,
            "confidence": self.confidence,
            "reason": self.reason.value,
            "created_at": self.created_at,
        })
    
    @classmethod
    def deserialize(cls, data: str) -> "HandoffContext":
        """Deserialize from JSON."""
        obj = json.loads(data)
        obj["reason"] = HandoffReason(obj["reason"])
        return cls(**obj)

Step 2: Implement the Handoff Protocol

python
class HandoffProtocol:
    """Standard handoff protocol between agents."""
    
    def __init__(self, registry):
        self.registry = registry  # Agent registry
        self.active_handoffs: dict[str, HandoffContext] = {}
    
    async def initiate_handoff(self, context: HandoffContext) -> str:
        """Begin a handoff to another agent."""
        
        # 1. Validate target agent exists
        target = self.registry.get_agent(context.target_agent)
        if not target:
            raise ValueError(f"Unknown target agent: {context.target_agent}")
        
        # 2. Check target is ready
        if not await target.is_ready():
            # Fallback: try next available or escalate
            return await self._handle_unavailable_target(context)
        
        # 3. Store handoff context
        self.active_handoffs[context.handoff_id] = context
        
        # 4. Prepare receiving agent
        await target.prepare_for_handoff(context)
        
        # 5. Execute handoff
        result = await target.receive_handoff(context)
        
        # 6. Cleanup
        self.active_handoffs.pop(context.handoff_id, None)
        
        return result
    
    async def _handle_unavailable_target(self, context: HandoffContext) -> str:
        """Handle case where target agent is unavailable."""
        # Try finding an alternative
        alternatives = self.registry.find_alternatives(
            context.target_agent
        )
        
        if alternatives:
            context.target_agent = alternatives[0]
            return await self.initiate_handoff(context)
        
        # No alternatives — emergency escalation
        return await self._emergency_escalation(context)
    
    async def acknowledge_handoff(self, handoff_id: str, 
                                  accepted: bool, message: str = ""):
        """Target agent acknowledges (accepts or rejects) a handoff."""
        context = self.active_handoffs.get(handoff_id)
        if not context:
            raise ValueError(f"Unknown handoff: {handoff_id}")
        
        if accepted:
            context.source_agent = context.target_agent  # Transfer identity
            return {"status": "accepted", "context": context}
        else:
            # Handoff rejected — source must retry or escalate
            return {"status": "rejected", "reason": message}

Step 3: Agent Handoff Receiver

python
class HandoffReceiver:
    """Mixin for agents that can receive handoffs."""
    
    def __init__(self):
        self.handoff_buffer: dict[str, HandoffContext] = {}
        self.current_handoff: Optional[HandoffContext] = None
    
    async def prepare_for_handoff(self, context: HandoffContext):
        """Prepare to receive a handoff (pre-load context)."""
        self.handoff_buffer[context.handoff_id] = context
    
    async def receive_handoff(self, context: HandoffContext) -> str:
        """Accept and process an incoming handoff."""
        self.current_handoff = context
        
        # Build system prompt with transferred context
        handoff_prompt = self._build_handoff_prompt(context)
        
        # Run the agent with the prepared context
        result = await self.run(
            context.original_task,
            system_override=handoff_prompt
        )
        
        self.current_handoff = None
        return result
    
    def _build_handoff_prompt(self, context: HandoffContext) -> str:
        """Build system prompt with full handoff context."""
        facts = "\n".join(f"- {f}" for f in context.key_facts)
        decisions = "\n".join(f"- {d}" for d in context.decisions_made)
        
        return f"""You are taking over from {context.source_agent}.

## Current Task
{context.original_task}

## What Has Been Done
{context.current_state}

## Key Facts Discovered
{facts}

## Decisions Made So Far
{decisions}

## Collected Data
{json.dumps(context.collected_data, indent=2)}

## Reason for Handoff
{context.reason.value}

Your job is to continue from where {context.source_agent} left off. 
Do not redo work that has already been completed."""

Step 4: Escalation Chain

python
class EscalationChain:
    """Define and execute escalation paths for handoffs."""
    
    def __init__(self, protocol: HandoffProtocol):
        self.protocol = protocol
        self.chains = {}  # agent_type -> escalation path
    
    def define_chain(self, agent_type: str, chain: list[str]):
        """Define escalation chain (e.g., support -> billing -> manager)."""
        self.chains[agent_type] = chain
    
    async def escalate(self, context: HandoffContext, 
                       reason: str) -> str:
        """Escalate along the defined chain."""
        chain = self.chains.get(context.source_agent, [])
        
        if not chain:
            # End of chain — human escalation
            return await self._escalate_to_human(context, reason)
        
        next_agent = chain[0]
        context.reason = HandoffReason.ESCALATION
        context.target_agent = next_agent
        context.current_state += f"\n[Escalated: {reason}]"
        
        # Update chain (remove current level)
        self.chains[context.source_agent] = chain[1:]
        
        return await self.protocol.initiate_handoff(context)
    
    async def _escalate_to_human(self, context: HandoffContext, 
                                  reason: str) -> str:
        """When all agents exhausted, escalate to human."""
        ticket = {
            "handoff_id": context.handoff_id,
            "task": context.original_task,
            "context": context.serialize(),
            "reason": reason,
            "timestamp": time.time()
        }
        # Send to human operator queue
        await human_operator_queue.send(ticket)
        return f"Escalated to human operator. Ticket: {ticket['handoff_id']}"

Step 5: Conversation Continuity Across Handoffs

python
class ConversationContinuity:
    """Maintain conversation thread across multiple agent handoffs."""
    
    def __init__(self, storage):
        self.storage = storage
    
    async def log_turn(self, conversation_id: str, agent: str, 
                        message: str, role: str):
        """Log a single turn in a conversation thread."""
        entry = {
            "conversation_id": conversation_id,
            "agent": agent,
            "role": role,
            "message": message,
            "timestamp": time.time()
        }
        await self.storage.append(
            f"conversations:{conversation_id}",
            entry
        )
    
    async def get_history(self, conversation_id: str, 
                          limit: int = 50) -> list[dict]:
        """Get conversation history across agent handoffs."""
        return await self.storage.query(
            f"conversations:{conversation_id}",
            limit=limit
        )
    
    def build_continuity_prompt(self, history: list[dict], 
                                 current_agent: str) -> str:
        """Build a continuity prompt for the receiving agent."""
        previous_agents = set(
            entry["agent"] for entry in history 
            if entry["agent"] != current_agent
        )
        
        return f"""This conversation has involved: {', '.join(previous_agents)}.

## Previous Exchanges
{self._format_history(history)}

Continue naturally. If asked about something handled by a previous agent, 
reference that conversation."""
    
    def _format_history(self, history: list[dict]) -> str:
        formatted = []
        for entry in history[-10:]:  # Last 10 exchanges
            tag = f"[{entry['agent']}]" if entry['role'] == 'assistant' else "[User]"
            formatted.append(f"{tag}: {entry['message'][:200]}")
        return "\n".join(formatted)

Step 6: Handoff Decision Engine

python
class HandoffDecider:
    """Decide whether and where to hand off based on current state."""
    
    def __init__(self, llm, rules: list[dict]):
        self.llm = llm
        self.rules = rules  # Handoff trigger rules
    
    async def should_handoff(self, agent, task: str, 
                              current_state: dict) -> tuple[bool, str, str]:
        """Determine if handoff is needed and where to send."""
        
        # Check explicit rules first
        for rule in self.rules:
            if self._matches_rule(rule, agent, task, current_state):
                return True, rule["target"], rule["reason"]
        
        # If no rules match, ask LLM
        decision = await self.llm.generate(
            f"""Current agent: {agent.name}
Current task: {task}
Current state: {json.dumps(current_state, indent=2)}

Available agents: {', '.join(self._list_available_agents())}

Should this be handed off to another agent? If so, which one and why?
Respond in JSON: {{"handoff": true/false, "target": "agent_name", "reason": "why"}}""",
            temperature=0
        )
        
        try:
            result = json.loads(decision)
            return result["handoff"], result.get("target"), result.get("reason")
        except (json.JSONDecodeError, KeyError):
            return False, None, None
    
    def _matches_rule(self, rule: dict, agent, task: str, 
                      state: dict) -> bool:
        """Check if a handoff rule matches current conditions."""
        if "keywords" in rule:
            if any(kw in task.lower() for kw in rule["keywords"]):
                return True
        if "confidence_threshold" in rule:
            if state.get("confidence", 1.0) < rule["confidence_threshold"]:
                return True
        if "max_steps" in rule:
            if state.get("steps", 0) > rule["max_steps"]:
                return True
        return False

Handoff Flow Diagram

                    ┌───────────────────┐
                    │  User/System Task  │
                    └─────────┬─────────┘
                              │
                    ┌─────────▼─────────┐
                    │   Router Agent     │
                    │  (Intent Classify) │
                    └──┬────┬────┬──────┘
                       │    │    │
              ┌────────▼┐ ┌─▼──┐ ┌▼────────┐
              │ Support  │ │Billing│Research │
              │ Agent    │ │Agent │ Agent   │
              └──┬───────┘ └─────┘ └─────────┘
                 │
          Handoff Decision?
                 │
           ┌─────┴─────┐
           │           │
      Continue    Escalate
           │           │
           │     ┌─────▼──────┐
           │     │ Specialist  │
           │     │ Agent       │
           │     └─────┬──────┘
           │           │
           │     Still Stuck?
           │           │
           │     ┌─────▼──────┐
           │     │   Human     │
           └─────┘   Operator  │
                 └────────────┘

Trigger Phrases

PhraseAction
"Hand off to [agent]"Initiate warm handoff to specified agent
"Escalate this"Push up the escalation chain
"Take over from [agent]"Receive a handoff with full context
"What's the handoff history?"Show all handoffs for this conversation
"Transfer context to [agent]"Send full context to another agent
"This needs a specialist"Trigger routing to domain expert
"Agent [x] is stuck"Initiate recovery handoff to fallback
"Show active handoffs"List all in-progress handoffs

Anti-Patterns

Anti-PatternWhy It FailsFix
Cold handoffs with no contextReceiving agent starts blindAlways pass HandoffContext
Handoff loopsAgents keep passing back and forthSet max handoff count per task
Synchronous blockingCalling agent waits foreverTimeout + fallback path
No handoff validationTarget agent can't handle the taskVerify capability before transfer
Ignoring handoff failuresLost tasks with no traceDead-letter queue for failed handoffs
Unlimited escalation chainTask bounces foreverMax escalation depth (3-5 levels)

Frequently asked questions

What does the Agent Handoff Protocols AI skill do?

Design and implement agent-to-agent handoff protocols for multi-agent systems. Covers context passing, escalation patterns, handshake mechanisms, conversation continuity, and routing between specialized agents in production workflows.

Why use Agent Handoff Protocols on TypingMind?

Because you install it once and use it with any model. Agent Handoff Protocols is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Agent Handoff Protocols in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/cosmicstack-labs/mercury-agent-skills/tree/main/categories/ai-ml/agent-handoff-protocols. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Agent Handoff Protocols?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Agent Handoff Protocols?

As many as you like. As long as a model supports skills, you can use Agent Handoff Protocols with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Agent Handoff Protocols AI skill free?

Yes. It is published on GitHub by cosmicstack-labs under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇