agentleFS
khalilbenazkhalilbenaz/claude-skills-collection/skills/agent-handoff-designer/SKILL.md

agent-handoff-designer

Skill28 starsChanged 14 days agoMITView on GitHub

Design de handoffs fluides entre agents — transfert de contexte, d'état et de responsabilité. Se déclenche avec "handoff", "transfert agent", "agent handoff", "passer la main", "relay agent", "agent transition", "changement d'agent", "routing entre agents". Also triggers on "transfer context between agents", "agent escalation flow".

We split this skill into the rules it gives an agent

4 rules. About 0 are common, 4 are distinctive, and 0 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈4

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Never say "happy to help", "sure!", "great question", "let me", or similar.

    line 263
  2. Distinctive:

    Stop when done. No summary, no recap, no trailing commentary.

    line 266
  3. Distinctive:

    No politeness wrappers. Direct and blunt.

    line 267
  4. Distinctive:

    No emoji unless asked.

    line 270

≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Agent Handoff Designer

Quand utiliser ce skill

Un handoff est nécessaire dans quatre situations :

SituationSignalAgent cible
Capability boundaryOutil ou connaissance manquantSpécialiste équipé
SpécialisationSous-tâche mieux traitée ailleursAgent optimisé
EscalationRisque ou complexité dépassant le seuilAgent senior / humain
Load balancingQueue saturéeInstance parallèle

Décision : si l'agent courant peut compléter la tâche avec une qualité ≥ 80 % sans outil supplémentaire, ne pas handoff — le coût de transition n'est pas justifié.


Workflow en étapes

1. Évaluer le déclenchement

from enum import Enum

class HandoffReason(Enum):
    CAPABILITY_BOUNDARY = "capability_boundary"
    SPECIALIZATION      = "specialization"
    ESCALATION          = "escalation"
    LOAD_BALANCING      = "load_balancing"

def should_handoff(task: dict, agent_caps: list[str]) -> tuple[bool, HandoffReason | None]:
    missing = [c for c in task.get("required_capabilities", []) if c not in agent_caps]
    if missing:
        return True, HandoffReason.CAPABILITY_BOUNDARY
    if task.get("risk_level", 0) > 7:
        return True, HandoffReason.ESCALATION
    return False, None

Critères de seuil recommandés : risk_level > 7, complexity_score > 8, sentiment_score < 0.3.


2. Packager le contexte (≤ 2 000 tokens)

Contenu obligatoire du HandoffContext :

from dataclasses import dataclass, field
from typing import Any
import uuid

@dataclass
class HandoffContext:
    correlation_id: str          # inchangé sur toute la chaîne
    source_agent: str
    target_agent: str
    handoff_reason: str
    conversation_summary: str    # 5–10 phrases max
    user_intent: str             # reformulé explicitement
    key_facts: dict[str, Any]    # entités, montants, références
    task_progress: dict          # {"step": 3, "total": 7, "completed": [...]}
    user_preferences: dict       # langue, ton, contraintes détectées
    context_ref: str             # clé du snapshot complet dans le state store
    hop_count: int = 0
    handoff_chain: list[str] = field(default_factory=list)

def package_context(history: list, source: str, target: str, reason: str) -> HandoffContext:
    summary  = summarize_conversation(history)     # appel LLM interne
    facts    = extract_key_facts(history)
    ref      = store_context_snapshot(history)     # Redis / DynamoDB / mémoire partagée
    return HandoffContext(
        correlation_id=str(uuid.uuid4()),
        source_agent=source, target_agent=target,
        handoff_reason=reason,
        conversation_summary=summary,
        user_intent=detect_intent(history),
        key_facts=facts,
        task_progress={},
        user_preferences={},
        context_ref=ref,
        handoff_chain=[source],
    )

Règle de taille : résumé + faits = max 2 000 tokens. Le transcript complet va dans le state store, pas dans le package.


3. Exécuter le protocole (avec ACK obligatoire)

import asyncio

MAX_HOPS     = 5
TIMEOUT_S    = 5.0
RETRY_DELAY  = 2.0

async def execute_handoff(
    source_agent,
    target_agent,
    ctx: HandoffContext,
) -> dict:
    # Garde anti-boucle
    if ctx.hop_count >= MAX_HOPS:
        return {"success": False, "reason": "max_hops_exceeded"}
    if target_agent.id in ctx.handoff_chain:
        return {"success": False, "reason": "cycle_detected"}

    ctx.hop_count += 1
    ctx.handoff_chain.append(target_agent.id)

    for attempt in range(2):            # 1 essai + 1 retry
        try:
            ack = await asyncio.wait_for(
                target_agent.notify_handoff(ctx), timeout=TIMEOUT_S
            )
            if ack.get("ready"):
                source_agent.set_state("standby")
                return {"success": True, "target": target_agent.id}
        except asyncio.TimeoutError:
            if attempt == 0:
                await asyncio.sleep(RETRY_DELAY)

    # Fallback hiérarchique
    alt = find_alternative_agent(target_agent.id)
    if alt:
        return await execute_handoff(source_agent, alt, ctx)

    source_agent.set_state("active")    # return-to-sender
    return {"success": False, "reason": "target_unavailable"}

Séquence obligatoire : notify → ACK ready → switch source → confirm orchestrateur. Ne jamais passer en standby sans ACK.


4. Routing conditionnel

Définis les règles dans une table pure (testable unitairement) :

ROUTING_RULES = [
    {"cond": lambda c: c.get("intent") == "billing",        "target": "billing_agent"},
    {"cond": lambda c: c.get("sentiment_score", 1.0) < 0.3, "target": "escalation_agent"},
    {"cond": lambda c: c.get("complexity_score", 0) > 8,    "target": "senior_agent"},
    {"cond": lambda c: c.get("language") == "ar",           "target": "arabic_specialist"},
]

def route_handoff(context: dict) -> str:
    for rule in ROUTING_RULES:
        if rule["cond"](context):
            return rule["target"]
    return "default_agent"

LangGraph (2026) — utilise Command(goto="node", update={...}) dans le nœud source :

from langgraph.types import Command

def billing_router(state):
    if state["intent"] == "billing":
        return Command(goto="billing_node", update={"ctx": package_context(...)})
    return Command(goto="default_node")

OpenAI Assistants — partage le thread_id, annule le run courant, démarre un run sur l'assistant cible :

client.beta.threads.runs.cancel(thread_id=tid, run_id=rid)
new_run = client.beta.threads.runs.create(thread_id=tid, assistant_id=TARGET_ASST_ID)

CrewAI — Agent.delegate(task, agent=target_agent) + inject le contexte dans la description de la tâche.

Google A2A (2026) — expose un AgentCard avec capabilities, envoie un Task JSON via POST /tasks/send avec metadata.handoff_context.


5. UX transparente côté utilisateur

L'agent récepteur doit toujours :

  1. S'introduire brièvement : "Je prends en charge votre demande depuis [Agent A]."
  2. Démontrer le contexte : "Je vois que vous recherchez une facture pour le mois de mars…"
  3. Ne pas reposer une question déjà répondue — vérifier key_facts avant toute question.
  4. Si une info manque, expliquer pourquoi : "J'ai besoin de votre numéro client car il n'est pas encore dans le dossier."

Message de transition UI (optionnel) :

{ "type": "system_event", "event": "handoff_started",
  "message": "Transfert vers le spécialiste Facturation en cours…",
  "target_agent": "billing_agent" }

6. Chaînes multi-hop

À chaque hop :

  • Incrémenter hop_count, bloquer si >= MAX_HOPS.
  • Accumuler les résumés (pas re-résumer le résumé — perte de signal garanti).
  • Conserver le correlation_id original pour le tracing distribué.
  • Stocker un snapshot immutable dans le state store (ctx_snap_{hop}.json).
def accumulate_summary(existing: str, new_segment: str) -> str:
    # Concatène avec séparateur de hop — ne pas re-résumer
    return f"{existing}\n---hop---\n{new_segment}"

7. Métriques à suivre

MétriqueCalculSeuil alerte
Taux de succèshandoffs réussis / total< 95 %
Context loss scoreéval LLM du contexte reçu (0–1)< 0.8
Latence de transitionfin agent A → 1er message agent B> 3 s
Fréquence de retryretries / total handoffs> 10 %
Task completion post-handofftâches complétées après handoff< 80 %

Garde-fous et anti-patterns

Boucle infinie (A → B → A) — Sans détection de cycle, deux agents se renvoient la tâche indéfiniment. Fix : vérifier target in handoff_chain avant d'exécuter.

Handoff sans ACK — Switcher l'agent source avant que la cible soit prête provoque des messages perdus. Fix : ACK ready: true obligatoire, sinon fallback.

Contexte tronqué à l'intent seul — L'agent récepteur ne sait pas ce qui a déjà été répondu et refait les mêmes questions. Fix : packager key_facts + task_progress systématiquement.

Re-résumer le résumé à chaque hop — Chaque compression successive perd de l'information jusqu'à rendre le contexte inutilisable. Fix : accumulation par concaténation, re-résumé seulement au hop 1.

Handoff silencieux sans fallback — Si la cible ne répond pas et qu'il n'y a pas de return-to-sender, la tâche est perdue. Fix : toujours implémenter le chemin return-to-sender comme dernier recours.

Over-handoff — Créer des handoffs pour chaque micro-tâche gonfle la latence et le bruit de logs. Fix : handoff seulement si la tâche ne peut pas être complétée à ≥ 80 % par l'agent courant.

Contexte package > 2 000 tokens — Surcharger l'agent récepteur ralentit son inference et noie les faits clés. Fix : le transcript complet va dans le state store, le package ne contient que le résumé structuré.


Règles non-négociables

  1. HandoffContext obligatoire — Aucun handoff sans résumé + intent + key_facts, même minimal.
  2. ACK avant switch — Source reste active jusqu'à réception du ready: true.
  3. MAX_HOPS = 5 — Au-delà : escalade superviseur ou humain, jamais de boucle supplémentaire.
  4. correlation_id inchangé — Permet le tracing end-to-end sur toute la chaîne.
  5. Documente le trade-off — Contexte minimal = latence réduite, risque context loss élevé. Contexte riche = fiabilité élevée, latence +. Choix explicite selon le SLA.

Communication Rules — MANDATORY

  • Ultra-concise. No filler, no preamble, no pleasantries.
  • Never say "happy to help", "sure!", "great question", "let me", or similar.
  • Tool first, talk second. Act before explaining.
  • Result first. Lead with outcome, not process.
  • Stop when done. No summary, no recap, no trailing commentary.
  • No politeness wrappers. Direct and blunt.
  • Minimum words. If one word works, do not use ten.
  • No unsolicited explanations.
  • No emoji unless asked.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

Reviews can't be read right now.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.