a2a-adapter

module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 28, 2026 License: Apache-2.0

README

a2a-adapter

Wrap a local CLI coding agent — Claude Code, Codex — as a standard A2A v1.0.1 server. The agent you already run and log into becomes a plain A2A endpoint that any A2A client or management system can drive: send turns, stream the work, answer tool-approval prompts, cancel a run, get webhook callbacks, reconnect after a drop.

It is both a Go library and a thin CLI. One process serves one agent as one A2A endpoint — nothing more.

Scope

What this is, and deliberately what it is not:

  • Is: a faithful adapter. A driver turns a CLI agent's event stream into A2A task/artifact/status events; the protocol layer is the official a2aproject/a2a-go. Session continuity (multi-turn resume within one A2A context) is handled here because it is A2A semantics.
  • Is not: NAT traversal, multi-tenant routing, or a multi-agent daemon. Standard TLS/mTLS is built in and advertised as ordinary https://; the transport seam (Listen/DialContext/Identify) also allows a deployment or management layer to supply opaque tunnels or custom routing (tsnet, a relay, ...). Serving many agents on one host is many serve processes under systemd/launchd or a manager, not something this binary does.

This keeps the adapter composable: a manager builds a fleet by orchestrating these units (or by importing the library), while each unit stays a pure, standard A2A agent.

The latest stable A2A specification is normative. The currently verified baseline is A2A v1.0.1 (wire protocol 1.0), pinned in the compliance matrix; adapter SPI convenience never overrides a standard method, state, Part, error, security rule, or Extension negotiation rule. Internal implementations are replaceable at documented seams, while protocol semantics remain owned by the Endpoint.

Requirements

  • Go 1.25.12 or newer. With the default GOTOOLCHAIN=auto, an older Go launcher can download the required toolchain.
  • For claude: the Claude Code CLI, logged in.
  • For codex: the Codex CLI. The recorded compatibility baseline is 0.144.4. Remote approvals require the modern item-scoped app-server requests which carry a turn ID; uncorrelated legacy execCommandApproval / applyPatchApproval callbacks are denied and retire the session rather than being attributed to a possibly newer turn.

Each side uses its own CLI login; the adapter never handles or repackages another party's credentials.

Install

Install a tagged source release with the standard Go module toolchain:

VERSION=v1.0.0 # replace with the release tag you selected
go install github.com/vibe-agi/a2a-adapter/cmd/a2a-adapter@${VERSION}
a2a-adapter --version

The initial v1 distribution is source/Go-module based; CI verifies native builds on Linux, macOS, and Windows, but the project does not publish separate binary archives.

Build

go build -o bin/a2a-adapter ./cmd/a2a-adapter

Quick start

Serve the built-in echo agent (no external binary needed) and inspect its standard Agent Card:

bin/a2a-adapter serve echo --addr 127.0.0.1:9001
curl http://127.0.0.1:9001/.well-known/agent-card.json

Serve a real agent:

bin/a2a-adapter serve claude --workdir /path/to/project
bin/a2a-adapter serve codex  --workdir /path/to/project

The AgentCard is published at /.well-known/agent-card.json; any standard A2A client can resolve it and send a message.

Agents

Name Backing CLI Notes
echo none (in-process) Skeleton/self-test agent; echoes input.
claude Claude Code, via the in-tree adapter compatibility package based on roasbeef/claude-agent-sdk-go Long-lived stream-json control protocol; tool approvals via can_use_tool; library wrappers may enable JSON-schema output with claude.WithOutputSchema.
codex Codex, via pmenglund/codex-sdk-go Long-lived codex app-server; approvals via the app-server's server→client RPCs.

A2A capabilities

  • Streaming — A2A 1.0 SendStreamingMessage yields the turn as it unfolds (working → artifact → completed) over SSE.
  • Negotiated structured agent events — callers can opt in through the standard A2A extension mechanism to receive versioned tool activity, file changes, cumulative usage, structured output, and a closed terminal-failure class. Durable events are ordinary artifact DataParts; streamed events use status metadata, whose latest envelope may remain in Task.Metadata. Stock clients that do not request the extension keep the core A2A behavior unchanged. Original agent.Thinking content and high-volume tool-output deltas are hidden by default; see the agent-events v1 contract. Embedders can register other bounded implementations through the common extension framework.
  • Tool approvals — a gated tool surfaces as an auth-required task. When the caller activates the advertised tool-approval extension, the task also carries the structured request and accepts a structured approval decision. Without that negotiation, the text is explanatory only and authorization must be resolved out-of-band; plain text is never treated as a credential. Both drivers gate every tool, Bash included.
  • Cancel — A2A 1.0 CancelTask interrupts the in-flight turn for real: the agent subprocess is interrupted and the session dropped, not just the task status flipped.
  • Push notifications — register a webhook (in the send config or through CreateTaskPushNotificationConfig) and the server POSTs every task update, terminal included — so a manager learns a turn finished without holding an SSE stream open. On by default; under the default guarded policy, the sender accepts only callbacks which pass its public-network URL policy both when the config is saved and when the connection is actually dialed.
  • Resubscribe — A2A 1.0 SubscribeToTask reconnects to an in-flight task and delivers the current snapshot plus live updates. (It does not replay the discrete SSE events missed while disconnected; a finished task uses GetTask.)
  • Task and session persistence--store <file.db> keeps A2A tasks in SQLite (pure-Go, no CGO) across restarts and always requires a stable, non-secret --workspace-id. For the built-in Claude and Codex drivers, the same store also persists A2A context resume checkpoints, so a later adapter process can continue the agent context for that workspace and conversation. Echo and third-party drivers do not get durable resume automatically. On startup, the built-in SQL store atomically marks tasks abandoned in a nonterminal state as failed, so a hard restart cannot leave false working tasks behind. Any taskstore.Store can be injected instead by library users.

Configuration

Precedence, highest first: command-line flag → A2A_ADAPTER_* env var → config file (a2a-adapter.yaml, .yml, .toml, or .json in the current directory or ~/.config/a2a-adapter/) → default. See a2a-adapter.example.yaml.

A2A_ADAPTER_ENV accepts comma-separated KEY=VALUE pairs; individual --env values override matching keys from it and from the config file.

serve flags:

Flag Default Purpose
--addr 127.0.0.1:9001 Listen address.
--advertise Externally-reachable base URL for the card (needed when binding a wildcard address).
--trusted-tls-proxy false Explicitly trust an upstream TLS terminator for an HTTPS --advertise URL; the backend stays plaintext and must not be directly reachable.
--store SQLite file for A2A task persistence plus supported-driver resume checkpoints (empty = in-memory); requires --workspace-id.
--no-push false Disable push notifications.
--allow-peer Restrict callers to these transport peer identities (repeatable); requires an identifying transport.
--tls-cert / --tls-key Server certificate/key — switches to the encrypted transport.
--tls-client-ca CA to verify client certs (mutual TLS; required for --allow-peer to name callers).
--model Model override. Claude: empty uses the SDK's pinned default (Sonnet 4.5), not your CLI default; Codex: empty uses the CLI default.
--workdir Agent working directory.
--workspace-id Stable, non-secret workspace identity used for session continuity; required with --store.
--continuity-id default-v1 Non-secret resume-policy epoch. Change it when credentials, backend, or resume policy changes so incompatible sessions are not resumed.
--shared-principal Explicit owner for anonymous A2A callers on a single-trust endpoint; every caller shares the same tasks and sessions.
--permission-mode default, plan, acceptEdits, bypassPermissions.
--shutdown-timeout 30s Graceful pipeline drain after SIGTERM (0 = hard stop); final driver teardown has its own bounded window.
--env KEY=VALUE injected into the agent subprocess (repeatable).

Security

The A2A endpoint fails closed at the session-ownership boundary. Anonymous loopback requests receive the built-in local principal. When binding it to a non-loopback address, startup requires either authenticated clients via --tls-client-ca or an explicit --shared-principal; a server certificate by itself encrypts traffic but does not identify callers. For distinct caller identities, use mutual TLS and gate on them:

a2a-adapter serve claude \
  --tls-cert server.pem --tls-key server-key.pem \
  --tls-client-ca clients-ca.pem \
  --allow-peer manager-1 --allow-peer manager-2

--tls-client-ca turns on mutual TLS (every caller must present a cert signed by that CA); --allow-peer then restricts service to the listed certificate common names. Authorization fails closed — a caller the transport can't identify, or one not on the list, gets 403. An allowlist without an identifying transport is refused at startup rather than silently denying everyone.

--shared-principal <name> is the deliberate single-trust-domain escape hatch for an endpoint whose surrounding network or reverse proxy already supplies the trust boundary but not an identity that the adapter can consume. It assigns every anonymous caller the same owner, so those callers share task visibility, session continuity, and resume checkpoints. Do not use it to separate mutually untrusted clients.

TLS is standard: with --tls-cert/--tls-key the transport serves ordinary TLS and advertises an https:// card, so any stock A2A client reaches it with a normal TLSClientConfig — no custom dialer. Adding --tls-client-ca requires verified client certificates (mutual TLS); the card then declares the standard mutualTLS security scheme and the certificate CommonName becomes the caller identity for --allow-peer.

Library embedders must serve a securetls endpoint on the listener returned by that transport; mounting it on a plain listener fails closed. A custom transport advertising https:// likewise defaults to requiring direct TLS. Only a trusted reverse-proxy transport that intentionally terminates TLS upstream should implement transport.TLSRequirement and return false; a transport declaring client-certificate authentication must use direct TLS.

The built-in plain transport requires that decision explicitly: --advertise https://... without direct TLS is rejected unless --trusted-tls-proxy is set (library: local.WithTrustedTLSProxy()). That flag only describes a protected TLS-termination hop; it neither authenticates callers nor trusts forwarding headers, and untrusted clients must not be able to reach the plaintext backend listener.

The negotiated agent-events extension is not an automatic sanitizer. Tool names/input/output, file paths and diffs, and structured output can contain source code or secrets. Only offer it to callers inside the endpoint's trust boundary, or replace the default with a redacting server.WithA2AEventProjectionPolicy. The original text of an agent.Thinking event is never serialized through this A2A extension; a wrapper must deliberately provide a safe summary. The default policy does not semantically inspect agent.Raw or tool/file payloads, so drivers and wrappers must not disguise private reasoning inside those event types. Durable event parts and the latest streamed-status envelope enter the task's storage/push trust boundary; extension negotiation does not filter a later authorized GetTask response. Terminal failure metadata is the exception to caller-owned projection: turn.failure contains only a closed agent.FailureKind value and bypasses EventProjectionPolicy. Drivers must derive it only from structured upstream fields or local protocol state, never provider/assistant/tool text or error.Error(); an unknown value becomes generic before storage. The complete policy and wire contract is documented here.

The default push implementation treats every callback URL as untrusted. It validates the URL and its DNS result before acknowledging or persisting a push config, repeats the check at delivery, refuses redirects, and validates the resolved IP used by the actual dial. This two-stage policy also wraps a caller-supplied push config store. WithPushNetworkPolicy replaces only the authoritative URL/DNS/dial decision; WithPushRequestPreparer replaces only authentication/header preparation. Core still owns the HTTP method, standard media type and payload, redirect refusal, protected headers, timeout, and the composition boundary, so replacing request signing cannot silently turn off SSRF enforcement.

This is a single-agent endpoint. Every task-addressed operation—including get, list, continuation, cancel, subscribe, referenced tasks, and push-config CRUD—is authorized against the same authenticated owner before task access. The built-in task authorizer deliberately makes foreign and nonexistent IDs indistinguishable. Organization-specific roles or policy can be injected with WithTaskAuthorizer; multi-agent routing and a tenant control plane remain out of scope.

Remote-facing defaults are bounded: 64 accepted HTTP connections, 4 concurrent large buffered request bodies (32 MiB each, held only through JSON decode), 32 active HTTP handlers split into 24 work and 8 control slots, 16 agent executions, and 64 resident Registry sessions (10-minute idle TTL). Every HTTP request also passes a bounded process-local admission bucket keyed by verified transport peer or canonical socket IP before body work; every decoded A2A operation then passes one aggregate bucket keyed by its authenticated principal when available. The defaults are a 256-request/4-per-second source bucket, a 512-request/8-per-second principal bucket, at most 4096 retained subjects, and ten-minute idle reclamation. Forwarding headers are never trusted. HTTP admission returns 429 plus Retry-After; JSON-RPC overload returns ServerError with standard RetryInfo (and unary responses also carry the header; an already flushed SSE response uses the in-band detail). This is a safe single-process abuse baseline, not a distributed quota: multi-replica wrappers can inject their own aggregate server.RateLimitPolicy, whose ownership and public harness are documented in the embedding API policy. Compact bodies up to 64 KiB bypass the large-buffer slots, so image uploads cannot starve card discovery or cancellation; a separate 64-prefix admission cap bounds their aggregate memory even over multiplexed HTTP/2. Request headers/body inactivity, response writes, TLS handshakes, session startup, and silent turns also have size/time limits. A progressing upload may run for up to five minutes; its rolling 30-second idle deadline still rejects a stalled peer sooner. A session holding a pending tool approval is pinned across the idle TTL, turn- and generation-bound, and retired after 30 minutes unless it is answered or canceled. Library users can override these policies through server options and sessionregistry.Config. The CLI gives detached A2A execution pipelines 30 seconds to finish after SIGTERM before hard-aborting them; this includes task persistence and synchronous push delivery, not merely the HTTP handler.

Library

The CLI is a thin wrapper; the same pieces compose directly. The public stability, ownership, shutdown, and conformance-harness rules are specified in docs/embedding-api-policy.md.

import (
    "context"

    "github.com/vibe-agi/a2a-adapter/pkg/drivers/claude"
    "github.com/vibe-agi/a2a-adapter/pkg/server"
    "github.com/vibe-agi/a2a-adapter/pkg/sessionregistry"
    "github.com/vibe-agi/a2a-adapter/pkg/transport/local"
)

tr := local.New("127.0.0.1:9001")
drv := claude.New()
registration, err := sessionregistry.NewRegistration(sessionregistry.RegistrationSpec{
    NamespaceID: "my-wrapper",
    RegistrationID: "primary-agent",
    ContinuityID: "policy-v1", // rotate when credentials/resume policy changes
    Driver: drv,
    Workspace: sessionregistry.WorkspaceSpec{
        ID: "project-1", Path: "/path/to/project",
    },
})
if err != nil { return err }
registry, err := sessionregistry.New(registration, sessionregistry.Config{})
if err != nil { return err }
defer func() { _ = registry.Close(context.Background()) }()

err = server.Serve(ctx, tr, drv,
    server.WithSessionRegistry(registry),
    server.WithTaskStore(store), // optional: persist tasks
)

For a wrapper that already owns an HTTP server, mux, or shared port, assemble an Endpoint instead of copying Serve internals:

import (
    "context"
    "errors"
    "net"
    "net/http"
    "time"
)

raw, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil { return err }
tr := local.NewWithListener(raw)
ln, err := tr.Listen(ctx) // caller drives the transport lifecycle
if err != nil { return err }
defer tr.Close()

endpoint, err := server.NewEndpoint(ctx, tr, drv,
    server.WithSessionRegistry(registry),
    server.WithTaskStore(store),
)
if err != nil { return err }
defer endpoint.Close() // hard-abort fallback; prefer Shutdown below

routes := endpoint.Routes()
mux := http.NewServeMux()
mux.Handle(routes.JSONRPCPath, endpoint)
for _, route := range routes.AgentCardPaths {
    mux.Handle(route, endpoint)
}
mux.Handle("/my-wrapper/healthz", healthHandler)

host := &http.Server{Handler: mux}
serveListener, err := endpoint.PrepareHTTPServer(host, ln)
if err != nil { return err }
if _, err := endpoint.ReconcileStartup(ctx); err != nil { return err }
serveErr := make(chan error, 1)
go func() { serveErr <- host.Serve(serveListener) }()

select {
case <-ctx.Done(): // the wrapper's process/lifecycle signal
case err := <-serveErr:
    if err != nil && !errors.Is(err, http.ErrServerClosed) { return err }
}

// During shutdown, stop this endpoint and wait for detached Execute/Cancel,
// task-store updates, and synchronous push delivery. The shared host and
// transport remain caller-owned.
shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := endpoint.Shutdown(shutdownCtx); err != nil { return err }

NewEndpoint never calls Transport.Listen or Transport.Close, never closes a caller-supplied SessionRegistry, task/push store, custom push sender, or host server, and does not mutate durable tasks during construction. The activation gate returns 503 until ReconcileStartup succeeds. Routes reports the exact JSON-RPC and AgentCard mount paths; PrepareHTTPServer composes connection identity, timeouts, header limits, and accepted-connection admission with the host's existing hooks. Shutdown closes endpoint admission and drains the full a2a-go execution pipeline; Close is the explicit hard-abort path.

Endpoint.Snapshot() exposes a bounded, payload-free view of activation, readiness, serving/draining state, in-flight HTTP requests and executions, the startup reconciliation count, and hard-abort state. sessionregistry.Registry.Stats() similarly reports aggregate session phases, attachments/waiters, active turns, pending approvals, ended generations, and replay-budget use without exposing conversation keys, principals, workspaces, tokens, approvals, or event data. These are inputs for a caller-owned health/readiness handler, metrics exporter, or tracing integration; the adapter does not install such a product or choose a telemetry backend.

Operational records emitted through server.WithLogger pass through the adapter's sensitive-key boundary, including a2a-go records and net/http panic output. The logger handler/sink and attributes bound before WithLogger remain caller-owned: Go's slog.Handler cannot expose those pre-bound values for the adapter to inspect, so wrappers must pass a bare logger or pre-bind only reviewed, non-sensitive correlation data. Attributes attached after the boundary under credential, payload, error, panic, or stack keys are redacted.

Codex's optional codex.WithSensitiveWireTranscript is deliberately outside operational logging. It writes the complete, unredacted native JSON-RPC stream, which can contain prompts, assistant output, tool arguments/results, file paths and contents, and protocol-carried authentication material. It is disabled by default. Calls from both directions and all sessions opened by the Driver are serialized, but writes are synchronous and best effort; a blocking sink can stall Codex. A wrapper enabling it must therefore use a promptly returning, access-controlled, appropriately encrypted sink with an explicit retention/deletion policy; never route it to a shared application log.

Wrapper policy seams include WithAuthenticationProvider, WithTaskAuthorizer, WithCallInterceptors, WithPushNetworkPolicy, WithPushRequestPreparer, and WithAgentCardTransform. The authenticated A2A user name is the single owner identity for both task storage and Registry sessions; core does not expose a second resolver that could split those scopes. The built-in interface does not advertise AgentInterface.tenant. A future tenant-aware wrapper must add card declaration, authenticated routing, task ownership, and Registry scoping atomically rather than deriving identity from request metadata. Push network policy and request preparation are separate decorators, so replacing request signing never disables dial-time SSRF checks.

Execution/resource seams include WithWorkspaceCoordinator, WithWorkspaceLeaseProvider, WithExecutionPolicy, WithSessionEnvironmentProvider, WithFileReferencePolicy, WithA2AProjectionLimits, and WithLifecycleObserver. Static workspace, process-local exclusive leasing, bounded execution admission, deny-by-default URL references, hard projection limits, and a no-op observer are built in; a wrapper can replace only the policy or backend it disagrees with. Durable push uses one WithDurablePushBackend aggregate for the task/config/outbox transaction, while sender, retry, outbound-network, and request-preparation policies remain separately composable. The aggregate boundary is intentional: splitting those persistence pieces would reintroduce crash and generation/ABA windows. Core calls the aggregate's CreateWithPush, UpdateWithPush, and ReconcileNonTerminalWithPush methods with the full authoritative Task mutation plus a separately constructed opaque pushdelivery.OutboundEvent; implementations commit the mutation and every corresponding outbox row in one transaction and must enqueue exactly that opaque event. A pushed Task deliberately omits its optional history field before either an immediate callback or durable outbox write, while the authoritative Task retains its full history. Normal Send/SSE/Get/List response projections are unchanged: historyLength controls those responses, not webhook payloads. This is a specific data-minimization boundary, not a general secret scrubber; standard Message, Artifact, status.message, and metadata payloads remain visible to the configured webhook.

local has no authenticated peer identity, so it cannot be combined with WithAllowedPeers. Use securetls with verified client certificates (or another identifying transport) before enabling an identity allowlist.

A driver implements agent.Driver/agent.Session (a token-correlated, channel-based event pump with SendTurn/TurnEvents/Close, plus optional turn-control interfaces); the transport implements transport.Transport; the task store implements a2a-go's taskstore.Store. All are injection points — the core only calls the interfaces. SessionRegistry is the sole session owner and event consumer behind the standard A2A projection; its immutable Registration is where a wrapper pins workspace, driver profile, continuity epoch, and an optional per-principal secret environment provider. A registration Profile carries the portable access level, model, and bounded driver-specific settings. Its portable shape is validated first; any non-empty profile also requires the driver to implement the optional, side-effect-free agent.ProfileValidator. Unsupported or unknown policy fails before the Registration becomes active and is checked again before Driver.Open, rather than being silently ignored.

Third-party driver authors should run the public pkg/agent/agenttest contract harness against a fresh, hermetic scripted backend:

func TestDriverContract(t *testing.T) {
    agenttest.Harness{
        New: func(t testing.TB) agenttest.Fixture {
            return agenttest.Fixture{
                Driver:  newHermeticDriver(t), // no installed agent CLI
                Options: agent.SessionOptions{Workspace: t.TempDir()},
                Scenarios: agenttest.Scenarios{
                    Send: agent.NewTextTurn("scripted normal turn"),
                },
            }
        },
    }.Run(t)
}

The harness checks portable admission and lifecycle behavior plus scripted Send/Done traces, stale-turn fencing, interrupt, approval, elicitation, steering, resume, and usage accounting. A deterministic scenario is required for every capability the manifest advertises. Harness.New itself must return promptly, and driver-owned asynchronous goroutines must contain their own panics; an in-process harness can isolate only calls made through the portable Driver/Session boundary.

agent.Done carries no provider diagnostic. A failed turn uses the closed agent.FailureKind vocabulary documented in the Driver SDK contract; empty or unprovable failures are generic. Fixed A2A status wording remains available to every client, while the exact safe class is exposed only through negotiated agent-events v1 turn.failure metadata.

Manifest.InputModes and Manifest.OutputModes are executable MIME contracts, not card-only documentation. The server rejects inbound parts outside the advertised input modes and rejects a request whose acceptedOutputModes has no compatible mode. Every admitted standard A2A text/raw/data/URL part is preserved in order as the sole content representation, agent.Turn.Parts; there are no parallel text/image fields to reconcile, and adapter core never fetches URL references. A driver can emit agent.ArtifactPart for a standard user-facing output part; the bridge validates its media type against the registered output modes before projecting it into a task artifact. A task store may additionally implement store.StartupReconciler; Serve supplies the default failed-on-restart policy, while wrappers can replace it with server.WithStartupReconciliation or take full ownership with server.WithoutStartupReconciliation. Automatic reconciliation assumes one live endpoint incarnation owns the store; wrappers sharing a backend across endpoints must scope or lease that ownership and disable the default policy.

server.Serve advertises and runs the optional structured-agent-event projection by default. server.WithA2AEventProjectionPolicy lets a wrapper redact or select events, and a nil policy disables both projection and the card declaration. server.NewEndpoint atomically aligns that declaration, per-request negotiation, output modes, and Registry-backed projection; there is no supported Driver-direct executor assembly. See the v1 extension contract for negotiation, carriers, schema, decoder helpers, and persistence semantics. Durable events enter task artifacts; streamed status metadata retains its latest envelope in task metadata, so wrappers must treat both paths as stored/push-visible data. Other extension implementations use the same immutable declaration, exact-version negotiation, codec/input/projection, and hard-limit contracts in pkg/extension; extension code cannot replace the endpoint's A2A 1.0 negotiation rules.

Development

go test ./...           # unit + in-process round-trip tests
go test -race ./...
go vet ./...

The container interoperability suite is a nested module so Testcontainers and Docker dependencies do not enter the adapter module. Ordinary CI runs its Docker-free contracts with scripts/run-agentmatrix.sh. The dedicated pull-request/push/scheduled/manual scripts/run-agentmatrix-real.sh gate builds an immutable image and selects the applicable C01-C24 protocol, lifecycle, recovery, provider-fault, concurrency, delivery-commit, and hygiene cases plus the ordered A2A cycle Claude → Codex → OpenCode → Claude. It runs real pinned Claude 2.1.217, Codex 0.144.4, and OpenCode 1.18.4 CLIs against provider-specific deterministic model servers. On 2026-07-26 the expanded C09-C24 fresh-image matrix passed on sha256:3306e9e6b983238fa31134f620cdd54dac029065ddb46b239e7a5ea6d89a8e7a (go test: 946.235s), including verified C24 aggregate hygiene. See integration/agentmatrix/README.md for the isolation, causality, redaction, and optional live-model boundaries. The Agent CLIs are real, but their model backends are deterministic scripted servers. An optional live-model gate is not implemented and is not a deterministic release blocker.

The pinned official 1.0.0.alpha2 TCK raw report is 91 passed, 2 failed, and 172 skipped. The two failures are locked, reviewed alpha2 known issues accepted by the repository's exact known-issues gate; the raw result is not represented as an official TCK pass.

Real-agent smoke tests are gated behind env vars (A2A_TEST_CLAUDE=1, A2A_TEST_CODEX=1) so the default suite needs no external binary. Offline coverage differs per driver: codex's wire handling has unit tests over hand-written app-server notifications; the claude driver (whose wire parsing lives in the SDK) has lifecycle tests over an injectable stream — stream death, interrupt deadlines, duplicate-terminal suppression. Recorded, sanitized wire fixtures for the real CLI shapes (claude 2.1.211, codex 0.144.4) are committed under each driver's testdata/real/ and replayed offline through the projection layer; a CI gate scans them for secrets. Separate synthetic bidirectional captures replay the approval/control path through the real SDK transports, and bridge tests pin agent.Event → A2A projection across JSON, protobuf, streaming, unary, and stored-task paths.

Linting uses golangci-lint v2 (the repo .golangci.yml is v2 format; a v1 binary refuses it):

golangci-lint run ./...

Release builds inject the version and strip local paths (the || echo dev fallback matters: in a source tarball git describe yields nothing, and an empty version would otherwise be injected):

go build -trimpath \
  -ldflags "-X github.com/vibe-agi/a2a-adapter/internal/version.Version=$(git describe --tags --always 2>/dev/null || echo dev)" \
  -o bin/a2a-adapter ./cmd/a2a-adapter
bin/a2a-adapter --version
Claude SDK compatibility package

Claude image input and the adapter's approval/interrupt lifecycle need SDK behavior that upstream roasbeef/claude-agent-sdk-go does not yet expose. internal/third_party/claude-agent-sdk-go is therefore a source-compatible package kept inside the root module, rather than a machine-local replacement or an unpublished nested module. Its exact upstream base, retained patches, and removal criteria are recorded in FORK.md.

Directories

Path Synopsis
cmd
a2a-adapter command
Command a2a-adapter wraps a local agent and serves it over standard A2A.
Command a2a-adapter wraps a local agent and serves it over standard A2A.
conformance
tckreportcheck command
Command tckreportcheck verifies that a raw report from the pinned prerelease A2A TCK contains exactly the two reviewed alpha2 harness defects and no other test failures.
Command tckreportcheck verifies that a raw report from the pinned prerelease A2A TCK contains exactly the two reviewed alpha2 harness defects and no other test failures.
tcksut command
Command tcksut starts the hermetic System Under Test used only by the pinned official A2A conformance runner.
Command tcksut starts the hermetic System Under Test used only by the pinned official A2A conformance runner.
tcksut/fixture
Package fixture contains the hermetic, conformance-only agent used when the official A2A TCK exercises this repository.
Package fixture contains the hermetic, conformance-only agent used when the official A2A TCK exercises this repository.
internal
agentguard
Package agentguard is the defensive synchronous-call boundary around third-party agent.Driver and agent.Session implementations.
Package agentguard is the defensive synchronous-call boundary around third-party agent.Driver and agent.Session implementations.
requestcontext
Package requestcontext carries adapter-owned request semantics across the a2a-go executor boundary without writing private fields into A2A metadata.
Package requestcontext carries adapter-owned request semantics across the a2a-go executor boundary without writing private fields into A2A metadata.
third_party/claude-agent-sdk-go/cmd/demo command
Demo program for Claude Agent SDK.
Demo program for Claude Agent SDK.
third_party/claude-agent-sdk-go/cmd/example-mcp-server command
Example MCP server demonstrating how to create tools that Claude can use.
Example MCP server demonstrating how to create tools that Claude can use.
version
Package version holds the build version shared by every a2a-adapter binary.
Package version holds the build version shared by every a2a-adapter binary.
wirecap
Package wirecap defines the capture format for driver wire recordings: an immutable, direction-tagged frame log of everything a CLI agent said to us and everything we said to it.
Package wirecap defines the capture format for driver wire recordings: an immutable, direction-tagged frame log of everything a CLI agent said to us and everything we said to it.
pkg
agent
Package agent defines the SDK-agnostic core abstractions for wrapping a local CLI coding agent (claude, codex, ...) so it can be exposed over A2A.
Package agent defines the SDK-agnostic core abstractions for wrapping a local CLI coding agent (claude, codex, ...) so it can be exposed over A2A.
agent/agenttest
Package agenttest provides a hermetic conformance harness for implementations of agent.Driver and agent.Session.
Package agenttest provides a hermetic conformance harness for implementations of agent.Driver and agent.Session.
bridge
Package bridge projects the portable agent/sessionregistry model into the standard A2A task, message, artifact, streaming, cancellation, and negotiated extension semantics implemented by a2a-go.
Package bridge projects the portable agent/sessionregistry model into the standard A2A task, message, artifact, streaming, cancellation, and negotiated extension semantics implemented by a2a-go.
bridge/bridgetest
Package bridgetest provides public conformance harnesses for implementations injected into pkg/bridge.
Package bridgetest provides public conformance harnesses for implementations injected into pkg/bridge.
drivers/claude
Package claude wraps the Anthropic Claude Code CLI as an a2a-adapter Driver.
Package claude wraps the Anthropic Claude Code CLI as an a2a-adapter Driver.
drivers/codex
Package codex wraps the OpenAI Codex CLI as an a2a-adapter Driver.
Package codex wraps the OpenAI Codex CLI as an a2a-adapter Driver.
drivers/echo
Package echo is a trivial in-process Driver used to validate the adapter skeleton end-to-end without depending on any external agent binary.
Package echo is a trivial in-process Driver used to validate the adapter skeleton end-to-end without depending on any external agent binary.
extension
Package extension implements the common runtime contract for negotiated A2A protocol extensions.
Package extension implements the common runtime contract for negotiated A2A protocol extensions.
extension/extensiontest
Package extensiontest provides a public conformance harness for A2A extension implementations.
Package extensiontest provides a public conformance harness for A2A extension implementations.
lifecycle
Package lifecycle defines the adapter's bounded, payload-free observation seam.
Package lifecycle defines the adapter's bounded, payload-free observation seam.
lifecycle/lifecycletest
Package lifecycletest provides a hermetic public contract harness for lifecycle.Observer implementations used by embedding applications.
Package lifecycletest provides a hermetic public contract harness for lifecycle.Observer implementations used by embedding applications.
pushdelivery
Package pushdelivery defines the durable delivery seam used by an A2A task store and a push-notification worker.
Package pushdelivery defines the durable delivery seam used by an A2A task store and a push-notification worker.
pushdelivery/pushdeliverytest
Package pushdeliverytest provides public conformance harnesses for the independently replaceable push-delivery assembly seams.
Package pushdeliverytest provides public conformance harnesses for the independently replaceable push-delivery assembly seams.
server
Package server assembles a driver, the bridge, and the a2a-go protocol handlers into a standard A2A endpoint.
Package server assembles a driver, the bridge, and the a2a-go protocol handlers into a standard A2A endpoint.
server/servertest
Package servertest contains public conformance harnesses for server embedding interfaces.
Package servertest contains public conformance harnesses for server embedding interfaces.
sessionregistry
Package sessionregistry defines the immutable registration and authenticated conversation identity consumed by the session registry.
Package sessionregistry defines the immutable registration and authenticated conversation identity consumed by the session registry.
sessionregistry/registrytest
Package registrytest provides hermetic contract harnesses for implementations injected into sessionregistry.
Package registrytest provides hermetic contract harnesses for implementations injected into sessionregistry.
sessionstore
Package sessionstore defines the durable checkpoint contract used to resume a local agent session after its live process has been retired or the adapter has restarted.
Package sessionstore defines the durable checkpoint contract used to resume a local agent session after its live process has been retired or the adapter has restarted.
sessionstore/sessionstoretest
Package sessionstoretest provides the authoritative conformance harness for third-party sessionstore.ResumeStore implementations.
Package sessionstoretest provides the authoritative conformance harness for third-party sessionstore.ResumeStore implementations.
store
Package store defines optional task-store capabilities used by the adapter.
Package store defines optional task-store capabilities used by the adapter.
store/sqlstore
Package sqlstore is a persistent task, push-configuration, and session-checkpoint store backed by database/sql, so a single A2A server's tasks, callbacks, and resumable agent context can survive a restart.
Package sqlstore is a persistent task, push-configuration, and session-checkpoint store backed by database/sql, so a single A2A server's tasks, callbacks, and resumable agent context can survive a restart.
store/storetest
Package storetest provides public conformance harnesses for task stores used by the adapter.
Package storetest provides public conformance harnesses for task stores used by the adapter.
transport
Package transport abstracts endpoint binding, the externally advertised URL, outbound dialing, and authenticated peer identity.
Package transport abstracts endpoint binding, the externally advertised URL, outbound dialing, and authenticated peer identity.
transport/local
Package local is a plain-TCP Transport for development and LAN use.
Package local is a plain-TCP Transport for development and LAN use.
transport/securetls
Package securetls is a standard-TLS Transport built on crypto/tls.
Package securetls is a standard-TLS Transport built on crypto/tls.
transport/transporttest
Package transporttest provides the public conformance harness for transport.Transport implementations.
Package transporttest provides the public conformance harness for transport.Transport implementations.
scripts
internal/agentmatrixlock command
Command agentmatrixlock audits the npm manifest and lockfile used to build the hermetic agent-matrix image.
Command agentmatrixlock audits the npm manifest and lockfile used to build the hermetic agent-matrix image.
internal/agentmatrixtarget command
Command agentmatrixtarget is a test-only A2A endpoint for the real-Agent container matrix.
Command agentmatrixtarget is a test-only A2A endpoint for the real-Agent container matrix.
internal/externalgate command
Command externalgate verifies that an independently packaged conformance module both discovers and actually executes every test in its release manifest.
Command externalgate verifies that an independently packaged conformance module both discovers and actually executes every test in its release manifest.
internal/modproxy command
Command modproxy writes one source directory as a local Go module proxy version.
Command modproxy writes one source directory as a local Go module proxy version.
internal/opencodereplay command
Command opencodereplay exposes one strict OpenCode wirecap transcript over a real local HTTP/SSE listener.
Command opencodereplay exposes one strict OpenCode wirecap transcript over a real local HTTP/SSE listener.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL