Skip to content

feat(a2a): add outbound A2A client (CLI + SDK)#60

Open
Nivesh353 wants to merge 3 commits into
open-gitagent:mainfrom
Nivesh353:feat/a2a-client
Open

feat(a2a): add outbound A2A client (CLI + SDK)#60
Nivesh353 wants to merge 3 commits into
open-gitagent:mainfrom
Nivesh353:feat/a2a-client

Conversation

@Nivesh353

Copy link
Copy Markdown
Contributor

Summary

Adds an A2A (Agent2Agent) client so gitagent can call remote A2A-compatible
agents — built on LangGraph, CrewAI, Google ADK, or any A2A server — and use
their results mid-conversation. Each remote skill shows up as a normal tool, so
delegation looks identical to any other tool call.

Outbound only — no server is started. The feature is fully opt-in: with no
a2a_agents configured, behavior is unchanged and zero network calls are made.

A2A is complementary to local tools: tools give the agent capabilities, A2A
gives it peers.

Motivation

Today gitagent can't talk to other agents. As teams build specialized agents on
other frameworks, users want gitagent to delegate to them instead of doing
everything itself. A2A is the open, Linux-Foundation interop standard for exactly
this, so it's the natural way to make gitagent interoperable.

How it works

  1. Discovery — at startup, for each configured agent gitagent fetches its
    Agent Card (/.well-known/agent-card.json).
  2. Tools — each skill becomes one tool named <agent>__<skill>
    (skill-less agents → a single tool named after the agent). Name collisions
    are skipped with a warning.
  3. Delegation — when the model calls one, gitagent sends the task via
    message/send (blocking) or message/stream (SSE streaming), flattens the
    reply to text, and returns it.

Failed/unreachable agents are non-fatal — they warn and are skipped; the rest
of the session works normally.

Configuration

# agent.yaml
a2a_agents:
  research-agent:
    url: https://research.example.com          # required
    headers:                                    # optional — ${VAR} read from env
      Authorization: "Bearer ${RESEARCH_TOKEN}"
    stream: true                                # optional (default true; false = blocking)
    timeoutMs: 30000                            # optional (default 30000)
    cardPath: /.well-known/agent-card.json      # optional override

Testing

Unit — test/a2a.test.ts (14 cases): skill→tool mapping + namespacing, zero-skill fallback, name collisions, missing/unreachable agents (non-fatal), blocking, error propagation, streaming + partial updates. Full suite: 41/41 green.
End-to-end — verified against two real A2A servers built for this: a LangGraph agent with tools and a CrewAI multi-agent crew. Confirmed discovery, tool registration, blocking + streaming, bearer-token auth (correct/wrong/missing → connect/skip), and timeout handling — through both the CLI and the SDK.

Configure remote A2A agents under `a2a_agents` in agent.yaml; each remote skill
becomes a `<agent>__<skill>` tool. Blocking + streaming, header auth, per-agent
timeout. Opt-in, non-fatal, no server started. Wired into both the CLI and the
programmatic query() SDK path.
New `a2aAgents` option on query(), merged on top of agent.yaml's `a2a_agents`
(code wins on collision). Lets SDK users configure remote agents without a yaml.

@shreyas-lyzr shreyas-lyzr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall the implementation is solid — clean separation into manager/types, good non-fatal error handling, reasonable test coverage. Three issues worth addressing: one correctness bug in the SDK merge logic, one architecture concern about module-scoped cleanup state, and one minor safety gap in the cleanup chain. Details inline.

Comment thread src/sdk.ts
Comment thread src/index.ts
Comment thread src/index.ts

@shreyas-lyzr shreyas-lyzr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All three comments addressed:

  • Operator-precedence fix: the a2aAgents merge now correctly uses { ...loaded.manifest.a2a_agents, ...options.a2aAgents } guarded by the truthiness check, so the merge is unambiguous.
  • Module-scoped a2aCleanup in src/index.ts is now nulled out before invoking (const fn = a2aCleanup; a2aCleanup = null; await fn()), preventing double-execution in repeated-call scenarios.
  • SIGTERM handler now chains runA2ACleanup() before telemetry shutdown, aligning the two exit paths.

Good to merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants