Skip to content

Agent-to-Agent (A2A) ​

The A2A protocol is an open standard for agent discovery, communication, and collaboration across platforms and services. Elsai implements A2A Protocol v1.0 via a2a-sdk>=1.0.0, so your agents can call remote agents — or expose themselves as — A2A-compatible services.

The high-level Elsai API (A2AServer, A2AAgent) is stable across the v1 upgrade. Upgrading from a2a-sdk 0.3.x? See Migrating from A2A v0.3.

Default model is Amazon Bedrock

Agent() with no model= uses Amazon Bedrock (Claude Sonnet 4.6 in us-west-2). Configure AWS credentials (aws configure or AWS_* env vars), or pass an explicit model — see Installation and Model Providers.

A2A protocol ​

  1. Discover — The client fetches the remote agent card from /.well-known/agent-card.json.
  2. Connect — Requests are sent to the server's JSON-RPC endpoint (POST /) with A2A-Version: 1.0.
  3. Execute — The remote A2AServer runs the hosted agent and returns the result.
  4. Integrate — Use A2AAgent directly, as an orchestrator tool (as_tool()), or as a node in a Graph.

Installation ​

Requires elsai-agents>=0.3.3, which pulls a2a-sdk>=1.0.0 via the a2a extra:

bash
pip install --extra-index-url https://elsai-agents.elsai.ai/root/ "elsai-agents[a2a]==0.3.5"

Exposing an agent as an A2A server ​

python
from elsai import Agent, tool
from elsai.agent import AgentConfig
from elsai.multiagent.a2a import A2AServer

@tool
def calculate(expression: str) -> str:
    """Evaluate a mathematical expression."""
    return str(eval(expression))

agent = Agent(
    system_prompt="You are a precise calculator agent.",
    tools=[calculate],
    config=AgentConfig(name="calculator", description="A precise calculator agent"),
)

server = A2AServer(agent=agent, host="0.0.0.0", port=9000)
server.serve()
# Agent card served at: GET  /.well-known/agent-card.json
# JSON-RPC endpoint at:  POST /

Embedding in an existing ASGI app ​

python
from fastapi import FastAPI

app = FastAPI()
a2a_app = server.to_fastapi_app()   # or server.to_starlette_app()
app.mount("/a2a", a2a_app)

Path-based routing behind a load balancer ​

When the public URL includes a path prefix, set http_url so the agent card advertises the correct endpoint:

python
server = A2AServer(
    agent=agent,
    http_url="https://my-alb.amazonaws.com/agent1",
)
# Agent card: GET  /agent1/.well-known/agent-card.json
# JSON-RPC:     POST /agent1/

Calling a remote A2A agent ​

python
from elsai.agent import A2AAgent

remote = A2AAgent(
    endpoint="http://localhost:9000",
    name="remote-calculator",
    timeout=300,   # seconds
)

result = remote("What is 10 ^ 6?")
print(result)   # → "10^6 = 1,000,000"

The agent card at /.well-known/agent-card.json is fetched and cached automatically on the first call.

Authenticated endpoints ​

Pass a custom httpx client through ClientConfig for SigV4, OAuth, bearer tokens, or other auth:

python
import httpx
from a2a.client import ClientConfig
from elsai.agent import A2AAgent

client = httpx.AsyncClient(
    headers={"Authorization": "Bearer your-token"},
    timeout=300,
)
remote = A2AAgent(
    endpoint="https://secure-agent.example.com",
    client_config=ClientConfig(httpx_client=client),
)

A2A as an orchestrator tool ​

Wrap a remote agent as a tool for a local orchestrator:

python
from elsai import Agent
from elsai.agent import AgentConfig, A2AAgent

calculator = A2AAgent(endpoint="http://calc-service:9000")
researcher  = A2AAgent(endpoint="http://research-service:9000")

orchestrator = Agent(
    system_prompt="Delegate calculations to calculator, research to researcher.",
    tools=[calculator.as_tool(), researcher.as_tool()],
    config=AgentConfig(name="orchestrator"),
)

result = orchestrator("Research Tokyo's population and calculate its density per km²")
print(result)

A2A as a graph node ​

Mix local and remote agents inside a Graph:

python
from elsai import Agent
from elsai.agent import AgentConfig, A2AAgent
from elsai.multiagent import GraphBuilder

local_writer   = Agent(system_prompt="Write polished reports.", config=AgentConfig(name="writer"))
remote_analyst = A2AAgent(endpoint="http://analyst-service:9000")

builder = GraphBuilder()
builder.add_node(remote_analyst, "analyst")
builder.add_node(local_writer, "writer")
builder.add_edge("analyst", "writer")

graph = builder.build()
result = graph("Analyse and write a report on Q3 revenue trends")
print(result)

Session isolation ​

Each A2A context_id maps to a dedicated Agent instance with its own SessionManager, so concurrent remote callers do not share conversation state.

By default, sessions are stored on disk under $ELSAI_A2A_SESSION_DIR or {tempdir}/elsai-a2a-sessions. Configure isolation with:

python
server = A2AServer(
    agent=agent,
    max_active_contexts=100,      # LRU eviction when the pool is full
    strict_tool_policy=False,     # set True to reject stateful tools (shell, browser, …)
)

Stateful built-in tools (shell, browser, python_repl, and others) log a warning when used without strict_tool_policy=True. Custom tools can declare per-context scope via A2AToolSessionScope.PER_CONTEXT on the @tool decorator.

Async and streaming ​

python
import asyncio

async def main():
    remote = A2AAgent(endpoint="http://localhost:9000")

    # Async invocation
    result = await remote.invoke_async("Calculate √144")
    print(result)

    # Streaming — yields normalized A2A events, then a final AgentResult
    async for event in remote.stream_async("Explain quantum entanglement"):
        if event.get("type") == "a2a_stream":
            print(event["event"])          # Message or (Task, update) tuple
        elif "result" in event:
            print(event["result"].message) # final AgentResult

asyncio.run(main())

stream_async abstracts the underlying v1 StreamResponse chunks into a stable Elsai event shape. If you inspect raw events, task states use v1 enum names such as TASK_STATE_COMPLETED rather than completed. Streaming completes when a terminal status update arrives or an artifact update sets last_chunk=true.

Agent card ​

Every A2A server advertises capabilities at /.well-known/agent-card.json:

json
{
  "name": "calculator",
  "description": "A precise calculator agent",
  "protocolVersion": "1.0",
  "supportedInterfaces": [
    {
      "url": "http://localhost:9000/",
      "protocolBinding": "JSONRPC"
    }
  ],
  "url": "http://localhost:9000/",
  "capabilities": { "streaming": true },
  "skills": [
    {
      "id": "calculate",
      "name": "calculate",
      "description": "Evaluate a mathematical expression."
    }
  ]
}

Per-context session isolation ​

A2A clients send a context_id with each task. Elsai maps that ID to an isolated agent instance so conversation history and tool state never leak across concurrent conversations on the same server.

  • A2ASessionStore — Maps each context_id to a dedicated Agent with its own SessionManager whose session_id equals that context_id.
  • Session backing — By default, FileSessionManager stores conversation state under $ELSAI_A2A_SESSION_DIR/<context_id>/ (default base: {tempdir}/elsai-a2a-sessions).
  • In-memory pool — Active context agents are cached with LRU eviction (max_active_contexts, default 100).
  • Per-context locks — Concurrent requests for the same context_id are serialized; different contexts can run in parallel.

See Sessions — A2A sessions for how this relates to file, S3, and custom session backends.

Template-clone mode (default) ​

Pass a template Agent. A2AServer clones a fresh agent per context_id, attaching a SessionManager from session_manager_factory:

python
from elsai import Agent, tool
from elsai.agent import AgentConfig
from elsai.multiagent.a2a import A2AServer
from elsai.session import FileSessionManager

@tool
def calculate(expression: str) -> str:
    """Evaluate a mathematical expression."""
    return str(eval(expression))

agent = Agent(
    system_prompt="You are a precise calculator agent.",
    tools=[calculate],
    config=AgentConfig(name="calculator"),
)

server = A2AServer(
    agent=agent,
    host="0.0.0.0",
    port=9000,
    session_manager_factory=lambda ctx_id: FileSessionManager(
        session_id=ctx_id,
        storage_dir="/data/a2a-sessions",
    ),
    max_active_contexts=50,
    strict_tool_policy=True,
)
server.serve()

Agent factory mode ​

Pass a callable instead of a template agent when each context needs a fully custom configuration — for example, per-tenant models, plugins, or session backends:

python
from elsai import Agent
from elsai.agent import AgentConfig
from elsai.multiagent.a2a import A2AServer, A2AContextContext
from elsai.session import S3SessionManager

def build_agent(ctx: A2AContextContext) -> Agent:
    """Build a fully configured agent for one A2A context."""
    session_manager = S3SessionManager(
        session_id=ctx.context_id,
        bucket="my-a2a-sessions",
        prefix="contexts/",
        region_name="us-east-1",
    )
    return Agent(
        system_prompt="You are a helpful assistant.",
        config=AgentConfig(
            agent_id="assistant",
            session_manager=session_manager,
        ),
    )

server = A2AServer(
    agent=build_agent,
    name="multi-tenant-assistant",
    strict_tool_policy=True,
)
server.serve()

Requirements for factory mode:

  • The factory receives an A2AContextContext with context_id, optional task_id, and optional metadata.
  • The returned agent must have a session_manager attached.
  • session_manager.session_id must equal ctx.context_id.
  • When strict_tool_policy=True, the store runs probe_strict_tool_policy() at startup — it builds a probe agent via the factory and validates warnlisted tools without caching it.

Production: S3-backed sessions ​

For multi-tenant A2A servers on AWS, store each context's conversation history in S3:

python
import os
from elsai import Agent, tool
from elsai.agent import AgentConfig
from elsai.multiagent.a2a import A2AServer
from elsai.session import S3SessionManager
from elsai_tools.python_repl import python_repl

@tool
def summarize(text: str) -> str:
    """Summarize the given text."""
    return text[:200] + "…" if len(text) > 200 else text

template = Agent(
    system_prompt="You are a coding assistant with a persistent REPL.",
    tools=[python_repl, summarize],
    config=AgentConfig(name="coding-assistant"),
)

def session_for_context(context_id: str) -> S3SessionManager:
    return S3SessionManager(
        session_id=context_id,
        bucket=os.environ["A2A_SESSIONS_BUCKET"],
        prefix=os.getenv("A2A_SESSIONS_PREFIX", "a2a/contexts/"),
        region_name=os.getenv("AWS_REGION", "us-east-1"),
    )

server = A2AServer(
    agent=template,
    host="0.0.0.0",
    port=9000,
    session_manager_factory=session_for_context,
    max_active_contexts=200,
    strict_tool_policy=True,
    http_url=os.environ.get("A2A_PUBLIC_URL"),  # advertised in the agent card
)
server.serve()

Per-context tool state (REPL pickles, shell working dirs, browser profiles, etc.) still lives under $ELSAI_A2A_SESSION_DIR/<context_id>/ on the server filesystem unless your tools use external stores. Set ELSAI_A2A_SESSION_DIR to a persistent volume (EBS, EFS) in production.

Required IAM permissions for the session bucket match Sessions — S3 storage structure.

Storage layout ​

$ELSAI_A2A_SESSION_DIR/
└── <context_id>/
    ├── session.json              ← conversation session metadata
    ├── agents/
    │   └── agent_<agent_id>/
    │       ├── agent.json
    │       └── messages/
    │           └── message_<id>.json
    ├── repl/                     ← python_repl (when used)
    │   └── repl_state.pkl
    ├── shell/                    ← shell default work_dir (when used)
    ├── use_computer/
    │   └── screenshots/          ← use_computer (when used)
    ├── browser/
    │   └── user_data/            ← browser profile (when used)
    ├── workflows/                ← workflow tool definitions (when used)
    └── mem0/
        └── faiss/                ← mem0_memory vector store (when used)

Set ELSAI_A2A_SESSION_DIR to control where A2A session files and per-context tool state are written on the server host.

LRU pool and concurrency ​

A2ASessionStore keeps recently used context agents in memory for fast reuse. Understanding eviction and locking helps when sizing production servers.

MechanismBehavior
max_active_contextsLRU limit on in-memory agents (default 100; None = unlimited). When exceeded, the least recently used idle context is evicted.
Eviction vs persistenceEviction calls agent.cleanup() and removes the in-memory agent only. Persisted session data (and tool state on disk) is retained — the next request for that context_id recreates the agent from storage.
hold_context()While a request holds the per-context lock, that context_id is marked in-flight and cannot be evicted by LRU.
close(context_id)Removes an agent from the in-memory pool. Skipped if the context is currently in-flight. Does not delete repository or tool-state files.
close_all()Evicts all non-in-flight agents from memory. Persisted data survives; agents are restored on the next request.
Per-context locksTwo concurrent requests for the same context_id are serialized. Different context_id values can execute in parallel.
A2ASessionStoreMetricsExposes cache_hits, cache_misses, and evictions for observability and testing.

When all cached contexts are in-flight and the pool is at capacity, LRU eviction pauses until a context finishes — the store logs a debug message rather than evicting active work.

Stateful tools and session policy ​

Some tools keep mutable state between calls. On an A2A server, that state must not leak across context_id values.

Elsai classifies tools into three A2A session scopes:

ScopeBehaviorWhen to use
SHAREDSame tool instance or reload path is safe across contextsStateless tools (default for @tool)
CONTEXT_KEYEDOne shared tool; mutable state keyed by invocation_state["a2a_context_id"]Custom tools backed by external stores you partition yourself
PER_CONTEXTEach context_id gets a dedicated tool instance via clone_for_a2a_context()Prebuilt tools with in-process state, or custom tools with per-context resources

Startup warnlist ​

When A2AServer starts, it inspects the template agent (or factory probe agent) for stateful tools on this warnlist:

python_repl, shell, use_computer, code_interpreter, browser, mcp_client, graph, workflow, mem0_memory

  • Default (strict_tool_policy=False) — Logs a startup warning listing detected stateful tools.
  • strict_tool_policy=True — Fails fast at startup if any warnlisted tool is not PER_CONTEXT-safe (missing scope declaration or clone_for_a2a_context()).

Stateful tools on A2A (operator reference) ​

Prebuilt tools in elsai-agents-tools that declare PER_CONTEXT and clone per context_id:

ToolA2A scopePer-context state locationNotes
python_replPER_CONTEXT$ELSAI_A2A_SESSION_DIR/<context_id>/repl/Isolated REPL namespace + pickle
shellPER_CONTEXT$ELSAI_A2A_SESSION_DIR/<context_id>/shell/Default work_dir scoped per context
use_computerPER_CONTEXT$ELSAI_A2A_SESSION_DIR/<context_id>/use_computer/screenshots/Screenshot storage isolated
code_interpreterPER_CONTEXTRemote sandbox sessions namespaced by context_idVia AgentCoreCodeInterpreter
browserPER_CONTEXT$ELSAI_A2A_SESSION_DIR/<context_id>/browser/user_data/Use LocalChromiumBrowser or AgentCoreBrowser — the abstract Browser base does not implement clone_for_a2a_context()
mcp_clientPER_CONTEXTIn-process MCP connection pool per contextEach context gets its own tool instance
graphPER_CONTEXTIn-memory graph manager; IDs prefixed with context_idGraph IDs auto-scoped
workflowPER_CONTEXT$ELSAI_A2A_SESSION_DIR/<context_id>/workflows/Workflow files and IDs scoped
mem0_memoryPER_CONTEXT$ELSAI_A2A_SESSION_DIR/<context_id>/mem0/faiss/Local FAISS store per context

See per-tool API pages for parameter details: Code interpretation, Shell and system, Web and network, Agents and workflows, RAG and memory.

Stateful prebuilt tools — examples ​

You do not call clone_for_a2a_context() yourself for prebuilt tools. Register them on a template Agent, expose the agent via A2AServer, and the session store clones each warnlisted tool per incoming context_id.

Set ELSAI_A2A_SESSION_DIR for on-disk tool state. Use strict_tool_policy=True in production so startup fails if a stateful tool is not PER_CONTEXT-safe.

Shared server pattern ​

python
import os
from elsai import Agent
from elsai.multiagent.a2a import A2AServer
from elsai.session import FileSessionManager

def build_server(tools: list) -> A2AServer:
    agent = Agent(
        system_prompt="You are a helpful assistant.",
        tools=tools,
    )
    return A2AServer(
        agent=agent,
        host="0.0.0.0",
        port=9000,
        session_manager_factory=lambda ctx_id: FileSessionManager(
            session_id=ctx_id,
            storage_dir=os.path.join(
                os.environ.get("ELSAI_A2A_SESSION_DIR", "/data/a2a-sessions"),
                ctx_id,
            ),
        ),
        max_active_contexts=100,
        strict_tool_policy=True,
    )

Combined multi-tool example ​

A coding assistant with REPL, shell, and browser — all isolated per context_id:

python
from elsai_tools.python_repl import python_repl
from elsai_tools.shell import shell
from elsai_tools.browser import LocalChromiumBrowser

browser = LocalChromiumBrowser()
server = build_server(tools=[python_repl, shell, browser.browser])
server.serve()

Each concurrent A2A client (context_id) gets its own REPL namespace, shell working directory, and browser profile. strict_tool_policy=True validates all three at startup.

python_repl ​

python
from elsai_tools.python_repl import python_repl

server = build_server(tools=[python_repl])
server.serve()
  • PER_CONTEXT is built in — no extra registration step.
  • State: $ELSAI_A2A_SESSION_DIR/<context_id>/repl/repl_state.pkl
  • Standalone (non-A2A) agents use PYTHON_REPL_PERSISTENCE_DIR instead — do not confuse the two paths.

shell ​

python
from elsai_tools.shell import shell

server = build_server(tools=[shell])
server.serve()
  • When work_dir is omitted, defaults to $ELSAI_A2A_SESSION_DIR/<context_id>/shell/.
  • Each context_id gets its own ShellTool instance — command history and cwd do not leak across conversations.

use_computer ​

python
from elsai_tools.use_computer import use_computer

server = build_server(tools=[use_computer])  # requires elsai-agents-tools[use-computer]
server.serve()
  • Screenshots: $ELSAI_A2A_SESSION_DIR/<context_id>/use_computer/screenshots/
  • Requires a GUI desktop environment on the host running the A2A server.

code_interpreter ​

Register the tool from a class-based provider — not a bare function:

python
from elsai_tools.code_interpreter import AgentCoreCodeInterpreter

interpreter = AgentCoreCodeInterpreter(region="us-west-2")
server = build_server(tools=[interpreter.code_interpreter])
server.serve()
  • Requires elsai-agents-tools[agent-core-code-interpreter] and AWS credentials.
  • Remote sandbox session names are prefixed with context_id automatically.

browser ​

Use a concrete browser implementation — the abstract Browser base does not implement clone_for_a2a_context():

python
from elsai_tools.browser import LocalChromiumBrowser
# or: from elsai_tools.browser import AgentCoreBrowser

browser = LocalChromiumBrowser()
server = build_server(tools=[browser.browser])
server.serve()
  • Profile path: $ELSAI_A2A_SESSION_DIR/<context_id>/browser/user_data/
  • Session names passed to the tool are prefixed with context_id:.
  • Optional: ELSAI_BROWSER_HEADLESS, ELSAI_BROWSER_WIDTH, ELSAI_BROWSER_HEIGHT.

mcp_client ​

python
from elsai_tools.mcp_client import mcp_client

server = build_server(tools=[mcp_client])
server.serve()
  • Each context_id gets its own in-process MCP connection pool.
  • Connection IDs from one conversation are not visible to another.
  • Optional: ELSAI_MCP_TIMEOUT (default 30.0 seconds).

graph ​

python
from elsai_tools.graph import graph

server = build_server(tools=[graph])
server.serve()
  • Graph IDs passed by the model are auto-prefixed: a call with graph_id="research" is stored as "<context_id>:research".
  • In-memory graph state is per context — two A2A clients cannot see each other's graphs even with the same logical ID.

workflow ​

python
from elsai_tools.workflow import workflow

server = build_server(tools=[workflow])
server.serve()
  • Workflow files: $ELSAI_A2A_SESSION_DIR/<context_id>/workflows/
  • Workflow IDs are prefixed with context_id the same way as graph.

mem0_memory ​

python
from elsai_tools.mem0_memory import mem0_memory

server = build_server(tools=[mem0_memory])  # requires elsai-agents-tools[mem0-memory]
server.serve()
  • Local FAISS store: $ELSAI_A2A_SESSION_DIR/<context_id>/mem0/faiss/
  • Mem0 backend selection (MEM0_API_KEY, OpenSearch, Neptune, etc.) is unchanged — see RAG and memory (per-tool env vars).

Operator checklist

  1. Install elsai-agents[a2a]==0.3.5 and elsai-agents-tools==0.3.0.
  2. Set ELSAI_A2A_SESSION_DIR to a persistent volume.
  3. Register stateful tools on the template agent (examples above).
  4. Enable strict_tool_policy=True for multi-tenant production.
  5. For S3 conversation history, see Production: S3-backed sessions.

Custom PER_CONTEXT tools ​

python
from elsai.types.tools import A2AToolSessionScope, AgentTool

class MyStatefulTool(AgentTool):
    def __init__(self, context_id: str | None = None) -> None:
        self._context_id = context_id

    def a2a_session_scope(self) -> A2AToolSessionScope:
        return A2AToolSessionScope.PER_CONTEXT

    def clone_for_a2a_context(self, context_id: str) -> "MyStatefulTool":
        return MyStatefulTool(context_id=context_id)

Custom CONTEXT_KEYED tools ​

Use when one tool instance is shared across contexts but mutable state lives in an external store you partition by context:

python
from elsai import tool
from elsai.types.tools import A2AToolSessionScope

# In-memory store keyed by A2A context — replace with Redis, DynamoDB, etc. in production
_notes: dict[str, list[str]] = {}

@tool(a2a_session_scope=A2AToolSessionScope.CONTEXT_KEYED)
def context_notes(action: str, text: str = "", invocation_state: dict | None = None) -> str:
    """Store and retrieve notes scoped to the current A2A conversation."""
    ctx_id = (invocation_state or {}).get("a2a_context_id", "default")
    bucket = _notes.setdefault(ctx_id, [])

    if action == "add":
        bucket.append(text)
        return f"Added note ({len(bucket)} total)."
    if action == "list":
        return "\n".join(bucket) or "(empty)"
    return f"Unknown action: {action}"

The A2A executor injects invocation_state["a2a_context_id"] on each tool call. Your tool reads that key to isolate mutable state without cloning the tool instance.

A2AServer options ​

Constructor arguments name, description, version, and skills configure the agent card. Additional options are grouped below.

Network ​

ParameterDefaultDescription
host127.0.0.1Bind address
port9000Port
http_urlauto-detectedPublic URL advertised in the agent card; path component sets mount path
serve_at_rootFalseWhen http_url has a path, mount routes at / instead of the path prefix
public_url_schemehttp / httpsScheme for auto-generated URLs (http for localhost)

Session isolation ​

ParameterDefaultDescription
session_storeauto-createdCustom A2ASessionStore for per-context agent pooling
session_manager_factoryfile-backedFactory (context_id) -> SessionManager
max_active_contexts100Maximum concurrent context agents before LRU eviction
strict_tool_policyFalseReject stateful tools that are unsafe for multi-tenant A2A

Request handler ​

ParameterDefaultDescription
task_storein-memoryPersistence backend for A2A task state
queue_managerNoneOptional QueueManager for the request handler
push_config_storeNonePush notification config store
push_senderNonePush notification sender
enable_a2a_compliant_streamingTrueStream content via artifact updates (A2A v1 compliant)
enable_v0_3_compatFalseAccept legacy v0.3 JSON-RPC wire format

A2AAgent options ​

ParameterDefaultDescription
endpointrequiredRemote A2A server base URL
namefrom agent cardName for this client agent
descriptionfrom agent cardDescription used when wrapping as a tool
timeout300Request timeout in seconds (when no custom httpx_client)
client_configNonea2a.client.ClientConfig for auth, transport, and streaming settings

Deprecated

a2a_client_factory is deprecated — use client_config instead.

Migrating from A2A v0.3 ​

Who needs this section?

Read this if you previously used A2A on a2a-sdk 0.3.x, call an Elsai server with raw HTTP, or integrate a third-party v0.3 client. New projects can skip it.

Elsai SDK users — upgrade to elsai-agents>=0.3.3 with the a2a extra. Typical A2AServer and A2AAgent code requires no changes.

Legacy wire-format clients — v1.0 servers expect the new protocol by default. Migrate the client to v1, or enable backward compatibility on the server:

python
server = A2AServer(agent=agent, enable_v0_3_compat=True)
Aspectv0.3v1.0
JSON-RPC methodmessage/sendSendMessage
Version headeroptionalA2A-Version: 1.0 required
Role"user""ROLE_USER"
Text parts{"kind":"text","text":"..."}{"text":"..."}
File parts{"kind":"file","file":{"bytes":"<b64>"}}{"raw":"<bytes>","mediaType":"..."}
Task state"completed""TASK_STATE_COMPLETED"
Response shapeflat task in resultresult.task wrapper

See the upstream migration guide for the full specification. Building directly on a2a-sdk 1.x? See Stream normalizer exports.

Environment variables ​

Configuration for A2A servers, session storage, and related prebuilt tools. Production deployments typically set the session / storage and AWS variables together.

Session and on-disk tool state ​

VariableDefaultDescription
ELSAI_A2A_SESSION_DIR{tempdir}/elsai-a2a-sessionsRoot directory for file-backed A2A session data and per-context tool state (repl/, shell/, browser/, etc.)
PYTHON_REPL_PERSISTENCE_DIR./repl_state/ (cwd)Standalone (non-A2A) persistence root for python_repl when no context_id is present

Set ELSAI_A2A_SESSION_DIR to a persistent volume (EBS, EFS) in production. Conversation history stored via S3SessionManager lives in S3; on-disk tool state still uses this directory unless tools use external stores.

These names are used in the S3 production example — set them in your deployment environment or secret store:

VariableDescription
A2A_SESSIONS_BUCKETS3 bucket for per-context_id conversation history
A2A_SESSIONS_PREFIXKey prefix inside the bucket (default in examples: a2a/contexts/)
AWS_REGIONAWS region for S3SessionManager and Bedrock-backed tools
A2A_PUBLIC_URLPublic URL passed to A2AServer(http_url=…) for the agent card (e.g. https://agent.example.com)

Standard AWS credential variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, or an IAM role) are required when using S3 session backing.

Server behaviour (code or env) ​

Most A2A server tuning is passed as A2AServer constructor arguments rather than environment variables:

SettingParameterDefault
Bind addresshost127.0.0.1
Portport9000
In-memory context poolmax_active_contexts100
Stateful tool startup checkstrict_tool_policyFalse
Public endpointhttp_urlauto-detected (or set via A2A_PUBLIC_URL in production)

See A2A API — Environment variables for the full API-level list.

Swarm not supported

A2A is not yet supported inside Swarm patterns. Use Graph or agent-as-tool instead.

Copyright © 2026 elsai foundry.