A2A Protocol
Talon’s Agent-to-Agent (A2A) protocol lets personas delegate tasks to each other over the internal durable queue. No external orchestration needed — delegation is just a tool call.
How it works
Section titled “How it works”- Persona A calls
persona_sendwith a target persona name and a message - Talon enqueues a
collaborationqueue item for persona B - Persona B picks it up, runs its agent, and optionally returns a result
- If
await_reply: true, persona A’s tool call blocks until the reply arrives
The queue handles durability, retries, and crash recovery. Collaboration items bypass the inflight lock so they can be processed even while the source persona’s run is still active.
:::tip Provider isolation Delegated A2A tasks always run on the target persona’s configured provider, regardless of which provider the source thread was using. This prevents provider affinity contamination and ensures each persona runs on its intended model. :::
Capability requirement
Section titled “Capability requirement”All three delegation tools — persona_send, persona_task_status, and persona_list — are gated by the single persona.send:* capability. Grant it to any persona that needs to delegate work:
personas: - name: orchestrator capabilities: allow: - persona.send:*Without this capability, the tools are hidden from the agent’s tool list entirely.
Sending a task
Section titled “Sending a task”Use the persona_send host tool:
persona_send({ target_persona: "researcher", message: "Find the latest pricing for AWS Lambda in eu-west-1", await_reply: true})| Parameter | Type | Required | Description |
|---|---|---|---|
target_persona | string | Yes | Name of the target persona (must be loaded) |
message | string | Yes | Task description sent to the target |
await_reply | boolean | No | true blocks until the target responds (default: false) |
timeout_ms | number | No | Max ms to wait when await_reply: true; default 300000 (5 minutes) |
When await_reply: false (fire-and-forget), the tool returns immediately with the task ID. The target processes the task asynchronously.
When await_reply: true, the call waits up to 5 minutes (configurable with timeout_ms). If the target finishes before the timeout expires, the result is returned inline. If the timeout expires first, a structured timeout response is returned containing the task_id. Use persona_task_status to retrieve the final result later.
Discovering personas
Section titled “Discovering personas”Use persona_list to discover available delegation targets:
persona_list()Returns an array of persona cards:
[ { "name": "researcher", "description": "Research assistant with web search capabilities", "skills": ["web-search", "file-analysis"] }, { "name": "writer", "description": "Content writer for blog posts and documentation", "skills": ["markdown-editor"] }]The calling persona is excluded from the list. description is extracted from the first line of the persona’s system prompt file (# prefix stripped). skills are parsed from the persona’s configured skill list.
Checking task status
Section titled “Checking task status”When await_reply: true times out before the target persona finishes, persona_send returns a structured response containing the task_id. Use persona_task_status to retrieve the result:
persona_task_status({ task_id: "abc123", wait_ms: 60000 // optional: poll for up to 60 s})| Parameter | Type | Required | Description |
|---|---|---|---|
task_id | string | Yes | Task ID from the persona_send timeout response |
wait_ms | number | No | Poll until terminal state or this duration elapses |
Terminal states are completed and failed. Requires persona.send:* capability.
Task lifecycle
Section titled “Task lifecycle”A2A tasks follow the same lifecycle as regular queue items:
| Status | Meaning |
|---|---|
pending | Enqueued, waiting for the target persona |
claimed | Target persona’s agent runner has picked it up |
processing | Agent is executing |
completed | Task finished, reply available |
failed | Task failed after retries |
Tasks are persisted in SQLite and survive daemon restarts.
maxConcurrentPerTarget gates how many A2A tasks can target a single persona simultaneously. Additional submissions are rejected until one of the in-flight tasks finishes, preventing a single persona from being overwhelmed by concurrent delegation.
maxAttempts controls how many times a failed collaboration item is retried before it is moved to the dead-letter queue.
Multi-hop prevention
Section titled “Multi-hop prevention”Each A2A task carries a hop counter. When persona A delegates to persona B, the hop count increments. Talon enforces a maximum hop depth to prevent infinite delegation chains.
The default limit is 4 hops, configurable via a2a.maxHops in talond.yaml (bounds: 1–32). When the limit is reached, the task is rejected with a clear error message so the calling persona can handle the failure.
Configuring A2A limits
Section titled “Configuring A2A limits”All three A2A guardrails live under the top-level a2a: key in talond.yaml:
a2a: maxHops: 4 maxConcurrentPerTarget: 1 maxAttempts: 3| Field | Default | Bounds | When to change |
|---|---|---|---|
maxHops | 4 | 1–32 | Increase if you have deep orchestrator chains; decrease to tighten delegation depth. |
maxConcurrentPerTarget | 1 | 1–100 | Raise when a single persona is a hot-spot and can safely handle multiple A2A tasks in parallel. |
maxAttempts | 3 | 1–20 | Increase for transient downstream failures; decrease to fail faster. |
Existing configurations that do not include an a2a: block continue to work unchanged — the defaults match the previous hardcoded values.
Example: orchestrator pattern
Section titled “Example: orchestrator pattern”personas: - name: orchestrator model: claude-sonnet-4-6 systemPromptFile: personas/orchestrator/system.md capabilities: allow: - persona.send:* - channel.send:my-slack
- name: researcher model: claude-sonnet-4-6 systemPromptFile: personas/researcher/system.md skills: - web-search capabilities: allow: - net.http
- name: writer model: claude-sonnet-4-6 systemPromptFile: personas/writer/system.md capabilities: allow: - channel.send:my-slackThe orchestrator receives user requests, delegates research to researcher (with await_reply: true), then delegates writing to writer. Each persona has only the capabilities it needs.