From agent-architect
Use this skill when designing task handoffs between agents. Activate when the user needs to pass work between agents, transfer context between agents, implement agent-to-agent communication, or design protocols for agents to collaborate on sequential tasks.
npx claudepluginhub latestaiagents/agent-skills --plugin agent-pluginThis skill uses the workspace's default tool permissions.
Design clean, reliable protocols for passing tasks and context between AI agents.
Designs handoff protocols for smooth transitions between AI agents and humans in multi-agent systems. Covers anatomy, types, context transfer, user experience, and anti-patterns.
Creates structured context packets for subagent handoffs, packaging task details, constraints, progress, and decisions to prevent context loss in multi-agent delegation.
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
Design clean, reliable protocols for passing tasks and context between AI agents.
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;
};
}
┌─────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ PENDING │───►│ ACCEPTED │───►│ WORKING │───►│ COMPLETE │
└─────────┘ └──────────┘ └──────────┘ └──────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ TIMEOUT │ │ REJECTED │ │ FAILED │ │ RETURNED │
└─────────┘ └──────────┘ └──────────┘ └──────────┘
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 };
}
}
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;
}
}
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);
}
}
Transfer everything - simple but expensive.
function fullContextTransfer(context: Context): TransferredContext {
return {
...context,
_transferType: 'full',
_tokenEstimate: estimateTokens(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)
};
}
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
}))
};
}
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]
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}`);
}
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);
}
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);
}
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
};
}