agent-handoff-protocols
SKILL.md
Agent Handoff Protocols
Design clean, reliable protocols for passing tasks and context between AI agents.
When to Use
- Agents need to pass work to each other
- Context must be preserved across agent transitions
- Building agent pipelines or workflows
- Implementing specialized agent chains
Handoff Fundamentals
What Gets Transferred
interface HandoffPackage {
// The work item
task: {
id: string;
type: string;
description: string;
requirements: string[];
};
// Context from previous work
context: {
originalRequest: string;
completedSteps: Step[];
intermediateResults: Record<string, unknown>;
relevantFiles: string[];
decisions: Decision[];
};
// Metadata
metadata: {
sourceAgent: string;
targetAgent: string;
timestamp: Date;
priority: number;
deadline?: Date;
attempt: number;
};
// Expectations
expectations: {
outputFormat: JSONSchema;
successCriteria: string[];
timeoutMs: number;
};
}
Handoff States
┌─────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ PENDING │───►│ ACCEPTED │───►│ WORKING │───►│ COMPLETE │
└─────────┘ └──────────┘ └──────────┘ └──────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ TIMEOUT │ │ REJECTED │ │ FAILED │ │ RETURNED │
└─────────┘ └──────────┘ └──────────┘ └──────────┘
Protocol Patterns
Pattern 1: Direct Handoff
Simple point-to-point transfer.
class DirectHandoff {
async transfer(
from: Agent,
to: Agent,
package: HandoffPackage
): Promise<HandoffResult> {
// Validate target can accept
const canAccept = await to.canAccept(package);
if (!canAccept.ok) {
return { status: 'rejected', reason: canAccept.reason };
}
// Prepare context summary
const contextSummary = await from.summarizeContext(package.context);
// Execute handoff
const ack = await to.receive({
...package,
context: {
...package.context,
summary: contextSummary
}
});
if (ack.accepted) {
await from.releaseTask(package.task.id);
return { status: 'accepted', receivedAt: ack.timestamp };
}
return { status: 'rejected', reason: ack.reason };
}
}
Pattern 2: Queued Handoff
For async/decoupled systems.
class QueuedHandoff {
constructor(private queue: MessageQueue) {}
async submit(package: HandoffPackage): Promise<string> {
const ticket = generateTicketId();
await this.queue.enqueue({
ticket,
package,
status: 'pending',
submittedAt: new Date()
});
return ticket;
}
async checkStatus(ticket: string): Promise<HandoffStatus> {
return this.queue.getStatus(ticket);
}
// Target agent polls for work
async claim(agentId: string): Promise<HandoffPackage | null> {
const item = await this.queue.dequeue({
filter: { targetAgent: agentId }
});
if (item) {
await this.queue.updateStatus(item.ticket, 'claimed');
}
return item?.package || null;
}
}
Pattern 3: Brokered Handoff
Intermediary manages transfers.
class HandoffBroker {
private pending = new Map<string, PendingHandoff>();
async initiateHandoff(
source: Agent,
targetCapability: string,
package: HandoffPackage
): Promise<HandoffResult> {
// Find suitable targets
const candidates = await this.registry.findByCapability(targetCapability);
// Negotiate with candidates
for (const candidate of candidates) {
const offer = await candidate.negotiate({
taskType: package.task.type,
estimatedTokens: this.estimateTokens(package),
deadline: package.metadata.deadline
});
if (offer.accepted) {
// Execute transfer through broker
return this.executeTransfer(source, candidate, package, offer);
}
}
// No takers - queue or return to source
return this.handleNoTakers(source, package);
}
}
Context Transfer Strategies
Strategy 1: Full Context
Transfer everything - simple but expensive.
function fullContextTransfer(context: Context): TransferredContext {
return {
...context,
_transferType: 'full',
_tokenEstimate: estimateTokens(context)
};
}
Strategy 2: Summarized Context
AI summarizes before transfer.
async function summarizedTransfer(
context: Context,
targetNeeds: string[]
): Promise<TransferredContext> {
const summary = await summarizeAgent.run(`
Summarize this context for an agent that needs to:
${targetNeeds.join('\n')}
Context:
${JSON.stringify(context)}
Keep only information relevant to those needs.
Be concise but complete.
`);
return {
summary,
_transferType: 'summarized',
_originalTokens: estimateTokens(context),
_summaryTokens: estimateTokens(summary)
};
}
Strategy 3: Reference-Based
Transfer references, target fetches as needed.
interface ReferenceContext {
taskId: string;
contextUrl: string; // URL to fetch full context
keyPoints: string[]; // Essential facts
fileReferences: {
path: string;
relevantSections: string[];
}[];
}
async function referenceTransfer(context: Context): Promise<ReferenceContext> {
// Store full context
const contextId = await contextStore.save(context);
// Extract key points
const keyPoints = await extractKeyPoints(context);
return {
taskId: context.taskId,
contextUrl: `context://${contextId}`,
keyPoints,
fileReferences: context.relevantFiles.map(f => ({
path: f.path,
relevantSections: f.sections
}))
};
}
Handoff Instructions Template
Include clear instructions for receiving agent.
## Handoff Package
### Task
[Clear description of what needs to be done]
### Context Summary
- Original request: [What the user asked for]
- What's been done: [Completed steps]
- Current state: [Where we are now]
### Your Specific Task
[Exact instructions for this agent]
### Key Information
- [Critical fact 1]
- [Critical fact 2]
- [Decision that was made and why]
### Constraints
- [Time/token budget]
- [Must use existing patterns in X file]
- [Cannot modify Y]
### Expected Output
[Format and content expectations]
### Success Criteria
- [Criterion 1]
- [Criterion 2]
### If You Get Stuck
- [Fallback option 1]
- [Escalation path]
Error Handling
Rejection Handling
async function handleRejection(
package: HandoffPackage,
reason: string
): Promise<HandoffResult> {
// Log rejection
await log.handoffRejected(package.task.id, reason);
if (package.metadata.attempt < MAX_ATTEMPTS) {
// Retry with different target
return retryHandoff({
...package,
metadata: {
...package.metadata,
attempt: package.metadata.attempt + 1
}
}, { excludeAgent: package.metadata.targetAgent });
}
// Max retries - escalate
return escalateToHuman(package, `Handoff failed: ${reason}`);
}
Timeout Handling
async function monitorHandoff(
ticket: string,
timeoutMs: number
): Promise<void> {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const status = await handoffQueue.getStatus(ticket);
if (status === 'complete') return;
if (status === 'failed') throw new HandoffError('Task failed');
await sleep(POLL_INTERVAL);
}
// Timeout
await handoffQueue.cancel(ticket);
throw new HandoffTimeoutError(ticket);
}
Rollback Handling
async function rollbackHandoff(
package: HandoffPackage,
reason: string
): Promise<void> {
// Return task to source
await package.metadata.sourceAgent.receive({
...package,
context: {
...package.context,
rollbackReason: reason,
partialResult: await getPartialResult(package.task.id)
}
});
// Clean up any partial work
await cleanupPartialWork(package.task.id);
// Log for analysis
await log.handoffRolledBack(package, reason);
}
Acknowledgment Patterns
interface HandoffAck {
status: 'accepted' | 'rejected' | 'conditional';
timestamp: Date;
agentId: string;
// If rejected
reason?: string;
suggestedAlternative?: string;
// If conditional
conditions?: {
needsMoreContext?: string[];
needsPermission?: string[];
estimatedCompletionMs?: number;
};
}
// Receiving agent response
async function acknowledgeHandoff(
package: HandoffPackage
): Promise<HandoffAck> {
// Validate we can handle this
const validation = await this.validateTask(package.task);
if (!validation.canHandle) {
return {
status: 'rejected',
timestamp: new Date(),
agentId: this.id,
reason: validation.reason,
suggestedAlternative: validation.alternativeAgent
};
}
if (validation.needsMore) {
return {
status: 'conditional',
timestamp: new Date(),
agentId: this.id,
conditions: {
needsMoreContext: validation.missingContext
}
};
}
return {
status: 'accepted',
timestamp: new Date(),
agentId: this.id
};
}
Best Practices
- Define clear contracts - What each agent expects and provides
- Include enough context - Target shouldn't need to ask questions
- Set explicit timeouts - Don't let tasks hang forever
- Implement idempotency - Handoffs may be retried
- Log all transitions - Essential for debugging
- Version your protocols - Agents may be updated independently
Weekly Installs
3
Repository
latestaiagents/…t-skillsGitHub Stars
2
First Seen
Feb 4, 2026
Installed on
mcpjam3
claude-code3
replit3
junie3
windsurf3
zencoder3