Build once, invoke by Code or AI.
apcore-a2a is an automatic A2A (Agent-to-Agent) protocol adapter for the apcore ecosystem. It allows you to expose any apcore Module Registry as a fully functional, standards-compliant A2A 1.0 agent with zero manual effort.
It solves a common problem: you've built AI capabilities with apcore modules, but you need them to talk to other AI agents over a standard protocol. apcore-a2a reads your existing module metadata (schemas, descriptions, examples) and exposes them as a standards-compliant A2A server — no hand-written Agent Cards, no JSON-RPC boilerplate, no manual task lifecycle management.
All three SDKs implement the same A2A 1.0 contract defined in this repository's specs.
- Python SDK: aiperceivable/apcore-a2a-python
- TypeScript SDK: aiperceivable/apcore-a2a-typescript
- Rust SDK: aiperceivable/apcore-a2a-rust
By reading the existing apcore metadata—including input_schema, output_schema, descriptions, behavioral annotations (readonly, cacheable, paginated, etc.), and AI metadata conventions (x-preconditions, x-cost-per-call, etc.)—apcore-a2a eliminates the need to hand-write Agent Cards, JSON-RPC endpoints, and task lifecycle logic.
- 🚀 One-Call Server: Launch a compliant A2A server using
serve(registry). - 📇 Auto Agent Card: Generates
/.well-known/agent-card.json(A2A 1.0;/.well-known/agent.jsonalias for 0.3 clients) automatically from your module metadata. - 🛠️ Skill Mapping: Converts apcore modules into A2A Skills, preserving schemas and examples;
metadata["display"]["a2a"]overrides surface-facing fields (§5.13). - 🔄 Task Lifecycle: Full implementation of the A2A task state machine (submitted, working, completed, failed, canceled, input-required).
- 📡 Streaming & SSE: Native support for
message/streamwith real-time status and artifact updates, plus cooperative cancellation (tasks/cancel). - 🔗 A2A Client: A built-in
A2AClientto discover and invoke other A2A agents. - 🛡️ Enterprise Ready: JWT/Bearer authentication bridged directly to apcore's ACL (Identity) system; configurable CORS.
- 🔔 Push Notifications: Webhook-based task state updates with retry.
- 🔍 Explorer UI: Built-in browser UI for discovering and testing skills.
- 📊 Observability:
/healthand/metricsendpoints with structured logging; optional apcoresys.*modules. - 🧩 Pluggable Storage: Swap in Redis/PostgreSQL via the
TaskStoreprotocol.
=== "Python"
bash pip install apcore-a2a
Requires Python 3.11+ and apcore 0.22.0+.
=== "TypeScript"
bash npm install apcore-a2a
Requires Node.js 18+ and apcore-js 0.22.0+.
=== "Rust"
bash cargo add apcore-a2a
Requires Rust 1.75+ and apcore 0.22+.
If you already have apcore modules, you can expose them as an A2A agent in just a few lines:
=== "Python" ```python from apcore import Registry from apcore_a2a import serve
# 1. Initialize your registry and discover modules
registry = Registry(extensions_dir="./extensions")
registry.discover()
# 2. Start the A2A server
serve(registry, name="My Assistant Agent", host="0.0.0.0", port=8000)
```
=== "TypeScript" ```typescript import { Registry } from "apcore-js"; import { serve } from "apcore-a2a";
const registry = new Registry({ extensionsDir: "./extensions" });
await registry.discover();
serve(registry, {
name: "My Assistant Agent",
port: 8000,
});
```
=== "Rust" ```rust use apcore_a2a::{async_serve, APCoreA2AConfig, BackendSource};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = APCoreA2AConfig {
name: "My Assistant Agent".into(),
url: "http://0.0.0.0:8000".into(),
..Default::default()
};
async_serve(BackendSource::from("./extensions"), config).await?;
Ok(())
}
```
Use the A2AClient to interact with any A2A-compliant agent:
=== "Python" ```python import asyncio from apcore_a2a import A2AClient
async def main():
async with A2AClient("http://remote-agent:8000") as client:
# Discover skills
card = await client.discover()
print(f"Connected to {card['name']} with {len(card['skills'])} skills")
# Send a message
task = await client.send_message({
"role": "user",
"parts": [{"text": "Hello, how can you help?"}]
}, metadata={"skillId": "general.chat"})
print(f"Task status: {task['status']['state']}")
if __name__ == "__main__":
asyncio.run(main())
```
=== "TypeScript" ```typescript import { A2AClient } from "apcore-a2a";
const client = new A2AClient("http://remote-agent:8000");
// Discover skills
const card = await client.discover();
console.log(`Connected to ${card.name}`);
// Send a message
const task = await client.sendMessage(
{ role: "user", parts: [{ text: "Hello, how can you help?" }] },
{ metadata: { skillId: "general.chat" } }
);
console.log(`Task status: ${task.status.state}`);
```
=== "Rust" ```rust use apcore_a2a::A2AClient; use serde_json::json;
let client = A2AClient::new("http://remote-agent:8000");
// `metadata.skillId` selects the module; structured inputs go in a `data` part.
let task = client
.send_message(
json!({
"messageId": "m1",
"role": "ROLE_USER",
"parts": [{ "text": "Hello, how can you help?" }]
}),
Some(json!({ "skillId": "general.chat" })),
None, // optional contextId
)
.await?;
println!("Task status: {}", task["status"]["state"]);
```
apcore-a2a acts as a thin, protocol-specific layer on top of apcore (Python, TypeScript, and Rust). It maps A2A concepts to apcore primitives:
| A2A Concept | apcore Mapping |
|---|---|
| Agent Card | Derived from Registry configuration |
| Skill id | module_id |
| Skill name | metadata["display"]["a2a"]["alias"] or humanized module_id |
| Skill desc | metadata["display"]["a2a"]["description"] or module.description |
| Skill tags | metadata["display"]["tags"] or module.tags |
| Task | Managed execution of Executor.call_async() |
| Streaming | Wrapped Executor.stream() via SSE |
| Security | Bridged to apcore's Identity context |
- Full Documentation Site
- Getting Started Guide — Install, serve, and call agents
- Feature Specs Overview — All feature specifications
- Specifications (PRD, TDD, SRS)
This project is licensed under the Apache License 2.0. See the LICENSE file for details.