# Use these docs with AI (/docs/ai)
Everything on this site is available in forms that AI assistants and coding agents can read without scraping HTML.
## llms.txt and llms-full.txt [#llmstxt-and-llms-fulltxt]
| URL | What it is |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| [`/llms.txt`](/llms.txt) | A short index of every page with its description and link, following the [llms.txt](https://llmstxt.org) convention |
| [`/llms-full.txt`](/llms-full.txt) | The full text of all pages in one Markdown file, for loading into a long context |
Point your assistant at `/llms.txt` so it can find the right page, or paste `/llms-full.txt` when you want it to know the whole project.
## Markdown for every page [#markdown-for-every-page]
Append `.md` to any docs URL to get that page as clean Markdown:
```bash
curl https://a2a-wrapper.allsrc.dev/docs/getting-started/quickstart.md
```
You can also ask for Markdown with the `Accept` header. The same URL then returns Markdown instead of HTML:
```bash
curl -H 'Accept: text/markdown' https://a2a-wrapper.allsrc.dev/docs/getting-started/quickstart
```
This is content negotiation, so crawlers and agents that request Markdown get it automatically.
## Copy as Markdown, and open in an assistant [#copy-as-markdown-and-open-in-an-assistant]
Every page has a **Copy Markdown** button and an **Open** menu that hands the page to an assistant or to GitHub, so you can ask questions about the page you are reading.
## Docs MCP server [#docs-mcp-server]
Coding agents can query these docs directly through an [MCP server](/docs/ai/mcp-server) at `/mcp`, with tools to search pages and read them.
## Why this matters for a2a-wrapper [#why-this-matters-for-a2a-wrapper]
The wrappers exist so agents can work with agents. The documentation follows the same idea: an agent that is asked to set up an A2A server should be able to read the docs itself and get it right.
The site is fully public and `robots.txt` allows all crawlers, including AI crawlers.
# Docs MCP server (/docs/ai/mcp-server)
The documentation is also available as a read-only [MCP](https://modelcontextprotocol.io) server. Your coding agent can search the docs and read pages on its own while it works.
**Endpoint:** `https://a2a-wrapper.allsrc.dev/mcp` (Streamable HTTP, stateless, no authentication, read-only)
## Connect [#connect]
```bash
claude mcp add --transport http a2a-wrapper-docs https://a2a-wrapper.allsrc.dev/mcp
```
Add a remote HTTP MCP server with the URL above. The exact configuration varies by client, for example:
```json
{
"mcpServers": {
"a2a-wrapper-docs": { "url": "https://a2a-wrapper.allsrc.dev/mcp" }
}
}
```
## Tools [#tools]
| Tool | Arguments | Returns |
| ------------- | --------------------------------------------------------- | --------------------------------------------------------------- |
| `search_docs` | `query` (string), `limit` (number, default 5) | Matching pages with path, description and URL, best match first |
| `get_page` | `path` (string), for example `getting-started/quickstart` | The full Markdown of that page |
| `list_pages` | none | Every page with its path, title and description |
## Try it with curl [#try-it-with-curl]
```bash
curl -s https://a2a-wrapper.allsrc.dev/mcp -H 'content-type: application/json' -d '{
"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": { "name": "search_docs", "arguments": { "query": "sandbox mode" } }
}'
```
## Notes [#notes]
* The server is stateless and read-only. It never writes anything.
* It returns the same content as [`/llms-full.txt`](/docs/ai), organised for retrieval.
* For the project's own agents, this is separate from the [sub-agent MCP bridge](/docs/concepts/sub-agents), which connects a2a-wrapper agents to other agents.
# a2a-antigravity changelog (/docs/community/changelog/a2a-antigravity)
> This page is generated from [`a2a-antigravity/CHANGELOG.md`](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/a2a-antigravity/CHANGELOG.md) and stays in sync with the repository.
## 0.2.2 [#022]
### Patch Changes [#patch-changes]
* 4e2bd82: No code changes. A prior release run published every package's exact
target version under the `canary` npm dist-tag instead of `latest` (the
`canary` job's snapshot step no-ops when there are no pending changesets,
which was the case immediately after that Version Packages PR merged —
see the canary-job fix in a preceding commit). Since npm never allows
republishing an already-used version string, the only way to get a
`latest`-tagged release out is a fresh patch bump.
* Updated dependencies \[4e2bd82]
* @a2a-wrapper/core\@2.1.1
## 0.2.1 [#021]
### Minor Changes [#minor-changes]
* d6c2701: Add native A2A protocol v1.0 support, built on `@a2a-js/sdk@1.0.0`, while preserving full backward compatibility with A2A v0.3.x clients.
**`@a2a-wrapper/core` (breaking):**
* `buildAgentCard()` now produces the native v1.0 `AgentCard` shape (`supportedInterfaces[]` instead of `url`/`additionalInterfaces`/`protocolVersion`; `capabilities.extendedAgentCard` instead of `supportsAuthenticatedExtendedCard`; `stateTransitionHistory` removed). Callers reading the old v0.3 fields directly off the return value must update — the server itself still serves a fully v0.3-shaped card to legacy clients (see below).
* `createA2AServer()`'s JSON-RPC/REST transports and the `/.well-known/agent-card.json` route now negotiate protocol version per-request via the `A2A-Version` header: `1.0`/absent-with-recent-header gets the native v1.0 card and wire shapes, while `0.3`/no-header-at-all (matching the SDK's own default) gets a fully v0.3-shaped card and JSON-RPC/REST translation via `@a2a-js/sdk`'s `compat/v0_3` layer.
* `ServerOptions.protocolVersion` default changed from `"0.3"` to `"1.0"`.
* New `ServerOptions.onListening` hook and `ServerOptions.agentCardSigning` option (Signed Agent Cards, RFC 7515 JWS, opt-in via `AgentCardConfig.signing` or the programmatic option).
* New `extractUserText(message)` export centralizing inbound `Part` parsing for the v1.0 proto-oneof `Part` shape.
* `TaskState` is now re-exported as a value (real numeric enum in v1.0, was a string-literal type in v0.3).
* `@a2a-js/sdk` peer/dev dependency bumped to `^1.0.0`.
**Wrapper packages (`a2a-claude`, `a2a-codex`, `a2a-copilot`, `a2a-opencode`, `a2a-antigravity`):**
* Each wrapper's `createA2AServer()` public API is unchanged, but is now a thin delegate to `@a2a-wrapper/core`'s `createA2AServer` instead of duplicating Express/SDK server wiring — restoring the "A2A protocol code lives only in core" design.
* `a2a-copilot` and `a2a-opencode` no longer ship their own local `event-publisher.ts`; both now use `@a2a-wrapper/core`'s (v1.0-aware) event publisher.
* `@a2a-js/sdk` dependency bumped to `^1.0.0` (previously an inconsistent mix of `^0.3.9`/`^0.3.13`).
### Patch Changes [#patch-changes-1]
* Updated dependencies \[f3c7062]
* Updated dependencies \[41e2d82]
* Updated dependencies \[d6c2701]
* @a2a-wrapper/core\@2.1.0
## 0.1.1 [#011]
### Patch Changes [#patch-changes-2]
Initial release of `a2a-antigravity`, an A2A protocol wrapper for Google Antigravity.
This package exposes Antigravity as a standalone HTTP A2A agent while keeping the public wrapper in Node/TypeScript. The Google Antigravity SDK remains isolated behind a private Python subprocess and JSONL bridge.
**Features:**
* **A2A-compliant server** - Agent Card, JSON-RPC, REST, and health endpoints via `@a2a-js/sdk`.
* **Google Antigravity SDK integration** - backed by `google-antigravity==0.1.5` through a private Python bridge.
* **Managed Python setup** - `a2a-antigravity setup` creates a virtual environment, installs bundled requirements, and verifies `google.antigravity` imports.
* **Gemini and Vertex auth** - supports Gemini API key auth and Vertex/ADC project/location configuration.
* **Tested Flash-Lite model** - example configs use `gemini-3.1-flash-lite`.
* **Command policy examples** - default config keeps SDK command policy defaults; `config.run-command.json` demonstrates trusted-workspace `run_command` enablement.
* **Sideband trace artifacts** - lifecycle, tool-start, usage, error, and response artifacts are mapped into A2A trace artifacts.
* **MCP passthrough** - stdio and Streamable HTTP MCP server configs are translated into Antigravity SDK config.
* **Memory materialization** - memory files are written to `AGENTS.md` and `.antigravity/skills/` before session startup.
* **A2A sub-agent delegation** - sub-agents are exposed through the shared `a2a-mcp-skillmap` bridge.
* **Subprocess lifecycle hardening** - bridge spawn errors, unexpected exits, write failures, shutdown, and signal handling are surfaced cleanly instead of hanging requests.
* **CI coverage** - GitHub Actions verifies TypeScript build/test plus managed Python setup and package contents before publish.
**Operational notes:**
* The default A2A port is `3040`, intentionally offset from `a2a-claude` on `3030`.
* The npm package does not run `pip` during install. Run `a2a-antigravity setup` once before starting the server, or provide your own Python with `ANTIGRAVITY_PYTHON`.
* `run_command` is denied by the Antigravity SDK default policy unless explicitly allowed.
# a2a-claude changelog (/docs/community/changelog/a2a-claude)
> This page is generated from [`a2a-claude/CHANGELOG.md`](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/a2a-claude/CHANGELOG.md) and stays in sync with the repository.
## 0.4.1 [#041]
### Patch Changes [#patch-changes]
* 4e2bd82: No code changes. A prior release run published every package's exact
target version under the `canary` npm dist-tag instead of `latest` (the
`canary` job's snapshot step no-ops when there are no pending changesets,
which was the case immediately after that Version Packages PR merged —
see the canary-job fix in a preceding commit). Since npm never allows
republishing an already-used version string, the only way to get a
`latest`-tagged release out is a fresh patch bump.
* Updated dependencies \[4e2bd82]
* @a2a-wrapper/core\@2.1.1
## 0.4.0 [#040]
### Minor Changes [#minor-changes]
* f3c7062: Handle Claude rate limit events instead of failing the turn generically.
Rate-limit signals were previously unrecognised: `rate_limit_event`,
`system/api_retry`, and assistant `rate_limit` errors all fell through to a
debug log, and the turn surfaced as `failed` with `"Error during execution."`
A rejection now ends the turn immediately with an `input-required` status naming
the limit type and reset time, plus structured metadata (`reason`,
`rateLimitType`, `resetsAt`, `resetsAtIso`, `utilization`, and `errorCode` /
`canPurchaseCredits` when the SDK reports them). The task stays non-terminal, so
the client continues the same conversation on the same task once the limit
resets. Configurable via `rateLimit.taskState` for clients that require a
terminal state.
This does not shorten the SDK's own retry behaviour: a `system/api_retry` is
deliberately treated as a warning that does not end the turn, so the SDK's
internal retries still run to exhaustion inside the same `timeouts.prompt`
window. The turn ends only once the SDK gives up and emits an assistant
`rate_limit` error. Those retries are at least visible now, as `rate_limit`
sideband events with `action: "retrying"` carrying the SDK's `attempt`,
`maxRetries`, and `delayMs`.
A rejection whose overage window is still open is treated as a warning rather
than a rejection, since the request may proceed on overage credits; if it does
not, the assistant `rate_limit` error still ends the turn. Limit details are
never fabricated — a limit type or reset time is inherited by a later signal
only from a snapshot that reported pressure, utilization is never inherited, and
a reset time that is not in the future is dropped.
Adds a `rate_limit` sideband event type to `@a2a-wrapper/core`, gated by
`features.emitRateLimitEvents`.
* 41e2d82: Hold the A2A Task open while Claude has background work in flight.
A Task used to reach a terminal state as soon as Claude's first turn ended —
even when that turn had just started a background process and said it was
waiting on the result. A2A gives an agent no way to open a new turn against a
terminal Task, so the follow-up report had nowhere to land.
The Task now stays in `working` for as long as Claude reports background work
running, and completes only once a turn ends with nothing left. Each turn
publishes its own `response` artifact and a non-final `working` status update
whose `metadata.backgroundTasks` lists what is still in flight. Chains of any
length work this way, as rounds of one Task rather than several Tasks.
Controlled by `features.holdTaskForBackgroundWork` (default `true`; set
`false` for the old complete-at-first-result behavior) and
`features.emitBackgroundTaskEvents` (default `true`), which publishes a new
`background_tasks` sideband event — added to `@a2a-wrapper/core` — each time
the live set changes.
Bumps `@anthropic-ai/claude-agent-sdk` from `0.3.202` to `0.3.245`. The
feature needs at least `0.3.235`, the first version to emit
`background_tasks_changed`.
Three changes apply even with `holdTaskForBackgroundWork` off:
* Queries now use streaming input rather than a string prompt. A string prompt
makes the SDK close the CLI subprocess's stdin on the first result, which
ends the process before a second round is possible. This is not switchable.
* `agent_started` / `agent_finished` are emitted once per A2A Task rather than
once per SDK turn.
* A success result with empty text no longer publishes an empty `response`
artifact.
See the a2a-claude README for caveats, including how `claude.maxTurns` and
`timeouts.prompt` now span a held-open Task's rounds.
* ddc0861: Allow `timeouts.prompt` to be disabled by setting it to `0` (or any value `<= 0`). Previously every turn was bounded at ten minutes by default with no way to opt out, which cut off legitimately long-running turns.
Negative values were also a footgun: `setTimeout` coerces a negative delay to the next tick, so `prompt: -1` — the intuitive spelling for "no timeout" — aborted the turn immediately instead of disabling the bound. The timer is now armed only for positive values, matching the "set to `0` to disable" convention already used for `healthCheck`.
* a9101a1: **Breaking:** a rate limit now always fails the task. The `rateLimit.taskState`
config option is removed.
It previously defaulted to `input-required`, on the reasoning that leaving the
task open let the client continue the same conversation. That reasoning does not
hold:
* **The interrupted turn cannot resume.** `ctx.task` only suppresses the initial
`Task` record; the prompt sent is always the new user message, and there is no
replay or continue-previous-turn logic. A follow-up on the open task is just a
new prompt appended to the conversation — exactly what a new task would send.
* **Continuity never depended on the task state.** It comes from the
`contextId` → Claude session mapping in `SessionManager`, which is indifferent
to how a task ended. A new task on the same `contextId` resumes the identical
Claude session.
* **`input-required` means the agent lacks information.** A rate limit lacks
quota; nothing the client sends unblocks it, only elapsed time. Clients with
generic `input-required` handling would prompt a human for input nobody wants.
* Publishing a non-terminal state alongside `final: true` was also internally
inconsistent.
The status message now always points at the `contextId` rather than the closed
task, and still carries the structured metadata (`reason`, `rateLimitType`,
`resetsAt`, `resetsAtIso`, `utilization`) that an orchestrator needs to schedule
its own retry. `features.emitRateLimitEvents` is unchanged.
Migration: remove any `rateLimit` block from your config. If your orchestrator
alerts on failed tasks, key the suppression on `metadata.reason === "rate_limit"`.
* 3ec9e49: **Breaking:** session expiry is now disabled by default (`session.ttl` defaults
to `0`), and the background cleanup sweep with it (`session.cleanupInterval`
also defaults to `0`). Set `"session.ttl": 3600000` and
`"session.cleanupInterval": 300000` to restore the previous behaviour.
Previously sessions expired one hour after their **first** message regardless of
activity, which silently dropped the `contextId` → Claude session mapping and
made a conversation lose all of its context with no error and only an
`info`-level log line. Evicting the record reclaimed no disk either — the wrapper
never deletes SDK session files, so eviction orphaned the transcript rather than
removing it.
`session.ttl <= 0` now disables expiry in both eviction paths. A positive `ttl`
behaves as before.
`cleanupInterval` is only ever consulted when `ttl > 0` — with expiry off there
is nothing to sweep — so leaving it at `300000` alongside `ttl: 0` was dead
configuration that read as if it did something. It now defaults to `0` to match,
and both example configs drop it entirely. If you set `ttl` on its own you get
expiry via the lazy check in `getOrCreate`, but a context that is never used
again holds its record until the process exits; set `cleanupInterval` too if you
want that reclaimed. Both disabled states are now logged at startup, so neither
can be lost silently.
Known consequence: if a Claude session's on-disk transcript is removed while the
server is running, the stored `contextId` → `sessionId` mapping is now pinned for
the life of the process instead of being evicted within the hour, so turns on
that context keep failing to resume until the server restarts. Previously the
one-hour expiry masked this by self-healing.
* b3673aa: Add marketplace plugin support: `claude.marketplaces` and `claude.enabledPlugins` map to the SDK's flag-tier `settings` (`extraKnownMarketplaces` / `enabledPlugins`), so the SDK fetches and installs plugins itself — no pre-baked plugin directories, and no dependence on `settingSources` (marketplace plugins load even under full isolation).
Because the SDK installs marketplace plugins asynchronously by default — installing nothing, reporting no error, and loading zero plugins on every subsequent run — the wrapper sets `CLAUDE_CODE_SYNC_PLUGIN_INSTALL=1` for those sessions and runs a startup preflight that verifies every enabled plugin actually loaded, failing `initialize()` with the missing plugin names if not. The preflight diffs the session init message's plugin list rather than the `plugin_install` events, which report per-marketplace status and so read as successful even when the named plugin does not exist. The probe costs no tokens (init precedes any model call) and warms the plugin cache.
* 9e47b08: Add `claude.effort` and `claude.thinking`, mapping onto the Claude Agent SDK's `Options.effort` and `Options.thinking`. `effort` accepts `low`/`medium`/`high`/`xhigh`/`max` and is also settable via the `CLAUDE_EFFORT` environment variable; `thinking` accepts `{ type: "adaptive" }`, `{ type: "enabled", budgetTokens?, display? }`, or `{ type: "disabled" }`.
Both are additive and optional — `claude.model` remains a plain string and `claude.fallbackModel` is unchanged. Values are validated at `initialize()`, so an unsupported effort level or a malformed `thinking` object fails at startup with a message naming the allowed values rather than surfacing as an SDK error mid-task.
Also fixes `features.emitThinkingEvents` producing no events at all. The SDK leaves `thinking.display` at `"omitted"`, so thinking blocks arrive with an empty string and the mapper's non-empty guard drops every one of them. When thinking events are enabled the wrapper now requests `display: "summarized"` — supplying a full `{ type: "adaptive", display: "summarized" }` when no thinking config is set, and filling in `display` on an explicit `adaptive`/`enabled` config that omits it. An explicit `display` (including `"omitted"`) and `{ type: "disabled" }` are both left untouched. Note that with thinking events on and no `claude.thinking` of your own, this turns adaptive thinking on and it costs thinking tokens.
* d6c2701: Add native A2A protocol v1.0 support, built on `@a2a-js/sdk@1.0.0`, while preserving full backward compatibility with A2A v0.3.x clients.
**`@a2a-wrapper/core` (breaking):**
* `buildAgentCard()` now produces the native v1.0 `AgentCard` shape (`supportedInterfaces[]` instead of `url`/`additionalInterfaces`/`protocolVersion`; `capabilities.extendedAgentCard` instead of `supportsAuthenticatedExtendedCard`; `stateTransitionHistory` removed). Callers reading the old v0.3 fields directly off the return value must update — the server itself still serves a fully v0.3-shaped card to legacy clients (see below).
* `createA2AServer()`'s JSON-RPC/REST transports and the `/.well-known/agent-card.json` route now negotiate protocol version per-request via the `A2A-Version` header: `1.0`/absent-with-recent-header gets the native v1.0 card and wire shapes, while `0.3`/no-header-at-all (matching the SDK's own default) gets a fully v0.3-shaped card and JSON-RPC/REST translation via `@a2a-js/sdk`'s `compat/v0_3` layer.
* `ServerOptions.protocolVersion` default changed from `"0.3"` to `"1.0"`.
* New `ServerOptions.onListening` hook and `ServerOptions.agentCardSigning` option (Signed Agent Cards, RFC 7515 JWS, opt-in via `AgentCardConfig.signing` or the programmatic option).
* New `extractUserText(message)` export centralizing inbound `Part` parsing for the v1.0 proto-oneof `Part` shape.
* `TaskState` is now re-exported as a value (real numeric enum in v1.0, was a string-literal type in v0.3).
* `@a2a-js/sdk` peer/dev dependency bumped to `^1.0.0`.
**Wrapper packages (`a2a-claude`, `a2a-codex`, `a2a-copilot`, `a2a-opencode`, `a2a-antigravity`):**
* Each wrapper's `createA2AServer()` public API is unchanged, but is now a thin delegate to `@a2a-wrapper/core`'s `createA2AServer` instead of duplicating Express/SDK server wiring — restoring the "A2A protocol code lives only in core" design.
* `a2a-copilot` and `a2a-opencode` no longer ship their own local `event-publisher.ts`; both now use `@a2a-wrapper/core`'s (v1.0-aware) event publisher.
* `@a2a-js/sdk` dependency bumped to `^1.0.0` (previously an inconsistent mix of `^0.3.9`/`^0.3.13`).
### Patch Changes [#patch-changes-1]
* Updated dependencies \[f3c7062]
* Updated dependencies \[41e2d82]
* Updated dependencies \[d6c2701]
* @a2a-wrapper/core\@2.1.0
## 0.2.0 [#020]
### Minor Changes [#minor-changes-1]
* 219f014: New wrapper: a2a-claude exposes Claude Code (via @anthropic-ai/claude-agent-sdk) as a spec-compliant A2A server — codex-parity feature set: streaming, per-context session resume, cancellation, permission-mode guardrails, MCP servers, A2A sub-agent delegation, memory materialization, and sideband observability events.
# a2a-codex changelog (/docs/community/changelog/a2a-codex)
> This page is generated from [`a2a-codex/CHANGELOG.md`](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/a2a-codex/CHANGELOG.md) and stays in sync with the repository.
## 1.7.2 [#172]
### Patch Changes [#patch-changes]
* 4e2bd82: No code changes. A prior release run published every package's exact
target version under the `canary` npm dist-tag instead of `latest` (the
`canary` job's snapshot step no-ops when there are no pending changesets,
which was the case immediately after that Version Packages PR merged —
see the canary-job fix in a preceding commit). Since npm never allows
republishing an already-used version string, the only way to get a
`latest`-tagged release out is a fresh patch bump.
* Updated dependencies \[4e2bd82]
* @a2a-wrapper/core\@2.1.1
## 1.7.1 [#171]
### Minor Changes [#minor-changes]
* d6c2701: Add native A2A protocol v1.0 support, built on `@a2a-js/sdk@1.0.0`, while preserving full backward compatibility with A2A v0.3.x clients.
**`@a2a-wrapper/core` (breaking):**
* `buildAgentCard()` now produces the native v1.0 `AgentCard` shape (`supportedInterfaces[]` instead of `url`/`additionalInterfaces`/`protocolVersion`; `capabilities.extendedAgentCard` instead of `supportsAuthenticatedExtendedCard`; `stateTransitionHistory` removed). Callers reading the old v0.3 fields directly off the return value must update — the server itself still serves a fully v0.3-shaped card to legacy clients (see below).
* `createA2AServer()`'s JSON-RPC/REST transports and the `/.well-known/agent-card.json` route now negotiate protocol version per-request via the `A2A-Version` header: `1.0`/absent-with-recent-header gets the native v1.0 card and wire shapes, while `0.3`/no-header-at-all (matching the SDK's own default) gets a fully v0.3-shaped card and JSON-RPC/REST translation via `@a2a-js/sdk`'s `compat/v0_3` layer.
* `ServerOptions.protocolVersion` default changed from `"0.3"` to `"1.0"`.
* New `ServerOptions.onListening` hook and `ServerOptions.agentCardSigning` option (Signed Agent Cards, RFC 7515 JWS, opt-in via `AgentCardConfig.signing` or the programmatic option).
* New `extractUserText(message)` export centralizing inbound `Part` parsing for the v1.0 proto-oneof `Part` shape.
* `TaskState` is now re-exported as a value (real numeric enum in v1.0, was a string-literal type in v0.3).
* `@a2a-js/sdk` peer/dev dependency bumped to `^1.0.0`.
**Wrapper packages (`a2a-claude`, `a2a-codex`, `a2a-copilot`, `a2a-opencode`, `a2a-antigravity`):**
* Each wrapper's `createA2AServer()` public API is unchanged, but is now a thin delegate to `@a2a-wrapper/core`'s `createA2AServer` instead of duplicating Express/SDK server wiring — restoring the "A2A protocol code lives only in core" design.
* `a2a-copilot` and `a2a-opencode` no longer ship their own local `event-publisher.ts`; both now use `@a2a-wrapper/core`'s (v1.0-aware) event publisher.
* `@a2a-js/sdk` dependency bumped to `^1.0.0` (previously an inconsistent mix of `^0.3.9`/`^0.3.13`).
### Patch Changes [#patch-changes-1]
* Updated dependencies \[f3c7062]
* Updated dependencies \[41e2d82]
* Updated dependencies \[d6c2701]
* @a2a-wrapper/core\@2.1.0
## 1.6.1 [#161]
### Patch Changes [#patch-changes-2]
* Updated dependencies
* @a2a-wrapper/core\@1.7.0
## 1.6.0 [#160]
### Minor Changes [#minor-changes-1]
Initial release of `a2a-codex` — an A2A protocol wrapper for the OpenAI Codex SDK.
**Features:**
* **A2A-compliant server** — JSON-RPC and REST transports via `@a2a-js/sdk`, Agent Card at `/.well-known/agent-card.json`, health endpoint at `/health`
* **OpenAI Codex SDK integration** — backs every A2A task with a Codex thread (`@openai/codex-sdk`); supports `workspace-write`, `read-only`, and `danger-full-access` sandbox modes
* **Multi-turn context continuity** — each A2A `contextId` maps to a persistent Codex thread; turns are serialized per-context via a promise queue
* **AbortController cancellation** — `cancelTask` aborts the in-flight `runStreamed` call and publishes a `canceled` status
* **MCP tool support** — stdio and http transports; config baked at SDK construction time; `${ENV_VAR}` substitution in args/env/headers
* **Multi-agent delegation** — A2A sub-agents auto-bootstrapped via `bootstrapSubAgents` from `@a2a-wrapper/core`; synthesized as an MCP server entry before client construction
* **Sideband events** — reasoning summaries, command events, file-change events, and trace artifacts emitted through `AgentEventEmitter`
* **Streaming artifacts** — opt-in delta streaming via `features.streamArtifactChunks`; buffered artifact mode (Inspector-compatible) by default
* **Memory materialization** — memory files written to workspace before each session
* **JSON config** — `config.json` driven with `${ENV_VAR}` token substitution; precedence: defaults ← file ← env ← CLI
* **CLI** — `a2a-codex --config agents/example/config.json`; individual flags: `--port`, `--workspace`, `--model`, `--sandbox`, `--log-level`, etc.
* **Context API** — `GET /context` to read a context file; `POST /context/build` to generate one via a read-only Codex thread
* **Bundled example agents** — `agents/example/` (workspace engineer), `agents/read-only-reviewer/` (code review), `agents/multi-agent/` (lead engineer with sub-agent delegation)
# a2a-copilot changelog (/docs/community/changelog/a2a-copilot)
> This page is generated from [`a2a-copilot/CHANGELOG.md`](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/a2a-copilot/CHANGELOG.md) and stays in sync with the repository.
## 1.8.2 [#182]
### Patch Changes [#patch-changes]
* 4e2bd82: No code changes. A prior release run published every package's exact
target version under the `canary` npm dist-tag instead of `latest` (the
`canary` job's snapshot step no-ops when there are no pending changesets,
which was the case immediately after that Version Packages PR merged —
see the canary-job fix in a preceding commit). Since npm never allows
republishing an already-used version string, the only way to get a
`latest`-tagged release out is a fresh patch bump.
* Updated dependencies \[4e2bd82]
* @a2a-wrapper/core\@2.1.1
## 1.8.1 [#181]
### Minor Changes [#minor-changes]
* d6c2701: Add native A2A protocol v1.0 support, built on `@a2a-js/sdk@1.0.0`, while preserving full backward compatibility with A2A v0.3.x clients.
**`@a2a-wrapper/core` (breaking):**
* `buildAgentCard()` now produces the native v1.0 `AgentCard` shape (`supportedInterfaces[]` instead of `url`/`additionalInterfaces`/`protocolVersion`; `capabilities.extendedAgentCard` instead of `supportsAuthenticatedExtendedCard`; `stateTransitionHistory` removed). Callers reading the old v0.3 fields directly off the return value must update — the server itself still serves a fully v0.3-shaped card to legacy clients (see below).
* `createA2AServer()`'s JSON-RPC/REST transports and the `/.well-known/agent-card.json` route now negotiate protocol version per-request via the `A2A-Version` header: `1.0`/absent-with-recent-header gets the native v1.0 card and wire shapes, while `0.3`/no-header-at-all (matching the SDK's own default) gets a fully v0.3-shaped card and JSON-RPC/REST translation via `@a2a-js/sdk`'s `compat/v0_3` layer.
* `ServerOptions.protocolVersion` default changed from `"0.3"` to `"1.0"`.
* New `ServerOptions.onListening` hook and `ServerOptions.agentCardSigning` option (Signed Agent Cards, RFC 7515 JWS, opt-in via `AgentCardConfig.signing` or the programmatic option).
* New `extractUserText(message)` export centralizing inbound `Part` parsing for the v1.0 proto-oneof `Part` shape.
* `TaskState` is now re-exported as a value (real numeric enum in v1.0, was a string-literal type in v0.3).
* `@a2a-js/sdk` peer/dev dependency bumped to `^1.0.0`.
**Wrapper packages (`a2a-claude`, `a2a-codex`, `a2a-copilot`, `a2a-opencode`, `a2a-antigravity`):**
* Each wrapper's `createA2AServer()` public API is unchanged, but is now a thin delegate to `@a2a-wrapper/core`'s `createA2AServer` instead of duplicating Express/SDK server wiring — restoring the "A2A protocol code lives only in core" design.
* `a2a-copilot` and `a2a-opencode` no longer ship their own local `event-publisher.ts`; both now use `@a2a-wrapper/core`'s (v1.0-aware) event publisher.
* `@a2a-js/sdk` dependency bumped to `^1.0.0` (previously an inconsistent mix of `^0.3.9`/`^0.3.13`).
### Patch Changes [#patch-changes-1]
* Updated dependencies \[f3c7062]
* Updated dependencies \[41e2d82]
* Updated dependencies \[d6c2701]
* @a2a-wrapper/core\@2.1.0
## 1.7.0 [#170]
### Minor Changes [#minor-changes-1]
* feat: add comprehensive LLM usage telemetry and tracking
### Patch Changes [#patch-changes-2]
* Updated dependencies
* @a2a-wrapper/core\@1.7.0
## 1.6.0 — 2026-06-03 [#160--2026-06-03]
### Added [#added]
* **Bring Your Own Model (BYOK)** — new `copilot.provider` config block lets you point the wrapper at any OpenAI-compatible endpoint instead of GitHub Copilot. Supports Ollama (local), OpenAI, Anthropic, Azure OpenAI, Azure AI Foundry, vLLM, LiteLLM, and any OpenAI-compatible API. The same `COPILOT_PROVIDER_*` env vars the `gh copilot` CLI uses are supported. No GitHub Copilot account required when using a custom provider.
* **New bundled example: `agents/ollama/`** — ready-to-run local agent backed by Ollama (verified with `qwen3.6`).
* **MCP custom headers** — `http` and `sse` MCP server configs now accept a `headers: Record` map. Use for auth tokens and API keys against hosted MCP servers (Linear, Notion, remote GitHub MCP, etc.).
* **Env-var substitution extended** — `${ENV_VAR}` (explicit, recommended) and `$ENV_VAR` (bare, backward-compatible) substitution now applies to stdio `args`, stdio `env` values, and http/sse `headers` values. Keeps secrets out of `config.json`.
### Fixed [#fixed]
* **BYOK models emitting tool calls as plain text** — when a BYOK provider is configured and the model outputs a raw `{"name":...,"arguments":...}` JSON blob instead of a proper response (common with small local models lacking native tool-calling support), the wrapper detects it and returns an actionable error message instead of leaking raw JSON to the caller.
* **Logger level propagation** — `level: "debug"` in config now correctly reaches all child loggers. Previously, child loggers captured the level at module-import time before config was loaded.
### Changed [#changed]
* **`@github/copilot-sdk` upgraded `0.2.2 → 1.0.0`** — migrated to the `RuntimeConnection` factory API; `session.destroy()` renamed to `session.disconnect()`. SDK 1.0.0 bumped CLI dependency to `@github/copilot@1.0.59`.
* **`@github/copilot` CLI upgraded `1.0.39 → 1.0.59`** — includes the v1.0.56 BYOK provider fix (custom providers now work correctly in headless SDK sessions), plus 20 versions of improvements.
* Updated dependencies
* @a2a-wrapper/core\@1.6.0
## 1.5.0 — 2026-05-13 [#150--2026-05-13]
### Added [#added-1]
* **A2A Sub-Agents** — new `subAgents` config section lets the parent agent expose remote A2A agents as MCP tools to the Copilot LLM. The wrapper spawns [`a2a-mcp-skillmap`](https://github.com/shashikanth-gs/a2a-mcp-skillmap) as a stdio MCP server and registers it under the reserved `a2a-subagents` key. Each remote skill becomes a callable tool the LLM can dispatch like any other MCP tool. See `agents/multi-agent/` for an example.
* **Pinned skillmap version** — the synthesized MCP entry invokes `npx -y a2a-mcp-skillmap@` rather than the unpinned package name, so a future skillmap release cannot silently change semantics.
* **JSON schema for `config.json`** — `schemas/agent-config.schema.json` generated from the TypeScript types via `npm run schema`. Drift-detection and schema validation tests ship in CI.
### Changed [#changed-1]
* Updated dependencies
* @a2a-wrapper/core\@1.5.0
## 1.4.0 [#140]
### Added [#added-2]
* **Memory Persistence** — agents can now declare `memory.instructions` and `memory.skills` in their config.json. At startup, the executor materializes these files into the workspace at `.github/copilot-instructions.md` and `.github/skills//`, making them available to the Copilot LLM across sessions. Skills include SKILL.md manifests and optional resource directories (scripts/, references/, assets/).
* **configDir injection** — the CLI now automatically derives and injects `configDir` from the config file path, enabling relative path resolution in memory configs.
### Changed [#changed-2]
* Updated dependencies
* @a2a-wrapper/core\@1.4.0
## 1.3.0 [#130]
### Added [#added-3]
* **Event Transport Integration** — executor and MCP hooks now route all trace events (tool calls, reasoning) through the new `@a2a-wrapper/core` event transport abstraction instead of calling `publishTraceArtifact` directly. Supports A2A sideband (default), HTTP collectors, and custom transports.
* **Agent card delegates to core** — `buildAgentCard()` now delegates to `@a2a-wrapper/core`'s shared implementation, eliminating duplicated card construction logic.
* **MCP hooks refactored** — `McpEvidenceHooks` now uses `AgentEventEmitter` instead of raw `ExecutionEventBus`. Emits separate `tool_call_start` and `tool_call_end` events for better observability granularity.
### Changed [#changed-3]
* **Fix MCP tool permissions hang in headless environments** — added `approveAll` from `@github/copilot-sdk` to `SessionManager.getOrCreate()` and `CopilotExecutor.buildContext()`. Without this, the SDK prompts for human approval on every MCP tool call and hangs indefinitely in Docker, Kubernetes, and CI environments.
* **Default events config** — defaults now include `events: { enabled: true, transport: "a2a" }`.
* Updated dependencies
* @a2a-wrapper/core\@1.3.0
## 1.2.2 [#122]
### Changed [#changed-4]
* Upgrade @github/copilot-sdk from ^0.1.25 to ^0.2.0 (stable release). vscode-jsonrpc is no longer a transitive dependency, resolving the Node 22 ESM crash upstream.
## 1.2.1 [#121]
### Changed [#changed-5]
* Fix post-release bugs: Node 22 ESM resolution (postinstall patch for vscode-jsonrpc), auth error message clarity (GITHUB_TOKEN guidance), README corrections (message/\* method names, messageId in examples), and ResultManager race condition (publish task event before status-update in both executors).
* Updated dependencies
* @a2a-wrapper/core\@1.2.1
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased [#unreleased]
## 1.2.0 - 2026-04-02 [#120---2026-04-02]
### Changed [#changed-6]
* Migrated to monorepo structure with npm workspaces, Turborepo, and Changesets
* Extracted shared infrastructure to `@a2a-wrapper/core` package (logging, config, events, server factory, session management, CLI scaffold)
* Now depends on `@a2a-wrapper/core` for all shared functionality
* Consolidated community docs (CONTRIBUTING, CODE_OF_CONDUCT, SECURITY, LICENSE) to repository root
* Unified CI/CD with Turborepo-powered GitHub Actions workflows
* Switched test script to `vitest --run` for non-interactive CI execution
## 1.0.0 - 2025-02-23 [#100---2025-02-23]
### Added [#added-4]
* A2A v0.3.0 protocol implementation over Express HTTP server
* Agent Card served at `/.well-known/agent-card.json`
* JSON-RPC endpoint at `/a2a/jsonrpc` — `message/send`, `message/stream`, `tasks/get`, `tasks/cancel`
* REST endpoint at `/a2a/rest`
* GitHub Copilot SDK backend (`@github/copilot-sdk`) for LLM inference
* Real-time SSE streaming of status updates and artifact chunks
* Multi-turn conversation support via `contextId` → Copilot session mapping
* MCP tool server support — HTTP and stdio transports
* JSON config file with `$comment` annotations for easy customisation
* Environment variable and CLI argument overrides (priority: defaults ← JSON ← ENV ← CLI)
* `example` and `filesystem-assistant` bundled agent configurations
* Docker support with multi-stage build and corporate proxy CA injection
* `server.sh` lifecycle manager (start / stop / restart / status / logs / foreground)
* Health check endpoint at `/health`
* Context building endpoint at `/context/build`
* `--stream-artifacts` / `--no-stream-artifacts` flag for SSE vs. buffered output
* TypeScript public API exports for programmatic use
[Unreleased]: https://github.com/shashikanth-gs/a2a-wrapper/compare/copilot-v1.0.0...HEAD
[1.0.0]: https://github.com/shashikanth-gs/a2a-wrapper/releases/tag/copilot-v1.0.0
# a2a-opencode changelog (/docs/community/changelog/a2a-opencode)
> This page is generated from [`a2a-opencode/CHANGELOG.md`](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/a2a-opencode/CHANGELOG.md) and stays in sync with the repository.
## 1.7.2 [#172]
### Patch Changes [#patch-changes]
* 4e2bd82: No code changes. A prior release run published every package's exact
target version under the `canary` npm dist-tag instead of `latest` (the
`canary` job's snapshot step no-ops when there are no pending changesets,
which was the case immediately after that Version Packages PR merged —
see the canary-job fix in a preceding commit). Since npm never allows
republishing an already-used version string, the only way to get a
`latest`-tagged release out is a fresh patch bump.
* Updated dependencies \[4e2bd82]
* @a2a-wrapper/core\@2.1.1
## 1.7.1 [#171]
### Minor Changes [#minor-changes]
* d6c2701: Add native A2A protocol v1.0 support, built on `@a2a-js/sdk@1.0.0`, while preserving full backward compatibility with A2A v0.3.x clients.
**`@a2a-wrapper/core` (breaking):**
* `buildAgentCard()` now produces the native v1.0 `AgentCard` shape (`supportedInterfaces[]` instead of `url`/`additionalInterfaces`/`protocolVersion`; `capabilities.extendedAgentCard` instead of `supportsAuthenticatedExtendedCard`; `stateTransitionHistory` removed). Callers reading the old v0.3 fields directly off the return value must update — the server itself still serves a fully v0.3-shaped card to legacy clients (see below).
* `createA2AServer()`'s JSON-RPC/REST transports and the `/.well-known/agent-card.json` route now negotiate protocol version per-request via the `A2A-Version` header: `1.0`/absent-with-recent-header gets the native v1.0 card and wire shapes, while `0.3`/no-header-at-all (matching the SDK's own default) gets a fully v0.3-shaped card and JSON-RPC/REST translation via `@a2a-js/sdk`'s `compat/v0_3` layer.
* `ServerOptions.protocolVersion` default changed from `"0.3"` to `"1.0"`.
* New `ServerOptions.onListening` hook and `ServerOptions.agentCardSigning` option (Signed Agent Cards, RFC 7515 JWS, opt-in via `AgentCardConfig.signing` or the programmatic option).
* New `extractUserText(message)` export centralizing inbound `Part` parsing for the v1.0 proto-oneof `Part` shape.
* `TaskState` is now re-exported as a value (real numeric enum in v1.0, was a string-literal type in v0.3).
* `@a2a-js/sdk` peer/dev dependency bumped to `^1.0.0`.
**Wrapper packages (`a2a-claude`, `a2a-codex`, `a2a-copilot`, `a2a-opencode`, `a2a-antigravity`):**
* Each wrapper's `createA2AServer()` public API is unchanged, but is now a thin delegate to `@a2a-wrapper/core`'s `createA2AServer` instead of duplicating Express/SDK server wiring — restoring the "A2A protocol code lives only in core" design.
* `a2a-copilot` and `a2a-opencode` no longer ship their own local `event-publisher.ts`; both now use `@a2a-wrapper/core`'s (v1.0-aware) event publisher.
* `@a2a-js/sdk` dependency bumped to `^1.0.0` (previously an inconsistent mix of `^0.3.9`/`^0.3.13`).
### Patch Changes [#patch-changes-1]
* Updated dependencies \[f3c7062]
* Updated dependencies \[41e2d82]
* Updated dependencies \[d6c2701]
* @a2a-wrapper/core\@2.1.0
## 1.6.1 [#161]
### Patch Changes [#patch-changes-2]
* Updated dependencies
* @a2a-wrapper/core\@1.7.0
## 1.6.0 — 2026-06-03 [#160--2026-06-03]
### Added [#added]
* **MCP custom headers** — `remote` MCP server configs now accept a `headers: Record` map. Use for auth tokens and API keys against hosted MCP servers (Linear, Notion, remote GitHub MCP, etc.). Header values support `${ENV_VAR}` substitution so secrets stay out of `config.json`.
* **Env-var substitution extended** — `${ENV_VAR}` (explicit, recommended) and `$ENV_VAR` (bare, backward-compatible) substitution now applies to local `command` args, local `environment` values, and remote `headers` values.
* **`@opencode-ai/sdk` upgraded `1.14.29 → 1.15.13`** — no breaking changes; v2 API surface is additive.
### Fixed [#fixed]
* **Logger level propagation** — `level: "debug"` in config now correctly reaches all child loggers. Previously, child loggers captured the level at module-import time before config was loaded.
### Changed [#changed]
* Updated dependencies
* @a2a-wrapper/core\@1.6.0
## 1.5.0 — 2026-05-13 [#150--2026-05-13]
### Added [#added-1]
* **A2A Sub-Agents** — new `subAgents` config section lets the parent agent expose remote A2A agents as MCP tools to the OpenCode LLM. The wrapper spawns [`a2a-mcp-skillmap`](https://github.com/shashikanth-gs/a2a-mcp-skillmap) as a stdio MCP server and registers it under the reserved `a2a-subagents` key. Each remote skill becomes a callable tool the LLM can dispatch like any other MCP tool. See `agents/multi-agent/` for an example.
* **Pinned skillmap version** — the synthesized MCP entry invokes `npx -y a2a-mcp-skillmap@` rather than the unpinned package name, so a future skillmap release cannot silently change semantics.
* **JSON schema for `config.json`** — `schemas/agent-config.schema.json` generated from the TypeScript types via `npm run schema`. Drift-detection and schema validation tests ship in CI.
### Changed [#changed-1]
* Updated dependencies
* @a2a-wrapper/core\@1.5.0
## 1.4.0 [#140]
### Added [#added-2]
* **Memory Persistence** — agents can now declare `memory.instructions` and `memory.skills` in their config.json. At startup, the executor materializes these files into the workspace at backend-specific paths. The target path is determined by the configured model: Claude models → `CLAUDE.md` + `.claude/skills/`, Codex models → `.codex/` + `.agents/skills/`, all others → `.opencode/instructions.md` + `.opencode/skills/`.
* **configDir injection** — the CLI now automatically derives and injects `configDir` from the config file path, enabling relative path resolution in memory configs.
### Changed [#changed-2]
* Updated dependencies
* @a2a-wrapper/core\@1.4.0
## 1.3.0 [#130]
### Added [#added-3]
* **Event Transport Integration** — executor now routes all trace events (tool calls, reasoning) through the new `@a2a-wrapper/core` event transport abstraction instead of calling `publishTraceArtifact` directly. Supports A2A sideband (default), HTTP collectors, and custom transports.
* **Agent card delegates to core** — `buildAgentCard()` now delegates to `@a2a-wrapper/core`'s shared implementation, eliminating duplicated card construction logic.
* **OpenCode SDK upgrade** — upgraded `@opencode-ai/sdk` from `1.3.13` to `1.4.3`. Renamed `FileDiff` → `SnapshotFileDiff` to match the new SDK export.
### Changed [#changed-3]
* **Default events config** — defaults now include `events: { enabled: true, transport: "a2a" }`.
* Updated dependencies
* @a2a-wrapper/core\@1.3.0
## 1.2.1 [#121]
### Changed [#changed-4]
* Fix post-release bugs: Node 22 ESM resolution (postinstall patch for vscode-jsonrpc), auth error message clarity (GITHUB_TOKEN guidance), README corrections (message/\* method names, messageId in examples), and ResultManager race condition (publish task event before status-update in both executors).
* Updated dependencies
* @a2a-wrapper/core\@1.2.1
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased [#unreleased]
## 1.2.0 - 2026-04-02 [#120---2026-04-02]
### Changed [#changed-5]
* Migrated to monorepo structure with npm workspaces, Turborepo, and Changesets
* Extracted shared infrastructure to `@a2a-wrapper/core` package (logging, config, events, server factory, session management, CLI scaffold)
* Now depends on `@a2a-wrapper/core` for all shared functionality
* Consolidated community docs (CONTRIBUTING, CODE_OF_CONDUCT, SECURITY, LICENSE) to repository root
* Unified CI/CD with Turborepo-powered GitHub Actions workflows
* Switched test script to `vitest --run` for non-interactive CI execution
## 1.0.0 - 2025-02-23 [#100---2025-02-23]
### Added [#added-4]
* A2A v0.3.0 protocol implementation over Express HTTP server
* Agent Card served at `/.well-known/agent-card.json`
* JSON-RPC endpoint at `/a2a/jsonrpc` — `message/send`, `message/stream`, `tasks/get`, `tasks/cancel`
* REST endpoint at `/a2a/rest`
* OpenCode backend (`@opencode-ai/sdk`) for LLM inference via `opencode serve`
* SSE event streaming with automatic reconnect and polling fallback
* Multi-turn conversation support via `contextId` → OpenCode session mapping
* MCP tool server support — HTTP, SSE, stdio, and OAuth transports
* Auto-approval of tool permissions (`PermissionHandler`)
* JSON config file with `$comment` annotations for easy customisation
* Environment variable and CLI argument overrides (priority: defaults ← JSON ← ENV ← CLI)
* `example` bundled agent configuration
* Docker support with multi-stage build and corporate proxy CA injection
* `server.sh` lifecycle manager (start / stop / restart / status / logs / foreground)
* Health check endpoint at `/health`
* Context building endpoint at `/context/build`
* `--stream-artifacts` / `--no-stream-artifacts` flag for SSE vs. buffered output
* Postman collection for all A2A and system endpoints
* TypeScript public API exports for programmatic use
[Unreleased]: https://github.com/shashikanth-gs/a2a-wrapper/compare/opencode-v1.0.0...HEAD
[1.0.0]: https://github.com/shashikanth-gs/a2a-wrapper/releases/tag/opencode-v1.0.0
# @a2a-wrapper/core changelog (/docs/community/changelog/core)
> This page is generated from [`packages/core/CHANGELOG.md`](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/packages/core/CHANGELOG.md) and stays in sync with the repository.
## 2.1.1 [#211]
### Patch Changes [#patch-changes]
* 4e2bd82: No code changes. A prior release run published every package's exact
target version under the `canary` npm dist-tag instead of `latest` (the
`canary` job's snapshot step no-ops when there are no pending changesets,
which was the case immediately after that Version Packages PR merged —
see the canary-job fix in a preceding commit). Since npm never allows
republishing an already-used version string, the only way to get a
`latest`-tagged release out is a fresh patch bump.
## 2.1.0 [#210]
### Major Changes [#major-changes]
* d6c2701: Add native A2A protocol v1.0 support, built on `@a2a-js/sdk@1.0.0`, while preserving full backward compatibility with A2A v0.3.x clients.
**`@a2a-wrapper/core` (breaking):**
* `buildAgentCard()` now produces the native v1.0 `AgentCard` shape (`supportedInterfaces[]` instead of `url`/`additionalInterfaces`/`protocolVersion`; `capabilities.extendedAgentCard` instead of `supportsAuthenticatedExtendedCard`; `stateTransitionHistory` removed). Callers reading the old v0.3 fields directly off the return value must update — the server itself still serves a fully v0.3-shaped card to legacy clients (see below).
* `createA2AServer()`'s JSON-RPC/REST transports and the `/.well-known/agent-card.json` route now negotiate protocol version per-request via the `A2A-Version` header: `1.0`/absent-with-recent-header gets the native v1.0 card and wire shapes, while `0.3`/no-header-at-all (matching the SDK's own default) gets a fully v0.3-shaped card and JSON-RPC/REST translation via `@a2a-js/sdk`'s `compat/v0_3` layer.
* `ServerOptions.protocolVersion` default changed from `"0.3"` to `"1.0"`.
* New `ServerOptions.onListening` hook and `ServerOptions.agentCardSigning` option (Signed Agent Cards, RFC 7515 JWS, opt-in via `AgentCardConfig.signing` or the programmatic option).
* New `extractUserText(message)` export centralizing inbound `Part` parsing for the v1.0 proto-oneof `Part` shape.
* `TaskState` is now re-exported as a value (real numeric enum in v1.0, was a string-literal type in v0.3).
* `@a2a-js/sdk` peer/dev dependency bumped to `^1.0.0`.
**Wrapper packages (`a2a-claude`, `a2a-codex`, `a2a-copilot`, `a2a-opencode`, `a2a-antigravity`):**
* Each wrapper's `createA2AServer()` public API is unchanged, but is now a thin delegate to `@a2a-wrapper/core`'s `createA2AServer` instead of duplicating Express/SDK server wiring — restoring the "A2A protocol code lives only in core" design.
* `a2a-copilot` and `a2a-opencode` no longer ship their own local `event-publisher.ts`; both now use `@a2a-wrapper/core`'s (v1.0-aware) event publisher.
* `@a2a-js/sdk` dependency bumped to `^1.0.0` (previously an inconsistent mix of `^0.3.9`/`^0.3.13`).
### Minor Changes [#minor-changes]
* f3c7062: Handle Claude rate limit events instead of failing the turn generically.
Rate-limit signals were previously unrecognised: `rate_limit_event`,
`system/api_retry`, and assistant `rate_limit` errors all fell through to a
debug log, and the turn surfaced as `failed` with `"Error during execution."`
A rejection now ends the turn immediately with an `input-required` status naming
the limit type and reset time, plus structured metadata (`reason`,
`rateLimitType`, `resetsAt`, `resetsAtIso`, `utilization`, and `errorCode` /
`canPurchaseCredits` when the SDK reports them). The task stays non-terminal, so
the client continues the same conversation on the same task once the limit
resets. Configurable via `rateLimit.taskState` for clients that require a
terminal state.
This does not shorten the SDK's own retry behaviour: a `system/api_retry` is
deliberately treated as a warning that does not end the turn, so the SDK's
internal retries still run to exhaustion inside the same `timeouts.prompt`
window. The turn ends only once the SDK gives up and emits an assistant
`rate_limit` error. Those retries are at least visible now, as `rate_limit`
sideband events with `action: "retrying"` carrying the SDK's `attempt`,
`maxRetries`, and `delayMs`.
A rejection whose overage window is still open is treated as a warning rather
than a rejection, since the request may proceed on overage credits; if it does
not, the assistant `rate_limit` error still ends the turn. Limit details are
never fabricated — a limit type or reset time is inherited by a later signal
only from a snapshot that reported pressure, utilization is never inherited, and
a reset time that is not in the future is dropped.
Adds a `rate_limit` sideband event type to `@a2a-wrapper/core`, gated by
`features.emitRateLimitEvents`.
* 41e2d82: Hold the A2A Task open while Claude has background work in flight.
A Task used to reach a terminal state as soon as Claude's first turn ended —
even when that turn had just started a background process and said it was
waiting on the result. A2A gives an agent no way to open a new turn against a
terminal Task, so the follow-up report had nowhere to land.
The Task now stays in `working` for as long as Claude reports background work
running, and completes only once a turn ends with nothing left. Each turn
publishes its own `response` artifact and a non-final `working` status update
whose `metadata.backgroundTasks` lists what is still in flight. Chains of any
length work this way, as rounds of one Task rather than several Tasks.
Controlled by `features.holdTaskForBackgroundWork` (default `true`; set
`false` for the old complete-at-first-result behavior) and
`features.emitBackgroundTaskEvents` (default `true`), which publishes a new
`background_tasks` sideband event — added to `@a2a-wrapper/core` — each time
the live set changes.
Bumps `@anthropic-ai/claude-agent-sdk` from `0.3.202` to `0.3.245`. The
feature needs at least `0.3.235`, the first version to emit
`background_tasks_changed`.
Three changes apply even with `holdTaskForBackgroundWork` off:
* Queries now use streaming input rather than a string prompt. A string prompt
makes the SDK close the CLI subprocess's stdin on the first result, which
ends the process before a second round is possible. This is not switchable.
* `agent_started` / `agent_finished` are emitted once per A2A Task rather than
once per SDK turn.
* A success result with empty text no longer publishes an empty `response`
artifact.
See the a2a-claude README for caveats, including how `claude.maxTurns` and
`timeouts.prompt` now span a held-open Task's rounds.
## 1.7.0 [#170]
### Minor Changes [#minor-changes-1]
* feat: add comprehensive LLM usage telemetry and tracking
## 1.6.1 — 2026-06-06 [#161--2026-06-06]
### Added [#added]
* **`publishTask`** — new exported helper. Registers a bare `Task` object with the A2A result manager before any status events are published. Previously duplicated in each wrapper; now shared from core so all wrappers can import it without a local copy.
## 1.6.0 — 2026-06-03 [#160--2026-06-03]
### Added [#added-1]
* **`substituteEnvTokensInString`** — new exported helper. Replaces `${VAR}` (explicit form) and `$VAR` (bare form) tokens in a single string. Works mid-string, e.g. `"Bearer ${TOKEN}"`.
* **`substituteEnvTokensInRecord`** — new exported helper. Applies env-var substitution to every value of a `Record`, e.g. HTTP headers or process environment maps. Returns `undefined` for `undefined` input.
* Both helpers are exported from the `@a2a-wrapper/core` barrel so wrapper loaders don't duplicate the implementation.
### Changed [#changed]
* **`substituteEnvTokens` (existing)** — now supports `${VAR}` in addition to bare `$VAR`. Backward-compatible.
## 1.5.0 — 2026-05-13 [#150--2026-05-13]
### Added [#added-2]
* **A2A Sub-Agents** — new `subAgents` config section lets a parent A2A agent expose remote A2A agents as MCP tools by spawning [`a2a-mcp-skillmap`](https://github.com/shashikanth-gs/a2a-mcp-skillmap) as a stdio MCP child process. The bridge is registered under the reserved `a2a-subagents` key in the resolved `mcp` map, so wrapper-side MCP wiring discovers it without any wrapper-specific code.
* **Bootstrap pipeline** — single entry point `bootstrapSubAgents()` orchestrates the full sequence: validate → build → write bridge config → probe → synthesize MCP descriptor. Wrapper integration is \~10 lines per wrapper.
* **Pinned skillmap version** — new `SKILLMAP_PACKAGE_VERSION` constant pins the `a2a-mcp-skillmap` version invoked via `npx` (pinned to `0.2.1`). Bumping the pin is a deliberate, reviewable change.
* **Reachability probe** — `probeSubAgents()` runs parallel HTTP probes against each sub-agent's effective URL at startup with structured `ProbeResult`s. Failures log warnings but never abort startup.
* **Sync budget** — new `SubAgentsOptions.syncBudgetMs` field controls how long the bridge waits for an A2A response before returning a task handle for async polling.
* **Reserved-key collision detection** — fail-fast validation rejects configs that manually define an MCP server under `a2a-subagents`.
* **Env-var substitution** — `auth.token` may reference environment variables via `${VAR}` syntax. Missing variables produce a startup warning and the auth block is omitted.
* **`BaseAgentConfig.subAgents`** — optional `subAgents?: SubAgentsConfig` field added to the base config type.
* **JSON schema** — `schemas/agent-config.schema.json` generated from TypeScript types via `npm run schema`. Drift-detection and schema validation tests ship in CI.
### New Exports [#new-exports]
* Types: `SubAgentConfig`, `SubAgentAuthConfig`, `SubAgentsOptions`, `SubAgentsConfig`, `SynthesizedMcpDescriptor`, `ProbeResult`, `BootstrapInput`, `BootstrapResult`, `BridgeConfigSource`, `BridgeConfigAgentEntry`, `BridgeConfig`, `ValidationOutcome`, `SubAgentValidationReason`, `SubAgentValidationErrorDetails`
* Functions: `validateSubAgents`, `buildBridgeConfig`, `resolveBridgeConfigPath`, `writeBridgeConfig`, `probeSubAgents`, `buildSynthesizedMcpEntry`, `bootstrapSubAgents`
* Error class: `SubAgentValidationError`
* Constants: `SUBAGENTS_MCP_KEY`, `SKILLMAP_PACKAGE_VERSION`
## 1.4.0 [#140]
### Added [#added-3]
* **Memory Persistence** — new `memory` config section allows agents to declare instructions and skills that are materialized into the workspace at startup. The core package provides a backend-agnostic materializer (`materializeMemory()`) that reads source files, validates SKILL.md frontmatter, and writes content to backend-specific paths before the executor handles its first request.
* **SKILL.md Parser** — `parseSkillManifest()`, `formatSkillManifest()`, and `validateSkillManifest()` functions for parsing YAML frontmatter from skill files. Uses a lightweight regex-based parser (no js-yaml dependency). Supports kebab-case name validation, arrays, quoted strings, and round-trip fidelity.
* **Well-Known Backend Paths** — `WELL_KNOWN_PATHS` constant with pre-defined path mappings for Copilot (`.github/`), Claude (`CLAUDE.md` + `.claude/`), OpenCode (`.opencode/`), and Codex (`.codex/` + `.agents/`). Wrappers can use these or define their own `BackendPaths`.
* **Path Resolution** — `resolveMemoryPath()` utility for resolving relative/absolute paths against the config directory.
* **BaseAgentConfig extended** — added optional `memory?: MemoryConfig` and `configDir?: string` fields to the base config type.
* **CLI scaffold injects configDir** — `createCli()` now automatically populates `configDir` from the config file path for memory path resolution.
### New Exports [#new-exports-1]
* Types: `MemoryConfig`, `SkillManifest`, `ParsedSkill`, `BackendPaths`, `MaterializeOptions`
* Functions: `materializeMemory`, `parseSkillManifest`, `formatSkillManifest`, `validateSkillManifest`, `resolveMemoryPath`
* Constants: `WELL_KNOWN_PATHS`
## 1.3.0 [#130]
### Added [#added-4]
* **Event Transport Abstraction** — new pluggable transport layer for sideband observability events. Built-in `A2ATransport` (default, publishes trace artifacts on the A2A EventBus) and `HttpTransport` (POSTs events as JSON to any HTTP collector). Custom transports (Kafka, Redis, DB) supported via the programmatic `createA2AServer()` API.
* **AgentEventEmitter** — per-execution emitter that stamps every event with agent identity, trace context, UUID, and ISO timestamp before routing through the transport.
* **EventsConfig** — new config section on `BaseAgentConfig` for controlling event emission, transport type, HTTP endpoint, timeout, and custom headers.
* **TRACE_EXTENSION_URI** — agent cards now declare `urn:x-a2a:trace:v1` in `capabilities.extensions` so orchestrators can discover sideband trace data.
* **ServerOptions.eventTransport** — `createA2AServer()` now accepts an optional custom event transport for programmatic use.
### Changed [#changed-1]
* **Trace artifacts now include extension metadata** — `publishTraceArtifact()` and `publishThoughtArtifact()` now emit `extensions: [TRACE_EXTENSION_URI]` and `metadata: { traceType, timestamp }` on every trace artifact, allowing orchestrators to reliably distinguish trace artifacts from response artifacts.
* **Fix flaky property-based test** — excluded `-0` from the `fc.double()` arbitrary in the config loader round-trip test. `-0` doesn't survive JSON serialization (`JSON.stringify(-0)` → `"0"` → `+0`), causing intermittent failures.
## 1.2.1 [#121]
### Changed [#changed-2]
* Fix post-release bugs: Node 22 ESM resolution (postinstall patch for vscode-jsonrpc), auth error message clarity (GITHUB_TOKEN guidance), README corrections (message/\* method names, messageId in examples), and ResultManager race condition (publish task event before status-update in both executors).
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 1.2.0 - 2026-04-02 [#120---2026-04-02]
### Added [#added-5]
* Initial extraction of shared infrastructure from `a2a-copilot` and `a2a-opencode`.
* `Logger` class with `createLogger` factory, `LogLevel` enum, and hierarchical child loggers.
* `Deferred` interface with `createDeferred` and `sleep` utilities.
* `deepMerge` function with immutable recursive merge and `substituteEnvTokens` for env var interpolation.
* `BaseAgentConfig` generic config type system with `AgentCardConfig`, `ServerConfig`, `SessionConfig`, `BaseFeatureFlags`, `TimeoutConfig`, `LoggingConfig`, `BaseMcpServerConfig`, and `SkillConfig`.
* `loadConfigFile` and `resolveConfig` for layered config resolution (defaults ← file ← env ← CLI).
* Event publisher functions: `publishStatus`, `publishFinalArtifact`, `publishStreamingChunk`, `publishLastChunkMarker`, `publishTraceArtifact`, `publishThoughtArtifact`.
* `buildAgentCard` for constructing A2A-spec-compliant agent cards from config.
* `createA2AServer` server factory with standard A2A routes, dynamic agent card URL rewriting, and configurable `A2A-Version` header.
* `BaseSessionManager` abstract class with TTL-based cleanup and task tracking.
* `A2AExecutor` interface defining the executor contract for wrapper projects.
* `createCli` CLI scaffold with common flag parsing, graceful shutdown, and extensible arg definitions.
* Barrel export (`src/index.ts`) with A2A SDK type re-exports for upgrade isolation.
* 19 property-based tests (fast-check) covering all correctness properties from the design document.
# Contributing (/docs/community/contributing)
> This page is generated from [`CONTRIBUTING.md`](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/CONTRIBUTING.md) and stays in sync with the repository.
Thanks for your interest in contributing! This is a monorepo containing multiple packages — this guide covers how to work across all of them.
## Prerequisites [#prerequisites]
* [Node.js](https://nodejs.org/) >= 20.0.0
* npm (comes with Node.js)
## Repository Structure [#repository-structure]
```
a2a-wrapper/
├── packages/
│ └── core/ # @a2a-wrapper/core — shared infrastructure
├── a2a-claude/ # a2a-claude — Claude wrapper
├── a2a-copilot/ # a2a-copilot — GitHub Copilot SDK wrapper
├── a2a-opencode/ # a2a-opencode — OpenCode wrapper
├── a2a-codex/ # a2a-codex — OpenAI Codex SDK wrapper
├── a2a-antigravity/ # a2a-antigravity — Google Antigravity SDK wrapper
├── turbo.json # Turborepo task pipeline
├── .changeset/ # Changesets versioning config
└── package.json # Root workspace config
```
* `packages/core/` — shared library (`@a2a-wrapper/core`): logging, config loading, event publishing, server factory, session management, CLI scaffold
* `a2a-claude/` — A2A wrapper for Claude
* `a2a-copilot/` — A2A wrapper for GitHub Copilot SDK
* `a2a-opencode/` — A2A wrapper for OpenCode
* `a2a-codex/` — A2A wrapper for OpenAI Codex SDK
* `a2a-antigravity/` — A2A wrapper for Google Antigravity SDK using a private Python subprocess
All packages are managed via [npm workspaces](https://docs.npmjs.com/cli/using-npm/workspaces) and built with [Turborepo](https://turbo.build/).
## Getting Started [#getting-started]
```bash
# Clone the repository
git clone https://github.com/shashikanth-gs/a2a-wrapper.git
cd a2a-wrapper
# Install dependencies for all packages (from the repo root)
npm install
# Build all packages so @a2a-wrapper/core is compiled to dist/
# (dist/ is git-ignored and is NOT produced by `npm install`)
npx turbo run build
```
A single `npm install` at the root resolves dependencies for every package and hoists shared ones. Note that `npm install` does **not** compile anything — `@a2a-wrapper/core` is consumed from its built `dist/` output, so run `npx turbo run build` before running any package or example.
## Development Commands [#development-commands]
All commands run from the repository root using Turborepo:
```bash
# Build all packages (core builds first, then wrappers in parallel)
npx turbo run build
# Run tests across all packages
npx turbo run test
# Type-check all packages
npx turbo run typecheck
# Clean build artifacts
npx turbo run clean
```
Turborepo caches build outputs — unchanged packages are skipped on subsequent runs.
## Working on a Specific Package [#working-on-a-specific-package]
You can scope Turborepo to a single package with `--filter`:
```bash
# Build only core
npx turbo run build --filter=@a2a-wrapper/core
# Test only a2a-copilot
npx turbo run test --filter=a2a-copilot
# Build a2a-opencode and its dependencies
npx turbo run build --filter=a2a-opencode...
# Test only a2a-antigravity
npx turbo run test --filter=a2a-antigravity
```
Or work directly inside a package directory:
```bash
cd packages/core
npm test
cd a2a-copilot
npm test
cd a2a-antigravity
npm test
```
## Creating Changesets [#creating-changesets]
This project uses [Changesets](https://github.com/changesets/changesets) for versioning and changelogs. Every PR that changes package behavior should include a changeset.
```bash
# Create a new changeset (interactive prompt)
npx changeset
```
The CLI will ask you:
1. Which packages were affected
2. The semver bump type (patch / minor / major)
3. A summary of the change
This creates a markdown file in `.changeset/` — commit it with your PR.
### When to create a changeset [#when-to-create-a-changeset]
* Bug fixes → `patch`
* New features (backward-compatible) → `minor`
* Breaking changes → `major`
* Documentation-only or CI-only changes → no changeset needed
## Release Process [#release-process]
Once a PR is merged to `main`, three things happen automatically:
1. **CI** re-validates `main` (build, typecheck, test).
2. **Canary publish** — once CI succeeds, every affected package is published to npm under the `canary` dist-tag with a snapshot version like `1.8.0-canary-20260812070958`. Install one with `npm install @canary`. This never touches `main` — the snapshot version bump happens only in the CI runner and is discarded, so pending changesets are left intact.
3. **Version Packages PR** — the Changesets bot opens/updates a PR on `main` that batches all pending changesets into real version bumps and CHANGELOG entries.
When a maintainer merges the Version Packages PR, the release workflow checks whether a release is ready (no pending changesets and at least one package version not yet published) and, if so, waits for **manual approval** via the `release` GitHub Environment before running `npx changeset publish` under the `latest` dist-tag and creating GitHub releases.
So `npm install ` always gets the latest approved stable release, while `npm install @canary` gets the most recent merge to `main`.
Both the canary and stable-release publish steps live in the single `.github/workflows/publish.yml` file, as separate jobs. This is required, not stylistic: npm Trusted Publishing allows exactly one trusted publisher per package, bound to one workflow filename, so every path that can call `npm publish` for these packages has to originate from that one file. `version.yml` (the Version Packages PR bot) is a separate file because it never publishes to npm.
## Adding a New Wrapper [#adding-a-new-wrapper]
Adding a new A2A wrapper (e.g. `a2a-claude`) requires no changes to the root config or core package:
1. Create a directory at the repo root following the `a2a-` naming convention:
```
a2a-claude/
├── package.json
├── tsconfig.json
├── src/
│ ├── index.ts
│ ├── cli.ts
│ ├── server/
│ │ └── index.ts # Thin delegate to core's createA2AServer
│ └── claude/
│ ├── executor.ts # Implements A2AExecutor
│ ├── session-manager.ts
│ └── config/
│ ├── types.ts # Extends BaseAgentConfig
│ └── defaults.ts
└── agents/
└── example/
└── config.json
```
2. In `package.json`, set:
* `name` to `a2a-`
* `@a2a-wrapper/core` as a dependency (`"*"`)
* `publishConfig.access` to `"public"`
* A `build`, `test`, and `typecheck` script
3. Implement the `A2AExecutor` interface from `@a2a-wrapper/core` and wire it up with `createCli()`.
**`src/server/index.ts` must be a thin delegate to core's `createA2AServer()`** — do not hand-roll Express routing, agent card building, or event publishing. All A2A protocol wiring (agent card, JSON-RPC/REST transports, v1.0/v0.3 version negotiation) lives in `@a2a-wrapper/core` on purpose, so every wrapper gets protocol updates for free. Follow the pattern in any existing `a2a-*/src/server/index.ts` — call `createA2AServer(config, executorFactory, options)` and use `registerRoutes` for wrapper-specific endpoints only.
4. Run `npm install` at the repo root to link the new package.
5. Verify everything works:
```bash
npx turbo run build test typecheck
```
6. Create a changeset for the initial release:
```bash
npx changeset
```
The `a2a-*` workspace glob in the root `package.json` automatically picks up the new directory — no config edits needed.
## Pull Request Process [#pull-request-process]
1. Fork the repo and create a feature branch from `main`.
2. Make your changes and add tests where appropriate.
3. Run the full build and test suite:
```bash
npx turbo run build typecheck test
```
4. Create a changeset if your change affects published packages:
```bash
npx changeset
```
5. Open a pull request against `main`. CI will build and test across Node.js 20 and 22.
6. Address review feedback and ensure CI passes.
## Code of Conduct [#code-of-conduct]
This project follows the [Contributor Covenant Code of Conduct](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/CODE_OF_CONDUCT.md). Please read it before participating.
# Roadmap (/docs/community/roadmap)
## Planned [#planned]
* **OpenTelemetry observability.** Optional distributed tracing and metrics for the task lifecycle and HTTP requests, through `@opentelemetry/api`. It would be a no-op by default, with zero cost unless a host application registers a real SDK or exporter. Not yet implemented.
## Influence the roadmap [#influence-the-roadmap]
Open an issue or a discussion on [GitHub](https://github.com/shashikanth-gs/a2a-wrapper). Pull requests are welcome; see [contributing](/docs/community/contributing).
# What is A2A? (/docs/concepts/a2a-protocol)
**Agent2Agent (A2A)** is an open protocol that lets independent AI agents talk to each other over HTTP. An agent built on one framework can discover an agent built on another, hand it a task, follow its progress, and receive results, without either side exposing its internals.
The specification lives at [a2a-protocol.org](https://a2a-protocol.org).
## A2A and MCP are complementary [#a2a-and-mcp-are-complementary]
| | MCP | A2A |
| ------------------- | --------------------------------- | ----------------------------------------------------- |
| Direction | Vertical: agent to tools and data | Horizontal: agent to agent |
| Question it answers | "How does this agent use a tool?" | "How do agents delegate to each other?" |
| Unit of work | A tool call | A task, possibly long-running, with its own lifecycle |
A typical coding agent already speaks MCP for its tools. a2a-wrapper adds the A2A side so that agent can be called by, and can call, other agents.
## The pieces you will meet [#the-pieces-you-will-meet]
**Agent card.** A JSON document at `/.well-known/agent-card.json` describing the agent: its name, skills, endpoints and supported protocol versions. It is how discovery works. See [agent cards](/docs/concepts/agent-cards).
**Message.** One turn of communication, made of *parts* (text, files or structured data), sent with a role of `user` or `agent`.
**Task.** A unit of work the agent tracks. A task moves through states such as `submitted`, `working`, `input-required`, `completed`, `failed` and `canceled`. See [tasks and streaming](/docs/concepts/tasks-and-streaming).
**Artifact.** An output of a task, for example the final answer or a generated file, delivered as parts.
**Context.** A `contextId` groups related messages and tasks into one conversation. a2a-wrapper uses it to keep a backend session alive across turns. See [sessions](/docs/concepts/sessions).
**Transports.** A2A can be carried over JSON-RPC and over HTTP+JSON (REST). The wrappers serve both.
## Opacity [#opacity]
A2A agents collaborate without sharing internal state, memory or tools. A caller sees tasks and artifacts, not the model, prompts or tool calls behind them. The wrappers respect this: rich detail such as tool calls and reasoning is published only as opt-in [sideband trace events](/docs/concepts/observability), not as part of the answer.
## What a2a-wrapper adds [#what-a2a-wrapper-adds]
Implementing the protocol correctly for each coding agent is repetitive and easy to get subtly wrong. a2a-wrapper centralises it once in [`@a2a-wrapper/core`](/docs/reference/packages/core) and adds a thin backend-specific executor per agent, so you get a compliant server from a config file.
# Agent cards (/docs/concepts/agent-cards)
An **agent card** is the public description of your agent. Clients fetch it to learn what the agent is called, what it can do, and where to send requests.
```bash
curl -s http://localhost:3000/.well-known/agent-card.json | jq .
```
## Configuring the card [#configuring-the-card]
The card is generated from the `agentCard` and `server` blocks of your config:
```json
{
"agentCard": {
"name": "Claude Workspace Engineer",
"description": "A repository-scoped software engineering agent backed by Claude.",
"version": "1.0.0",
"protocolVersion": "0.3.0",
"streaming": true,
"defaultInputModes": ["text"],
"defaultOutputModes": ["text"],
"skills": [
{
"id": "workspace-engineering",
"name": "Workspace Engineering",
"description": "Inspect, modify, and validate code within the configured workspace.",
"tags": ["code", "repository", "tests"]
}
]
},
"server": {
"port": 3030,
"hostname": "127.0.0.1",
"advertiseHost": "localhost",
"advertiseProtocol": "http"
}
}
```
| Field | Purpose |
| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `agentCard.name`, `description` | What callers see first. Write them for orchestrators deciding whether to route work to you |
| `agentCard.skills` | Advertised capabilities (`id`, `name`, `description`, `tags`, optional `examples`) |
| `server.port`, `server.hostname` | Where the server listens. `hostname` defaults to `0.0.0.0`, see the [security guide](/docs/guides/security) |
| `server.advertiseHost`, `advertiseProtocol` | The host and protocol written into the card's endpoint URLs. Set these when running behind Docker, a proxy or TLS |
`advertiseHost` only changes the URLs in the card. It does not restrict who can connect. Use `hostname` and your network for that.
## Skills: card versus memory [#skills-card-versus-memory]
Two similarly named concepts are easy to confuse:
* **`agentCard.skills`** is protocol metadata shown to callers. It describes what the agent can do at a high level, for discovery and routing.
* **`memory.skills`** is instructions written into the agent's workspace for the model. It is never exposed externally.
See [memory and skills](/docs/concepts/memory-and-skills).
## Version-aware responses [#version-aware-responses]
The card endpoint reads the caller's `A2A-Version` header:
* `1.0` returns the native v1.0 card with `supportedInterfaces[]`.
* `0.3`, `0.3.0` or no header returns a v0.3-shaped legacy card (`url`, `protocolVersion: "0.3.0"`).
So old and new clients both work with no extra configuration. Details in [protocol versions](/docs/concepts/protocol-versions).
## Signed agent cards [#signed-agent-cards]
A2A v1.0 supports JWS-signed cards (RFC 7515). This is opt-in: pass `ServerOptions.agentCardSigning`, or set `AgentCardConfig.signing` with the private key read from an environment variable holding a JWK. The card's `signatures` field is then populated automatically. Unsigned cards simply omit it.
## Trace extension [#trace-extension]
Cards declare the `urn:x-a2a:trace:v1` extension, which tells orchestrators the agent can emit [sideband trace artifacts](/docs/concepts/observability). It is optional for callers to use.
# Architecture (/docs/concepts/architecture)
## Layers [#layers]
```
┌────────────────────────────────────────────────────────────────────────┐
│ @a2a-wrapper/core │
│ Logger · Config · Events · Server · Session · CLI · Sub-Agents · │
│ Memory · Schema │
└──────────────────────────────┬─────────────────────────────────────────┘
▼
┌────────────────────────────────────────────────────────────────────────┐
│ Wrapper packages (one executor each) │
│ a2a-copilot → GitHub Copilot SDK a2a-claude → Claude Agent SDK │
│ a2a-codex → OpenAI Codex SDK a2a-opencode → OpenCode Server │
│ a2a-antigravity → Antigravity SDK (Python bridge) │
└────────────────────────────────────────────────────────────────────────┘
```
All protocol and spec-version logic lives in `@a2a-wrapper/core` and nowhere else in the monorepo. Each wrapper implements a single `A2AExecutor` interface plus a thin config and CLI layer.
## Request path [#request-path]
```
A2A client (orchestrator, inspector, curl)
│ JSON-RPC or REST over HTTP
▼
Express server (from core's createA2AServer)
├─ /.well-known/agent-card.json agent card
├─ /a2a/jsonrpc JSON-RPC (message/send, message/stream, …)
├─ /a2a/rest HTTP+JSON
└─ /health health check
▼
Executor (backend-specific)
├─ SessionManager contextId → backend session
├─ Event publishing backend events → A2A status / artifact events
└─ Backend SDK Copilot, Claude, Codex, OpenCode, Antigravity
▼
The backend's model and tools (MCP servers, shell, file edits)
```
Some wrappers also expose `/context` and `/context/build`, which read and build a `context.md` description of the workspace.
## What core provides [#what-core-provides]
| Area | What it does |
| ---------- | --------------------------------------------------------------------------------------- |
| Config | Loads the JSON file and merges layers: defaults, file, environment, CLI flags |
| Server | Builds the agent card, mounts the JSON-RPC and REST routes, handles version negotiation |
| Sessions | Maps `contextId` to a backend session with optional TTL cleanup |
| Events | Publishes task status, artifacts and sideband trace events |
| CLI | Parses common flags and starts the server with graceful shutdown |
| Memory | Writes instruction and skill files into the workspace at startup |
| Sub-agents | Lets an agent call other A2A agents as MCP tools |
## What a wrapper provides [#what-a-wrapper-provides]
A wrapper implements `A2AExecutor`: `initialize()`, `shutdown()` and `execute()`, plus optional `cancelTask()`. It maps the backend's session model and events onto A2A tasks. To write your own, see [build your own wrapper](/docs/guides/build-a-wrapper).
## Why a shared core [#why-a-shared-core]
The A2A SDK and specification evolve. Keeping all spec-version logic in one package means a major SDK upgrade, such as the move to A2A v1.0, changes one package and every wrapper benefits. Core also re-exports the SDK types wrappers need, so wrappers never import the SDK directly.
# Memory and skills (/docs/concepts/memory-and-skills)
The **`memory`** config block materialises instructions and skills into the agent's workspace at startup. The backend reads them as part of its normal context, so the agent has persistent knowledge across sessions.
```json
{
"memory": {
"instructions": "./memory/instructions.md",
"skills": ["./memory/skills/code-review", "./memory/skills/testing"]
}
}
```
Relative paths resolve against the directory containing the config file.
## Where files are written [#where-files-are-written]
Each backend has its own conventions, and core maps them for you:
| Backend | Instructions file | Skills directory |
| ----------- | --------------------------------- | ---------------------- |
| Copilot | `.github/copilot-instructions.md` | `.github/skills/` |
| Claude | `CLAUDE.md` | `.claude/skills/` |
| OpenCode | `.opencode/instructions.md` | `.opencode/skills/` |
| Codex | `.codex/instructions.md` | `.agents/skills/` |
| Antigravity | `AGENTS.md` | `.antigravity/skills/` |
Codex also reads `AGENTS.md` in the workspace root for project-level instructions.
## Skill format [#skill-format]
A skill is a folder with a `SKILL.md` containing frontmatter and instructions:
```markdown title="memory/skills/code-review/SKILL.md"
---
name: code-review
description: Provides code review guidelines and checklists
license: MIT
compatibility:
- copilot
- opencode
allowed-tools:
- read_file
- search_files
---
# Code Review Skill
Instructions for the model on how to perform code reviews...
```
The `name` must be kebab-case and at most 64 characters. A skill folder may also contain `scripts/`, `references/` and `assets/`.
## Memory skills versus card skills [#memory-skills-versus-card-skills]
* `memory.skills` are internal instructions for the model. They are never exposed externally.
* `agentCard.skills` are public metadata for discovery.
Keep them in sync by hand. They often deserve different wording: the card entry is short and aimed at orchestrators, while the memory skill is detailed and technical.
# Observability and events (/docs/concepts/observability)
Beyond the final answer, wrappers can emit **sideband events** describing what the agent did: tool calls, reasoning summaries, file changes, todo lists, errors and usage. Use them for tracing, dashboards and orchestration.
## Event types [#event-types]
| `eventType` | Emitted when |
| ---------------------------------- | ---------------------------------------------------- |
| `agent_started` | The backend session starts |
| `agent_finished` | A task completes (may include usage and cost) |
| `agent_error` | A task fails |
| `tool_call_start`, `tool_call_end` | A tool, shell command or MCP call begins and ends |
| `thinking` | A reasoning summary is available |
| `decision` | A notable choice, such as a file change or todo list |
Each event carries an `eventId`, `agentId`, `traceId`, a timestamp and a `data` payload. Which events a wrapper emits is controlled by `features` flags such as `emitThinkingEvents`, `emitToolEvents`, `emitFileChangeEvents`, `emitTodoEvents` and `emitRateLimitEvents`. Exact mappings per backend are in each package reference.
Secrets such as API keys, tokens and passwords are redacted. Tool output is truncated at 10,000 characters. File contents are never emitted, only the path and kind of change.
## Transports [#transports]
Configure where events go with the `events` block.
| Transport | Config | Behaviour |
| ------------------ | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------- |
| **A2A sideband** | `"transport": "a2a"` (default) | Publishes trace artifacts on the task. Orchestrators find them through the `urn:x-a2a:trace:v1` extension in the agent card |
| **HTTP collector** | `"transport": "http"` | POSTs each event as JSON to `httpUrl`, with optional headers for auth |
```json
{
"events": {
"enabled": true,
"transport": "http",
"httpUrl": "https://telemetry.example.com/events",
"httpTimeout": 5000,
"httpHeaders": { "Authorization": "Bearer ${TELEMETRY_TOKEN}" }
}
}
```
Some event types, such as `rate_limit` and `context_window` in `a2a-claude`, have no A2A trace-artifact mapping, so they are dropped on the default `a2a` transport. Use the `http` transport or a custom one to observe them.
## Custom sinks [#custom-sinks]
When embedding a wrapper programmatically, pass a function or an `EventTransport` to `createA2AServer` to send events anywhere, for example Kafka or a database:
```ts
import { createA2AServer } from "@a2a-wrapper/core";
const handle = await createA2AServer(config, executorFactory, {
eventTransport: async (event) => {
await kafkaProducer.send({
topic: "agent-traces",
messages: [{ value: JSON.stringify(event) }],
});
},
});
```
## Usage and cost telemetry [#usage-and-cost-telemetry]
`a2a-copilot` adds tiered usage telemetry: a summary on every completed task, plus optional per-call trace artifacts and context-window snapshots when `features.trackUsage` is `true`. `a2a-claude` reports usage, cost and turn count on `agent_finished`. See the package references for fields.
# Protocol versions (/docs/concepts/protocol-versions)
Every wrapper speaks **A2A v1.0 natively** and is **backward compatible with v0.3.x clients** out of the box. There is nothing to configure. The behaviour is built on the official `@a2a-js/sdk`'s own v0.3 compatibility layer, not a hand-written shim, and lives only in `@a2a-wrapper/core`.
## How negotiation works [#how-negotiation-works]
Per request:
**Agent card.** `GET /.well-known/agent-card.json` reads the caller's `A2A-Version` header.
| Header | Response |
| ----------------------- | ------------------------------------------------------------------------------------------- |
| `1.0` | The native v1.0 card, using `supportedInterfaces[]` |
| `0.3`, `0.3.0`, or none | A v0.3-shaped legacy card with `url`, `additionalInterfaces` and `protocolVersion: "0.3.0"` |
**JSON-RPC.** `POST /a2a/jsonrpc` accepts native v1.0 shapes and translates v0.3 requests and responses. Legacy method names such as `message/send` and `message/stream` keep working.
**REST.** The routes under `/a2a/rest` accept v1.0 shapes; legacy routes such as `/a2a/rest/v1/message:send` keep working.
No separately configured v0.3 server is needed.
## Which version should my client use? [#which-version-should-my-client-use]
* New clients should send `A2A-Version: 1.0`.
* Existing v0.3 clients need no changes. The examples in these docs use the v0.3 method names (`message/send`) because they work with every client.
## The protocolVersion config field [#the-protocolversion-config-field]
Example configs set `"protocolVersion": "0.3.0"` in `agentCard`. This is informational: it does not limit which wire format is served, because that is negotiated per request.
## Signed cards [#signed-cards]
v1.0 supports JWS-signed agent cards as an opt-in. See [agent cards](/docs/concepts/agent-cards#signed-agent-cards).
# Sessions and contexts (/docs/concepts/sessions)
A2A groups related messages with a **`contextId`**. Each wrapper maps that to a persistent session in its backend, so a follow-up message on the same context continues the same conversation with full memory.
| Wrapper | `contextId` maps to |
| ----------------- | -------------------------------------------------------- |
| `a2a-copilot` | a Copilot session |
| `a2a-claude` | a Claude session, resumed with the SDK's `resume` option |
| `a2a-codex` | a Codex thread |
| `a2a-opencode` | an OpenCode session |
| `a2a-antigravity` | an Antigravity session |
## Using it [#using-it]
Pass the same `contextId` on every message in a conversation. If you omit it, the server creates one and returns it in the response, which you then reuse.
```json
{
"jsonrpc": "2.0", "id": "2", "method": "message/send",
"params": {
"message": {
"kind": "message", "messageId": "m2", "role": "user",
"contextId": "",
"parts": [{ "kind": "text", "text": "Now add tests for that." }]
}
}
}
```
## Configuration [#configuration]
```json
{
"session": {
"reuseByContext": true,
"ttl": 3600000,
"cleanupInterval": 300000
}
}
```
| Field | Meaning |
| ----------------- | ---------------------------------------------------------------------------------------------------- |
| `reuseByContext` | Reuse one backend session per `contextId` |
| `ttl` | Milliseconds until a session expires. `0` disables expiry |
| `cleanupInterval` | Background sweep that reclaims expired sessions. `0` disables it, and it is only used when `ttl > 0` |
In `a2a-claude`, `ttl` counts from when the session was **created**, not from its last use, and defaults to `0` (no expiry). A positive value resets even an active conversation on a fixed schedule. When a session expires, the next turn on that `contextId` starts a fresh session with no memory of earlier turns. Defaults and exact semantics can differ per wrapper, so check the package reference.
## Multi-agent conversations [#multi-agent-conversations]
In a multi-agent workflow each agent keeps its own session. A planner can reuse one `contextId` across calls to each agent so every agent maintains its own history, as the [crew example](/docs/getting-started/multi-agent-crew) does.
# Sub-agents (/docs/concepts/sub-agents)
Any wrapper agent can **delegate to other A2A agents**. You declare them under `subAgents` in the config. The wrapper starts the [`a2a-mcp-skillmap`](https://github.com/shashikanth-gs/a2a-mcp-skillmap) bridge as a stdio MCP server, registered under the reserved key `a2a-subagents`. Each remote skill then appears to the model as an ordinary tool named `{name}__{skillId}`.
```
Parent agent (model)
│ calls tool: coding__review
▼
a2a-mcp-skillmap (MCP bridge, stdio)
│ A2A message/send
▼
Remote A2A agent (any framework)
```
## Configuration [#configuration]
```json
{
"subAgents": {
"agents": [
{
"name": "coding",
"agentCardUrl": "https://coding.example.com/.well-known/agent-card.json",
"auth": { "mode": "bearer", "token": "${CODING_AGENT_TOKEN}" }
},
{
"name": "research",
"agentCardUrl": "https://research.example.com/",
"endpointUrlOverride": "https://research.internal.local/.well-known/agent-card.json"
}
],
"options": { "responseMode": "artifact", "probeTimeoutMs": 5000, "syncBudgetMs": 30000 }
}
}
```
| Field | Meaning |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | Unique prefix for the tools this agent contributes |
| `agentCardUrl` | Where to fetch the remote agent's card |
| `endpointUrlOverride` | Use a different URL than the one in the card (for example an internal address) |
| `auth` | Outbound authentication: `none`, `bearer`, or `api_key` (with optional `headerName`). Tokens may reference environment variables as `${VAR}` |
| `options.responseMode` | How results come back, for example `artifact` |
| `options.probeTimeoutMs` | Timeout for the startup reachability probe |
| `options.syncBudgetMs` | How long a call may wait synchronously before returning a task handle |
When `subAgents` is absent, the agent starts normally with no side effects. Besides the `{name}__{skillId}` tools, the model also gets `task_status`, `task_result` and `task_cancel` tools for long-running delegated work.
## What happens at startup [#what-happens-at-startup]
1. Validate the config (unique names, valid URLs, no reserved-key collisions, env-var substitution on tokens).
2. Write the bridge config to `/.a2a/subagents-bridge.json` with mode `0600`.
3. Probe each sub-agent in parallel. An unreachable agent is logged, not fatal.
4. Register the bridge as an MCP server in the parent's MCP map.
The bridge is pinned to an exact `a2a-mcp-skillmap` version, so a new bridge release cannot silently change behaviour.
## Observability [#observability]
MCP calls to `a2a-subagents` are enriched with `toolKind: "a2a_subagent"` and `delegation: true` in [sideband events](/docs/concepts/observability), so you can see delegation in traces.
## Try it without API keys [#try-it-without-api-keys]
The repository has a self-contained example, [`examples/a2a-subagents-scenario`](https://github.com/shashikanth-gs/a2a-wrapper/tree/main/examples/a2a-subagents-scenario), with two fake sub-agents, a parent config and an end-to-end test that needs no keys.
```bash
npx turbo run build
cd examples/a2a-subagents-scenario
./start-all.sh
```
## Sub-agents versus the crew example [#sub-agents-versus-the-crew-example]
The [crew example](/docs/getting-started/multi-agent-crew) orchestrates agents from outside with a planner script. Sub-agents instead let the model itself decide when to delegate, using tools. Use an external planner for a fixed pipeline; use sub-agents when the model should choose.
# Tasks and streaming (/docs/concepts/tasks-and-streaming)
## The task lifecycle [#the-task-lifecycle]
Sending a message creates a **task**. The wrapper publishes status updates as the backend works, then delivers the result as an **artifact**.
| State | Meaning |
| ---------------- | ----------------------------------------------------------------------------------- |
| `submitted` | Accepted, not yet running |
| `working` | The backend is executing |
| `input-required` | The agent needs more input from the caller (an interrupted state) |
| `auth-required` | The agent needs the caller to authenticate before continuing (an interrupted state) |
| `completed` | Finished successfully; artifacts hold the result (terminal) |
| `failed` | Finished with an error (terminal) |
| `canceled` | Stopped by the caller (terminal) |
| `rejected` | The agent declined to perform the task (terminal) |
In A2A v1.0 these are written `TASK_STATE_SUBMITTED`, `TASK_STATE_WORKING` and so on; the v0.3 names above are still accepted by the wrappers' compatibility layer. The authoritative list is in the [A2A specification](https://a2a-protocol.org/latest/specification/).
## Two ways to call an agent [#two-ways-to-call-an-agent]
**Blocking.** `message/send` with `configuration.blocking: true` returns once the task is done. Simple, and what the [quick start](/docs/getting-started/quickstart) uses.
**Streaming.** `message/stream` returns a stream of events (status updates and artifact chunks) as they happen. Use it for long tasks or live UIs. Agent cards advertise `streaming: true` when supported.
## Buffered versus streamed artifacts [#buffered-versus-streamed-artifacts]
By default wrappers **buffer** the response and publish a single complete artifact at the end. This is the most compatible mode, including with the A2A Inspector.
Set `features.streamArtifactChunks: true` (or `STREAM_ARTIFACTS=true` where supported) to publish incremental chunks with `append: true`. Enable it only if your client correctly handles appending artifact updates.
## Cancellation [#cancellation]
Clients can cancel an in-flight task: `tasks/cancel` on v0.3 JSON-RPC, `CancelTask` on v1.0, or the REST route `POST /a2a/rest/v1/tasks/{taskId}:cancel` (v0.3). The wrapper aborts the backend call using an `AbortController`. Cancelling is idempotent, so repeating it for the same task is safe.
## Turns are serialised per context [#turns-are-serialised-per-context]
Within one `contextId`, turns run one at a time. Concurrent messages on the same context are queued, not run in parallel. Different contexts run independently.
## Long-running background work (a2a-claude) [#long-running-background-work-a2a-claude]
Claude Agent SDK sessions can start background tasks. `a2a-claude` can hold the A2A task open while that work is in flight, so the task stays `working` until the background work finishes rather than completing early. Background task membership is reported through a `background_tasks` event. See the [a2a-claude reference](/docs/reference/packages/a2a-claude).
## Rate limits (a2a-claude) [#rate-limits-a2a-claude]
When Claude reports a rate limit, `a2a-claude` always fails the task rather than hanging, and emits a `rate_limit` event with the reset time when available. Details are in the reference.
# Installation (/docs/getting-started/installation)
## Requirements [#requirements]
* **Node.js 20 or newer**
* An account or key for the backend you want to wrap (see [authentication](#authentication))
## Install from npm [#install-from-npm]
Each wrapper is its own package with its own CLI:
```bash
npm install -g a2a-copilot # GitHub Copilot
npm install -g a2a-claude # Claude
npm install -g a2a-codex # OpenAI Codex
npm install -g a2a-opencode # OpenCode
npm install -g a2a-antigravity # Google Antigravity
```
Then start one with a config file:
```bash
a2a-copilot --config ./config.json
```
You can also run without installing, for example `npx a2a-copilot --config ./config.json`.
## Authentication [#authentication]
Credentials always come from the environment, never from `config.json`.
| Wrapper | What it needs |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `a2a-copilot` | A GitHub account with Copilot access: run `gh auth login` or set `GITHUB_TOKEN`. With a custom BYOK provider such as a local Ollama, GitHub credentials are not required for that provider |
| `a2a-claude` | `ANTHROPIC_API_KEY` (recommended). Bedrock and Vertex environment variables pass through to the SDK untouched |
| `a2a-codex` | `OPENAI_API_KEY`, or credentials from `codex login`. With subscription auth, also set `CODEX_MODEL` |
| `a2a-opencode` | Whatever provider credentials your OpenCode configuration uses |
| `a2a-antigravity` | A Gemini API key, or Vertex/ADC. Run `a2a-antigravity setup` once to create the managed Python environment |
Wrappers that operate on a repository also need `WORKSPACE_DIR`: the directory the agent may read and change. For `a2a-codex` it must be a Git repository.
## Release channels [#release-channels]
| Channel | Install | Use it for |
| ---------- | -------------------------------- | ------------------------------------------------------------------------------------------------------ |
| **Stable** | `npm install a2a-copilot` | Production. Releases are reviewed and approved manually |
| **Canary** | `npm install a2a-copilot@canary` | Trying a fix before a stable release. Published after every merge to `main` that passes CI; unreviewed |
## Build from source [#build-from-source]
The repository is an npm workspaces monorepo managed with Turborepo.
```bash
git clone https://github.com/shashikanth-gs/a2a-wrapper.git
cd a2a-wrapper
npm install
# Compile @a2a-wrapper/core (dist/ is git-ignored and is NOT produced by npm install)
npx turbo run build
# Run a wrapper in development
cd a2a-copilot
npm run dev -- --config agents/example/config.json
```
Wrappers import `@a2a-wrapper/core` from its compiled `dist/`. If you pull new commits, re-run `npm install` and `npx turbo run build`, or you may hit errors such as a missing export.
## Next [#next]
# Multi-agent crew (/docs/getting-started/multi-agent-crew)
The [Software Engineering Crew](https://github.com/shashikanth-gs/a2a-wrapper/tree/main/examples/software-engineering-crew) example builds on the [quick start](/docs/getting-started/quickstart): instead of one agent, several agents cooperate.
## How it works [#how-it-works]
```
feature request ─▶ Planner (planner.mjs)
├─ 1. implement ─▶ a2a-claude (Claude) :3030
└─ 2. test & review ─▶ a2a-codex (Codex) :3020
both work in the same git workspace
```
1. The **planner** takes a feature request and sends it to Claude through `a2a-claude`.
2. Claude implements it in the shared workspace and returns a summary as an A2A artifact.
3. The planner hands that summary to Codex through `a2a-codex`, which writes tests, runs them and fixes what it finds.
4. The planner prints the combined result.
The planner is about 100 lines of dependency-free Node that speaks plain A2A JSON-RPC. It could be any A2A-capable orchestrator. The point is that the two coding agents are interchangeable A2A peers.
## Try it without API keys [#try-it-without-api-keys]
```bash
git clone https://github.com/shashikanth-gs/a2a-wrapper.git
cd a2a-wrapper/examples/software-engineering-crew
node planner.mjs --mock "Add a slugify(text) helper"
```
`--mock` starts two in-process stand-in agents so you can see the flow. They make no model calls and change no files.
## Run it for real [#run-it-for-real]
You need an `ANTHROPIC_API_KEY` and an `OPENAI_API_KEY` (or `codex login` plus `CODEX_MODEL`).
These agents can edit files and run commands in `WORKSPACE_DIR`. Use a scratch repository, not one you care about. See the [security guide](/docs/guides/security).
### Install and build [#install-and-build]
From the repository root:
```bash
npm install
npx turbo run build
```
### Create a shared scratch repository [#create-a-shared-scratch-repository]
Codex requires a Git repository.
```bash
export WORKSPACE_DIR=$(mktemp -d)
git -C "$WORKSPACE_DIR" init && git -C "$WORKSPACE_DIR" commit --allow-empty -m init
```
### Start the implementer (Claude) [#start-the-implementer-claude]
```bash
export ANTHROPIC_API_KEY=sk-ant-...
cd a2a-claude && npm run dev -- --config agents/example/config.json
```
### Start the tester (Codex) [#start-the-tester-codex]
```bash
export OPENAI_API_KEY=sk-... # same WORKSPACE_DIR as above
cd a2a-codex && npm run dev -- --config agents/example/config.json
```
### Run the crew [#run-the-crew]
```bash
cd examples/software-engineering-crew
node planner.mjs "Add a slugify(text) helper to src/slugify.js that lowercases and hyphenates"
```
Use `IMPLEMENTER_URL` and `TESTER_URL` if your agents run on other ports or hosts.
## What the planner does [#what-the-planner-does]
* **Discovery**: fetches `/.well-known/agent-card.json` from each agent first.
* **Delegation**: sends `message/send` with `configuration.blocking: true`.
* **Artifacts**: reads the text parts of each agent's returned artifacts.
* **Sessions**: shares one `contextId` across both calls so each agent can keep multi-turn history.
## Ideas to extend it [#ideas-to-extend-it]
* Add a third agent (`a2a-copilot` or `a2a-opencode`) as a reviewer.
* Loop until the tester reports passing tests.
* Replace the planner with LangGraph, Google ADK or any other A2A client.
* For the variant where sub-agents are exposed as MCP tools, see [sub-agents](/docs/concepts/sub-agents).
The `--mock` path runs with no external services. The real path depends on live model APIs and is not covered by CI, so treat it as a working reference.
# Quick start (/docs/getting-started/quickstart)
This is the simplest useful thing you can do: expose one agent over A2A and talk to it.
## 1. Install and sign in [#1-install-and-sign-in]
You need Node 20+ and a GitHub account with Copilot access.
```bash
npm install -g a2a-copilot
gh auth login # or: export GITHUB_TOKEN=...
```
## 2. Write a config file [#2-write-a-config-file]
Save this as `config.json`:
```json title="config.json"
{
"agentCard": {
"name": "Copilot Agent",
"description": "GitHub Copilot as an A2A agent",
"version": "1.0.0",
"protocolVersion": "0.3.0",
"streaming": true,
"defaultInputModes": ["text"],
"defaultOutputModes": ["text"],
"skills": [
{ "id": "assistant", "name": "Coding Assistant", "description": "Answer coding questions", "tags": ["code"] }
]
},
"server": { "port": 3000, "hostname": "127.0.0.1" },
"copilot": {
"model": "auto",
"systemPrompt": "You are a concise coding assistant.",
"systemPromptMode": "replace"
}
}
```
`"model": "auto"` lets Copilot choose a model your plan can use. If you pin a model and see `Model "..." is not available`, that model is not enabled for your account.
## 3. Start the server [#3-start-the-server]
```bash
a2a-copilot --config ./config.json
```
The server binds to `127.0.0.1`, so only your machine can reach it.
## 4. Discover the agent [#4-discover-the-agent]
Every A2A agent publishes an agent card. Any client starts here:
```bash
curl -s localhost:3000/.well-known/agent-card.json | jq '{name, protocolVersion, url, skills: [.skills[].id]}'
```
## 5. Talk to it [#5-talk-to-it]
```bash
curl -s -X POST localhost:3000/a2a/jsonrpc -H 'content-type: application/json' -d '{
"jsonrpc": "2.0", "id": "1", "method": "message/send",
"params": {
"message": { "kind": "message", "messageId": "m1", "role": "user",
"parts": [{ "kind": "text", "text": "What does Array.prototype.flatMap do?" }] },
"configuration": { "blocking": true }
}
}'
```
`"blocking": true` makes the call return when the task completes. The answer is in the task's `artifacts`. The repository also ships a tiny dependency-free client, [`ask.mjs`](https://github.com/shashikanth-gs/a2a-wrapper/tree/main/examples/copilot-quickstart), that does exactly this.
Because this is standard A2A, the same server works with the official A2A SDKs, inspectors and other orchestrators.
## Other backends [#other-backends]
Claude, Codex, OpenCode and Antigravity work the same way. Only the package name and the backend block in the config change. See [choosing a wrapper](/docs/guides/choosing-a-wrapper).
## Next [#next]
Combine several agents: one implements, another tests.
Before you expose an agent beyond localhost.
# Build your own wrapper (/docs/guides/build-a-wrapper)
Because the protocol work lives in [`@a2a-wrapper/core`](/docs/reference/packages/core), a new wrapper only needs a backend-specific executor and a thin CLI. You get the server, agent card, version negotiation, sessions and config loading for free.
## Install [#install]
```bash
npm install @a2a-wrapper/core
npm install @a2a-js/sdk express uuid # peer dependencies
```
## A minimal wrapper [#a-minimal-wrapper]
```ts title="src/cli.ts"
import { createCli, type BaseAgentConfig, type A2AExecutor } from "@a2a-wrapper/core";
// 1. Define your backend-specific config
interface MyBackendConfig {
apiUrl: string;
}
type MyConfig = BaseAgentConfig;
// 2. Implement the executor
class MyExecutor implements A2AExecutor {
constructor(private config: Required) {}
async initialize() { /* connect to your backend */ }
async shutdown() { /* clean up */ }
async execute(context: any, event: any) { /* handle an A2A task */ }
}
// 3. Wire it up
createCli({
packageName: "my-a2a-wrapper",
version: "1.0.0",
defaults: { /* full default config */ } as Required,
usage: "Usage: my-a2a-wrapper [options]",
executorFactory: (config) => new MyExecutor(config),
parseBackendArgs: (values) => ({ backend: { apiUrl: values["api-url"] as string } }),
loadEnvOverrides: () => ({ backend: { apiUrl: process.env.MY_API_URL } }),
extraArgDefs: { "api-url": { type: "string" } },
});
```
Run it:
```bash
node dist/cli.js --port 3000 --log-level debug --api-url http://localhost:8080
```
## What `execute()` should do [#what-execute-should-do]
1. Register the task with `publishTask(bus, taskId, contextId)` when there is no prior task record.
2. Publish progress with `publishStatus(...)` (`working`, then `completed` or `failed`).
3. Deliver output with `publishFinalArtifact(...)`, or stream with `publishStreamingChunk(...)` and `publishLastChunkMarker(...)`.
4. Optionally publish [sideband events](/docs/concepts/observability) through `AgentEventEmitter`.
5. Honour cancellation through an `AbortController`.
For session handling, extend `BaseSessionManager` and implement `getOrCreate(contextId)`.
## Adding it to this monorepo [#adding-it-to-this-monorepo]
The repository's [contributing guide](/docs/community/contributing) has the full checklist for adding a new wrapper package: directory layout, workspace registration, changesets and documentation.
# Choosing a wrapper (/docs/guides/choosing-a-wrapper)
All five wrappers share the same core: A2A v1.0 native with v0.3.x compatibility, sub-agent delegation, MCP tool integration, and JSON config with environment and CLI overrides. They differ in the backend behind them.
## Comparison [#comparison]
| | Backend SDK | Provider flexibility | Safety controls | Default port (example config) |
| ------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ----------------------------- |
| **a2a-copilot** | GitHub Copilot SDK | BYOK: Ollama, OpenAI, Anthropic, Azure, vLLM, any OpenAI-compatible endpoint | Run in a container with a scratch workspace | 3000 |
| **a2a-claude** | `@anthropic-ai/claude-agent-sdk` | Claude models (Anthropic API, Bedrock, Vertex) | `permissionMode`: `plan`, `acceptEdits`, `dontAsk`, gated `bypassPermissions` | 3030 |
| **a2a-codex** | `@openai/codex-sdk` | OpenAI Codex models | `sandboxMode`: `read-only`, `workspace-write`, `danger-full-access`; `approvalPolicy` | 3020 |
| **a2a-opencode** | OpenCode | Multi-provider, switch in one config line | `--auto-approve` is on by default; run in a container | 3000 |
| **a2a-antigravity** | Google Antigravity Python SDK, through a managed bridge | Gemini (API key or Vertex/ADC) | Antigravity policies, `run_command` gating | 3040 |
## Which should I pick? [#which-should-i-pick]
**a2a-opencode** routes to Anthropic, OpenAI, GitHub Copilot and more. **a2a-copilot** adds BYOK for local and self-hosted models.
**a2a-claude** wraps Claude: repository navigation, planning, shell and edits, with explicit permission modes.
**a2a-codex** confines work to the workspace with `read-only` or `workspace-write` sandboxes.
**a2a-antigravity** exposes Google Antigravity. It needs a one-time Python environment setup.
## Things worth knowing [#things-worth-knowing]
* **Local or self-hosted models:** use `a2a-copilot` with a BYOK provider. The model must support native tool calling; some small local models emit tool calls as plain text and will not work.
* **Read-only review agent:** `a2a-claude` with `permissionMode: "plan"` or `a2a-codex` with `sandboxMode: "read-only"`.
* **Docker:** most wrappers ship a Dockerfile. Codex shell sandboxing needs extra privileges inside a container; see [Docker](/docs/guides/docker).
* **OpenCode needs an OpenCode server.** `a2a-opencode` connects to a running OpenCode server at `opencode.baseUrl` (the example uses `http://localhost:4097`).
* **Mixing wrappers:** any combination works in one system because they all speak A2A. The [crew example](/docs/getting-started/multi-agent-crew) pairs Claude with Codex.
## Wrapper or something else? [#wrapper-or-something-else]
| You want to… | Use |
| ----------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| Expose an existing coding agent over A2A with no code | a2a-wrapper |
| Build a fully custom agent from scratch | The official A2A SDKs |
| Orchestrate many agents with a framework | LangGraph, Google ADK, CrewAI and similar as the orchestrator, calling wrappers as execution nodes |
See also [integrate with agent frameworks](/docs/guides/integrate-with-frameworks).
# Configuration (/docs/guides/configuration)
Every wrapper is configured with a single JSON file, passed with `--config`. The same shape is shared across wrappers, with one backend-specific block (`copilot`, `claude`, `codex`, `opencode` or `antigravity`).
## Layering [#layering]
Settings resolve in this order, with later layers overriding earlier ones:
```
built-in defaults ← config file ← environment variables ← CLI flags
```
So a flag such as `--port 4000` beats `PORT=3000`, which beats `"port": 3010` in the file.
## Secrets and substitution [#secrets-and-substitution]
Never put credentials in `config.json`. Reference environment variables instead:
* `${VAR}` works anywhere inside a string, including in the middle of one.
* Bare `$VAR` works in some fields, for example MCP `args`.
* If a variable is not set, the token is left as-is so a misconfiguration stays visible rather than silently sending an empty value.
```json
{ "headers": { "Authorization": "Bearer ${LINEAR_API_KEY}" } }
```
## Shared blocks [#shared-blocks]
| Block | Purpose | Key fields |
| ----------- | ---------------------------------------------------------------------------------- | -------------------------------------------------------- |
| `agentCard` | What the agent advertises. See [agent cards](/docs/concepts/agent-cards) | `name`, `description`, `version`, `skills`, `streaming` |
| `server` | Networking | `port`, `hostname`, `advertiseHost`, `advertiseProtocol` |
| `session` | Multi-turn behaviour. See [sessions](/docs/concepts/sessions) | `reuseByContext`, `ttl`, `cleanupInterval` |
| `features` | Behaviour flags | `streamArtifactChunks`, and per-wrapper event flags |
| `events` | Sideband events. See [observability](/docs/concepts/observability) | `enabled`, `transport`, `httpUrl`, `httpHeaders` |
| `timeouts` | Limits | `prompt` (milliseconds) |
| `logging` | Logs | `level`: `debug`, `info`, `warn`, `error` |
| `mcp` | Tool servers. See [MCP tools](/docs/guides/mcp-tools) | one entry per server |
| `memory` | Instructions and skills. See [memory and skills](/docs/concepts/memory-and-skills) | `instructions`, `skills` |
| `subAgents` | Delegation. See [sub-agents](/docs/concepts/sub-agents) | `agents`, `options` |
## Backend blocks [#backend-blocks]
| Wrapper | Block | Notable fields |
| ----------------- | ------------- | ------------------------------------------------------------------------------------ |
| `a2a-copilot` | `copilot` | `model`, `systemPrompt`, `systemPromptMode`, `provider` (BYOK) |
| `a2a-claude` | `claude` | `workingDirectory`, `model`, `permissionMode`, `thinking`, `settingSources` |
| `a2a-codex` | `codex` | `workingDirectory`, `model`, `sandboxMode`, `approvalPolicy`, `networkAccessEnabled` |
| `a2a-opencode` | `opencode` | `baseUrl`, `projectDirectory`, `model`, `agent`, `systemPromptMode` |
| `a2a-antigravity` | `antigravity` | `pythonPath`, auth mode, policies |
Full field tables are in each [package reference](/docs/reference/packages/core).
## Common environment variables and flags [#common-environment-variables-and-flags]
| Setting | Flag | Environment |
| ------------------ | ---------------------------------------------- | ---------------------------------------------------------------------------- |
| Config file | `--config ` | `CONFIG_FILE` (used by the `server.sh` scripts) |
| Port | `--port` | `PORT` |
| Bind address | `--hostname` | `HOSTNAME` |
| Advertised host | `--advertise-host` | `ADVERTISE_HOST` |
| Workspace | `--workspace` | `WORKSPACE_DIR` |
| Model | `--model` | backend-specific, for example `COPILOT_MODEL`, `CLAUDE_MODEL`, `CODEX_MODEL` |
| Streamed artifacts | `--stream-artifacts` / `--no-stream-artifacts` | `STREAM_ARTIFACTS` |
| Log level | `--log-level` | `LOG_LEVEL` |
Exact flags differ slightly between wrappers. Run ` --help` or see the package reference.
## Start from an example [#start-from-an-example]
Each wrapper ships example agents under `agents/`. Copy one and edit it:
```bash
cp -r agents/example agents/my-agent
./agents/my-agent/start.sh start
```
The `start.sh` scripts support `start`, `stop`, `restart`, `status`, `logs` and `foreground`.
# Docker (/docs/guides/docker)
Most wrappers ship a Dockerfile. Running an agent in a container is the recommended way to limit what it can reach. See the [security guide](/docs/guides/security).
## Build from the monorepo root [#build-from-the-monorepo-root]
The image needs the local `@a2a-wrapper/core` source, so build from the repository root:
```bash
docker build -f a2a-claude/Dockerfile -t a2a-claude:latest .
```
Replace `a2a-claude` with the wrapper you want.
## Run [#run]
Pass credentials as environment variables and mount only the workspace:
```bash
docker run -p 3030:3030 \
-e ANTHROPIC_API_KEY=sk-ant-... \
-e WORKSPACE_DIR=/workspace \
-v /host/path/to/repo:/workspace \
a2a-claude:latest
```
Inside a container the server must bind to a reachable interface. Publish the port with `-p`, and on a shared network put an authenticating proxy in front, since the endpoint itself has no authentication.
## Wrapper notes [#wrapper-notes]
| Wrapper | Note |
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `a2a-claude` | Pass `ANTHROPIC_API_KEY` (or your Bedrock / Vertex environment) with `-e`. The Dockerfile header has a ready-made `docker run` |
| `a2a-codex` | Codex wraps shell commands in Bubblewrap, which needs Linux user namespaces. Standard containers block them, so shell tool calls fail unless you run with `--privileged` or `--security-opt=seccomp:unconfined`. For local development, run without Docker |
| `a2a-copilot`, `a2a-opencode` | Support a corporate proxy CA (Netskope, Zscaler). See their references |
| `a2a-antigravity` | No published Docker image yet, because of the Node and Python bridge |
Pass API keys at runtime with `-e` or your orchestrator's secret store. Never `COPY` a config containing credentials.
# Integrate with agent frameworks (/docs/guides/integrate-with-frameworks)
Orchestration frameworks such as LangGraph, Google ADK, Microsoft Agent Framework and CrewAI manage state, memory and control flow. a2a-wrapper does not replace them. It gives them **execution nodes** that are real coding agents.
```
LangGraph / ADK / Microsoft Agent Framework / CrewAI
(state, memory, flow control)
↓
A2A protocol
↓
a2a-copilot · a2a-claude · a2a-codex · a2a-opencode · a2a-antigravity
```
## What the orchestrator needs to do [#what-the-orchestrator-needs-to-do]
Any orchestrator that can make HTTP calls can drive a wrapper, in three steps:
1. **Discover**: `GET {base}/.well-known/agent-card.json`. Read `url` and `skills`.
2. **Send**: `POST` a `message/send` (or `message/stream`) JSON-RPC request to the card's `url`, reusing a `contextId` across turns.
3. **Read**: take the answer from the task's `artifacts`, and check `status.state`.
The [quick start](/docs/getting-started/quickstart) shows the exact requests, and the [crew example](/docs/getting-started/multi-agent-crew) shows a planner doing this against two agents.
## Use an A2A client library [#use-an-a2a-client-library]
If your framework or language has an A2A client, point it at the wrapper's base URL. The wrappers implement the standard protocol, so the official SDKs and inspectors work with them.
Check your framework's documentation for its current A2A client support and API. The wrapper side is framework-independent.
## Two ways to compose agents [#two-ways-to-compose-agents]
| Approach | How | Good for |
| -------------------------- | ------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------- |
| **External orchestration** | A planner or framework calls each agent | Fixed pipelines, explicit control, audit |
| **Sub-agents** | A wrapper agent declares `subAgents` and the model calls other agents as tools | The model should decide when and what to delegate. See [sub-agents](/docs/concepts/sub-agents) |
## Tips [#tips]
* **Reuse one `contextId` per agent per workflow** so each agent keeps its own history.
* **Give each agent its own workspace or branch** if they edit files, to avoid conflicting changes.
* **Use streaming** (`message/stream`) for long tasks, or blocking calls with a generous timeout.
* **Secure the wiring.** Each agent is an unauthenticated entry point unless you add a gateway. See the [security guide](/docs/guides/security).
# MCP tool servers (/docs/guides/mcp-tools)
Wrappers pass an `mcp` map to the backend so it can use MCP tool servers. The shape differs slightly per backend; the concept is the same.
MCP configuration is fixed when the agent starts. Declare every server in `config.json` before launching. The key `a2a-subagents` is reserved for the [sub-agent bridge](/docs/concepts/sub-agents).
## Supported transports [#supported-transports]
| Wrapper | Transports |
| ----------------- | ----------------------------------------------------------------- |
| `a2a-copilot` | HTTP, SSE, stdio |
| `a2a-claude` | stdio, Streamable HTTP (SSE-only servers are rejected at startup) |
| `a2a-codex` | stdio, Streamable HTTP (SSE-only is not supported) |
| `a2a-opencode` | local (stdio), remote (HTTP/SSE), remote with OAuth |
| `a2a-antigravity` | Antigravity-supported stdio and Streamable HTTP |
## A local stdio server [#a-local-stdio-server]
```json
{
"mcp": {
"filesystem": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "${WORKSPACE_DIR}"]
}
}
}
```
```json
{
"mcp": {
"filesystem": {
"type": "local",
"command": ["npx", "@modelcontextprotocol/server-filesystem", "/path/to/workspace"],
"enabled": true,
"timeout": 10000
}
}
}
```
## A remote HTTP server [#a-remote-http-server]
```json
{
"mcp": {
"my-tools": { "type": "http", "url": "http://localhost:8002/mcp" }
}
}
```
In `a2a-opencode` use `"type": "remote"` instead of `"http"`.
## Authenticated servers [#authenticated-servers]
Hosted servers such as Linear or Notion need credentials. Add a `headers` map. Values support `${ENV_VAR}` substitution, so secrets never live in the config file.
```json
{
"mcp": {
"linear": {
"type": "http",
"url": "https://mcp.linear.app/mcp",
"headers": { "Authorization": "Bearer ${LINEAR_API_KEY}" }
}
}
}
```
```bash
LINEAR_API_KEY=lin_xxx a2a-claude --config ./config.json
```
For stdio servers, `args` and `env` values support the same substitution, so the spawned server receives its tokens without them being committed.
## OAuth (OpenCode) [#oauth-opencode]
When a remote server requires an OAuth2 flow:
```json
{
"mcp": {
"my-oauth-tools": {
"type": "remote",
"url": "https://api.example.com/mcp",
"oauth": { "clientId": "...", "clientSecret": "...", "scope": "read write" }
}
}
}
```
Prefer `headers` with an environment variable whenever you control the token.
## Scope tools tightly [#scope-tools-tightly]
An MCP server runs as a separate process with its own permissions. Give it the narrowest arguments and environment it needs, and use the backend's own allow and deny controls where available (for example `enabledTools` and `disabledTools` in `a2a-codex`). See the [security guide](/docs/guides/security).
# Security guide (/docs/guides/security)
> This page is generated from [`docs/security.md`](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/docs/security.md) and stays in sync with the repository.
An `a2a-*` wrapper turns a coding agent — one that can read files, edit files and often run shell commands — into an HTTP service. Treat it like exposing a shell, not like exposing a chatbot. This page describes what the wrappers do and do not protect you from, so you can deploy them safely.
> Found a vulnerability? Please report it privately — see [SECURITY.md](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/SECURITY.md).
## The short version [#the-short-version]
1. **The A2A endpoint has no built-in authentication.** The server is built with `UserBuilder.noAuthentication` (see `packages/core/src/server/factory.ts`). Anyone who can reach the port can send the agent tasks.
2. **The default bind address is `0.0.0.0`** (all interfaces). Change it unless you mean to expose the agent.
3. **Put the agent in a workspace you can afford to lose** — a scratch clone, a container, or a VM — and keep secrets out of it.
4. **Use the narrowest permission / sandbox mode that still does the job.**
## 1. Network exposure [#1-network-exposure]
| Situation | Do this |
| ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| Local development, agents only talk to each other on one machine | Set `"server": { "hostname": "127.0.0.1" }` in the agent config |
| Agents on different hosts | Keep the agent on a private network and put a reverse proxy in front that enforces authentication (bearer token, mTLS, or your SSO) and TLS |
| Public internet | Don't expose the agent directly. If you must, authenticate at a gateway first |
`server.hostname` controls the bind address. `server.advertiseHost` / `advertiseProtocol` only change the URLs written into the agent card; they do not restrict who can connect.
Outbound calls are a separate matter: when a wrapper calls *another* A2A agent (sub-agents), you can attach headers such as `Authorization: Bearer ${TOKEN}` from the environment. Keep tokens in environment variables, not in config files.
## 2. What the agent is allowed to do [#2-what-the-agent-is-allowed-to-do]
Each wrapper has its own guardrails. They limit what the *backend* may do inside the workspace; they are not a substitute for network controls.
| Wrapper | Control | Safe choice | Dangerous choice |
| ----------------- | ----------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `a2a-claude` | `claude.permissionMode` | `plan` (read-only), or the default `acceptEdits` | `bypassPermissions` (needs `dangerouslyAllowBypassPermissions: true`; logs a warning at startup). `default` and `auto` are rejected because headless runs can't prompt a human |
| `a2a-codex` | `codex.sandboxMode` | `read-only`, or `workspace-write` with `networkAccessEnabled: false` | `danger-full-access` |
| `a2a-copilot` | Backend tool permissions | Run in a container with a scratch workspace | Mounting your home directory or real credentials |
| `a2a-opencode` | `--auto-approve` / `AUTO_APPROVE` (**on by default**) | `--no-auto-approve` where a human can approve, otherwise a container with a scratch workspace | Leaving auto-approve on with a real workspace and a network-reachable port |
| `a2a-antigravity` | `policies` (see "Command Policy and `run_command`" in its README) | Keep `run_command` disabled unless the workspace is trusted | Enabling `run_command` on untrusted input |
See each package README for the exact options.
## 3. Workspace and secrets hygiene [#3-workspace-and-secrets-hygiene]
* **Point `WORKSPACE_DIR` at a dedicated checkout**, not at `~` and not at a repo holding production credentials.
* **Never store credentials in config files.** Use `${ENV_VAR}` placeholders, as the shipped examples do.
* **Assume prompt injection.** An agent reading untrusted files, web pages or issue text can be steered by that content. Combined with shell access, this is the main real-world risk. Don't point an agent with write access at untrusted input and then let it touch anything valuable.
* **Keep your own API keys private.** Anyone who can reach an unauthenticated agent spends your model quota.
* **Prefer containers.** Most wrappers ship a Dockerfile; run them with a read-only root filesystem where possible and mount only the workspace.
## 4. Multi-agent setups [#4-multi-agent-setups]
In a planner → implementer → tester chain, each agent is another entry point. Give every agent its own workspace or branch, bind them all to loopback or a private network, and remember that an injected instruction in one agent's output becomes input to the next.
## Checklist before you expose an agent [#checklist-before-you-expose-an-agent]
* [ ] Bound to `127.0.0.1` or behind an authenticating proxy
* [ ] Runs in a container or disposable workspace
* [ ] Narrowest permission / sandbox mode that works
* [ ] No secrets in the workspace or config files
* [ ] Model-provider key has a spending limit
# Troubleshooting (/docs/guides/troubleshooting)
## `Model "..." is not available` [#model--is-not-available]
The model you configured is not enabled for your account. This happens with `a2a-copilot` when a pinned model is outside your plan.
* Set `"model": "auto"` (or `--model auto`) to let Copilot choose.
* Or pick a model your plan includes.
## Authentication errors on startup [#authentication-errors-on-startup]
| Wrapper | Check |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `a2a-copilot` | Run `gh auth login` or set `GITHUB_TOKEN`, and confirm your account has Copilot access |
| `a2a-claude` | `ANTHROPIC_API_KEY` is exported in the same shell that starts the agent |
| `a2a-codex` | `OPENAI_API_KEY` is set, or `codex login` was run. With subscription auth set `CODEX_MODEL`; tokens from `codex login` expire, so re-run it or use an API key for unattended use |
| `a2a-antigravity` | Run `a2a-antigravity setup` once, and set a Gemini key or Vertex/ADC credentials |
## "does not provide an export named …" or build errors after pulling [#does-not-provide-an-export-named--or-build-errors-after-pulling]
Wrappers import `@a2a-wrapper/core` from its compiled output, which is git-ignored. After pulling new commits:
```bash
npm install
npx turbo run build
```
## "Cannot reach an agent" or connection refused [#cannot-reach-an-agent-or-connection-refused]
* Confirm the agent is running and on the port you expect.
* If it binds to `127.0.0.1`, it is reachable only from the same machine. Inside Docker, publish the port with `-p`.
* Fetch `http://HOST:PORT/health` and the agent card to check.
## The task never finishes, or returns an error artifact [#the-task-never-finishes-or-returns-an-error-artifact]
* Increase `timeouts.prompt` (milliseconds) for long tasks.
* Switch to `message/stream` for long-running work.
* For `a2a-claude`, check for a `rate_limit` event; rate limits fail the task.
## The agent forgot the conversation [#the-agent-forgot-the-conversation]
The next turn started a fresh session. Either the `contextId` changed between messages, or the session expired because `session.ttl` is set. Reuse the same `contextId` and check `ttl`. See [sessions](/docs/concepts/sessions).
## Shell commands fail in Docker (a2a-codex) [#shell-commands-fail-in-docker-a2a-codex]
Codex wraps commands in Bubblewrap, which needs Linux user namespaces that standard containers block. Run with `--privileged` or `--security-opt=seccomp:unconfined`, or run without Docker for local development. See [Docker](/docs/guides/docker).
## A client receives odd or duplicated artifact chunks [#a-client-receives-odd-or-duplicated-artifact-chunks]
Streamed artifact chunks use `append: true`. If your client does not handle appends, turn streaming off with `features.streamArtifactChunks: false` (the default), which publishes one complete artifact.
## Local models do not call tools (a2a-copilot BYOK) [#local-models-do-not-call-tools-a2a-copilot-byok]
The model must support native tool calling. Some small or older local models print tool calls as plain text and will not work. Use a tool-capable model.
## Still stuck? [#still-stuck]
Search the [GitHub issues](https://github.com/shashikanth-gs/a2a-wrapper/issues) or open a new one with the package, its version, your Node version, a redacted config and the logs.
# Introduction (/docs)
**a2a-wrapper** is a set of Node.js packages that put production coding agents behind the open [Agent2Agent (A2A) protocol](https://a2a-protocol.org). You write a JSON config file; you get an HTTP server that any A2A client can discover and call.
Turn GitHub Copilot into an A2A server in about a minute.
Claude implements, Codex writes the tests, all over A2A.
Read this before exposing an agent beyond localhost.
Answers to the questions people ask most.
## Find your way around [#find-your-way-around]
The documentation is organised into sections. Use the section switcher at the top of the sidebar to move between them.
| Section | Start here if you want to… |
| ------------------------------------------------- | -------------------------------------------------------------------------- |
| [Get started](/docs/getting-started/installation) | Install a wrapper and run your first agent |
| [Concepts](/docs/concepts/a2a-protocol) | Understand A2A, agent cards, tasks, sessions and sub-agents |
| [Guides](/docs/guides/choosing-a-wrapper) | Choose a wrapper, configure it, add MCP tools, run it in Docker, secure it |
| [Reference](/docs/reference/api) | Look up the HTTP API, the glossary, or a package's full options |
| [AI & agents](/docs/ai) | Use these docs from an AI assistant, including the docs MCP server |
| [Community](/docs/community/contributing) | Contribute, read the roadmap, or check the changelogs |
## The problem [#the-problem]
Coding agents such as Claude, Codex and Copilot are powerful but isolated. There is no clean way to hand a task from one to another, track long-running work, or compose several of them into a team, unless you write custom glue for each pair.
## What a2a-wrapper gives you [#what-a2a-wrapper-gives-you]
Each wrapper takes one backend and exposes it as a first-class A2A agent. Out of the box you get:
* **Discovery** through an [agent card](/docs/concepts/agent-cards) served at `/.well-known/agent-card.json`.
* **A task lifecycle** with streaming, artifacts and cancellation. See [tasks and streaming](/docs/concepts/tasks-and-streaming).
* **Multi-turn [sessions](/docs/concepts/sessions)**: each A2A `contextId` maps to a persistent backend session.
* **[Sub-agent delegation](/docs/concepts/sub-agents)**: an agent can call other A2A agents as tools.
* **MCP tool support** for the backend's own tools.
* **[Observability](/docs/concepts/observability)** through sideband trace events.
* **[Protocol compatibility](/docs/concepts/protocol-versions)**: native A2A v1.0, with v0.3.x clients still supported.
## Supported agents [#supported-agents]
| Backend | Package | Notes |
| ------------------ | ------------------------------------------------------------- | ------------------------------------------------------------- |
| GitHub Copilot | [`a2a-copilot`](/docs/reference/packages/a2a-copilot) | Bring your own model (Ollama, OpenAI, Anthropic, Azure, vLLM) |
| Claude | [`a2a-claude`](/docs/reference/packages/a2a-claude) | Permission modes, background tasks, rate-limit handling |
| OpenAI Codex | [`a2a-codex`](/docs/reference/packages/a2a-codex) | Repository sandboxing, approval policy |
| OpenCode | [`a2a-opencode`](/docs/reference/packages/a2a-opencode) | Multi-provider by default |
| Google Antigravity | [`a2a-antigravity`](/docs/reference/packages/a2a-antigravity) | Node server with a managed Python bridge |
All of them are built on [`@a2a-wrapper/core`](/docs/reference/packages/core), which owns the protocol logic, config loading, server, sessions and events. Each wrapper only implements its backend-specific executor.
## How it fits with MCP [#how-it-fits-with-mcp]
MCP is the vertical rail: how an agent reaches tools. A2A is the horizontal rail: how agents talk to each other. These wrappers add the horizontal rail to agents that already have the vertical one.
## When to use it [#when-to-use-it]
| You want to… | Use |
| ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- |
| Expose Claude, Codex, Copilot, OpenCode or Antigravity as an A2A server with a config file | **a2a-wrapper** |
| Build a fully custom agent from scratch | The official [A2A SDKs](https://a2a-protocol.org) |
| Orchestrate agents with LangGraph, Google ADK, CrewAI or similar | Those frameworks as the orchestrator, calling these wrappers as execution nodes |
The A2A endpoint has no built-in authentication and the agents can edit files and run commands. Bind to `127.0.0.1` for local use and read the [security guide](/docs/guides/security) before exposing anything on a network.
# HTTP API (/docs/reference/api)
Every wrapper serves the same endpoints. `{base}` below is the agent's URL, for example `http://localhost:3000`.
| Endpoint | Method | Purpose |
| ------------------------------ | ------ | ------------------------------------------------------------------------------ |
| `/.well-known/agent-card.json` | GET | The [agent card](/docs/concepts/agent-cards). Honours the `A2A-Version` header |
| `/a2a/jsonrpc` | POST | A2A over JSON-RPC 2.0 |
| `/a2a/rest/...` | varies | A2A over HTTP+JSON |
| `/health` | GET | Health check |
| `/context` | GET | Read the workspace `context.md` (some wrappers) |
| `/context/build` | POST | Build `context.md` by exploring the workspace read-only (some wrappers) |
None of these endpoints is authenticated by the wrapper. Bind to loopback or put an authenticating proxy in front. See the [security guide](/docs/guides/security).
## JSON-RPC methods [#json-rpc-methods]
The examples use the v0.3 method names, which every client supports. The server also accepts native v1.0 shapes. See [protocol versions](/docs/concepts/protocol-versions).
| Method | Purpose |
| ---------------- | --------------------------------------------- |
| `message/send` | Send a message and get a task or message back |
| `message/stream` | Send a message and receive a stream of events |
| `tasks/get` | Fetch a task's current state |
| `tasks/cancel` | Cancel an in-flight task |
### message/send [#messagesend]
```bash
curl -s -X POST {base}/a2a/jsonrpc -H 'content-type: application/json' -d '{
"jsonrpc": "2.0", "id": "1", "method": "message/send",
"params": {
"message": {
"kind": "message", "messageId": "m1", "role": "user",
"contextId": "optional-conversation-id",
"parts": [{ "kind": "text", "text": "Explain this repository." }]
},
"configuration": { "blocking": true }
}
}'
```
A successful blocking response contains a task:
```json
{
"jsonrpc": "2.0", "id": "1",
"result": {
"kind": "task",
"id": "…", "contextId": "…",
"status": { "state": "completed" },
"artifacts": [{ "artifactId": "…", "parts": [{ "kind": "text", "text": "…" }] }]
}
}
```
Read the answer from `result.artifacts[].parts[].text`. Reuse `result.contextId` on follow-up messages to continue the conversation.
### message/stream [#messagestream]
Same request body with `"method": "message/stream"`. The response is a Server-Sent Events stream of status updates and artifact chunks. Agent cards advertise support with `streaming: true`.
### tasks/get and tasks/cancel [#tasksget-and-taskscancel]
```bash
curl -s -X POST {base}/a2a/jsonrpc -H 'content-type: application/json' -d '{
"jsonrpc": "2.0", "id": "2", "method": "tasks/cancel",
"params": { "id": "" }
}'
```
Cancelling is idempotent.
## Errors [#errors]
Failures use JSON-RPC's `error` object: `{ "error": { "code": …, "message": "…" } }`. A task that fails during execution returns a task with `status.state` of `failed`. Check both.
## REST (HTTP+JSON) [#rest-httpjson]
The REST binding lives under `/a2a/rest`. Legacy v0.3 routes are available, for example `POST /a2a/rest/v1/message:send` and `POST /a2a/rest/v1/tasks/{taskId}:cancel`. v1.0 routes omit the `/v1` segment, for example `POST /a2a/rest/tasks/{taskId}:cancel`.
## Health [#health]
```bash
curl -s {base}/health
```
Use it for container and load-balancer probes.
# Glossary (/docs/reference/glossary)
**A2A (Agent2Agent).** An open protocol for agents to discover each other and exchange tasks over HTTP. See [What is A2A?](/docs/concepts/a2a-protocol).
**Agent card.** JSON at `/.well-known/agent-card.json` describing an agent's name, skills and endpoints. See [agent cards](/docs/concepts/agent-cards).
**Artifact.** An output of a task, delivered as parts. The final answer is normally an artifact.
**Backend.** The agent product a wrapper exposes: Copilot, Claude, Codex, OpenCode or Antigravity.
**BYOK (bring your own key/model).** Pointing `a2a-copilot` at your own provider, such as Ollama, OpenAI, Anthropic or Azure, instead of GitHub Copilot's hosted models.
**Canary.** An unreviewed npm release published after every merge to `main`. Installed with `@canary`.
**Context / contextId.** An identifier grouping related messages into one conversation. Wrappers map it to a backend session. See [sessions](/docs/concepts/sessions).
**Core (`@a2a-wrapper/core`).** The shared package holding all protocol, server, config, session and event logic.
**Executor.** The backend-specific class a wrapper implements (`A2AExecutor`) to run tasks.
**JSON-RPC.** The message format for A2A's primary transport. The endpoint is `/a2a/jsonrpc`.
**MCP (Model Context Protocol).** An open protocol for connecting agents to tools and data. A2A is its horizontal counterpart. See [MCP tool servers](/docs/guides/mcp-tools).
**Message.** One turn of communication, made of parts, with a `user` or `agent` role.
**Orchestrator.** A client or framework that discovers agents and delegates work to them.
**Permission mode / sandbox mode.** Controls on what a backend may do in the workspace, for example `permissionMode` in `a2a-claude` and `sandboxMode` in `a2a-codex`. See the [security guide](/docs/guides/security).
**Sideband event.** An observability event (tool call, reasoning, file change, usage) emitted alongside the answer. See [observability](/docs/concepts/observability).
**Skill (agent card).** A capability advertised to callers for discovery and routing.
**Skill (memory).** A `SKILL.md` folder of instructions written into the workspace for the model. See [memory and skills](/docs/concepts/memory-and-skills).
**Sub-agent.** A remote A2A agent a wrapper exposes to its model as tools. See [sub-agents](/docs/concepts/sub-agents).
**Task.** A unit of work with a lifecycle: `submitted`, `working`, `input-required`, `completed`, `failed` or `canceled`.
**Workspace.** The directory a wrapper's agent may read and modify, set with `WORKSPACE_DIR`.
# a2a-antigravity (/docs/reference/packages/a2a-antigravity)
> This page is generated from [`a2a-antigravity/README.md`](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/a2a-antigravity/README.md) and stays in sync with the repository.
Google Antigravity is an agent runtime for Gemini-backed local and remote
software engineering workflows. It exposes repository tools, policy controls,
hooks, MCP integration, and multi-turn agent sessions through the
`google-antigravity` Python SDK.
**a2a-antigravity** exposes Antigravity as a standalone, interoperable agent via
the [A2A protocol](https://github.com/google-deepmind/a2a). Drop a JSON config
file in, get a spec-compliant A2A server out. Any orchestrator that speaks A2A
can discover and call it without embedding Antigravity-specific integration
code.
> **What is different here:** the public wrapper is Node/TypeScript, but the
> Antigravity SDK is Python. This package runs the A2A HTTP server in Node and
> starts a private Python subprocess for `google-antigravity`. The subprocess is
> managed over a small JSONL bridge; it is not a public API surface.
> **The pattern:** MCP is the vertical rail — how agents access tools. A2A is
> the horizontal rail — how agents talk to each other. This library adds the
> horizontal rail to Google Antigravity.
**Features:**
* Native [A2A v1.0](https://a2a-protocol.org) protocol, backward compatible with v0.3.x clients — Agent Card, JSON-RPC, REST, and SSE-compatible behavior through `@a2a-js/sdk`
* Powered by the Google Antigravity Python SDK through a private Python bridge
* Explicit `a2a-antigravity setup` command for managed Python virtualenv creation
* Gemini API key and Vertex/ADC auth modes exposed through JSON/env/CLI config
* Tested with `gemini-3.1-flash-lite`
* Antigravity policy support, including `run_command` enablement for trusted workspaces
* MCP server config passthrough for Antigravity-supported stdio and Streamable HTTP transports
* Multi-turn context continuity — each A2A `contextId` maps to a persistent Antigravity session
* Sideband trace artifacts for lifecycle, tool-call start, usage, errors, and structured output
* JSON config file with layered overrides (JSON → env vars → CLI flags)
* TypeScript source with full type declarations
## Why a Node wrapper around a Python subprocess? [#why-a-node-wrapper-around-a-python-subprocess]
The rest of this monorepo exposes A2A servers from Node packages. Antigravity's
public SDK surface is Python. Keeping the public server in Node gives
orchestrators the same A2A HTTP surface, packaging model, config layering, and
sideband event conventions as `a2a-codex`, `a2a-copilot`, and `a2a-opencode`,
while letting the Python subprocess own SDK-specific session and tool behavior.
This split is intentionally narrow:
* Node owns the A2A server, config loading, agent card, task lifecycle, and
event mapping.
* Python owns `google-antigravity` imports, `LocalAgentConfig`, sessions, chats,
built-in tools, policies, and SDK-native chunks.
* The bridge protocol is JSONL over stdio and treated as an internal boundary.
## Works with agent frameworks [#works-with-agent-frameworks]
This library complements — not replaces — frameworks like LangGraph, Google ADK,
Microsoft Agent Framework, and CrewAI. Use those frameworks for orchestration,
state, and memory control. Use `a2a-antigravity` as the execution node they call.
```
LangGraph / ADK / Microsoft Agent Framework
(state, memory, flow control)
↓
A2A Protocol
↓
a2a-antigravity
(Node A2A server + Python SDK bridge)
↓
Google Antigravity SDK / Gemini
```
## Quick Start [#quick-start]
```bash
# Install globally
npm install -g a2a-antigravity
# Create the managed Python environment once
a2a-antigravity setup
# Run the bundled example agent
export GEMINI_API_KEY=...
export WORKSPACE_DIR=/path/to/your/repo
a2a-antigravity --config agents/example/config.json
```
Or run without installing globally:
```bash
npx a2a-antigravity setup
GEMINI_API_KEY=... WORKSPACE_DIR=/path/to/repo npx a2a-antigravity --config agents/example/config.json
```
The agent card is available at
`http://localhost:3040/.well-known/agent-card.json`.
## Tested With [#tested-with]
| Component | Version |
| ----------------------------- | ------------------------------------------------ |
| Google Antigravity Python SDK | **0.1.5** |
| Gemini model | **gemini-3.1-flash-lite** |
| `@a2a-js/sdk` | **1.0.0** |
| A2A protocol | **v1.0 (native) + v0.3.x (backward compatible)** |
| Node.js | **>=20** |
| Python | **>=3.10** |
Other versions may work, but the above combination is what has been tested
end-to-end.
## Architecture [#architecture]
```
A2A Client (Orchestrator / Inspector / curl)
│
│ JSON-RPC or REST over HTTP
▼
Express Server (a2a-antigravity, Node/TypeScript)
│ ├─ /.well-known/agent-card.json → Agent Card
│ ├─ /a2a/jsonrpc → JSON-RPC (message/send, message/stream, …)
│ ├─ /a2a/rest → REST handler
│ └─ /health → Health check
│
│ @a2a-js/sdk DefaultRequestHandler
▼
AntigravityExecutor (AgentExecutor)
│ ├─ SessionManager — contextId → Antigravity session id
│ ├─ EventMapper — SDK bridge events → A2A trace artifacts
│ └─ BridgeClient — JSONL over stdio
▼
Private Python subprocess
│ ├─ src/antigravity/python/bridge.py
│ ├─ google-antigravity LocalAgentConfig
│ ├─ Agent.chat(...)
│ └─ SDK chunks: text, thoughts, tool calls, usage, structured output
▼
Google Antigravity SDK
│ ├─ Gemini / Vertex model access
│ ├─ Built-in tools such as run_command, view_file, edit_file
│ └─ MCP servers configured through LocalAgentConfig
```
## Installation [#installation]
```bash
# npm
npm install a2a-antigravity
# yarn
yarn add a2a-antigravity
# pnpm
pnpm add a2a-antigravity
```
## Release Channels [#release-channels]
| Channel | Install | Cadence |
| ------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| Stable | `npm install a2a-antigravity` | Manually reviewed and approved — see [Release Process](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/CONTRIBUTING.md#release-process) |
| Canary | `npm install a2a-antigravity@canary` | Every merge to `main` that passes CI |
Canary builds publish automatically after each merge, tagged like `a2a-antigravity@0.2.1-canary-20260812081117`. They're unreviewed and have no changelog entry — useful for trying a fix ahead of a stable release, not for production. `npm install a2a-antigravity` always resolves to the latest stable release regardless of how many canaries have shipped since.
## Python Runtime Setup [#python-runtime-setup]
`a2a-antigravity` is published as an npm package, but the Antigravity runtime is
the Google Antigravity Python SDK. The npm install does not create a Python
environment or run `pip` automatically. Run the explicit setup command once:
```bash
npx a2a-antigravity setup
```
`setup` creates a managed Python virtual environment in the user's cache
directory and installs the bundled `requirements.txt`
(`google-antigravity==0.1.5`). Later server starts automatically use this
managed environment when `ANTIGRAVITY_PYTHON`, `--python`, and
`antigravity.pythonPath` are not set.
Useful setup options:
```bash
npx a2a-antigravity setup --python /path/to/python3
npx a2a-antigravity setup --venv-dir /path/to/venv
npx a2a-antigravity setup --force
```
If you prefer to manage Python yourself, install the SDK in the Python
environment used by `antigravity.pythonPath` or `ANTIGRAVITY_PYTHON`:
```bash
python3 -m pip install google-antigravity
```
Then run with:
```bash
ANTIGRAVITY_PYTHON=/path/to/venv/bin/python a2a-antigravity --config agents/example/config.json
```
## Python Subprocess Lifecycle [#python-subprocess-lifecycle]
The Python bridge is a private child process owned by the Node server. The
wrapper handles the normal production failure paths:
* Startup validates that the selected Python can import `google.antigravity`;
otherwise the server fails fast with a setup-focused error.
* Bridge spawn errors and unexpected bridge exits are logged and reject any
pending or active agent requests instead of hanging callers.
* Shutdown sends a bridge `shutdown` command, closes stdin so the Python read
loop can exit naturally, waits for process close, then escalates to `SIGTERM`
and `SIGKILL` if needed.
* CLI `SIGINT`, `SIGTERM`, and `SIGHUP` handlers close the HTTP server before
shutting down the executor and bridge.
Live smoke tests covered both parent and child failure paths:
* Sending `SIGTERM` to the Node server closed the HTTP server and removed the
Python child process.
* Killing the Python bridge with `SIGKILL` logged the bridge exit, kept the Node
server alive, and `/health` continued to respond.
Operational limits still apply. If the Node process is terminated with
`SIGKILL`, the runtime cannot run cleanup handlers. If Antigravity or a tool
starts detached grandchildren, killing the direct Python bridge may not clean up
that unrelated process tree. For heavily sandboxed command execution, combine
Antigravity policy controls with your platform's process supervision, job
objects, containers, or cgroups.
## Usage [#usage]
### CLI [#cli]
```bash
a2a-antigravity --config agents/example/config.json
```
Full flag reference:
```
a2a-antigravity [options]
a2a-antigravity setup [options]
--config, --agent-json JSON agent config file
--port A2A server port (default: 3040)
--hostname Bind address (default: 0.0.0.0)
--advertise-host Hostname for agent card URLs (default: localhost)
--workspace Primary Antigravity workspace
--model Antigravity/Gemini model
--auth-mode sdkDefault | apiKey | adc
--api-key Gemini API key (prefer GEMINI_API_KEY)
--project Vertex/ADC project
--location Vertex/ADC location
--python Python executable for the bridge
--stream-artifacts Stream artifact chunks
--no-stream-artifacts Buffer artifacts (default)
--log-level debug | info | warn | error
--help Show this help
--version Show version
Setup options:
setup --python Python 3.10+ executable used to create the managed venv
setup --venv-dir Override managed venv directory
setup --force Recreate the managed venv before installing requirements
```
### Programmatic API [#programmatic-api]
```typescript
import { createA2AServer, resolveConfig } from "a2a-antigravity";
const config = resolveConfig("agents/example/config.json");
const handle = await createA2AServer(config);
console.log("Antigravity A2A server running");
await handle.shutdown();
```
## Environment Variables [#environment-variables]
| Variable | Required | Description |
| ------------------------------------------- | --------------------- | ---------------------------------------------- |
| `GEMINI_API_KEY` | Yes for `apiKey` auth | Gemini Developer API / AI Studio key |
| `WORKSPACE_DIR` | Yes\* | Workspace directory Antigravity can operate in |
| `ANTIGRAVITY_MODEL` / `GEMINI_MODEL` | No | Override model, e.g. `gemini-3.1-flash-lite` |
| `ANTIGRAVITY_AUTH_MODE` | No | `sdkDefault`, `apiKey`, or `adc` |
| `ANTIGRAVITY_PYTHON` | No | Explicit Python executable for the bridge |
| `A2A_ANTIGRAVITY_HOME` | No | Base directory for managed Python environment |
| `A2A_ANTIGRAVITY_VENV` | No | Exact managed Python virtualenv directory |
| `GOOGLE_CLOUD_PROJECT` / `GOOGLE_PROJECT` | Required for `adc` | Vertex project |
| `GOOGLE_CLOUD_LOCATION` / `GOOGLE_LOCATION` | Required for `adc` | Vertex location |
| `PORT` | No | A2A server port |
| `HOSTNAME` | No | Bind address |
| `ADVERTISE_HOST` | No | Hostname embedded in agent card URLs |
| `STREAM_ARTIFACTS` | No | Set `"true"` to stream artifact chunks |
| `LOG_LEVEL` | No | `debug` \| `info` \| `warn` \| `error` |
\*Can also be set via `antigravity.workingDirectory` in config or `--workspace`.
## Model Access [#model-access]
The wrapper supports the auth surfaces exposed by the current Python SDK:
* `sdkDefault`: omit provider auth fields and let the SDK read environment/defaults.
* `apiKey`: Gemini Developer API / AI Studio key via `GEMINI_API_KEY` or `provider.apiKey`.
* `adc`: Vertex / Gemini Enterprise Agent Platform with Application Default Credentials.
Google AI Pro/Ultra product-login OAuth is not exposed by `google-antigravity`
0.1.5 as a public Python SDK config surface, so this wrapper does not implement
an unofficial subscription-login path.
## Local Development [#local-development]
```bash
GEMINI_API_KEY=... WORKSPACE_DIR=/path/to/repo npm run dev -w a2a-antigravity -- --config agents/example/config.json
```
Vertex/ADC:
```bash
gcloud auth application-default login
npm run dev -w a2a-antigravity -- \
--workspace /path/to/repo \
--auth-mode adc \
--project my-gcp-project \
--location us-central1
```
## Example Configs [#example-configs]
The default example uses Gemini API key auth, reads the key from
`GEMINI_API_KEY`, and pins the tested Flash-Lite model:
```json
{
"antigravity": {
"workingDirectory": "${WORKSPACE_DIR}",
"model": "gemini-3.1-flash-lite",
"provider": {
"authMode": "apiKey",
"apiKey": "${GEMINI_API_KEY}"
}
}
}
```
Run it from the monorepo:
```bash
GEMINI_API_KEY=... WORKSPACE_DIR="$PWD" npm run dev -w a2a-antigravity -- --config agents/example/config.json
```
The repository also includes
`agents/example/config.run-command.json`, which keeps the same model and auth
but enables Antigravity shell command execution with:
```json
{
"policies": {
"mode": "allowAll"
}
}
```
Use that config only in trusted workspaces. It lets the agent execute local
commands through Antigravity's built-in `run_command` tool.
```bash
GEMINI_API_KEY=... WORKSPACE_DIR="$PWD" npm run dev -w a2a-antigravity -- --config agents/example/config.run-command.json
```
Or, from the example directory:
```bash
cd a2a-antigravity/agents/example
GEMINI_API_KEY=... CONFIG_FILE="$PWD/config.run-command.json" ./start.sh
```
## Model Notes [#model-notes]
Live tests confirmed the wrapper works with `gemini-3.1-flash-lite` through the
Google Antigravity SDK and Gemini API key auth. The request completed through
the A2A JSON-RPC endpoint and usage telemetry reported
`model: "gemini-3.1-flash-lite"`.
`gemini-3.5-flash` is listed by Google as a Flash model, but
`gemini-3.5-flash-lite` was not available from the Gemini model list at the
time of testing. A real request using `gemini-3.5-flash-lite` reached Gemini and
returned `404 NOT_FOUND`. Use Google's Gemini model documentation to confirm
current model IDs before changing the example:
* [https://ai.google.dev/gemini-api/docs/models](https://ai.google.dev/gemini-api/docs/models)
* [https://ai.google.dev/gemini-api/docs/models/gemini-3.1-flash-lite](https://ai.google.dev/gemini-api/docs/models/gemini-3.1-flash-lite)
## Command Policy and `run_command` [#command-policy-and-run_command]
The Antigravity SDK default policy denies `run_command` unless command execution
is explicitly allowed. With the default example config, the model can attempt a
shell command, and the wrapper emits a sideband tool-start event, but the SDK
denies execution.
For example, asking the agent to check system date/time and free disk space made
it attempt:
```text
date && df -h .
```
With the default config, the final response reported that `run_command` was
blocked by policy, so the agent could not read the date or disk details.
With `agents/example/config.run-command.json`, the same request succeeded. A
live test observed:
```text
Thu Jul 9 15:26:50 IST 2026
/dev/disk3s5 926Gi 292Gi 613Gi 33% ... /System/Volumes/Data
```
The wrapper supports these policy modes under `antigravity.policies`:
* `sdkDefault`: omit SDK policy config and use Antigravity defaults.
* `allowAll`: pass `policy.allow_all()`; this allows `run_command`.
* `denyAll`: pass `policy.deny_all()`.
* `custom`: pass explicit allow/deny rules.
For a deny-by-default config that allows only shell commands:
```json
{
"antigravity": {
"policies": {
"mode": "custom",
"rules": [
{ "decision": "allow", "tool": "run_command" },
{ "decision": "deny", "tool": "*" }
]
}
}
}
```
Refer to Google's Antigravity SDK documentation for the authoritative policy
semantics, priority rules, and safety guidance:
* [https://antigravity.google/docs/sdk/overview](https://antigravity.google/docs/sdk/overview)
* [https://github.com/google-antigravity/antigravity-sdk-python/blob/main/google/antigravity/hooks/README.md](https://github.com/google-antigravity/antigravity-sdk-python/blob/main/google/antigravity/hooks/README.md)
* [https://github.com/google-antigravity/antigravity-sdk-python/blob/main/google/antigravity/tools/README.md](https://github.com/google-antigravity/antigravity-sdk-python/blob/main/google/antigravity/tools/README.md)
* [https://raw.githubusercontent.com/google-antigravity/antigravity-sdk-python/main/google/antigravity/hooks/policy.py](https://raw.githubusercontent.com/google-antigravity/antigravity-sdk-python/main/google/antigravity/hooks/policy.py)
## Sideband Events [#sideband-events]
When `events.transport` is `"a2a"`, sideband events are returned as A2A
`trace.*` artifacts. In live tests, the Antigravity wrapper emitted:
* `trace.lifecycle`: agent start and finish.
* `trace.mcp.start`: built-in tool call start, including `tool:
"run_command"` and arguments such as `command_line` and `working_dir`.
* `trace.usage`: token usage and model name.
Current limitation found in testing: command output did not appear as a separate
tool-result sideband artifact. When `run_command` was allowed, the command
output appeared in the final `response` artifact. No `trace.thinking` artifact
was observed with `gemini-3.1-flash-lite` for the command test.
Example JSON-RPC request for checking the command path:
```bash
curl -s -X POST http://localhost:3040/a2a/jsonrpc \
-H 'content-type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": "sideband-1",
"method": "message/send",
"params": {
"message": {
"kind": "message",
"messageId": "msg-sideband-1",
"role": "user",
"parts": [
{
"kind": "text",
"text": "Use local tools to check the current system date/time and free disk space for the current workspace filesystem. Run commands equivalent to date and df -h . Then report what succeeded and any permission failures."
}
],
"contextId": "ctx-sideband-1"
}
}
}'
```
## Config Shape [#config-shape]
Common wrapper concerns stay top-level: `agentCard`, `server`, `session`,
`features`, `timeouts`, `logging`, `events`, `memory`, `mcp`, and `subAgents`.
Antigravity-specific runtime settings live under `antigravity`. Provider/model
auth and routing stay under `antigravity.provider`.
Unset SDK-owned fields are omitted from the Python bridge config so SDK defaults
remain authoritative.
## Related Packages [#related-packages]
This package is part of the [a2a-wrapper](https://github.com/shashikanth-gs/a2a-wrapper) monorepo:
| Package | Description |
| ---------------------------------------------------------------------- | --------------------------------------------------------------------- |
| [`@a2a-wrapper/core`](https://www.npmjs.com/package/@a2a-wrapper/core) | Shared infrastructure (logging, config, server, events, session, CLI) |
| [`a2a-copilot`](https://www.npmjs.com/package/a2a-copilot) | A2A wrapper for GitHub Copilot |
| [`a2a-opencode`](https://www.npmjs.com/package/a2a-opencode) | A2A wrapper for OpenCode |
| [`a2a-claude`](https://www.npmjs.com/package/a2a-claude) | A2A wrapper for Claude |
| [`a2a-codex`](https://www.npmjs.com/package/a2a-codex) | A2A wrapper for OpenAI Codex |
## Contributor Notes [#contributor-notes]
This package intentionally has two runtimes:
* Node/TypeScript is the public package surface. Keep A2A protocol handling,
config loading, server behavior, and npm-facing CLI UX here.
* Python is the private SDK adapter. Keep it small and focused on translating
JSONL bridge commands into `google-antigravity` SDK calls.
When adding Antigravity SDK features, prefer extending the typed JSON config and
bridge protocol narrowly instead of exposing Python internals directly. If the
SDK changes policy names, model behavior, hook semantics, or built-in tool
payloads, update this README and the example configs with the tested behavior.
# a2a-claude (/docs/reference/packages/a2a-claude)
> This page is generated from [`a2a-claude/README.md`](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/a2a-claude/README.md) and stays in sync with the repository.
Claude is Anthropic's model family, and the Claude Agent SDK gives it a production-grade agent loop. It handles repository navigation, multi-step planning, shell commands, file editing, and permission management — all the plumbing you'd spend months building from scratch.
**a2a-claude** exposes that agent as a standalone, interoperable agent via the [A2A protocol](https://github.com/google-deepmind/a2a), using the [`@anthropic-ai/claude-agent-sdk`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk). Drop a JSON config file in, get a fully spec-compliant A2A server out. Any orchestrator that speaks A2A can discover and call it — no Claude-specific integration code required.
> **The pattern:** MCP is the vertical rail — how agents access tools. A2A is the horizontal rail — how agents talk to each other. This library adds the horizontal rail to Claude.
**Features:**
* Native [A2A v1.0](https://a2a-protocol.org) protocol, backward compatible with v0.3.x clients — Agent Card, JSON-RPC, REST, streaming
* Powered by `@anthropic-ai/claude-agent-sdk` (pinned `0.3.245`) — `claude-sonnet-5`, `claude-opus-4-8`, and any SDK-compatible model
* Permission-mode guardrails — headless-safe modes only, with an explicit opt-in for unrestricted access
* MCP tool support — stdio and Streamable HTTP transports
* Multi-turn context continuity — each A2A `contextId` maps to a persistent Claude session (resumed via the SDK's `resume` option)
* AbortController-based cancellation
* Multi-agent delegation via A2A sub-agents
* Sideband observability events — thinking summaries, tool calls, file changes, todo lists, usage/cost
* JSON config file with layered overrides (JSON → env vars → CLI flags)
* Docker-ready
* TypeScript source with full type declarations
## Quick Start [#quick-start]
```bash
export ANTHROPIC_API_KEY=sk-ant-...
export WORKSPACE_DIR=/path/to/your/repo
npm install
npm run dev -w a2a-claude -- --config agents/example/config.json
```
Fetch the agent card:
```bash
curl -s http://localhost:3030/.well-known/agent-card.json | jq .
```
The CLI is also runnable directly:
```bash
npm install -g a2a-claude
export ANTHROPIC_API_KEY=sk-ant-... WORKSPACE_DIR=/path/to/your/repo
a2a-claude --config agents/example/config.json
```
## Release Channels [#release-channels]
| Channel | Install | Cadence |
| ------- | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| Stable | `npm install a2a-claude` | Manually reviewed and approved — see [Release Process](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/CONTRIBUTING.md#release-process) |
| Canary | `npm install a2a-claude@canary` | Every merge to `main` that passes CI |
Canary builds publish automatically after each merge, tagged like `a2a-claude@0.4.0-canary-20260812081117`. They're unreviewed and have no changelog entry — useful for trying a fix ahead of a stable release, not for production. `npm install a2a-claude` always resolves to the latest stable release regardless of how many canaries have shipped since.
## Authentication [#authentication]
`ANTHROPIC_API_KEY` is the primary and recommended authentication path — export it before starting the agent (or set it in `.env`, see `.env.example`).
Bedrock, Vertex, and related Claude authentication environment variables (e.g. `CLAUDE_CODE_USE_BEDROCK`, `CLAUDE_CODE_USE_VERTEX`, and related credentials) pass through untouched to the Claude Agent SDK — the wrapper never reads, validates, or stores them. Set whichever auth environment your deployment needs; a2a-claude only cares that the SDK's `query()` call can authenticate when invoked.
The API key (or any other credential) is **never** placed in `config.json`. Config fields that need a secret use `${ENV_VAR}` substitution instead (see MCP servers below).
## Configuration Reference [#configuration-reference]
All settings live in a single JSON config file. Priority order: **built-in defaults ← config file ← environment variables ← CLI flags**.
### `claude` block fields [#claude-block-fields]
Fields map 1:1 onto `@anthropic-ai/claude-agent-sdk` `Options` (source of truth: `src/config/types.ts`).
| Field | Type | Description |
| ----------------------------------- | -------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `workingDirectory` | `string` | Absolute path to the workspace Claude operates on. Required at runtime. Supports `${ENV_VAR}`. |
| `model` | `string` | Model (e.g. `claude-sonnet-5`). Supports `${CLAUDE_MODEL}`. SDK default when omitted. |
| `fallbackModel` | `string` | Fallback model when the primary is overloaded/unavailable. |
| `effort` | `"low" \| "medium" \| "high" \| "xhigh" \| "max"` | Reasoning effort level. Overridable with the `CLAUDE_EFFORT` env var. SDK default when omitted. |
| `thinking` | `{ type: "adaptive" }` \| `{ type: "enabled", budgetTokens?, display? }` \| `{ type: "disabled" }` | Extended thinking behavior. SDK default when omitted. |
| `outputFormat` | `{ type: "json_schema", schema: object }` | Request structured JSON output — see **Structured output** below. SDK default (freeform text) when omitted. |
| `permissionMode` | `"acceptEdits" \| "dontAsk" \| "plan" \| "bypassPermissions"` | Permission mode. `"default"` and `"auto"` are rejected — see **Permission modes** below. Default: `"acceptEdits"`. |
| `allowedTools` | `string[]` | Tools auto-allowed without prompting. |
| `disallowedTools` | `string[]` | Tools removed from the model's context entirely. |
| `systemPromptAppend` | `string` | Appended to the `claude_code` preset system prompt. |
| `customSystemPrompt` | `string` | Full system prompt replacement. Mutually exclusive with `systemPromptAppend`. |
| `settingSources` | `Array<"user" \| "project" \| "local">` | Filesystem settings sources to load. Default `[]` = full isolation from host `~/.claude` and project settings. Include `"project"` to load workspace `CLAUDE.md`. |
| `marketplaces` | `Record` | Plugin marketplaces to register, keyed by marketplace id. The SDK fetches and installs the plugins itself — see **Marketplace plugins** below. Every string field in `source` supports `${ENV_VAR}` substitution. |
| `enabledPlugins` | `Record` | Plugins to enable, keyed `"@"`. The marketplace id must appear in `marketplaces`. Startup fails if an enabled plugin does not load. |
| `maxTurns` | `number` | Max conversation turns per query (runaway protection). |
| `maxBudgetUsd` | `number` | Max budget in USD per query. |
| `additionalDirectories` | `string[]` | Additional directories Claude can access. Supports `${ENV_VAR}` per entry. |
| `sandbox` | `object` | Opaque SDK sandbox settings passthrough (OS-level command sandboxing). |
| `executablePathOverride` | `string` | Override the path to the Claude executable. |
| `dangerouslyAllowBypassPermissions` | `boolean` | Must be `true` when `permissionMode` is `"bypassPermissions"`. |
| `contextFile` | `string` | Filename for the pre-built domain context file within `workingDirectory`. Default `"context.md"`. |
| `contextPrompt` | `string` | Default prompt used when `buildContext()` is called without an explicit prompt. |
### Reasoning effort and extended thinking [#reasoning-effort-and-extended-thinking]
`effort` and `thinking` are independent knobs that both affect how much the model reasons before answering.
```json
{
"claude": {
"effort": "high",
"thinking": { "type": "adaptive" }
}
}
```
**`thinking`** selects the mode:
| Value | Behavior |
| --------------------------------------------- | ------------------------------------------------------------- |
| `{ "type": "adaptive" }` | Claude decides when and how much to think. Newer models only. |
| `{ "type": "enabled", "budgetTokens": 4096 }` | Fixed thinking token budget. `budgetTokens` is optional. |
| `{ "type": "disabled" }` | No extended thinking. |
`adaptive` and `enabled` also accept `display`, either `"summarized"` or `"omitted"`.
**`effort`** guides thinking depth, trading latency and token spend against answer quality. Models that do not support the requested level silently downgrade it, and `"max"` is available on select models only — so a level accepted at startup is not a guarantee that the model honors it.
#### Thinking summaries are requested automatically [#thinking-summaries-are-requested-automatically]
The SDK leaves `display` at `"omitted"`, which makes the model emit thinking blocks whose text is an **empty string**. Nothing reaches the sideband, and `features.emitThinkingEvents` looks broken even though it is on.
So whenever `emitThinkingEvents` is enabled — the default — the wrapper asks for summaries:
| Your `claude.thinking` | Sent to the SDK |
| ---------------------------------------------- | ---------------------------------------------------------------------- |
| unset | `{ "type": "adaptive", "display": "summarized" }` |
| `{ "type": "adaptive" }` | `{ "type": "adaptive", "display": "summarized" }` |
| `{ "type": "enabled", "budgetTokens": 8000 }` | `{ "type": "enabled", "budgetTokens": 8000, "display": "summarized" }` |
| `{ "type": "adaptive", "display": "omitted" }` | unchanged — an explicit `display` always wins |
| `{ "type": "disabled" }` | unchanged |
Note the first row: with thinking events on and no `thinking` block of your own, the wrapper turns adaptive thinking **on**, which costs thinking tokens. Set `"thinking": { "type": "disabled" }` or `"emitThinkingEvents": false` to opt out.
Two more things worth knowing:
* `{ "type": "disabled" }` means no `thinking` sideband events can ever fire, regardless of `features.emitThinkingEvents`.
* Both values are validated at startup. An unsupported effort level or a malformed `thinking` object fails `initialize()` with a message naming the allowed values.
### Structured output [#structured-output]
* **`claude.outputFormat`** (object, optional) — Request structured JSON output.
Maps 1:1 onto the Claude Agent SDK's `Options.outputFormat`. Shape:
`{ "type": "json_schema", "schema": { ... } }`, where `schema` is a JSON
Schema the model's output is constrained to match. When set, the parsed
object is published as an `application/json` **data part** on the `response`
artifact, alongside the usual text part (the text part is still emitted, so
text-only clients are unaffected). Omit for freeform text (the SDK default).
### Full config reference [#full-config-reference]
> `agentCard.protocolVersion` below is optional and kept only for backward
> compatibility with older config files — the server negotiates A2A v1.0 vs.
> v0.3.x automatically per request and no longer reads this field. See
> [Protocol Versions](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/packages/core/README.md#protocol-versions) for how
> negotiation works.
```json
{
"agentCard": {
"name": "Claude Workspace Engineer",
"description": "...",
"version": "1.0.0",
"protocolVersion": "0.3.0",
"streaming": true,
"defaultInputModes": ["text"],
"defaultOutputModes": ["text"],
"skills": [
{
"id": "workspace-engineering",
"name": "Workspace Engineering",
"description": "Inspect, modify, and validate code within the configured workspace.",
"tags": ["code", "repository", "tests", "refactoring"]
}
]
},
"server": {
"port": 3030,
"hostname": "0.0.0.0",
"advertiseHost": "localhost",
"advertiseProtocol": "http"
},
"claude": {
"workingDirectory": "${WORKSPACE_DIR}",
"model": "${CLAUDE_MODEL}",
"effort": "high",
"thinking": { "type": "adaptive" },
"permissionMode": "acceptEdits",
"settingSources": [],
"additionalDirectories": [],
"systemPromptAppend": "Operate only within the configured workspace."
},
"session": {
"reuseByContext": true,
"ttl": 0
},
"features": {
"streamArtifactChunks": false,
"emitThinkingEvents": true,
"emitToolEvents": true,
"emitFileChangeEvents": true,
"emitTodoEvents": true,
"emitRateLimitEvents": true,
"holdTaskForBackgroundWork": true,
"emitBackgroundTaskEvents": true
},
"timeouts": {
"prompt": 600000
},
"logging": {
"level": "info"
}
}
```
### Prompt timeout [#prompt-timeout]
`timeouts.prompt` bounds one A2A task from start to terminal state, in milliseconds (default `600000`, ten minutes). When it elapses the task is aborted and published as `failed`.
The timer is armed once, when the task starts, and is never re-armed. With `features.holdTaskForBackgroundWork` on (the default) a task can span several SDK turns, and this one budget covers all of them — including the idle gaps while Claude's background work runs elsewhere. See [Background tasks → Caveats](#caveats) before choosing a value.
Set it to `0` — or any value `<= 0` — to disable the bound entirely and let a task run until it completes. This is the right setting for agents whose turns legitimately run for hours.
Disabling it has one consequence worth knowing: turns are serialized per context, so a turn that never finishes holds its context's queue indefinitely and every later turn on the same `contextId` blocks behind it. Cancelling the task (`tasks/cancel`) still aborts the running turn and is the escape hatch. With background-task holding on, that escape hatch is the *only* release — see [Background tasks → Caveats](#caveats).
### Session lifetime [#session-lifetime]
`session.ttl` controls expiry of the `contextId` → Claude session mapping. It
defaults to `0`, which disables expiry: a conversation resumes the same Claude
session indefinitely. A positive value expires a session that many milliseconds
after it was **created** — measured from the conversation's first message, not
its last — so an active conversation is reset on a fixed schedule rather than
after a period of inactivity. When a session is evicted, the next turn on that
`contextId` starts a brand-new Claude session with no memory of the conversation
so far.
`session.cleanupInterval` is a background sweep that reclaims expired sessions
without waiting for their context to be used again. It also defaults to `0`
(disabled), and it is only consulted at all when `ttl > 0` — with expiry off
there is nothing to sweep. Setting `ttl` alone gives you expiry via the lazy
check, but a context that is never touched again holds its record until the
process exits; set `cleanupInterval` as well if you want that memory reclaimed.
Both disabled states are logged at startup.
### Permission modes [#permission-modes]
The SDK's `permissionMode` controls whether tool calls are auto-approved. Headless A2A execution cannot show an interactive approval prompt to a human, so two of the SDK's four modes are rejected at startup:
| Mode | Behaviour | Headless-safe |
| ------------------- | ----------------------------------------------------- | ------------------------------------------------------------ |
| `default` | Interactive approval per tool call | **Rejected** — no human in the loop |
| `auto` | Heuristic/classifier-based approval | **Rejected** — not supported for headless A2A |
| `acceptEdits` | Auto-approve file edits; other guardrails still apply | Yes (default) |
| `dontAsk` | Never prompt; broadest auto-approval short of bypass | Yes |
| `plan` | Read/analyze only — no mutating tool calls | Yes |
| `bypassPermissions` | Unrestricted tool access | Yes, **only** with `dangerouslyAllowBypassPermissions: true` |
Setting `permissionMode: "bypassPermissions"` without `dangerouslyAllowBypassPermissions: true` throws at startup. When bypass is enabled, the executor logs a loud warning on every startup as a reminder that Claude has unrestricted tool access — only use it inside an isolated container or VM.
### `settingSources` isolation [#settingsources-isolation]
`settingSources` defaults to `[]`, meaning the SDK loads **no** host `~/.claude` user settings and **no** project `CLAUDE.md` / `.claude/settings.json` — every session starts from a clean, isolated slate driven entirely by `config.json`. Add `"project"` to `settingSources` to let Claude read the workspace's `CLAUDE.md` and project-level settings (useful when the target repository already documents its own conventions). Add `"user"` to load the host user's `~/.claude` settings — only do this in trusted, single-tenant deployments, since it pulls in configuration outside the agent's config file.
`strictMcpConfig` is always enabled internally (not user-configurable) — Claude is only allowed to use MCP servers explicitly declared in `config.json`, never ones discovered from ambient settings.
### Marketplace plugins [#marketplace-plugins]
`marketplaces` + `enabledPlugins` let the SDK fetch and install plugins itself, so nothing has to be baked into the image:
```json
"claude": {
"marketplaces": {
"superpowers-marketplace": {
"source": { "source": "github", "repo": "obra/superpowers-marketplace", "ref": "v6.1.1" }
}
},
"enabledPlugins": { "superpowers@superpowers-marketplace": true }
}
```
`source` is passed through to the SDK unchanged, so every source kind it supports works — `github` (`repo`), `git` (`url`), `git-subdir` (`url` + `path`, for monorepos), `url` (direct `marketplace.json`), `npm` (`package`), and local `directory`. Each accepts `ref` or `sha`.
**Pin every marketplace by `ref` or `sha`.** Plugin hooks and bundled MCP servers execute as code at the session's permission mode, so an unpinned marketplace means two sessions of the same agent can run different code.
These map to the SDK's *flag-tier* `settings`, which composes with `settingSources` rather than competing with it — marketplace plugins load even under the default `settingSources: []` isolation, and combining them with `["project"]` for `CLAUDE.md` works as expected.
Private marketplaces are cloned by `git`, so credentials must be supplied through the process environment (e.g. `GIT_ASKPASS`, or a configured credential helper) rather than per-source config.
#### Startup preflight [#startup-preflight]
The SDK installs marketplace plugins **asynchronously by default** — with `CLAUDE_CODE_SYNC_PLUGIN_INSTALL` unset it installs nothing, reports no error, and loads zero plugins on every subsequent run too. The wrapper therefore sets that flag for sessions with marketplaces configured, and `initialize()` runs a preflight that verifies each enabled plugin actually loaded:
```
Configured plugin(s) did not load: superpowers@superpowers-marketplace. Check that each
marketplace source and ref/sha is correct and that the plugin name exists in that
marketplace's manifest.
```
The check diffs the session init message's plugin list, **not** the `plugin_install` events — those report per-marketplace status, so a marketplace that clones cleanly but contains no plugin by the configured name still reports `installed` and `completed`. The probe costs no tokens (the init message precedes any model call) and warms the plugin cache so the first task doesn't pay the clone.
The preflight has no timeout of its own: the SDK already bounds marketplace fetches (an unreachable host fails in \~75s), and the session still reaches init afterwards, so a fetch failure surfaces as the precise "did not load" error above rather than a generic timeout. Tune `CLAUDE_CODE_PLUGIN_GIT_TIMEOUT_MS` or `CLAUDE_CODE_SYNC_PLUGIN_INSTALL_TIMEOUT_MS` if you need different bounds.
### Rate limits [#rate-limits]
When Claude reports a rate-limit rejection, the wrapper ends the turn
immediately rather than waiting out the reset or letting the request decay into
a prompt timeout. The task is published as `failed`, with a message naming the
limit and its reset time, for example:
> Rate limit reached (5-hour limit). Resets at 2026-08-11T18:00:00.000Z. Retry
> on the same contextId to continue this conversation.
The same status carries machine-readable metadata for orchestrators that
schedule their own retry:
```json
{
"reason": "rate_limit",
"source": "rate_limit_event",
"rateLimitType": "five_hour",
"resetsAt": 1786471200000,
"resetsAtIso": "2026-08-11T18:00:00.000Z",
"utilization": 1
}
```
The task is terminal, and deliberately so. The Claude Agent SDK cannot resume an
interrupted turn — a follow-up message is a new prompt appended to the
conversation either way — so holding the task open with `input-required` would
promise a continuation that never happens, and would misuse a state that means
"I need information from you" for what is really exhausted quota.
Continuity does not come from the task. It comes from the `contextId` → Claude
session mapping, which is unaffected by how a task ends. Once the limit resets,
the client starts a **new task on the same `contextId`**: the Claude session is
resumed and no context is lost.
Rate limit details (`resetsAt`, `rateLimitType`) are only available under
claude.ai subscription auth. Under API key, Bedrock, or Vertex the wrapper still
ends the turn cleanly, but the message omits the reset time because the SDK does
not report one. Details are never invented: a limit type or reset time is
carried onto a later rejection only if the SDK had already reported that limit
under pressure, and a reset time that is not in the future is dropped rather
than printed.
A rejection whose overage window is still open (`overageStatus` of `allowed` or
`allowed_warning`) is treated as a warning, not a rejection — the request may
well proceed on overage credits, and if it does not, the assistant's own
`rate_limit` error still ends the turn. When the SDK reports
`errorCode: "credits_required"`, no reset will restore capacity, so the message
says additional credits are required and omits the reset clause entirely.
Warnings — approaching a limit, or an internal retry after a 429 — do not end
the turn. They surface as `rate_limit` sideband events, which can be disabled
with `features.emitRateLimitEvents: false`; that flag only suppresses the
sideband events, and the turn still ends on a rejection either way.
The SDK retries 429s internally before giving up, and the wrapper does not
shorten that: those retries still run to exhaustion inside the same
`timeouts.prompt` window. They are at least visible now, as `rate_limit`
sideband events with `action: "retrying"` carrying the SDK's `attempt`,
`maxRetries`, and `delayMs`. Set a lower `timeouts.prompt` if a retry storm
burning the window matters more to you than the retries succeeding.
### Background tasks [#background-tasks]
Claude can start work that outlives a single SDK turn — a background shell
process, a long-running build — and end its turn saying it's waiting on the
result. Left alone, an A2A Task has no way to represent that: the Task reaches
a terminal state the moment the turn ends, and A2A gives an agent no way to
open a new turn against a terminal Task, so the eventual follow-up report
would have nowhere to land.
`features.holdTaskForBackgroundWork` (default `true`) keeps the Task in
`working` for as long as Claude reports background work in flight, instead of
completing it at the first SDK result. Each SDK turn — a "round" — publishes
its own `response` artifact plus a non-final `working` status update whose
`metadata.backgroundTasks` lists what's still running (`taskId`, `type`, and
`description`, mirrored from the SDK's own `background_tasks_changed`
message). The Task only reaches a terminal state once a round ends with that
set empty. A chain of any length works as rounds of one Task rather than a
string of separate ones — check the build, kick off a deploy, report the
result.
Set `holdTaskForBackgroundWork: false` to complete the Task at the first SDK
result, as before, regardless of what Claude reports is still running.
The flag governs that completion decision and nothing else. Queries are issued
in streaming-input mode either way: with a plain string prompt the SDK closes
the CLI subprocess's stdin on the first result and the process exits, so
streaming input is what makes a second round possible at all. There is no
setting that reverts it.
`features.emitBackgroundTaskEvents` (default `true`) publishes a
`background_tasks` sideband event each time the live set changes, carrying the
same `taskId`/`type`/`description` list plus a `count`. See
[Sideband Events](#sideband-events).
#### Caveats [#caveats]
Four things worth knowing before relying on this.
**`claude.maxTurns` now spans rounds.** A held-open Task accumulates SDK turns
across every round it takes, so a chain that used to run as several Tasks
under several separate budgets is now one Task under one budget. A
`maxTurns` that was comfortable before can be exhausted mid-chain, ending the
Task with `error_max_turns`.
**Further messages on the same `contextId` queue behind a held-open Task.**
Turns are serialized per context — see [Prompt timeout](#prompt-timeout) —
so a Task that's waiting on background work blocks every later message on
that context, the same as any other slow turn would. `cancelTask`
(`tasks/cancel`) is currently the only way to release the queue early, and
there's a sharp edge worth calling out: the remedy a user would naturally
reach for — sending another message on the same context — is exactly what's
blocked.
**`timeouts.prompt` bounds the whole Task, not one round.** The timer is
armed once at turn start and is never re-armed, so it also covers the idle
gaps between rounds while Claude's background work runs elsewhere. If you run
with a non-zero prompt timeout, raise it: the ten-minute default is usually
too low for a chain that holds the Task open, and a Task that runs out the
budget ends `failed` even if every round up to that point succeeded.
**With `timeouts.prompt: 0`, a held-open Task has no automatic release.**
This is the sharpest edge of the four, and it applies directly to any
deployment that disables the prompt timeout. If the background-task set
never empties — and the SDK's `background_tasks_changed` level is the only
settle signal available, with no wake-up turn guaranteed to ever follow — the
Task stays in `working` indefinitely and holds its context's queue open with
it. `cancelTask` is the only escape. This is a known limitation of the
current design; a non-timeout release mechanism is planned. Operators running
with the prompt timeout disabled should monitor for Tasks stuck in `working`.
## Example Agents [#example-agents]
| Config | Port | Permission mode | Description |
| --------------------------------------- | ------ | ---------------------------------------------------------- | ----------------------------------------------------- |
| `agents/example/config.json` | `3030` | `acceptEdits` | Workspace engineer — read + write access |
| `agents/read-only-reviewer/config.json` | `3031` | `plan` (+ `Write`/`Edit`/`NotebookEdit`/`Bash` disallowed) | Code reviewer — never modifies files or runs commands |
Each agent directory bundles a `start.sh` lifecycle script and a `workspace/` placeholder directory:
```bash
# Start in the background
agents/example/start.sh start
# Check status / health
agents/example/start.sh status
# Tail logs
agents/example/start.sh logs
# Stop
agents/example/start.sh stop
# Run in the foreground (useful for debugging / Docker)
agents/example/start.sh foreground
```
Point either agent at a real repository by overriding `WORKSPACE_DIR` before calling `start.sh`:
```bash
WORKSPACE_DIR=/path/to/repo agents/read-only-reviewer/start.sh start
```
Copy a directory to create your own agent: `cp -r agents/example agents/my-agent`.
## MCP Servers [#mcp-servers]
MCP configuration is baked at SDK construction time — all servers must be declared in `config.json` before the agent starts. Only `stdio` and Streamable `http` transports are supported (SSE-only servers are rejected at startup).
```json
{
"mcp": {
"filesystem": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "${WORKSPACE_DIR}"]
},
"linear": {
"type": "http",
"url": "https://mcp.linear.app/mcp",
"headers": { "Authorization": "Bearer ${LINEAR_API_KEY}" }
}
}
}
```
The key **`a2a-subagents`** is reserved for the sub-agent bridge described below — using it for a user-defined MCP server fails validation at startup.
## Sub-agents [#sub-agents]
Any `a2a-claude` agent can delegate to other A2A agents by declaring them under `subAgents` in `config.json`. The wrapper bootstraps [`a2a-mcp-skillmap`](https://www.npmjs.com/package/a2a-mcp-skillmap) as a stdio MCP server registered under the reserved `a2a-subagents` key, and each remote skill becomes a callable tool for Claude (`{name}__{skillId}`). MCP calls to `a2a-subagents` are enriched with `toolKind: "a2a_subagent"` and `delegation: true` in sideband events.
See the [root README's Sub-Agents section](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/README.md#calling-other-a2a-agents-sub-agents) for the full `subAgents` schema, auth modes, and a runnable example.
## Sideband Events [#sideband-events]
Sideband events are published through `AgentEventEmitter` for every Claude Agent SDK message. Use them for observability, tracing, and orchestration. Secrets are redacted (API keys, tokens, passwords, etc.) and tool output is truncated at 10,000 characters; file contents are never emitted, only path + operation kind.
| Event | Emitted when | Notes |
| ---------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `agent_started` | SDK `system`/`init` message | Includes `backend: "claude"` and the resolved model; emitted once per A2A Task, even across a held-open Task's several rounds — the SDK re-emits `init` on every background-task wake, so this bookend is deduplicated per Task rather than per SDK turn |
| `thinking` | Assistant `thinking` content block | Controlled by `features.emitThinkingEvents` |
| `tool_call_start` / `tool_call_end` | Assistant `tool_use` block / matching `tool_result` | `toolKind` is `"shell"` (Bash), `"mcp"`, `"a2a_subagent"` (mcp server `a2a-subagents`), or `"builtin"`; controlled by `features.emitToolEvents` |
| `decision` (`kind: "file_change"`) | `Edit` / `Write` / `NotebookEdit` tool call | Path and change kind only — never file contents; controlled by `features.emitFileChangeEvents` |
| `decision` (`kind: "todo_list"`) | `TodoWrite` tool call | Controlled by `features.emitTodoEvents` |
| `decision` (`kind: "permission_denied"`) | SDK `system`/`permission_denied` message | Tool name + sanitized message |
| `agent_finished` | SDK `result`/`success` message | Includes sanitized `usage`, `totalCostUsd`, `numTurns`; emitted once per A2A Task, on the round that finally completes it — not on every intermediate round of a held-open Task |
| `agent_error` | SDK `result` failure subtypes / `error` message | Sanitized error message; reason mapped from the SDK's failure subtype (e.g. max turns, max budget) |
| `rate_limit` | SDK `rate_limit_event`, `system`/`api_retry` with `error: "rate_limit"`, or an assistant `rate_limit` error | `action` is `"ended_turn"` (rejection — the turn stops), `"retrying"` (SDK internal retry, with the `retry` counters), or `"warning"`; carries `status` plus `rateLimitType` / `resetsAt` / `utilization` when the SDK reports them. Controlled by `features.emitRateLimitEvents` |
| `background_tasks` | SDK `system`/`background_tasks_changed` message | Level signal with replace semantics — each event carries the full live set (`taskId` / `type` / `description`) plus `count`, and is only emitted when membership actually changes. On the default `a2a` transport it arrives as a `trace.background_tasks` artifact. See [Background tasks](#background-tasks). Controlled by `features.emitBackgroundTaskEvents` |
> **Note:** `rate_limit` (and `context_window`) have no A2A trace-artifact
> mapping, so on the default `a2a` transport they are dropped rather than
> delivered. They are observable on the `http` transport or a custom one. This
> is a pre-existing gap, tracked separately.
## Docker [#docker]
Build from the **monorepo root** (the image needs local `@a2a-wrapper/core` source):
```bash
docker build -f a2a-claude/Dockerfile -t a2a-claude:latest .
```
Run with an API key:
```bash
docker run -p 3030:3030 \
-e ANTHROPIC_API_KEY=sk-ant-... \
-e WORKSPACE_DIR=/workspace \
-v /host/path/to/repo:/workspace \
a2a-claude:latest
```
## Manual E2E Verification [#manual-e2e-verification]
The automated test suite (`npm test -w a2a-claude`) uses a fake SDK client and never calls the real Anthropic API. To validate the full stack against a real backend, run this one-shot check with a real `ANTHROPIC_API_KEY`:
```bash
export ANTHROPIC_API_KEY=sk-ant-... WORKSPACE_DIR=/path/to/scratch-repo
npm run dev -- --config agents/example/config.json &
curl -s -X POST http://localhost:3030/a2a/jsonrpc -H 'content-type: application/json' -d '{
"jsonrpc": "2.0", "id": "1", "method": "message/send",
"params": { "message": { "kind": "message", "messageId": "m1", "role": "user",
"parts": [{ "kind": "text", "text": "List the files in this repository and summarize what it does." }] } }
}'
```
This is a manual, documented step — it is **not** part of automated CI and requires a funded Anthropic API key.
## Phase 2 Roadmap [#phase-2-roadmap]
The following are explicitly out of scope for this release and tracked as future work:
* hooks configuration
* native Claude subagents (`agents` option)
* skills
* richer usage/cost telemetry
* session forking
* `canUseTool` policy engine
* file checkpointing/rewind
* plan-mode review workflows
# a2a-codex (/docs/reference/packages/a2a-codex)
> This page is generated from [`a2a-codex/README.md`](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/a2a-codex/README.md) and stays in sync with the repository.
OpenAI Codex is a production-grade software engineering agent. It handles repository navigation, multi-step planning, shell command execution, file editing, and sandbox isolation — all the plumbing you'd spend months building from scratch.
**a2a-codex** exposes it as a standalone, interoperable agent via the [A2A protocol](https://github.com/google-deepmind/a2a). Drop a JSON config file in, get a fully spec-compliant A2A server out. Any orchestrator that speaks A2A can discover and call it — no Codex-specific integration code required.
> **The pattern:** MCP is the vertical rail — how agents access tools. A2A is the horizontal rail — how agents talk to each other. This library adds the horizontal rail to OpenAI Codex.
**Features:**
* Native [A2A v1.0](https://a2a-protocol.org) protocol, backward compatible with v0.3.x clients — Agent Card, JSON-RPC, REST, streaming
* Powered by `@openai/codex-sdk` — `o4-mini`, `o3`, `gpt-4o`, and any Codex-compatible model
* Repository sandboxing — `read-only`, `workspace-write`, `danger-full-access` modes
* MCP tool support — stdio and Streamable HTTP transports
* Multi-turn context continuity — each A2A `contextId` maps to a persistent Codex thread
* AbortController-based cancellation
* Multi-agent delegation via A2A sub-agents
* Sideband events — reasoning summaries, command events, file-change events
* JSON config file with layered overrides (JSON → env vars → CLI flags)
* Docker-ready with corporate proxy CA support
* TypeScript source with full type declarations
## Quick Start [#quick-start]
**Option A — OpenAI API key:**
```bash
npm install -g a2a-codex
export OPENAI_API_KEY=sk-...
export WORKSPACE_DIR=/path/to/your/repo
a2a-codex --config agents/example/config.json
```
**Option B — ChatGPT subscription (no API key needed):**
If you have a ChatGPT Plus or Pro subscription and have already run `codex login` locally, the Codex CLI reads credentials from `~/.codex/auth.json` automatically — no `OPENAI_API_KEY` required. Specify a model that your subscription supports:
```bash
export WORKSPACE_DIR=/path/to/your/repo
export CODEX_MODEL=gpt-5.5
a2a-codex --config agents/example/config.json
```
The agent card is available at `http://localhost:3020/.well-known/agent-card.json`.
## Release Channels [#release-channels]
| Channel | Install | Cadence |
| ------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| Stable | `npm install a2a-codex` | Manually reviewed and approved — see [Release Process](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/CONTRIBUTING.md#release-process) |
| Canary | `npm install a2a-codex@canary` | Every merge to `main` that passes CI |
Canary builds publish automatically after each merge, tagged like `a2a-codex@1.7.1-canary-20260812081117`. They're unreviewed and have no changelog entry — useful for trying a fix ahead of a stable release, not for production. `npm install a2a-codex` always resolves to the latest stable release regardless of how many canaries have shipped since.
## Architecture [#architecture]
```
A2A Client (Orchestrator / Inspector / curl)
│
│ JSON-RPC or REST over HTTP
▼
Express Server (a2a-codex)
│ ├─ /.well-known/agent-card.json → Agent Card
│ ├─ /a2a/jsonrpc → JSON-RPC (message/send, message/stream, …)
│ ├─ /a2a/rest → REST handler
│ ├─ /context → Read context.md
│ ├─ /context/build → Trigger context discovery
│ └─ /health → Health check
▼
CodexExecutor
├─ SessionManager (contextId → Codex Thread)
├─ EventMapper (ThreadEvent → A2A sideband events)
└─ @openai/codex-sdk
└─ Codex Thread (sandboxed repository operations)
```
## Required Environment Variables [#required-environment-variables]
| Variable | Required | Description |
| ------------------ | -------- | ------------------------------------------------------------------------- |
| `OPENAI_API_KEY` | No\* | OpenAI API key; omit after authenticating with `codex login` |
| `WORKSPACE_DIR` | Yes\* | Absolute path to the Git repository Codex will operate in |
| `CODEX_MODEL` | No\*\* | Override model; required in practice when using ChatGPT subscription auth |
| `LOG_LEVEL` | No | `debug` \| `info` \| `warn` \| `error` (default: `info`) |
| `PORT` | No | A2A server port (default: from config, fallback `3020`) |
| `ADVERTISE_HOST` | No | Hostname embedded in agent card URLs (default: `localhost`) |
| `STREAM_ARTIFACTS` | No | Set `"true"` to stream artifact chunks (default: buffered) |
\* Authentication requires either `OPENAI_API_KEY` or credentials from `codex login`. `WORKSPACE_DIR` can instead be set via `codex.workingDirectory` or `--workspace`.
\*\* Subscription users should select a supported model explicitly so the SDK does not inherit an incompatible desktop-app default.
## Configuration Reference [#configuration-reference]
All settings live in a single JSON config file. Priority order: **built-in defaults ← config file ← environment variables ← CLI flags**.
### Minimal config [#minimal-config]
```json
{
"agentCard": {
"name": "My Codex Agent",
"description": "Repository-scoped software engineering agent"
},
"server": { "port": 3020 },
"codex": {
"workingDirectory": "${WORKSPACE_DIR}"
}
}
```
### Full config reference [#full-config-reference]
> `agentCard.protocolVersion` below is optional and kept only for backward
> compatibility with older config files — the server negotiates A2A v1.0 vs.
> v0.3.x automatically per request and no longer reads this field. See
> [Protocol Versions](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/packages/core/README.md#protocol-versions) for how
> negotiation works.
```json
{
"agentCard": {
"name": "Codex Workspace Engineer",
"description": "...",
"version": "1.0.0",
"protocolVersion": "0.3.0",
"streaming": true,
"defaultInputModes": ["text"],
"defaultOutputModes": ["text"],
"skills": [
{
"id": "workspace-engineering",
"name": "Workspace Engineering",
"description": "Inspect, modify, and validate code within the configured repository.",
"tags": ["code", "repository", "tests", "refactoring"]
}
]
},
"server": {
"port": 3020,
"hostname": "0.0.0.0",
"advertiseHost": "localhost",
"advertiseProtocol": "http"
},
"codex": {
"workingDirectory": "${WORKSPACE_DIR}",
"model": "${CODEX_MODEL}",
"sandboxMode": "workspace-write",
"approvalPolicy": "never",
"networkAccessEnabled": false,
"webSearchMode": "disabled",
"skipGitRepoCheck": false,
"additionalDirectories": [],
"developerInstructions": "Operate only within the configured workspace.",
"baseUrl": "https://api.openai.com/v1",
"contextFile": "context.md",
"contextPrompt": "Explore this repository…"
},
"session": {
"reuseByContext": true,
"ttl": 3600000,
"cleanupInterval": 300000
},
"features": {
"streamArtifactChunks": false,
"emitReasoningSummaries": true,
"emitCommandEvents": true,
"emitFileChangeEvents": true
},
"timeouts": {
"prompt": 600000
},
"logging": {
"level": "info"
}
}
```
### Sandbox modes [#sandbox-modes]
| Mode | File access | When to use |
| -------------------- | ----------------------------------------------- | --------------------------------------- |
| `read-only` | No writes | Code review, repository analysis |
| `workspace-write` | Writes within `workingDirectory` only (default) | General engineering tasks |
| `danger-full-access` | Unrestricted | Only inside an isolated container or VM |
> **Security note:** Never use `danger-full-access` outside a fully isolated environment. The executor logs a warning on startup if this mode is set.
### Approval policy [#approval-policy]
| Policy | Behaviour |
| ------------ | -------------------------------------------------------------------------------------- |
| `never` | Auto-approve all tool calls (default — required for headless A2A) |
| `on-failure` | Approve only when a command fails |
| `untrusted` | Approve tools from untrusted sources |
| `on-request` | **Not supported** — interactive approvals are incompatible with headless A2A execution |
## AGENTS.md and `.agents/skills/` [#agentsmd-and-agentsskills]
Codex reads `AGENTS.md` in the workspace root for project-level instructions. Place agent configuration, coding standards, and tool usage rules there. The wrapper materializes memory-backed instructions to that path before each session.
Skills can be placed in `.agents/skills//SKILL.md`. Configure them via the `memory` block in `config.json`.
```
your-repo/
├── AGENTS.md ← project instructions for Codex
└── .agents/
└── skills/
├── security-audit/
│ └── SKILL.md ← skill instructions
└── test-generation/
└── SKILL.md
```
## MCP Support Matrix [#mcp-support-matrix]
| Transport | Supported | Notes |
| ------------------------ | --------- | --------------------------------------------------------------------- |
| `stdio` | Yes | Spawns process at startup; args/env support `${VAR}` substitution |
| `http` (Streamable HTTP) | Yes | HTTP headers support `${VAR}` substitution for auth tokens |
| `sse` | **No** | SSE-only MCP servers are not supported; use `http` or `stdio` instead |
MCP configuration is baked at SDK construction time. All MCP servers must be declared in `config.json` before the agent starts — runtime registration is not possible.
```json
{
"mcp": {
"filesystem": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "${WORKSPACE_DIR}"]
},
"linear": {
"type": "http",
"url": "https://mcp.linear.app/mcp",
"headers": { "Authorization": "Bearer ${LINEAR_API_KEY}" }
}
}
}
```
## Sub-Agent Delegation [#sub-agent-delegation]
Any `a2a-codex` agent can delegate to other A2A agents. Declare them under `subAgents` — the wrapper bootstraps [`a2a-mcp-skillmap`](https://www.npmjs.com/package/a2a-mcp-skillmap) as a stdio MCP server and makes each remote skill available as a Codex tool.
```json
{
"subAgents": {
"agents": [
{
"name": "security",
"agentCardUrl": "http://localhost:3010/.well-known/agent-card.json"
},
{
"name": "test-engineer",
"agentCardUrl": "http://localhost:3011/.well-known/agent-card.json"
}
],
"options": {
"responseMode": "artifact",
"probeTimeoutMs": 5000,
"syncBudgetMs": 30000
}
}
}
```
The Codex LLM sees `security__` and `test-engineer__` tools. MCP calls targeting the `a2a-subagents` server are enriched with `delegation: true` in sideband events for observability.
See `agents/multi-agent/config.json` for a bundled lead-engineer example.
## Sideband Event Mapping [#sideband-event-mapping]
A2A sideband events are emitted through `AgentEventEmitter` for every Codex `ThreadEvent`. Use them for observability, tracing, and orchestration.
| Codex event / item | A2A sideband event | Notes |
| --------------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------ |
| `reasoning` item | `thinking` | Text summary only — raw reasoning never exposed |
| `command_execution` item | `tool_call_start` + `tool_call_end` | Output truncated at 10,000 chars; controlled by `emitCommandEvents` |
| `file_change` item | `decision` | Path and operation kind only — file contents never emitted; controlled by `emitFileChangeEvents` |
| `mcp_tool_call` → `a2a-subagents` | `tool_call_start` + `tool_call_end` | Enriched with `toolKind: "a2a_subagent"`, `delegation: true` |
| `mcp_tool_call` → other servers | `tool_call_start` + `tool_call_end` | Standard MCP tool call |
| `todo_list` item | `decision` | Codex's internal planning list |
| `web_search` item | `tool_call_start` | Web search metadata |
| `turn.completed` | Status `completed` | Includes token usage |
| `turn.failed` | Status `failed` | Sanitized error message |
| `error` | `agent_error` | Sanitized error message |
## Buffered vs Streaming Artifacts [#buffered-vs-streaming-artifacts]
By default, `a2a-codex` buffers the full response and publishes a single artifact at completion. This is compatible with the A2A Inspector and most orchestrators.
Set `features.streamArtifactChunks: true` (or `STREAM_ARTIFACTS=true`) to stream delta chunks as the model generates them. Use this only if your client handles `append: true` artifact updates correctly.
## Cancellation [#cancellation]
Call `CancelTask` (v1 JSON-RPC) or `tasks/cancel` (v0.3 JSON-RPC) to cancel an in-flight task. For HTTP+JSON, use `POST /a2a/rest/tasks/{taskId}:cancel` with v1 or `POST /a2a/rest/v1/tasks/{taskId}:cancel` with v0.3. The executor calls `abortController.abort()` on the Codex `runStreamed` call. Cancellation is idempotent — multiple calls for the same task ID are safe.
## Context API [#context-api]
| Endpoint | Method | Description |
| ---------------- | ------ | -------------------------------------------------------------------------------------------- |
| `/context` | `GET` | Read the pre-built `context.md` from the workspace |
| `/context/build` | `POST` | Trigger a read-only Codex thread to explore and describe the repository; writes `context.md` |
POST body (optional): `{ "prompt": "Describe the API layer in detail." }`
## CLI Reference [#cli-reference]
```
Usage: a2a-codex [options]
Options:
--agent-json Path to agent JSON config file (alias: --config)
--config Path to agent JSON config file (alias: --agent-json)
--port A2A server port (default: 3020)
--hostname Bind address (default: 0.0.0.0)
--advertise-host Hostname for agent card URLs (default: localhost)
--workspace Workspace directory (Git repo) (alias: --working-dir)
--working-dir Workspace directory (Git repo) (alias: --workspace)
--model Codex model (e.g. o4-mini, gpt-4o)
--sandbox Sandbox mode (read-only | workspace-write | danger-full-access)
--agent-name Agent display name
--agent-description Agent description
--stream-artifacts Stream artifact chunks (spec-correct, streaming clients)
--no-stream-artifacts Buffer artifacts — Inspector-compatible (default)
--log-level Log level: debug | info | warn | error (default: info)
--help Show this help message
--version Show version
```
## Bundled Example Agents [#bundled-example-agents]
| Config | Port | Description |
| --------------------------------------- | ---- | -------------------------------------------------------------- |
| `agents/example/config.json` | 3020 | Workspace engineer — read + write access |
| `agents/read-only-reviewer/config.json` | 3022 | Code reviewer — read-only, no file writes |
| `agents/multi-agent/config.json` | 3021 | Lead engineer — delegates to sub-agents on ports 3010 and 3011 |
## Security Boundaries [#security-boundaries]
```
OS / Container boundary
│
└─ Codex workspace boundary (sandboxMode)
│
└─ Individual MCP server permissions
```
1. **OS / container boundary** — The Docker image runs as a non-root `node` user. For `danger-full-access` mode, use an isolated container or VM with no access to host secrets.
2. **Codex workspace boundary** — `workspace-write` confines all file operations to `workingDirectory`. `read-only` prevents any writes. Both modes prevent access to directories outside the configured paths.
3. **MCP server permissions** — Each MCP server runs as a separate process (stdio) or connects to an external endpoint (http). Scope their permissions via the `args`, `env`, and `enabledTools` / `disabledTools` settings.
In headless mode Codex cannot prompt, so MCP tools must be pre-approved or calls fail with `MCP tool call requires approval, but approval policy is never`. Set `defaultToolsApprovalMode: "approve"` on the server (or `toolApprovalModes: { "tool": "approve" }` per tool); values: `auto`, `prompt`, `writes`, `approve`. The `subAgents` bridge is pre-approved automatically.
## Docker [#docker]
Build from the **monorepo root** (the image needs local `@a2a-wrapper/core` source):
```bash
docker build -f a2a-codex/Dockerfile -t a2a-codex:latest .
```
**Option A — OpenAI API key (recommended for CI / cloud):**
```bash
docker run -p 3020:3020 \
-e OPENAI_API_KEY=sk-... \
-e WORKSPACE_DIR=/workspace \
-v /host/path/to/repo:/workspace \
a2a-codex:latest
```
**Option B — ChatGPT subscription (mount local credentials):**
```bash
docker run -p 3020:3020 \
-e WORKSPACE_DIR=/workspace \
-e CODEX_MODEL=gpt-5.5 \
-v ~/.codex:/home/node/.codex \
-v /host/path/to/repo:/workspace \
a2a-codex:latest
```
The container runs as the `node` user (`HOME=/home/node`), so `~/.codex` maps to `/home/node/.codex`. Mount read-write (not `:ro`) because the Codex CLI writes session state alongside auth tokens.
**Shell sandboxing in Docker:** Codex isolates every shell command it runs using Linux user namespaces via `bwrap` (Bubblewrap). Standard Docker containers disable unprivileged namespaces, so shell tool calls fail unless you add `--privileged` or `--security-opt=seccomp:unconfined`. For local development with a ChatGPT subscription, using `server.sh` directly (without Docker) avoids this constraint entirely.
To inject a corporate proxy CA certificate, mount it at `/etc/ssl/certs/corporate-ca.crt` — the entrypoint script merges it with the system CA bundle automatically.
## Programmatic API [#programmatic-api]
```typescript
import { createA2AServer, resolveConfig } from "a2a-codex";
const config = resolveConfig("agents/example/config.json");
const handle = await createA2AServer(config);
// Graceful shutdown
process.on("SIGTERM", () => handle.shutdown());
```
Or use the executor directly:
```typescript
import { CodexExecutor, resolveConfig } from "a2a-codex";
const config = resolveConfig("agents/example/config.json");
const executor = new CodexExecutor(config);
await executor.initialize();
// Build a context summary of the repository
const summary = await executor.buildContext();
console.log(summary);
```
## Live Smoke Test [#live-smoke-test]
```bash
# Start an agent with a test repository
export OPENAI_API_KEY=sk-...
export WORKSPACE_DIR=/tmp/test-repo
git init /tmp/test-repo
echo "console.log('hello')" > /tmp/test-repo/index.js
node dist/cli.js --config agents/read-only-reviewer/config.json &
SERVER_PID=$!
# Wait for startup
sleep 3
# Fetch agent card
curl -s http://localhost:3022/.well-known/agent-card.json | jq .name
# Send a task
curl -s -X POST http://localhost:3022/a2a/jsonrpc \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"message/send","params":{"message":{"kind":"message","messageId":"test-1","role":"user","parts":[{"kind":"text","text":"What does this repository do?"}],"contextId":"ctx-1"}}}' \
| jq .
kill $SERVER_PID
```
Set `RUN_CODEX_INTEGRATION_TESTS=true` to include the live integration test in `npm test`.
## Known Limitations [#known-limitations]
* **SSE-only MCP not supported.** MCP servers that only expose an SSE transport cannot be used. Use `stdio` or Streamable HTTP (`type: "http"`) instead.
* **Interactive approvals not supported.** `approvalPolicy: "on-request"` is rejected at startup. Headless A2A execution cannot present approval prompts to a human. Use `"never"` (default) for automated operation.
* **Buffered mode is the safe default.** `streamArtifactChunks: false` is the default. Some A2A clients (including the A2A Inspector) work best with complete, non-appending artifacts. Enable streaming only if your client handles `append: true` artifact updates.
* **MCP config is baked at startup.** MCP servers are registered with the Codex SDK at construction time. Runtime MCP registration is not possible — all servers must be declared in `config.json` before the agent starts.
* **One thread per A2A context.** Each A2A `contextId` maps to exactly one Codex thread. Turns within a context are serialized. Concurrent turns on the same context are queued, not parallelized.
* **Docker: shell sandboxing requires elevated privileges.** Codex wraps every shell command in `bwrap` (Bubblewrap), which creates Linux user namespaces. Standard Docker containers block unprivileged namespace creation, so shell tool calls fail inside a container unless you run with `--privileged` or `--security-opt=seccomp:unconfined`. For local development, running with `server.sh` directly (no Docker) is recommended.
* **ChatGPT subscription: OAuth tokens expire.** When using ChatGPT Plus/Pro authentication (via `codex login`), the tokens in `~/.codex/auth.json` are short-lived. When they expire the agent returns auth errors; re-run `codex login` on the host to refresh. For long-running or unattended deployments, `OPENAI_API_KEY` is more reliable.
* **ChatGPT subscription: model name is required.** The default model in `~/.codex/config.toml` (set by the desktop app) may differ from models available through the API. Always set `CODEX_MODEL` explicitly (e.g. `gpt-5.5`) when using subscription auth — leaving it unset causes the agent to fall back to the local default, which may be unsupported via the API endpoint.
# a2a-copilot (/docs/reference/packages/a2a-copilot)
> This page is generated from [`a2a-copilot/README.md`](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/a2a-copilot/README.md) and stays in sync with the repository.
GitHub Copilot is a production-grade agent. It already handles multi-step planning, MCP tool execution, context management, and streaming — everything you'd spend months rebuilding from scratch.
**a2a-copilot** exposes it as a standalone, interoperable agent via the [A2A protocol](https://github.com/google-deepmind/a2a). Drop a JSON config file in, get a fully spec-compliant A2A server out. Any orchestrator that speaks A2A can discover and call it — no Copilot-specific integration code required.
> **The pattern:** MCP is the vertical rail — how agents access tools. A2A is the horizontal rail — how agents talk to each other. This library adds the horizontal rail to GitHub Copilot.
**Features:**
* Native [A2A v1.0](https://a2a-protocol.org) protocol, backward compatible with v0.3.x clients — Agent Card, JSON-RPC, REST, SSE streaming
* Powered by GitHub Copilot (GPT-4.1, Claude Sonnet 4.5, and more)
* **Bring Your Own Model (BYOK)** — point at Ollama, OpenAI, Anthropic, Azure, vLLM, or any OpenAI-compatible endpoint. See [Bring Your Own Model (BYOK)](#bring-your-own-model-byok).
* MCP tool server support — HTTP and stdio transports
* Multi-turn conversations via persistent Copilot sessions
* JSON config file with layered overrides (JSON → env vars → CLI flags)
* Docker-ready with corporate proxy CA support
* TypeScript source with full type declarations
## Why not just embed the Copilot SDK directly? [#why-not-just-embed-the-copilot-sdk-directly]
Direct SDK embedding works — but it tightly couples your application to Copilot's session model and integration pattern. Swapping the AI backend means rewriting integration code. Adding a second agent means writing a second bespoke integration.
With the A2A protocol surface:
* Your orchestrator speaks one interface regardless of what's behind it
* Copilot becomes **swappable** — replace it without changing orchestration logic
* Copilot becomes **composable** — route tasks to it alongside other A2A agents
* Copilot becomes **discoverable** — any A2A-compatible system can find it via Agent Card
## Works with agent frameworks [#works-with-agent-frameworks]
This library complements — not replaces — frameworks like LangGraph, Google ADK, Microsoft Agent Framework, and CrewAI. Use those frameworks for orchestration, state, and memory control. Use a2a-copilot as the execution node they call.
```
LangGraph / ADK / Microsoft Agent Framework
(state, memory, flow control)
↓
A2A Protocol
↓
a2a-copilot
(GitHub Copilot execution)
```
## Quick Start [#quick-start]
```bash
# Install globally
npm install -g a2a-copilot
# Run the bundled example agent
a2a-copilot --config agents/example/config.json
```
Or run without installing:
```bash
npx a2a-copilot --config agents/example/config.json
```
> **⚠️ Authentication required:** You must set a `GITHUB_TOKEN` environment variable **or** run `gh auth login` before starting the server. Without valid GitHub credentials the server will fail with an auth error. You also need a GitHub account with Copilot access.
>
> **Using your own model?** If you configure a custom provider (BYOK) — e.g. a local Ollama instance — GitHub credentials are not required for that provider. See [Bring Your Own Model (BYOK)](#bring-your-own-model-byok).
## Architecture [#architecture]
```
A2A Client (Orchestrator / Inspector / curl)
│
│ JSON-RPC or REST over HTTP
▼
Express Server (a2a-copilot)
│ ├─ /.well-known/agent-card.json → Agent Card
│ ├─ /a2a/jsonrpc → JSON-RPC (message/send, message/stream, …)
│ ├─ /a2a/rest → REST handler
│ ├─ /context → Read context.md
│ ├─ /context/build → Trigger context discovery
│ └─ /health → Health check
│
│ @a2a-js/sdk DefaultRequestHandler
▼
CopilotExecutor (AgentExecutor)
│ ├─ SessionManager — contextId → Copilot session
│ ├─ Streaming — delta events → A2A artifact chunks
│ └─ EventPublisher — Copilot events → A2A events
│
│ @github/copilot-sdk
▼
GitHub Copilot
│ ├─ LLM inference (GPT-4.1, Claude Sonnet 4.5, …)
│ └─ MCP tool execution
│
│ MCP Protocol (HTTP / stdio)
▼
MCP Servers (filesystem, custom tools, …)
```
## Installation [#installation]
```bash
# npm
npm install a2a-copilot
# yarn
yarn add a2a-copilot
# pnpm
pnpm add a2a-copilot
```
## Release Channels [#release-channels]
| Channel | Install | Cadence |
| ------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| Stable | `npm install a2a-copilot` | Manually reviewed and approved — see [Release Process](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/CONTRIBUTING.md#release-process) |
| Canary | `npm install a2a-copilot@canary` | Every merge to `main` that passes CI |
Canary builds publish automatically after each merge, tagged like `a2a-copilot@1.8.1-canary-20260812081117`. They're unreviewed and have no changelog entry — useful for trying a fix ahead of a stable release, not for production. `npm install a2a-copilot` always resolves to the latest stable release regardless of how many canaries have shipped since.
## Usage [#usage]
### CLI [#cli]
```bash
a2a-copilot --config agents/example/config.json
```
Full flag reference:
```
a2a-copilot [options]
--config JSON agent config file
--port Server port (default: 3000)
--hostname Bind address (default: 0.0.0.0)
--advertise-host Hostname for agent card URLs (default: localhost)
--cli-url External Copilot CLI URL (default: auto)
--model LLM model (default: gpt-4.1)
--workspace Workspace directory
--agent-name Agent display name
--agent-description Agent description
--stream-artifacts Stream chunks in real time (A2A spec mode)
--no-stream-artifacts Buffer artifacts — Inspector-compatible (default)
--log-level debug | info | warn | error (default: info)
--help Show this help
--version Show version
```
### Programmatic API [#programmatic-api]
```typescript
import { createA2AServer, resolveConfig } from 'a2a-copilot';
const config = await resolveConfig({ configPath: 'agents/example/config.json' });
const { server, url } = await createA2AServer(config);
console.log(`Agent running at ${url}`);
```
## Configuration [#configuration]
Config is resolved in priority order: **defaults ← JSON file ← env vars ← CLI flags**
### JSON Config File [#json-config-file]
Create a `config.json` (see `agents/example/config.json` for the fully annotated template):
> `agentCard.protocolVersion` below is optional and kept only for backward
> compatibility with older config files — the server negotiates A2A v1.0 vs.
> v0.3.x automatically per request and no longer reads this field. See
> [Protocol Versions](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/packages/core/README.md#protocol-versions) for how
> negotiation works.
```json
{
"agentCard": {
"name": "My Agent",
"description": "What my agent does",
"version": "1.0.0",
"protocolVersion": "0.3.0",
"streaming": true,
"skills": [
{
"id": "my-skill",
"name": "My Skill",
"description": "Describe the skill",
"tags": ["example"]
}
]
},
"server": {
"port": 3000,
"hostname": "0.0.0.0",
"advertiseHost": "localhost"
},
"copilot": {
"model": "gpt-4.1",
"streaming": true,
"systemPrompt": "You are a specialist agent that...",
"contextFile": "context.md"
},
"mcp": {
"my-tools": {
"type": "http",
"url": "http://localhost:8002/mcp"
}
},
"events": {
"enabled": true,
"transport": "a2a"
}
}
```
### Environment Variables [#environment-variables]
| Variable | Description | Default |
| --------------------------- | --------------------------------------------------------- | -------------------------- |
| `GITHUB_TOKEN` | GitHub PAT for headless auth | uses `gh` CLI |
| `PORT` | Server port | `3000` |
| `HOSTNAME` | Bind address | `0.0.0.0` |
| `ADVERTISE_HOST` | Hostname in agent card URLs | `localhost` |
| `COPILOT_MODEL` | LLM model | `gpt-4.1` |
| `COPILOT_CLI_URL` | External Copilot CLI URL | auto |
| `COPILOT_PROVIDER_BASE_URL` | BYOK provider endpoint (e.g. `http://localhost:11434/v1`) | *(unset → GitHub Copilot)* |
| `COPILOT_PROVIDER_TYPE` | BYOK provider type: `openai`\|`azure`\|`anthropic` | `openai` |
| `COPILOT_PROVIDER_API_KEY` | BYOK API key (omit for local Ollama) | *(unset)* |
| `COPILOT_PROVIDER_WIRE_API` | BYOK wire API: `completions`\|`responses` | `completions` |
| `WORKSPACE_DIR` | Workspace directory | *(empty)* |
| `STREAM_ARTIFACTS` | Stream chunks in real time | `false` |
| `LOG_LEVEL` | `debug`\|`info`\|`warn`\|`error` | `info` |
| `AGENT_NAME` | Override agent card name | *(from config)* |
| `AGENT_DESCRIPTION` | Override agent card description | *(from config)* |
See [`.env.example`](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/a2a-copilot/.env.example) for the full reference.
## Bring Your Own Model (BYOK) [#bring-your-own-model-byok]
By default the wrapper uses GitHub Copilot's hosted models. You can instead point it at **any OpenAI-compatible endpoint** — Ollama, OpenAI, Anthropic, Azure OpenAI, Azure AI Foundry, vLLM, LiteLLM, or Microsoft Foundry Local — using the `copilot.provider` config block. This is GitHub Copilot's BYOK ("Bring Your Own Key") capability, surfaced through the wrapper.
When `provider` is omitted, nothing changes: the wrapper uses GitHub Copilot exactly as before. When `provider` is set, sessions bypass GitHub Copilot and call your endpoint directly.
### Configuration [#configuration-1]
```json
{
"copilot": {
"model": "qwen3.6",
"provider": {
"type": "openai",
"baseUrl": "http://localhost:11434/v1",
"wireApi": "completions"
}
}
}
```
> **The `model` field is required when `provider` is set.** The runtime cannot auto-detect models from custom providers.
### Provider fields [#provider-fields]
| Field | Values | Notes |
| ------------------ | ---------------------------------- | ------------------------------------------------------------------------- |
| `type` | `openai` \| `azure` \| `anthropic` | Defaults to `openai`. Use `openai` for Ollama, vLLM, LiteLLM, Foundry. |
| `baseUrl` | string (required) | API endpoint. For Ollama: `http://localhost:11434/v1`. |
| `apiKey` | string | Optional. Not needed for local Ollama. |
| `bearerToken` | string | Sets the `Authorization` header directly. Takes precedence over `apiKey`. |
| `wireApi` | `completions` \| `responses` | Defaults to `completions`. See note below. |
| `azure.apiVersion` | string | Azure only. Defaults to `2024-10-21`. |
### Provider quick reference [#provider-quick-reference]
| Provider | `type` | `baseUrl` | `wireApi` |
| ---------------- | ----------- | ------------------------------------------------ | ------------- |
| Ollama (local) | `openai` | `http://localhost:11434/v1` | `completions` |
| OpenAI | `openai` | `https://api.openai.com/v1` | `responses` |
| Anthropic | `anthropic` | `https://api.anthropic.com` | *(n/a)* |
| Azure OpenAI | `azure` | `https://.openai.azure.com` | `completions` |
| Azure AI Foundry | `openai` | `https://.openai.azure.com/openai/v1/` | `responses` |
| vLLM / LiteLLM | `openai` | `http://:/v1` | `completions` |
You can also configure everything with environment variables (the same vars the `gh copilot` CLI uses): `COPILOT_PROVIDER_BASE_URL`, `COPILOT_PROVIDER_TYPE`, `COPILOT_PROVIDER_API_KEY`, `COPILOT_PROVIDER_WIRE_API`, and `COPILOT_MODEL`.
### ⚠️ Model requirements (read this before using local models) [#️-model-requirements-read-this-before-using-local-models]
The wrapper drives an **agentic loop** through the Copilot runtime. That loop depends on the model supporting **native tool calling (function calling)** and **streaming**. Not every local model does, and a model that chats fine in isolation can still fail here.
Symptoms of an incompatible model:
* **Raw tool-call JSON as the response.** You ask "Who are you?" and get back `{"name":"view","arguments":{...}}` instead of an answer. This happens when the model emits tool calls as plain text instead of using the structured tool-call protocol — the runtime can't execute them, so the text leaks through as the final artifact. The wrapper detects this shape and replaces it with an actionable message, but the underlying cause is the model.
* **`400 ... does not support thinking`.** Seen with `wireApi: "responses"` against models that don't implement the reasoning parameter. Use `wireApi: "completions"` for local models.
Guidance:
* **Use a model with robust native tool-calling support.** Verified working locally: **`qwen3.6`**. Other strong options: `llama3.3`, `mistral-nemo`. Avoid small/older coder models like `qwen2.5-coder:7b` for agentic use — they tend to emit tool calls as text.
* **Prefer `wireApi: "completions"`** for Ollama and most local engines. Reserve `"responses"` for OpenAI GPT-4+ and Azure AI Foundry GPT-5-series.
* The model must support **streaming** (all the recommended models do).
See the [`agents/ollama/`](https://github.com/shashikanth-gs/a2a-wrapper/tree/main/a2a-copilot/agents/ollama/) example for a working local setup.
## Bundled Agent Examples [#bundled-agent-examples]
### Example Agent (minimal) [#example-agent-minimal]
```bash
./agents/example/start.sh start
./agents/example/start.sh status
./agents/example/start.sh logs
./agents/example/start.sh stop
```
Runs on port `3000`. No external tools. Good starting point for custom agents.
### Filesystem Assistant [#filesystem-assistant]
```bash
./agents/filesystem-assistant/start.sh start
```
Runs on port `3000` and connects to the `@modelcontextprotocol/server-filesystem` MCP server. The agent can read, write, and search files inside its `workspace/` directory.
### Ollama Agent (local / BYOK) [#ollama-agent-local--byok]
```bash
# Pull a tool-capable model and start Ollama first
ollama pull qwen3.6
ollama serve
./agents/ollama/start.sh start
```
Runs on port `3002` entirely against a local Ollama instance — no GitHub Copilot account required. See the [Bring Your Own Model (BYOK)](#bring-your-own-model-byok) section for model requirements (the model must support native tool calling).
### Creating Your Own Agent [#creating-your-own-agent]
```bash
# Copy the example agent
cp -r agents/example agents/my-agent
# Edit the config
$EDITOR agents/my-agent/config.json
# Start it
./agents/my-agent/start.sh start
```
## MCP Tool Servers [#mcp-tool-servers]
### HTTP / SSE server [#http--sse-server]
```json
"mcp": {
"my-tools": {
"type": "http",
"url": "http://localhost:8002/mcp"
}
}
```
### Authenticated remote servers (custom headers) [#authenticated-remote-servers-custom-headers]
Many hosted MCP servers (Linear, Notion, remote GitHub MCP, etc.) require auth headers. Add a `headers` map to any `http` or `sse` server. Header values support `${ENV_VAR}` substitution so **secrets never live in `config.json`** — reference an environment variable and supply the value at runtime:
```json
"mcp": {
"linear": {
"type": "http",
"url": "https://mcp.linear.app/mcp",
"headers": {
"Authorization": "Bearer ${LINEAR_API_KEY}"
}
},
"notion": {
"type": "sse",
"url": "https://mcp.notion.com/sse",
"headers": {
"X-Api-Key": "${NOTION_TOKEN}"
}
}
}
```
Run with the secrets in the environment:
```bash
LINEAR_API_KEY=lin_xxx NOTION_TOKEN=ntn_yyy ./agents/my-agent/start.sh start
```
Unresolved tokens (no matching env var) are left as-is so misconfigurations stay visible rather than silently sending an empty header.
### stdio server (child process) [#stdio-server-child-process]
```json
"mcp": {
"filesystem": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "$WORKSPACE_DIR"],
"env": {
"SOME_API_KEY": "${SOME_API_KEY}"
}
}
}
```
Both `args` and `env` values support env-var substitution — use `${VAR}` (recommended, works mid-string) or bare `$VAR`. This keeps tokens out of committed config while letting the spawned server receive them.
## Memory Persistence [#memory-persistence]
Give your agent persistent instructions and skills that survive across sessions. Declare them in config.json and they're materialized into the workspace at startup — the Copilot LLM reads them automatically.
### Config [#config]
```json
{
"memory": {
"instructions": "./memory/instructions.md",
"skills": ["./memory/skills/code-review"]
}
}
```
Paths are relative to the directory containing config.json.
### Instructions [#instructions]
A markdown file with project-level instructions, coding conventions, safety rules, or behavioral guidelines. Written to `.github/copilot-instructions.md` in the workspace.
### Skills [#skills]
Each skill is a directory containing a `SKILL.md` file with YAML frontmatter and optional resource directories:
```
memory/skills/code-review/
├── SKILL.md # Required: frontmatter + instructions
├── scripts/ # Optional: helper scripts
├── references/ # Optional: reference docs
└── assets/ # Optional: static files
```
**SKILL.md format:**
```markdown
---
name: code-review
description: Provides code review guidelines and checklists
license: MIT
allowed-tools:
- read_file
- search_files
---
# Code Review Skill
Detailed instructions for the LLM...
```
The `name` field must be kebab-case (lowercase + hyphens, max 64 chars). It determines the output directory name under `.github/skills/`.
### Where files are written [#where-files-are-written]
| Source | Target in workspace |
| -------------------------- | --------------------------------- |
| `memory.instructions` | `.github/copilot-instructions.md` |
| `memory.skills[]/SKILL.md` | `.github/skills//SKILL.md` |
| `memory.skills[]/scripts/` | `.github/skills//scripts/` |
### `agentCard.skills` vs `memory.skills` [#agentcardskills-vs-memoryskills]
These serve different purposes:
* **`agentCard.skills`** — External metadata for orchestrators. Advertised in the agent card for discovery and routing.
* **`memory.skills`** — Internal instructions for the LLM. Never exposed externally. Teaches the LLM *how* to perform tasks.
Both should be maintained — the agent card tells callers what the agent can do, while memory skills tell the LLM how to do it. Descriptions may differ: agent card skills are high-level and marketing-friendly, memory skills are technical and detailed.
### Example [#example]
See [`agents/filesystem-assistant/`](https://github.com/shashikanth-gs/a2a-wrapper/tree/main/a2a-copilot/agents/filesystem-assistant/) for a working example with instructions, a skill, and a helper script.
## Calling Other A2A Agents [#calling-other-a2a-agents]
Expose remote A2A agents as MCP tools by declaring them under `subAgents` in your config. The wrapper spawns [`a2a-mcp-skillmap`](https://www.npmjs.com/package/a2a-mcp-skillmap) ([GitHub](https://github.com/shashikanth-gs/a2a-mcp-skillmap)) as a stdio MCP server and registers it under the reserved `a2a-subagents` key in the resolved `mcp` map. Each remote skill becomes a callable tool the Copilot LLM can dispatch like any other MCP tool.
```json
{
"subAgents": {
"agents": [
{
"name": "coding",
"agentCardUrl": "https://coding.example.com/.well-known/agent-card.json",
"auth": { "mode": "bearer", "token": "${CODING_AGENT_TOKEN}" }
},
{
"name": "research",
"agentCardUrl": "https://research.example.com/",
"endpointUrlOverride": "https://research.internal.local/.well-known/agent-card.json"
}
]
}
}
```
The Copilot LLM sees `coding__` and `research__` tools (one per skill advertised in each sub-agent's card). Tokens may reference environment variables via `${VAR}` syntax — missing variables produce a startup warning and the auth block is omitted (the bridge calls without credentials).
When `subAgents` is absent or `agents` is empty, the wrapper skips every sub-agent code path with no side effects.
See [`agents/multi-agent/`](https://github.com/shashikanth-gs/a2a-wrapper/tree/main/a2a-copilot/agents/multi-agent/) for a working example.
## Event Transport (Observability) [#event-transport-observability]
Agents emit structured trace events for MCP tool calls, reasoning, and lifecycle. By default, these flow as sideband artifacts through the A2A protocol itself — orchestrators discover them via the `urn:x-a2a:trace:v1` extension on the agent card.
### Default (A2A sideband) [#default-a2a-sideband]
No config needed. Trace artifacts appear alongside response artifacts and can be filtered by the `urn:x-a2a:trace:v1` extension URI.
### HTTP collector [#http-collector]
Route events to an external telemetry endpoint:
```json
{
"events": {
"enabled": true,
"transport": "http",
"httpUrl": "https://telemetry.example.com/events",
"httpHeaders": {
"Authorization": "Bearer ${TELEMETRY_TOKEN}"
}
}
}
```
### Custom transport (programmatic) [#custom-transport-programmatic]
For Kafka, Redis, or database sinks, use the programmatic API. See the [`@a2a-wrapper/core` README](https://www.npmjs.com/package/@a2a-wrapper/core) for full details.
## LLM Usage and Cost Telemetry [#llm-usage-and-cost-telemetry]
Every task execution tracks token counts, latency, model, and cost. Telemetry is delivered in three tiers:
| Tier | Always-on? | What you get |
| ---------- | ------------------ | ----------------------------------------------------------- |
| **Tier 1** | ✅ Always | `metadata["x-usage"]` on the final `completed` status event |
| **Tier 2** | `trackUsage: true` | One `trace.usage` sideband artifact per LLM API call |
| **Tier 3** | `trackUsage: true` | `contextWindow` fill snapshot inside `x-usage` |
### Enabling [#enabling]
Add `trackUsage: true` to your `features` block:
```json
{
"features": {
"trackUsage": true
}
}
```
### Tier 1 — Session summary (always-on) [#tier-1--session-summary-always-on]
Every successfully completed task includes a `UsageTelemetryData` object under `metadata["x-usage"]` on the final status event — regardless of `trackUsage`. This gives you per-task totals with zero config.
```json
{
"kind": "status-update",
"final": true,
"status": { "state": "completed", "timestamp": "..." },
"metadata": {
"x-usage": {
"llmCalls": 1,
"model": "gpt-5.4-mini",
"inputTokens": 6226,
"outputTokens": 34,
"cacheReadTokens": 5632,
"cacheWriteTokens": 0,
"reasoningTokens": 27,
"durationMs": 3140,
"cost": 0.33,
"calls": [{ "model": "gpt-5.4-mini", "inputTokens": 6226, ... }],
"contextWindow": {
"currentTokens": 6957,
"tokenLimit": 272000,
"conversationTokens": 85,
"systemTokens": 194,
"toolDefinitionsTokens": 6678,
"messagesLength": 2
}
}
}
}
```
`cost` is always present — `null` when no cost was reported by the provider, a finite number ≥ 0 otherwise (`0` is a valid Copilot-native quota value).
`contextWindow` is present when at least one `session.usage_info` event was received from the SDK. Absent (not `null`) otherwise.
### Tier 2 — Per-call trace artifacts (`trackUsage: true`) [#tier-2--per-call-trace-artifacts-trackusage-true]
When `trackUsage` is `true`, one `trace.usage` artifact is emitted per LLM API call, carrying the full `UsageCallRecord`:
```json
{
"kind": "artifact-update",
"artifact": {
"name": "trace.usage",
"parts": [{
"kind": "data",
"data": {
"model": "gpt-5.4-mini",
"inputTokens": 6226,
"outputTokens": 34,
"cacheReadTokens": 5632,
"cacheWriteTokens": 0,
"reasoningTokens": 27,
"durationMs": 3140,
"timeToFirstTokenMs": 2887,
"cost": 0.33,
"apiEndpoint": "/responses",
"initiator": "user"
}
}]
}
}
```
For tool-heavy tasks that make multiple LLM calls, you'll see one `trace.usage` artifact per call, giving you a complete per-call breakdown.
### Tier 3 — Context-window snapshots (`trackUsage: true`) [#tier-3--context-window-snapshots-trackusage-true]
When `trackUsage` is `true`, the `contextWindow` field in `x-usage` reflects the most recent context-window snapshot from the SDK. This shows token fill by category (conversation, system prompt, tool definitions) and the model's total context limit.
### Field reference — `UsageTelemetryData` [#field-reference--usagetelemetrydata]
| Field | Type | Description |
| ------------------ | ----------------------- | ------------------------------------------------------- |
| `llmCalls` | `number` | Total number of LLM API calls in this task |
| `model` | `string \| null` | Model name from the last call; `null` if no calls |
| `inputTokens` | `number` | Sum of prompt tokens across all calls |
| `outputTokens` | `number` | Sum of completion tokens across all calls |
| `cacheReadTokens` | `number` | Sum of cache-hit tokens (prompt cache read) |
| `cacheWriteTokens` | `number` | Sum of cache-miss tokens (new cache entry) |
| `reasoningTokens` | `number` | Sum of internal reasoning tokens (o-series) |
| `durationMs` | `number` | Sum of call durations in milliseconds |
| `cost` | `number \| null` | Sum of provider-reported costs; `null` if none reported |
| `calls` | `UsageCallRecord[]` | Full per-call log in recording order |
| `contextWindow?` | `ContextWindowSnapshot` | Most recent context-window snapshot (optional) |
Field names align with [OpenTelemetry Generative AI Semantic Conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/) for downstream observability interoperability.
## Docker [#docker]
```bash
# Build
docker build -t a2a-copilot:latest .
# Run with a config file
docker run -p 3000:3000 \
-e GITHUB_TOKEN= \
a2a-copilot:latest --config agents/example/config.json
# Mount a custom agent config
docker run -p 3000:3000 \
-v /host/path/my-agent:/app/agents/my-agent \
-e GITHUB_TOKEN= \
a2a-copilot:latest --config agents/my-agent/config.json
```
### Corporate Proxy (Netskope / Zscaler) [#corporate-proxy-netskope--zscaler]
Mount your CA certificate into the container and the entrypoint injects it automatically:
```bash
docker run -p 3000:3000 \
-v /path/to/corporate-ca.crt:/etc/ssl/certs/corporate-ca.crt:ro \
-e GITHUB_TOKEN= \
a2a-copilot:latest --config agents/example/config.json
```
## A2A Protocol [#a2a-protocol]
### Protocol Versions [#protocol-versions]
Speaks **A2A v1.0 natively** and is **fully backward compatible with v0.3.x clients** — no configuration needed. Send `A2A-Version: 1.0` to discover the native v1.0 Agent Card. Send `A2A-Version: 0.3` or `0.3.0`—or omit the header for older clients—to receive the legacy card. JSON-RPC/REST handlers additionally auto-detect native and legacy method shapes. Both versions use the same endpoints below; see [Protocol Versions](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/packages/core/README.md#protocol-versions) for the complete negotiation rules and optional Signed Agent Card support.
| Endpoint | Description |
| ---------------------------------- | ------------------------------------------------------------------------------------------------ |
| `GET /.well-known/agent-card.json` | Agent identity and capabilities |
| `POST /a2a/jsonrpc` | JSON-RPC: `message/send`, `message/stream` (v0.3) / `SendMessage`, `SendStreamingMessage` (v1.0) |
| `POST /a2a/rest/message:send` | v1 HTTP+JSON message send |
| `POST /a2a/rest/v1/message:send` | v0.3 HTTP+JSON message send |
| `GET /health` | Health check |
| `POST /context/build` | Trigger context discovery |
| `GET /context` | Read the built context file |
Example JSON-RPC request (`message/send`, v0.3-shaped — still accepted and automatically translated):
```json
{
"jsonrpc": "2.0",
"id": 1,
"method": "message/send",
"messageId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"params": {
"message": {
"role": "user",
"parts": [{ "kind": "text", "text": "Hello, agent!" }]
}
}
}
```
Streaming uses SSE for real-time status updates and artifact chunks. Set `--stream-artifacts` for spec-correct chunk streaming or leave it unset (default) for buffered output compatible with the [A2A Inspector](https://github.com/google-deepmind/a2a).
## External Copilot CLI [#external-copilot-cli]
For debugging or sharing a single CLI instance across multiple agents:
```bash
# Start CLI in headless mode
copilot --headless --port 4321
# Point the wrapper at it
a2a-copilot --config agents/example/config.json --cli-url localhost:4321
```
## Known Issues [#known-issues]
### Node 22 ESM compatibility [#node-22-esm-compatibility]
The `vscode-jsonrpc` package (a transitive dependency of `@github/copilot-sdk`) lacks an `exports` map in its `package.json`. Node 22's stricter ESM resolver rejects the `vscode-jsonrpc/node` subpath import, causing a startup crash.
A `postinstall` script is included that automatically patches `vscode-jsonrpc/package.json` to add the missing `exports` field. The patch runs on every `npm install` and is idempotent — it is a no-op when the field already exists.
If you see `ERR_MODULE_NOT_FOUND` referencing `vscode-jsonrpc/node`, run `npm install` again to re-apply the patch.
## Related Packages [#related-packages]
This package is part of the [a2a-wrapper](https://github.com/shashikanth-gs/a2a-wrapper) monorepo:
| Package | Description |
| ---------------------------------------------------------------------- | --------------------------------------------------------------------- |
| [`@a2a-wrapper/core`](https://www.npmjs.com/package/@a2a-wrapper/core) | Shared infrastructure (logging, config, server, events, session, CLI) |
| [`a2a-opencode`](https://www.npmjs.com/package/a2a-opencode) | A2A wrapper for OpenCode |
| [`a2a-claude`](https://www.npmjs.com/package/a2a-claude) | A2A wrapper for Claude |
| [`a2a-codex`](https://www.npmjs.com/package/a2a-codex) | A2A wrapper for OpenAI Codex |
| [`a2a-antigravity`](https://www.npmjs.com/package/a2a-antigravity) | A2A wrapper for Google Antigravity |
## Contributing [#contributing]
Contributions are welcome! Please read [CONTRIBUTING.md](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/a2a-copilot/CONTRIBUTING.md) first.
# a2a-opencode (/docs/reference/packages/a2a-opencode)
> This page is generated from [`a2a-opencode/README.md`](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/a2a-opencode/README.md) and stays in sync with the repository.
[OpenCode](https://opencode.ai) is a production-grade agent runtime that supports Anthropic, OpenAI, GitHub Copilot, and more. It already handles multi-step planning, MCP tool execution, and streaming across any provider you configure.
**a2a-opencode** exposes it as a standalone, interoperable agent via the [A2A protocol](https://github.com/google-deepmind/a2a). Drop a JSON config file in, get a fully spec-compliant A2A server out. Any orchestrator that speaks A2A can discover and call it — swap the LLM provider in one config line without changing orchestration code.
> **The pattern:** MCP is the vertical rail — how agents access tools. A2A is the horizontal rail — how agents talk to each other. This library adds the horizontal rail to OpenCode, making it vendor-neutral by default.
**Features:**
* Native [A2A v1.0](https://a2a-protocol.org) protocol, backward compatible with v0.3.x clients — Agent Card, JSON-RPC, REST, SSE streaming
* Powered by OpenCode with support for any LLM provider (Anthropic, OpenAI, GitHub Copilot, and more)
* MCP tool server support — HTTP, SSE, stdio, and OAuth transports
* Multi-turn conversations via persistent OpenCode sessions
* SSE event streaming with automatic reconnect and polling fallback
* Auto-approval of tool permissions with configurable overrides
* JSON config file with layered overrides (JSON → env vars → CLI flags)
* Docker-ready with corporate proxy CA support
* TypeScript source with full type declarations
* Postman collection included for API exploration
## Why not just integrate the LLM provider API directly? [#why-not-just-integrate-the-llm-provider-api-directly]
Direct provider integrations work — but they create vendor lock-in at the integration layer. Switching from Claude to GPT-4.1 means rewriting SDK calls. Adding a second agent type means a second bespoke integration.
With the A2A protocol surface:
* Your orchestrator speaks one interface regardless of which provider is behind it
* Switch providers by changing **one line** in `config.json` — orchestration stays the same
* Run multiple specialized agents (different providers, different system prompts) behind a single protocol interface
* Any A2A-compatible system can discover and call your agent via Agent Card
## Works with agent frameworks [#works-with-agent-frameworks]
This library complements — not replaces — frameworks like LangGraph, Google ADK, Microsoft Agent Framework, and CrewAI. Use those frameworks for orchestration, state, and memory control. Use a2a-opencode as the execution node they call.
```
LangGraph / ADK / Microsoft Agent Framework
(state, memory, flow control)
↓
A2A Protocol
↓
a2a-opencode
(OpenCode + any LLM provider)
```
## Quick Start [#quick-start]
```bash
# Install globally
npm install -g a2a-opencode
# Start OpenCode server (required — runs on port 4096 by default)
opencode serve
# In a separate terminal, run the bundled example agent
a2a-opencode --config agents/example/config.json
```
Or run without installing:
```bash
npx a2a-opencode --config agents/example/config.json
```
> **Prerequisites:** [OpenCode](https://opencode.ai) installed and running (`opencode serve`). The wrapper connects to it on `http://localhost:4096` by default.
## Tested With [#tested-with]
| Component | Version |
| ------------------ | ------------------------------------------------ |
| OpenCode server | **v1.3.0** |
| `@opencode-ai/sdk` | **1.3.0** |
| `@a2a-js/sdk` | **1.0.0** |
| A2A protocol | **v1.0 (native) + v0.3.x (backward compatible)** |
| Node.js | **>=20** |
> Other versions may work, but the above combination is what has been tested end-to-end.
## Architecture [#architecture]
```
A2A Client (Orchestrator / Inspector / curl)
│
│ JSON-RPC or REST over HTTP
▼
Express Server (a2a-opencode)
│ ├─ /.well-known/agent-card.json → Agent Card
│ ├─ /a2a/jsonrpc → JSON-RPC (message/send, message/stream, …)
│ ├─ /a2a/rest → REST handler
│ ├─ /context → Read context.md
│ ├─ /context/build → Trigger context discovery
│ └─ /health → Health check
│
│ @a2a-js/sdk DefaultRequestHandler
▼
OpenCodeExecutor (AgentExecutor)
│ ├─ SessionManager — contextId → OpenCode session
│ ├─ EventStreamManager — SSE polling + automatic reconnect
│ ├─ PermissionHandler — auto-approves tool calls
│ └─ EventPublisher — OpenCode events → A2A events
│
│ @opencode-ai/sdk (HTTP + SSE)
▼
OpenCode Server (opencode serve)
│ ├─ LLM inference (Anthropic, OpenAI, GitHub Copilot, …)
│ └─ MCP tool execution
│
│ MCP Protocol (HTTP / SSE / stdio / OAuth)
▼
MCP Servers (filesystem, custom tools, …)
```
## Installation [#installation]
```bash
# npm
npm install a2a-opencode
# yarn
yarn add a2a-opencode
# pnpm
pnpm add a2a-opencode
```
## Release Channels [#release-channels]
| Channel | Install | Cadence |
| ------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| Stable | `npm install a2a-opencode` | Manually reviewed and approved — see [Release Process](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/CONTRIBUTING.md#release-process) |
| Canary | `npm install a2a-opencode@canary` | Every merge to `main` that passes CI |
Canary builds publish automatically after each merge, tagged like `a2a-opencode@1.7.1-canary-20260812081117`. They're unreviewed and have no changelog entry — useful for trying a fix ahead of a stable release, not for production. `npm install a2a-opencode` always resolves to the latest stable release regardless of how many canaries have shipped since.
## Usage [#usage]
### CLI [#cli]
```bash
a2a-opencode --config agents/example/config.json
```
Full flag reference:
```
a2a-opencode [options]
--config, --agent-json JSON agent config file
--port Server port (default: 3000)
--hostname Bind address (default: 0.0.0.0)
--advertise-host Hostname for agent card URLs (default: localhost)
--opencode-url OpenCode server URL (default: http://localhost:4096)
--model LLM model (default: provider default)
e.g. anthropic/claude-sonnet-4-20250514
--agent OpenCode agent preset
--directory Project directory for OpenCode
--agent-name Agent display name
--agent-description Agent description
--auto-approve Auto-approve all tool permissions (default: on)
--no-auto-approve Require manual permission approval
--auto-answer Auto-answer questions (default: on)
--no-auto-answer Do not auto-answer questions
--stream-artifacts Stream chunks in real time (A2A spec mode)
--no-stream-artifacts Buffer artifacts — Inspector-compatible (default)
--log-level debug | info | warn | error (default: info)
--help Show this help
--version Show version
```
### Programmatic API [#programmatic-api]
```typescript
import { createA2AServer, resolveConfig } from 'a2a-opencode';
const config = await resolveConfig({ configPath: 'agents/example/config.json' });
const { server, url } = await createA2AServer(config);
console.log(`Agent running at ${url}`);
```
## Configuration [#configuration]
Config is resolved in priority order: **defaults ← JSON file ← env vars ← CLI flags**
### JSON Config File [#json-config-file]
Create a `config.json` (see `agents/example/config.json` for the fully annotated template):
> `agentCard.protocolVersion` below is optional and kept only for backward
> compatibility with older config files — the server negotiates A2A v1.0 vs.
> v0.3.x automatically per request and no longer reads this field. See
> [Protocol Versions](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/packages/core/README.md#protocol-versions) for how
> negotiation works.
```json
{
"agentCard": {
"name": "My Agent",
"description": "What my agent does",
"version": "1.0.0",
"protocolVersion": "0.3.0",
"streaming": true,
"skills": [
{
"id": "my-skill",
"name": "My Skill",
"description": "Describe the skill",
"tags": ["example"]
}
]
},
"server": {
"port": 3000,
"hostname": "0.0.0.0",
"advertiseHost": "localhost"
},
"opencode": {
"baseUrl": "http://localhost:4096",
"model": "anthropic/claude-sonnet-4-20250514",
"systemPrompt": "You are a specialist agent that...",
"contextFile": "context.md",
"autoApprove": true,
"autoAnswer": true
},
"mcp": {
"my-tools": {
"type": "remote",
"url": "http://localhost:8002/mcp"
}
},
"events": {
"enabled": true,
"transport": "a2a"
}
}
```
### Environment Variables [#environment-variables]
| Variable | Description | Default |
| ------------------- | -------------------------------- | ----------------------- |
| `PORT` | Server port | `3000` |
| `HOSTNAME` | Bind address | `0.0.0.0` |
| `ADVERTISE_HOST` | Hostname in agent card URLs | `localhost` |
| `OPENCODE_URL` | OpenCode server URL | `http://localhost:4096` |
| `MODEL` | LLM model (`provider/model`) | *(OpenCode default)* |
| `OPENCODE_AGENT` | OpenCode agent preset | *(from config)* |
| `DIRECTORY` | Project directory for OpenCode | *(empty)* |
| `AUTO_APPROVE` | Auto-approve tool permissions | `true` |
| `AUTO_ANSWER` | Auto-answer questions | `true` |
| `STREAM_ARTIFACTS` | Stream chunks in real time | `false` |
| `LOG_LEVEL` | `debug`\|`info`\|`warn`\|`error` | `info` |
| `AGENT_NAME` | Override agent card name | *(from config)* |
| `AGENT_DESCRIPTION` | Override agent card description | *(from config)* |
See [`.env.example`](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/a2a-opencode/.env.example) for the full reference.
## Bundled Agent Examples [#bundled-agent-examples]
### Example Agent (minimal) [#example-agent-minimal]
```bash
./agents/example/start.sh start
./agents/example/start.sh status
./agents/example/start.sh logs
./agents/example/start.sh stop
./agents/example/start.sh foreground # useful for debugging
```
Runs on port `3000`. No external tools. Good starting point for custom agents.
> The `start.sh` script manages both the OpenCode subprocess and the A2A wrapper.
### Creating Your Own Agent [#creating-your-own-agent]
```bash
# Copy the example agent
cp -r agents/example agents/my-agent
# Edit the config
$EDITOR agents/my-agent/config.json
# Start it
./agents/my-agent/start.sh start
```
## MCP Tool Servers [#mcp-tool-servers]
### Local server (stdio child process) [#local-server-stdio-child-process]
```json
"mcp": {
"filesystem": {
"type": "local",
"command": ["npx", "@modelcontextprotocol/server-filesystem", "/path/to/workspace"],
"enabled": true,
"timeout": 10000
}
}
```
### Remote server (HTTP / SSE) [#remote-server-http--sse]
```json
"mcp": {
"my-tools": {
"type": "remote",
"url": "http://localhost:8002/mcp",
"enabled": true
}
}
```
### Authenticated remote servers (custom headers) [#authenticated-remote-servers-custom-headers]
Many hosted MCP servers (Linear, Notion, remote GitHub MCP, etc.) require auth headers. Add a `headers` map to any `remote` server. Header values support `${ENV_VAR}` substitution so **secrets never live in `config.json`** — reference an environment variable and supply the value at runtime:
```json
"mcp": {
"linear": {
"type": "remote",
"url": "https://mcp.linear.app/sse",
"headers": {
"Authorization": "Bearer ${LINEAR_API_KEY}"
}
}
}
```
```bash
LINEAR_API_KEY=lin_xxx ./agents/my-agent/start.sh start
```
Unresolved tokens (no matching env var) are left as-is so misconfigurations stay visible. The same `${VAR}` / `$VAR` substitution applies to a local server's `command` array and `environment` map — keep tokens out of committed config and inject them via the environment.
### Remote server with OAuth [#remote-server-with-oauth]
```json
"mcp": {
"my-oauth-tools": {
"type": "remote",
"url": "https://api.example.com/mcp",
"oauth": {
"clientId": "...",
"clientSecret": "...",
"scope": "read write"
}
}
}
```
For static auth where you control the token, prefer `headers` with `${ENV_VAR}` substitution. Use `oauth` when the server requires an OAuth2 authorization flow.
## Memory Persistence [#memory-persistence]
Give your agent persistent instructions and skills that survive across sessions. Declare them in config.json and they're materialized into the workspace at startup — the LLM reads them automatically as part of its context.
### Config [#config]
```json
{
"memory": {
"instructions": "./memory/instructions.md",
"skills": ["./memory/skills/code-review"]
}
}
```
Paths are relative to the directory containing config.json.
### Instructions [#instructions]
A markdown file with project-level instructions, coding conventions, safety rules, or behavioral guidelines.
### Skills [#skills]
Each skill is a directory containing a `SKILL.md` file with YAML frontmatter and optional resource directories:
```
memory/skills/code-review/
├── SKILL.md # Required: frontmatter + instructions
├── scripts/ # Optional: helper scripts
├── references/ # Optional: reference docs
└── assets/ # Optional: static files
```
**SKILL.md format:**
```markdown
---
name: code-review
description: Provides code review guidelines and checklists
license: MIT
allowed-tools:
- read
- bash
---
# Code Review Skill
Detailed instructions for the LLM...
```
The `name` field must be kebab-case (lowercase + hyphens, max 64 chars). It determines the output directory name.
### Where files are written [#where-files-are-written]
The target path depends on the configured model:
| Model contains | Instructions path | Skills base dir |
| ----------------- | --------------------------- | ------------------- |
| `claude` | `CLAUDE.md` | `.claude/skills/` |
| `codex` | `.codex/instructions.md` | `.agents/skills/` |
| *(anything else)* | `.opencode/instructions.md` | `.opencode/skills/` |
### `agentCard.skills` vs `memory.skills` [#agentcardskills-vs-memoryskills]
These serve different purposes:
* **`agentCard.skills`** — External metadata for orchestrators. Advertised in the agent card for discovery and routing.
* **`memory.skills`** — Internal instructions for the LLM. Never exposed externally. Teaches the LLM *how* to perform tasks.
Both should be maintained — the agent card tells callers what the agent can do, while memory skills tell the LLM how to do it. Descriptions may differ: agent card skills are high-level and marketing-friendly, memory skills are technical and detailed.
## Calling Other A2A Agents [#calling-other-a2a-agents]
Expose remote A2A agents as MCP tools by declaring them under `subAgents` in your config. The wrapper spawns [`a2a-mcp-skillmap`](https://www.npmjs.com/package/a2a-mcp-skillmap) ([GitHub](https://github.com/shashikanth-gs/a2a-mcp-skillmap)) as a stdio MCP server and registers it under the reserved `a2a-subagents` key in the resolved `mcp` map. Each remote skill becomes a callable tool the OpenCode LLM can dispatch like any other MCP tool.
```json
{
"subAgents": {
"agents": [
{
"name": "coding",
"agentCardUrl": "https://coding.example.com/.well-known/agent-card.json",
"auth": { "mode": "bearer", "token": "${CODING_AGENT_TOKEN}" }
},
{
"name": "research",
"agentCardUrl": "https://research.example.com/",
"endpointUrlOverride": "https://research.internal.local/.well-known/agent-card.json"
}
]
}
}
```
The OpenCode LLM sees `coding__` and `research__` tools (one per skill advertised in each sub-agent's card). Tokens may reference environment variables via `${VAR}` syntax — missing variables produce a startup warning and the auth block is omitted (the bridge calls without credentials).
When `subAgents` is absent or `agents` is empty, the wrapper skips every sub-agent code path with no side effects.
See [`agents/multi-agent/`](https://github.com/shashikanth-gs/a2a-wrapper/tree/main/a2a-opencode/agents/multi-agent/) for a working example.
## Event Transport (Observability) [#event-transport-observability]
Agents emit structured trace events for MCP tool calls, reasoning, and lifecycle. By default, these flow as sideband artifacts through the A2A protocol itself — orchestrators discover them via the `urn:x-a2a:trace:v1` extension on the agent card.
### Default (A2A sideband) [#default-a2a-sideband]
No config needed. Trace artifacts appear alongside response artifacts and can be filtered by the `urn:x-a2a:trace:v1` extension URI.
### HTTP collector [#http-collector]
Route events to an external telemetry endpoint:
```json
{
"events": {
"enabled": true,
"transport": "http",
"httpUrl": "https://telemetry.example.com/events",
"httpHeaders": {
"Authorization": "Bearer ${TELEMETRY_TOKEN}"
}
}
}
```
### Custom transport (programmatic) [#custom-transport-programmatic]
For Kafka, Redis, or database sinks, use the programmatic API. See the [`@a2a-wrapper/core` README](https://www.npmjs.com/package/@a2a-wrapper/core) for full details.
## Docker [#docker]
```bash
# Build
docker build -t a2a-opencode:latest .
# Run (OpenCode must be accessible from within the container)
docker run -p 3000:3000 \
a2a-opencode:latest --config agents/example/config.json
# Mount a custom agent config
docker run -p 3000:3000 \
-v /host/path/my-agent:/app/agents/my-agent \
a2a-opencode:latest --config agents/my-agent/config.json
```
### Corporate Proxy (Netskope / Zscaler) [#corporate-proxy-netskope--zscaler]
Mount your CA certificate into the container and the entrypoint injects it automatically:
```bash
docker run -p 3000:3000 \
-v /path/to/corporate-ca.crt:/etc/ssl/certs/corporate-ca.crt:ro \
a2a-opencode:latest --config agents/example/config.json
```
## A2A Protocol [#a2a-protocol]
### Protocol Versions [#protocol-versions]
Speaks **A2A v1.0 natively** and is **fully backward compatible with v0.3.x clients** — no configuration needed. Send `A2A-Version: 1.0` to discover the native v1.0 Agent Card. Send `A2A-Version: 0.3` or `0.3.0`—or omit the header for older clients—to receive the legacy card. JSON-RPC/REST handlers additionally auto-detect native and legacy method shapes. Both versions use the same endpoints below; see [Protocol Versions](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/packages/core/README.md#protocol-versions) for the complete negotiation rules and optional Signed Agent Card support.
| Endpoint | Description |
| ---------------------------------- | ------------------------------------------------------------------------------------------------ |
| `GET /.well-known/agent-card.json` | Agent identity and capabilities |
| `POST /a2a/jsonrpc` | JSON-RPC: `message/send`, `message/stream` (v0.3) / `SendMessage`, `SendStreamingMessage` (v1.0) |
| `POST /a2a/rest/message:send` | v1 HTTP+JSON message send |
| `POST /a2a/rest/v1/message:send` | v0.3 HTTP+JSON message send |
| `GET /health` | Health check |
| `POST /context/build` | Trigger context discovery |
| `GET /context` | Read the built context file |
Example JSON-RPC request (`message/send`, v0.3-shaped — still accepted and automatically translated):
```json
{
"jsonrpc": "2.0",
"id": 1,
"method": "message/send",
"messageId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"params": {
"message": {
"role": "user",
"parts": [{ "kind": "text", "text": "Hello, agent!" }]
}
}
}
```
Streaming uses SSE for real-time status updates and artifact chunks. Set `--stream-artifacts` for spec-correct chunk streaming or leave it unset (default) for buffered output compatible with the [A2A Inspector](https://github.com/google-deepmind/a2a).
## API Reference (Postman) [#api-reference-postman]
A full Postman collection covering all endpoints is included at [`docs/A2A-OpenCode-Wrapper.postman_collection.json`](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/a2a-opencode/docs/A2A-OpenCode-Wrapper.postman_collection.json). Import it into Postman and set the `baseUrl` variable to your running agent's URL.
## Context Building [#context-building]
```bash
# Trigger the LLM to discover available data and write context.md
curl -X POST http://localhost:3000/context/build
# Read the built context
curl http://localhost:3000/context
```
## Related Packages [#related-packages]
This package is part of the [a2a-wrapper](https://github.com/shashikanth-gs/a2a-wrapper) monorepo:
| Package | Description |
| ---------------------------------------------------------------------- | --------------------------------------------------------------------- |
| [`@a2a-wrapper/core`](https://www.npmjs.com/package/@a2a-wrapper/core) | Shared infrastructure (logging, config, server, events, session, CLI) |
| [`a2a-copilot`](https://www.npmjs.com/package/a2a-copilot) | A2A wrapper for GitHub Copilot |
| [`a2a-claude`](https://www.npmjs.com/package/a2a-claude) | A2A wrapper for Claude |
| [`a2a-codex`](https://www.npmjs.com/package/a2a-codex) | A2A wrapper for OpenAI Codex |
| [`a2a-antigravity`](https://www.npmjs.com/package/a2a-antigravity) | A2A wrapper for Google Antigravity |
## Contributing [#contributing]
Contributions are welcome! Please read [CONTRIBUTING.md](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/a2a-opencode/CONTRIBUTING.md) first.
# @a2a-wrapper/core (/docs/reference/packages/core)
> This page is generated from [`packages/core/README.md`](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/packages/core/README.md) and stays in sync with the repository.
Shared infrastructure core for [A2A protocol](https://a2a-protocol.org) wrapper projects. Provides logging, configuration loading, event publishing, agent card building, server bootstrapping, session management, and CLI scaffolding — so each wrapper only needs to implement its backend-specific executor. All A2A protocol/spec-version-specific logic lives here and nowhere else in this monorepo — see [Protocol Versions](#protocol-versions).
## Installation [#installation]
```bash
npm install @a2a-wrapper/core
```
Peer dependencies (your wrapper project must install these):
```bash
npm install @a2a-js/sdk express uuid
```
## Release Channels [#release-channels]
| Channel | Install | Cadence |
| ------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| Stable | `npm install @a2a-wrapper/core` | Manually reviewed and approved — see [Release Process](https://github.com/shashikanth-gs/a2a-wrapper/blob/main/CONTRIBUTING.md#release-process) |
| Canary | `npm install @a2a-wrapper/core@canary` | Every merge to `main` that passes CI |
Canary builds publish automatically after each merge, tagged like `@a2a-wrapper/core@2.1.0-canary-20260812081117`. They're unreviewed and have no changelog entry — useful for trying a fix ahead of a stable release, not for production. `npm install @a2a-wrapper/core` always resolves to the latest stable release regardless of how many canaries have shipped since.
## Quick Start [#quick-start]
A minimal wrapper project using `createCli`:
```typescript
import {
createCli,
type BaseAgentConfig,
type A2AExecutor,
} from "@a2a-wrapper/core";
// 1. Define your backend-specific config
interface MyBackendConfig {
apiUrl: string;
}
type MyConfig = BaseAgentConfig;
// 2. Implement the executor interface
class MyExecutor implements A2AExecutor {
constructor(private config: Required) {}
async initialize() { /* connect to backend */ }
async shutdown() { /* cleanup */ }
async execute(context: any, event: any) { /* handle A2A tasks */ }
}
// 3. Wire it up with createCli
createCli({
packageName: "my-a2a-wrapper",
version: "1.0.0",
defaults: { /* full default config */ } as Required,
usage: "Usage: my-a2a-wrapper [options]",
executorFactory: (config) => new MyExecutor(config),
parseBackendArgs: (values) => ({
backend: { apiUrl: values["api-url"] as string },
}),
loadEnvOverrides: () => ({
backend: { apiUrl: process.env.MY_API_URL },
}),
extraArgDefs: {
"api-url": { type: "string" },
},
});
```
Run it:
```bash
node dist/cli.js --port 3000 --log-level debug --api-url http://localhost:8080
```
## Protocol Versions [#protocol-versions]
`createA2AServer()` speaks **A2A v1.0 natively** and is **fully backward compatible with v0.3.x clients out of the box** — there is nothing to configure. This is built on `@a2a-js/sdk@^1.0.0`'s own `compat/v0_3` translation layer, not a hand-rolled shim.
**How negotiation works**, per request:
* `buildAgentCard()` produces the native v1.0 `AgentCard` shape — `supportedInterfaces[]` (not the old `url`/`additionalInterfaces`/`protocolVersion` fields), with both `0.3` and `0.3.0` legacy aliases per transport binding. This preserves compatibility with the SDK's canonical `0.3` value and this project's historically advertised `0.3.0` value.
* `GET /.well-known/agent-card.json` reads the caller's `A2A-Version` request header: `1.0` returns the native v1.0 card; `0.3`, `0.3.0`, or no header at all returns a fully v0.3-shaped legacy card (`url`, `additionalInterfaces`, `protocolVersion: "0.3.0"`), so old clients keep working unmodified.
* `POST /a2a/jsonrpc` and the HTTP+JSON routes below `/a2a/rest` accept their native v1.0 shapes and transparently translate v0.3 requests and responses. JSON-RPC uses legacy methods such as `message/send` and `message/stream`; HTTP+JSON uses legacy routes such as `/a2a/rest/v1/message:send`. No separately configured v0.3 server is required.
**Signed Agent Cards** (A2A v1.0, JWS per RFC 7515) are supported as an opt-in extra — pass `ServerOptions.agentCardSigning` (or set `AgentCardConfig.signing` in your resolved config, reading the private key from an env-var-held JWK) and `AgentCard.signatures` gets populated automatically. Unsigned cards (the default) simply omit `signatures`.
```typescript
import { createA2AServer } from "@a2a-wrapper/core";
// protocolVersion here is only the informational A2A-Version response
// header value — it does not gate which wire format is actually served;
// that's negotiated per-request as described above.
const handle = await createA2AServer(config, executorFactory, {
protocolVersion: "1.0", // default
});
```
## API Reference [#api-reference]
All public symbols are exported from the package root (`@a2a-wrapper/core`). Imports from internal module paths are not supported.
### Utils [#utils]
| Export | Description |
| --------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `createLogger(rootName)` | Factory that returns a new `Logger` instance with the given root name. |
| `Logger` | Structured logger with `debug`, `info`, `warn`, `error` methods and `child(name)` for hierarchical naming. |
| `LogLevel` | Enum — `DEBUG`, `INFO`, `WARN`, `ERROR`. |
| `createDeferred()` | Returns a `Deferred` with externally-resolvable `promise`, `resolve`, and `reject`. |
| `sleep(ms)` | Returns a Promise that resolves after `ms` milliseconds. |
| `deepMerge(target, source)` | Recursively merges objects. Arrays are replaced, inputs are not mutated. |
| `substituteEnvTokens(args)` | Replaces `$VAR_NAME` tokens in string arrays with matching env var values. |
### Config [#config]
| Export | Description |
| --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `BaseAgentConfig` | Generic config interface — includes `agentCard`, `server`, `session`, `logging`, `timeouts`, and `backend: TBackend`. |
| `AgentCardConfig` | Agent card fields (name, description, skills, capabilities). |
| `ServerConfig` | Server fields (port, hostname, advertiseHost, advertiseProtocol). |
| `SessionConfig` | Session fields (reuseByContext, ttlMs, cleanupIntervalMs). |
| `BaseFeatureFlags` | Shared feature flags (`streamArtifactChunks`). |
| `TimeoutConfig` | Timeout settings. |
| `LoggingConfig` | Logging settings (level). |
| `BaseMcpServerConfig` | Common MCP server config pattern. |
| `EventsConfig` | Event transport config — `enabled`, `transport`, `httpUrl`, `httpTimeout`, `httpHeaders`. |
| `SkillConfig` | Skill definition (id, name, description, tags, examples). |
| `loadConfigFile(filePath)` | Reads and parses a JSON config file. Throws descriptive errors on failure. |
| `resolveConfig(defaults, configFilePath?, envOverrides?, cliOverrides?)` | Merges config layers: defaults ← file ← env ← CLI. |
### Events [#events]
| Export | Description |
| ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `publishTask(bus, taskId, contextId)` | Registers a bare `Task` with the A2A result manager before any status events. Call once at the start of `execute()` when no prior task record exists. |
| `publishStatus(bus, taskId, contextId, state, messageText?, final?)` | Publishes a `TaskStatusUpdateEvent`. |
| `publishFinalArtifact(bus, taskId, contextId, text)` | Publishes a complete artifact (`lastChunk: true`). |
| `publishStreamingChunk(bus, taskId, contextId, artifactId, chunkText)` | Publishes an appending artifact chunk. |
| `publishLastChunkMarker(bus, taskId, contextId, artifactId, fullText)` | Publishes the final streaming chunk. |
| `publishTraceArtifact(bus, taskId, contextId, traceKey, data)` | Publishes a structured `DataPart` trace artifact. |
| `publishThoughtArtifact(bus, taskId, contextId, traceKey, text)` | Publishes a `TextPart` trace artifact. |
### Event Transport [#event-transport]
Pluggable transport layer for sideband observability events (MCP tool calls, agent reasoning, lifecycle). Decouples event emission from A2A protocol internals so you can route trace data to any backend.
| Export | Description |
| -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `A2ATransport` | Default transport — publishes trace artifacts on the A2A `ExecutionEventBus`. Zero dependencies. |
| `HttpTransport` | POST events as JSON to a configurable HTTP endpoint. Supports custom headers for auth. |
| `AgentEventEmitter` | Per-execution emitter that stamps events with agent identity, trace context, UUID, and timestamp. |
| `resolveTransport(cfg, bus, taskId, contextId, custom?)` | Resolve the correct transport for a single task execution. |
| `createTransport(cfg)` | Create a built-in transport from JSON config. |
| `wrapTransport(transport)` | Normalize a function or object into an `EventTransport`. |
| `EventTransport` | Interface — implement `send(event)` for custom transports. |
| `EventTransportFn` | Convenience type — a plain `async (event) => void` function. |
| `AgentEvent` | Structured event with `eventId`, `eventType`, `agentId`, `traceId`, `data`. |
| `EventType` | Union: `tool_call_start`, `tool_call_end`, `thinking`, `decision`, `agent_started`, `agent_finished`, `agent_error`. |
| `TRACE_EXTENSION_URI` | Constant `"urn:x-a2a:trace:v1"` — declared in agent card capabilities and on trace artifacts. |
| `EventsConfig` | Config interface — `enabled`, `transport` (`"a2a"` or `"http"`), `httpUrl`, `httpTimeout`, `httpHeaders`. |
#### Built-in transports [#built-in-transports]
| Transport | Config value | Description |
| -------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------- |
| A2A sideband | `"a2a"` (default) | Publishes trace artifacts on the `ExecutionEventBus`. Orchestrators discover them via the `urn:x-a2a:trace:v1` extension. |
| HTTP collector | `"http"` | POSTs each event as JSON to `httpUrl`. Supports `httpHeaders` for Bearer tokens / API keys. |
#### JSON config [#json-config]
```json
{
"events": {
"enabled": true,
"transport": "http",
"httpUrl": "https://telemetry.example.com/events",
"httpTimeout": 5000,
"httpHeaders": {
"Authorization": "Bearer ${TELEMETRY_TOKEN}"
}
}
}
```
#### Custom transport (programmatic API) [#custom-transport-programmatic-api]
For Kafka, Redis, databases, or any custom sink — pass a transport function to `createA2AServer()`:
```typescript
import { createA2AServer } from "@a2a-wrapper/core";
const handle = await createA2AServer(config, executorFactory, {
eventTransport: async (event) => {
await kafkaProducer.send({
topic: "agent-traces",
messages: [{ value: JSON.stringify(event) }],
});
},
});
```
Or implement the `EventTransport` interface for full control:
```typescript
import type { EventTransport, AgentEvent } from "@a2a-wrapper/core";
class MyTransport implements EventTransport {
async send(event: AgentEvent): Promise {
await db.insert("events", {
id: event.eventId,
type: event.eventType,
agent: event.agentId,
trace: event.traceId,
data: event.data,
ts: event.timestamp,
});
}
}
```
#### Using the emitter inside an executor [#using-the-emitter-inside-an-executor]
```typescript
import { resolveTransport, AgentEventEmitter } from "@a2a-wrapper/core";
// Inside your executor's execute() method:
const transport = resolveTransport(config.events, bus, taskId, contextId);
const emitter = new AgentEventEmitter({
agentId: "my-agent",
agentName: "My Agent",
traceId: ctx.contextId,
transport,
});
// Emit events — they flow through the resolved transport
await emitter.emit("tool_call_start", { toolName: "read_file", arguments: { path: "/data.json" } });
await emitter.emit("tool_call_end", { toolName: "read_file", result: "...", durationMs: 42 });
await emitter.emit("thinking", { content: "The data shows a spike in CPU usage..." });
```
### Server [#server]
| Export | Description |
| ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `buildAgentCard(config)` | Constructs an A2A `AgentCard` from `AgentCardConfig` + `ServerConfig`. |
| `createA2AServer(config, executorFactory, options?)` | Creates an Express app with standard A2A routes and starts listening. Returns a `ServerHandle`. |
| `ServerOptions` | Options for protocol version, custom route hooks, and custom `eventTransport`. |
| `ServerHandle` | Returned by `createA2AServer` — contains `app`, `server`, `executor`, `eventTransport`, `shutdown()`. |
### Session [#session]
| Export | Description |
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `BaseSessionManager` | Abstract class managing contextId → session mapping with TTL cleanup and task tracking. Subclass and implement `getOrCreate(contextId)`. |
| `SessionEntry` | Interface for session entries with `session` and `lastUsed` fields. |
### Executor [#executor]
| Export | Description |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `A2AExecutor` | Interface contract for backend executors — `initialize()`, `shutdown()`, `execute()`, and optional `cancelTask()`, `getContextContent()`, `buildContext()`. |
### CLI [#cli]
| Export | Description |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `createCli(options)` | Main entry point for wrapper CLIs. Handles arg parsing, config resolution, server creation, and graceful shutdown. |
| `CliOptions` | Configuration for `createCli` — package name, version, defaults, usage, executor factory, arg definitions. |
| `parseCommonArgs(argv, extraArgDefs?)` | Parses common CLI flags (`--port`, `--hostname`, `--log-level`, etc.) into typed config overrides. |
| `CommonArgsResult` | Result of `parseCommonArgs` — config path and partial overrides. |
### Memory Persistence [#memory-persistence]
Materialize instructions and skills into the agent's workspace at startup. The LLM reads these files as part of its context, giving it persistent knowledge across sessions.
| Export | Description |
| ----------------------------------------- | --------------------------------------------------------------------------------------------- |
| `materializeMemory(options)` | Main entry point — reads source files, validates manifests, writes to workspace. |
| `parseSkillManifest(content)` | Parse a SKILL.md file into frontmatter + body. |
| `formatSkillManifest(frontmatter, body)` | Serialize frontmatter + body back to SKILL.md format. |
| `validateSkillManifest(manifest)` | Validate name (kebab-case, ≤64 chars) and description. Returns `null` if valid. |
| `resolveMemoryPath(inputPath, configDir)` | Resolve relative/absolute paths against the config directory. |
| `WELL_KNOWN_PATHS` | Pre-defined backend path mappings (copilot, claude, opencode, codex). |
| `MemoryConfig` | Type — `{ instructions?: string; skills?: string[] }` |
| `SkillManifest` | Type — parsed SKILL.md frontmatter (name, description, license, compatibility, allowedTools). |
| `ParsedSkill` | Type — result of parsing: `{ manifest, body, rawFrontmatter }`. |
| `BackendPaths` | Type — `{ instructionsPath: string; skillsBaseDir: string }`. |
| `MaterializeOptions` | Type — input to `materializeMemory()`. |
#### Config example [#config-example]
```json
{
"memory": {
"instructions": "./memory/instructions.md",
"skills": ["./memory/skills/code-review", "./memory/skills/testing"]
}
}
```
#### SKILL.md format [#skillmd-format]
```markdown
---
name: code-review
description: Provides code review guidelines and checklists
license: MIT
compatibility:
- copilot
- opencode
allowed-tools:
- read_file
- search_files
---
# Code Review Skill
Instructions for the LLM on how to perform code reviews...
```
#### Skill directory structure [#skill-directory-structure]
```
memory/skills/code-review/
├── SKILL.md # Required: manifest + instructions
├── scripts/ # Optional: executable scripts
├── references/ # Optional: reference documents
└── assets/ # Optional: static assets
```
#### Backend path mapping [#backend-path-mapping]
| Backend | Instructions path | Skills base dir |
| ----------- | --------------------------------- | ---------------------- |
| Copilot | `.github/copilot-instructions.md` | `.github/skills/` |
| Claude | `CLAUDE.md` | `.claude/skills/` |
| OpenCode | `.opencode/instructions.md` | `.opencode/skills/` |
| Codex | `.codex/instructions.md` | `.agents/skills/` |
| Antigravity | `AGENTS.md` | `.antigravity/skills/` |
#### Usage in an executor [#usage-in-an-executor]
```typescript
import { materializeMemory, WELL_KNOWN_PATHS } from "@a2a-wrapper/core";
// In executor.initialize():
if (config.memory && workspaceDir) {
await materializeMemory({
memoryConfig: config.memory,
configDir: config.configDir ?? process.cwd(),
workspaceDir,
paths: WELL_KNOWN_PATHS.copilot, // or wrapper-specific BackendPaths such as Antigravity's
});
}
```
#### Note on `agentCard.skills` vs `memory.skills` [#note-on-agentcardskills-vs-memoryskills]
These are different concepts:
* **`agentCard.skills`** — A2A protocol metadata advertised to orchestrators and callers. External-facing. Describes what the agent *can do* at a high level for discovery and routing.
* **`memory.skills`** — Internal instructions materialized into the workspace for the LLM. Never exposed externally. Tells the LLM *how* to do things — patterns, guidelines, tool usage rules.
Keep both in sync manually. You may want different descriptions: the agent card skill is marketing-friendly for orchestrators, while the memory skill is technical and detailed for the LLM.
### Sub-Agents [#sub-agents]
Expose remote A2A agents as MCP tools to the parent agent's underlying LLM runtime by spawning [`a2a-mcp-skillmap`](https://www.npmjs.com/package/a2a-mcp-skillmap) ([GitHub](https://github.com/shashikanth-gs/a2a-mcp-skillmap)) as a stdio child process. The parent reads a `subAgents` array from its config, generates a bridge configuration for skillmap, and registers the bridge as an MCP server before the executor handles its first request.
See [`.kiro/specs/a2a-subagents/`](https://github.com/shashikanth-gs/a2a-wrapper/tree/main/.kiro/specs/a2a-subagents/) for the full spec (requirements, design, and tasks).
See the [`a2a-mcp-skillmap` GitHub repo](https://github.com/shashikanth-gs/a2a-mcp-skillmap) for the bridge's own documentation — config schema, response modes, session continuity, sync budget, OpenTelemetry, and HTTP transport.
| Export | Description |
| ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `bootstrapSubAgents(input)` | Single entry point — runs validate → build → write → probe → synthesize and returns the canonical MCP descriptor. |
| `validateSubAgents(agents, reservedKeys)` | Fail-fast checks (name uniqueness, URL shape, reserved-key collision) plus env-var substitution on `auth.token`. |
| `buildBridgeConfig(source)` | Produce the JSON document `a2a-mcp-skillmap` consumes. |
| `resolveBridgeConfigPath(workspaceDir)` | Compute `/.a2a/subagents-bridge.json` (or a tmpdir fallback). |
| `writeBridgeConfig(config, path)` | Write the bridge config with mode `0600`, creating intermediate directories. |
| `probeSubAgents(agents, timeoutMs)` | Parallel reachability probes, never throws — one `ProbeResult` per agent. |
| `buildSynthesizedMcpEntry(path)` | Produce the canonical, wrapper-agnostic `{ command, args }` MCP descriptor. |
| `SubAgentsConfig` | Top-level config type — `{ agents: SubAgentConfig[]; options?: SubAgentsOptions }`. |
| `SubAgentsOptions` | Bridge-wide options — `responseMode`, `probeTimeoutMs`, `syncBudgetMs` (sync budget passed to skillmap). |
| `SubAgentConfig` | One sub-agent — `{ name, agentCardUrl, endpointUrlOverride?, auth? }`. |
| `SubAgentAuthConfig` | Outbound auth — `none`, `bearer`, or `api_key` (with optional `headerName`). |
| `SynthesizedMcpDescriptor` | The wrapper-agnostic descriptor each wrapper translates to its own MCP entry shape. |
| `SUBAGENTS_MCP_KEY` | Reserved MCP map key — `"a2a-subagents"`. |
| `SKILLMAP_PACKAGE_VERSION` | Pinned `a2a-mcp-skillmap` version invoked via `npx`. Bumping it is a deliberate, reviewable change. |
| `SubAgentValidationError` | Thrown by `validateSubAgents` for every fail-fast case, with structured `details`. |
#### Config example [#config-example-1]
```json
{
"subAgents": {
"agents": [
{
"name": "coding",
"agentCardUrl": "https://coding.example.com/.well-known/agent-card.json",
"auth": { "mode": "bearer", "token": "${CODING_AGENT_TOKEN}" }
},
{
"name": "research",
"agentCardUrl": "https://research.example.com/",
"endpointUrlOverride": "https://research.internal.local/.well-known/agent-card.json"
}
],
"options": { "responseMode": "artifact", "probeTimeoutMs": 5000, "syncBudgetMs": 30000 }
}
}
```
#### Pinned skillmap version [#pinned-skillmap-version]
The MCP entry invokes `npx -y a2a-mcp-skillmap@` — never the unpinned package name — so a future skillmap release cannot silently change semantics. Bumping the pin is reviewed in PR.
#### Wrapper integration [#wrapper-integration]
Each wrapper provides a tiny adapter that maps the canonical `SynthesizedMcpDescriptor` into its own MCP entry shape (`McpStdioServerConfig` for a2a-copilot, `McpLocalServerConfig` for a2a-opencode), then merges the result into the resolved `mcp` map under `descriptor.key`:
```typescript
import { bootstrapSubAgents } from "@a2a-wrapper/core";
if (this.config.subAgents?.agents?.length) {
const result = await bootstrapSubAgents({
subAgents: this.config.subAgents,
workspaceDir,
parentLogLevel: this.config.logging.level ?? "info",
existingMcpKeys: new Set(Object.keys(this.config.mcp ?? {})),
});
this.config.mcp = {
...(this.config.mcp ?? {}),
[result.descriptor.key]: this.toWrapperMcpEntry(result.descriptor),
};
}
```
### A2A SDK Re-exports [#a2a-sdk-re-exports]
| Export | Source |
| --------------------------------------------------------------- | -------------------- |
| `AgentCard` | `@a2a-js/sdk` |
| `TaskState`, `TaskStatusUpdateEvent`, `TaskArtifactUpdateEvent` | `@a2a-js/sdk` |
| `ExecutionEventBus`, `RequestContext` | `@a2a-js/sdk/server` |
These re-exports isolate wrapper projects from direct SDK imports, so a major SDK upgrade only requires changes in `@a2a-wrapper/core`.
## Contributing [#contributing]
### Prerequisites [#prerequisites]
* Node.js ≥ 20
* npm
### Development [#development]
```bash
# Install dependencies
npm install
# Build
npm run build
# Run tests
npm test
# Type-check without emitting
npm run typecheck
# Clean build output
npm run clean
```
### Code Standards [#code-standards]
* TypeScript with `strict: true`
* JSDoc on every exported symbol
* Property-based tests (fast-check) alongside unit tests (vitest)
* Follow [Keep a Changelog](https://keepachangelog.com/) for CHANGELOG.md