Local and A2A dispatch

Harn trigger handlers can move between in-process execution and A2A dispatch without changing the handler's input contract. Keep the trigger id, provider match, retry policy, and handler source stable; change only the handler target when the boundary changes:

# local
handler = "handlers::triage_issue"

# federated
handler = "a2a://reviewer.example/triage"

The dispatcher still records the same core lifecycle shape:

  • dispatch_started
  • attempt_recorded
  • dispatch_succeeded, dispatch_failed, dispatch_waiting, or dlq_moved
  • a trust record for the terminal dispatch decision
  • action-graph nodes and edges for replay comparison

Choosing a mode

Use a local handler when the workflow should run inside the current Harn process:

  • development and deterministic test fixtures
  • low-latency handlers with no cross-tenant boundary
  • handlers that need direct access to local host capabilities already granted to the current orchestrator

Use A2A dispatch when execution crosses an agent or trust boundary:

  • a managed Harn Cloud worker owns the handler
  • a federated agent has separate credentials, quotas, or receipts
  • an operator needs to see that the work ran remotely
  • retries and DLQ movement should treat the remote endpoint as a destination

Trust and replay

Local and A2A dispatch share typed TriggerEvent input and JSON output semantics. A2A wraps the event in a harn.trigger.dispatch envelope, sends it with message/send, and unwraps the completed task's final text part as the logical handler result.

The difference is recorded as metadata, not as a handler-source requirement. Action-graph nodes and trust records include:

  • trust_boundary: local_process, federated_a2a, event_log_worker_queue, or persona_runtime
  • execution_location: in_process, remote, queued, or managed_persona
  • remote_identity: the A2A target agent label for remote dispatch
  • A2A task metadata such as task_id, state, card_url, rpc_url, and remote_agent_id when the remote card exposes it

Replay comparisons should assert the logical output and event shape while allowlisting location-specific metadata such as trust_boundary, remote_identity, remote URLs, and generated task ids.

Failure modes

A2A dispatch fails closed for trust-boundary denials:

  • cleartext HTTP endpoints require allow_cleartext = true
  • agent-card authority mismatches are denied
  • HTTP 401 / 403 dispatch responses are denied
  • A2A rejected task state becomes a permission-denied dispatch failure

Remote timeouts are classified as timeout failures and remain retryable under the trigger's retry policy. Incompatible A2A response schemas are protocol failures and move to DLQ when retries are exhausted.

Demo

The reference flow is in examples/triggers/local-a2a-dispatch/.

scripts/demo_local_a2a_dispatch.sh

The script starts a local harn serve A2A receiver on a random port, fires the same deterministic issue event through a local handler and through A2A, and prints whether the logical outputs match.