Classify Tasks to Avoid Fragile Orchestrators

Central agents fail in long workflows by accidentally managing state, scheduling, and reviews. Instead, use this Python classifier for each WorkItem based on five signals: expected_minutes >5, needs_state, transfers_responsibility, needs_human_review, not deterministic_result. If >=2 signals, choose AGENT_HANDOFF (A2A-style); else TOOL_CALL (MCP-style).

Example: document_search (1min, stateless, deterministic) → TOOL_CALL. proposal_review (45min, stateful, transfers responsibility, needs review, non-deterministic) → AGENT_HANDOFF.

This forces explicit design: tools stay bounded (input → output, caller owns workflow); collaboration models roles, handoffs, and accountability.

from enum import Enum
class Surface(str, Enum):
    TOOL_CALL = "tool_call"
    AGENT_HANDOFF = "agent_handoff"

@dataclass(frozen=True)
class WorkItem:
    name: str
    expected_minutes: int
    needs_state: bool
    transfers_responsibility: bool
    needs_human_review: bool
    deterministic_result: bool

def choose_surface(item: WorkItem) -> Surface:
    collaboration_signals = sum([
        item.expected_minutes > 5,
        item.needs_state,
        item.transfers_responsibility,
        item.needs_human_review,
        not item.deterministic_result,
    ])
    if collaboration_signals >= 2:
        return Surface.AGENT_HANDOFF
    return Surface.TOOL_CALL

Smell to watch: growing central agent prompts tracking stages, owners, timeouts—move to explicit task models with status and history.

Build MCP Tools with Strict Contracts

Expose capabilities via schemas enforcing inputs, permissions, outputs. Caller requests result; tool executes immediately without owning workflow.

Research assistant example: search documents, read calendar → pure tools. No negotiation; just bounded ops.

Minimal Python contract:

@dataclass(frozen=True)
class DocumentSearchRequest:
    query: str
    max_results: int = 5

@dataclass(frozen=True)
class SearchResult:
    title: str
    snippet: str

class DocumentIndex:
    def search(self, request: DocumentSearchRequest) -> tuple[SearchResult, ...]:
        # Validates query, limits results 1-20, returns matches
        pass

Test for schema compliance, errors, idempotency: test_document_search_stays_a_tool() asserts result shape and Surface.TOOL_CALL.

Mistake to avoid: forcing coordination into tools—leads to central agent as unintended orchestrator when steps take hours or need retries.

Model A2A Collaboration with Task Lifecycle

For publishing workflows (qualify manuscript → compare books → proposal → track submissions), track ownership, status, history across participants.

Key: handoffs transfer responsibility, not just payloads. Use Task with TaskStatus (OPEN, IN_PROGRESS, WAITING_FOR_REVIEW, DONE), Participant (name, role), history tuple.

Functions: handoff(task, new_owner, reason) updates owner/status/history; request_review(task, reviewer) sets WAITING_FOR_REVIEW.

Example:

class TaskStatus(str, Enum):
    OPEN = "open"
    IN_PROGRESS = "in_progress"
    WAITING_FOR_REVIEW = "waiting_for_review"
    DONE = "done"

@dataclass(frozen=True)
class Task:
    task_id: str
    title: str
    owner: Participant
    status: TaskStatus
    history: tuple[str, ...] = ()

def handoff(task: Task, new_owner: Participant, reason: str) -> Task:
    event = f"handoff:{task.owner.name}->{new_owner.name}:{reason}"
    return replace(task, owner=new_owner, status=TaskStatus.IN_PROGRESS, history=task.history + (event,))

Test handoffs: test_manuscript_review_becomes_a_handoff() verifies owner change, status, history entry, Surface.AGENT_HANDOFF.

Observability shifts: tools track call/input/duration/result; collaboration tracks owner, blockers, decisions.

Mistake to avoid: agent-ifying simple tools—adds unneeded ceremony to deterministic, single-owner tasks.

Ask These to Nail the Design

  1. Expose capability or own multi-step role?
  2. Need result or responsibility transfer?

Tools → MCP: immediate, stateless. Collaboration → A2A: stateful handoffs. Combine both in one system: tools for access, A2A for division of labor.