@nightowlsdev/engine-a2a
EngineAdapter, reduced governanceThe A2A (Agent2Agent) protocol adapter engine, renders any A2A v0.3-wire endpoint (Bedrock AgentCore, Azure AI Foundry/Copilot Studio, Google ADK/Vertex Agent Engine) as a single Night Owls agent. Adapter tier: reduced governance, tier-1 events (opaque remote, no tool visibility).
What it does
**Use this when:** you already run an agent on a standards-based A2A (Agent2Agent) endpoint — Amazon Bedrock AgentCore, Azure AI Foundry / Microsoft Copilot Studio, or Google ADK/Vertex Agent Engine — and want to front it as a single agent inside a Night Owls swarm without re-implementing it. v1 speaks the **A2A v0.3 wire** (the version every shipping A2A SDK/runtime actually talks today; the v0.3-wire endpoint is what Bedrock/Azure/Google expose — the v1.0-spec branch is scaffolded but spike-gated and not shipped). One client covers all three hyperscaler runtimes; the only per-target work is auth. **How.** `a2aEngine({ endpoint, auth?, agentCard?, dialect? })` fetches the remote's Agent Card and speaks the A2A v0.3 wire under the hood. **Implemented auth in v1:** static headers, bearer tokens, and API keys. `sigV4Auth` (Bedrock) and `oauth2ClientCredentials` (Copilot Studio / Vertex / Azure Entra) are **exported as spike stubs that throw `not yet implemented`** — the seams exist but are not functional yet, so wire your own header strategy for those until they land. Then `defineSwarm({ engine })` or `owl install engine-a2a`. **What it can and can't do (adapter tier).** The loop runs on the remote, so the in-process governance plane can't reach it: no fail-closed tool gate, no cost caps, no pre-generation veto, and `events.tier: 1` — message/status/HITL only, because A2A agents are opaque and expose no tool-call granularity (this is the one engine where you can't see the remote's tool calls; `engine-eve` and `engine-trigger-chat` are tier-2). What DOES cross the boundary: HITL — A2A's `input-required`/`auth-required` states map onto the normal ask/answer loop — and durable resume, which is native to A2A (the remote owns the `taskId`/`contextId`), independent of your own storage. Engine-wall clean: peers `@nightowlsdev/core` and bundles `@a2a-js/sdk` as a direct dependency.
Install
pnpm add @nightowlsdev/engine-a2aKey exports
- a2aEngine
- A2aEngine
- A2A_ENGINE_CAPABILITIES
- createA2aTransport
- noneAuth / staticHeaders / bearerToken / apiKey / sigV4Auth / oauth2ClientCredentials
- mapTaskEvent
- nightOwlsPlugin
Usage
import { defineSwarm } from "@nightowlsdev/core";
import { a2aEngine, A2A_ENGINE_CAPABILITIES } from "@nightowlsdev/engine-a2a";
// An ADAPTER, not a native engine, renders any A2A v0.3-wire endpoint (Bedrock AgentCore, Azure AI
// Foundry/Copilot Studio, Google ADK/Vertex Agent Engine) as one opaque Night Owls agent.
const swarm = defineSwarm({ agents, engine: a2aEngine({ endpoint: "https://agent.example.com" }) });
// Reduced-governance adapter tier, our pre-generation/tool gate can't reach a remote we don't run.
console.log(A2A_ENGINE_CAPABILITIES.kind); // "adapter"
console.log(A2A_ENGINE_CAPABILITIES.governance.preToolCall); // "none", nothing to gate (opaque remote)What it provides
engine-a2a is a protocol ADAPTER, not a native engine: it renders any A2A (Agent2Agent) endpoint as a single, opaque Night Owls agent. One client covers three hyperscaler runtimes — Amazon Bedrock AgentCore, Azure AI Foundry / Microsoft Copilot Studio, and Google ADK / Vertex Agent Engine — for near-zero marginal engineering beyond auth wiring. It fetches the remote's Agent Card and speaks the A2A wire underneath (@a2a-js/sdk); because the remote agent is opaque, the capability descriptor is honestly reduced: kind adapter, tier-1 events (message + status + HITL only, no tool granularity), and governance all off.
When to use it
- You need to call an A2A endpoint deployed on Bedrock AgentCore, Azure AI Foundry / Copilot Studio, or Google ADK / Vertex Agent Engine and surface it as one Night Owls agent.
- You want one wire and one auth story across those hyperscaler runtimes instead of three bespoke SDK integrations.
- The remote already owns its own tools, generation, and durability, and you only need to relay its messages, status, and input-required asks into a Night Owls run.
When not to
- You need tool visibility — A2A agents are opaque, so there are NO tool_call/tool_result events (tier 1); use engine-eve or engine-trigger-chat for a remote with tool granularity.
- You need Night Owls' governance to actually enforce — the pre-generation veto, fail-closed tool gate, and cost caps physically cannot reach a remote you don't execute inside of (governance all false/'none').
- You need per-tool approval, rules injection, scratchpad, delegation, or usage/token metering — none exist across the opaque A2A boundary (approval false, rules false, no swarm.usage).
Alternatives
- A native engine (engine-mastra / engine-ai-sdk / engine-openai-agents)You control the loop and want the full governance plane, tool visibility, and (mastra) delegation/workflows — run in-process instead of adapting a remote.
- engine-eve / engine-trigger-chatYour remote loop exposes tool events (tier 2) and you want that richer visibility than A2A's opaque tier-1 surface.
- The @a2a-js/sdk directlyYou want to speak A2A without the Night Owls event/HITL mapping, storage-backed history, or the swappable-engine contract.
Strengths
- One adapter, three hyperscaler runtimes — Bedrock AgentCore, Azure/Copilot Studio, Google ADK/Vertex — behind a single client with just auth to wire.
- Auth strategies: noneAuth, staticHeaders, bearerToken, and apiKey work today; sigV4Auth (Bedrock) and oauth2ClientCredentials (Copilot Studio / Vertex / Azure Entra) are exported as spike stubs that throw 'not yet implemented' in v1 — the seams exist, wire your own header strategy for those until they land.
- durableResume is STATICALLY true — A2A tasks are durable and resumable by the remote via taskId/contextId, independent of what your storage persists.
- Honest by construction: the descriptor never claims governance or tool visibility the opaque wire can't back, and it's engine-wall clean (peers @nightowlsdev/core; bundles @a2a-js/sdk as a direct dependency).
Limits & trade-offs
- Reduced-governance adapter tier: governance is all false/'none' — no pre-generation veto, no fail-closed tool gate, no cost kill-switch reach into the remote.
- Tier-1 events only: message + status + HITL; no tool_call/tool_result (opaque agents) and no swarm.usage (A2A carries no token data — swarm.turn_usage fires once with an empty breakdown).
- No per-tool approval (approval:false), no rules injection, no scratchpad, no delegation lane, and no OTel telemetry (metering is turn-usage only).
- The shipping wire dialect is v0.3 (the only wire current SDKs speak); the v1.0 dialect is scaffolded but SPIKE-GATED — do not pass it in production. Cancellation is advisory (between-steps), with no guaranteed mid-stream remote abort.
How it works
a2aEngine({ endpoint, auth?, agentCard?, dialect? }) builds a real @a2a-js/sdk transport (or takes a test transport override) and returns (opts) => new A2aEngine(opts, cfg). At run time it fetches the remote's Agent Card (from .well-known/agent-card.json unless overridden) and speaks the A2A wire, mapping task events into SwarmEvents. A2A's input-required / auth-required task states map onto the normal ask / suspend / resume loop; there is no per-tool approval surface to gate, so hitl.approval is false. hitl.durableResume is static true because the remote owns task durability. Reads/history come from OUR persisted event log, not the remote.
Examples
Adapt a remote A2A endpoint with a bearer token
One factory renders the remote A2A agent as a single opaque Night Owls agent. bearerToken/apiKey/staticHeaders are the functional strategies in v1; sigV4Auth (Bedrock) and oauth2ClientCredentials are exported but throw 'not yet implemented' — until they land, front a SigV4/OAuth endpoint with a gateway that accepts a bearer/API key, or supply your own signed headers via staticHeaders.
import { defineSwarm } from "@nightowlsdev/core";
import { a2aEngine, bearerToken } from "@nightowlsdev/engine-a2a";
export default defineSwarm({
agents,
engine: a2aEngine({
endpoint: "https://agent.example.com",
auth: bearerToken(() => process.env.A2A_BEARER_TOKEN!),
}),
});Confirm the reduced-governance adapter tier
The descriptor is honest that an opaque remote leaves nothing for us to gate — check it before assuming governance applies.
import { A2A_ENGINE_CAPABILITIES } from "@nightowlsdev/engine-a2a";
console.log(A2A_ENGINE_CAPABILITIES.kind); // "adapter"
console.log(A2A_ENGINE_CAPABILITIES.events.tier); // 1 — no tool events
console.log(A2A_ENGINE_CAPABILITIES.governance.preToolCall); // "none"
console.log(A2A_ENGINE_CAPABILITIES.hitl.durableResume); // true (static — the remote owns it)Doing the parts it doesn't support
- Governing the remote's tool callsYou can't — A2A agents are opaque, so there are no tool events to gate and governance is off. If you need the fail-closed tool gate and cost caps, run a native engine in-process, or move governance to the boundary the remote itself enforces.
- Seeing what tools the remote ranNot available at tier 1. Use engine-eve or engine-trigger-chat (tier 2, tool events visible) if your remote loop can expose actions/tool results.
- Injecting rules / soft policy into the remoterules is false — you can't compose Night Owls' advise/enforce prompt into a remote opaque agent. Configure policy on the remote's own side (its system prompt / guardrails).
Related
- core — The required base — the adapter peers core (its only peer) and bundles @a2a-js/sdk, mapping into core's SwarmEvent / ask loop.
- engine-eve — The other remote adapter with a richer tier-2 surface (tool events + usage visible).
- engine-mastra — The full-governance native default, for when you control the loop instead of adapting a remote.
- react — useEngineCapabilities() reads A2A_ENGINE_CAPABILITIES to hide affordances the opaque tier-1 wire can't back.