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.

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.0, 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.0"

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()

Operator checklist

  1. Install elsai-agents[a2a]==0.3.0 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.0 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.