Appearance
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
- Discover — The client fetches the remote agent card from
/.well-known/agent-card.json. - Connect — Requests are sent to the server's JSON-RPC endpoint (
POST /) withA2A-Version: 1.0. - Execute — The remote
A2AServerruns the hosted agent and returns the result. - Integrate — Use
A2AAgentdirectly, as an orchestrator tool (as_tool()), or as a node in aGraph.
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 eachcontext_idto a dedicatedAgentwith its ownSessionManagerwhosesession_idequals thatcontext_id.- Session backing — By default,
FileSessionManagerstores 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, default100). - Per-context locks — Concurrent requests for the same
context_idare 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
A2AContextContextwithcontext_id, optionaltask_id, and optionalmetadata. - The returned agent must have a
session_managerattached. session_manager.session_idmust equalctx.context_id.- When
strict_tool_policy=True, the store runsprobe_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.
| Mechanism | Behavior |
|---|---|
max_active_contexts | LRU limit on in-memory agents (default 100; None = unlimited). When exceeded, the least recently used idle context is evicted. |
| Eviction vs persistence | Eviction 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 locks | Two concurrent requests for the same context_id are serialized. Different context_id values can execute in parallel. |
A2ASessionStoreMetrics | Exposes 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:
| Scope | Behavior | When to use |
|---|---|---|
SHARED | Same tool instance or reload path is safe across contexts | Stateless tools (default for @tool) |
CONTEXT_KEYED | One shared tool; mutable state keyed by invocation_state["a2a_context_id"] | Custom tools backed by external stores you partition yourself |
PER_CONTEXT | Each 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 notPER_CONTEXT-safe (missing scope declaration orclone_for_a2a_context()).
Stateful tools on A2A (operator reference)
Prebuilt tools in elsai-agents-tools that declare PER_CONTEXT and clone per context_id:
| Tool | A2A scope | Per-context state location | Notes |
|---|---|---|---|
python_repl | PER_CONTEXT | $ELSAI_A2A_SESSION_DIR/<context_id>/repl/ | Isolated REPL namespace + pickle |
shell | PER_CONTEXT | $ELSAI_A2A_SESSION_DIR/<context_id>/shell/ | Default work_dir scoped per context |
use_computer | PER_CONTEXT | $ELSAI_A2A_SESSION_DIR/<context_id>/use_computer/screenshots/ | Screenshot storage isolated |
code_interpreter | PER_CONTEXT | Remote sandbox sessions namespaced by context_id | Via AgentCoreCodeInterpreter |
browser | PER_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_client | PER_CONTEXT | In-process MCP connection pool per context | Each context gets its own tool instance |
graph | PER_CONTEXT | In-memory graph manager; IDs prefixed with context_id | Graph IDs auto-scoped |
workflow | PER_CONTEXT | $ELSAI_A2A_SESSION_DIR/<context_id>/workflows/ | Workflow files and IDs scoped |
mem0_memory | PER_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_DIRinstead — do not confuse the two paths.
shell
python
from elsai_tools.shell import shell
server = build_server(tools=[shell])
server.serve()- When
work_diris omitted, defaults to$ELSAI_A2A_SESSION_DIR/<context_id>/shell/. - Each
context_idgets its ownShellToolinstance — 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_idautomatically.
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_idgets its own in-process MCP connection pool. - Connection IDs from one conversation are not visible to another.
- Optional:
ELSAI_MCP_TIMEOUT(default30.0seconds).
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_idthe same way asgraph.
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 Prebuilt Tools — Environment variables.
Operator checklist
- Install
elsai-agents[a2a]==0.3.0andelsai-agents-tools==0.3.0. - Set
ELSAI_A2A_SESSION_DIRto a persistent volume. - Register stateful tools on the template agent (examples above).
- Enable
strict_tool_policy=Truefor multi-tenant production. - 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
| Parameter | Default | Description |
|---|---|---|
host | 127.0.0.1 | Bind address |
port | 9000 | Port |
http_url | auto-detected | Public URL advertised in the agent card; path component sets mount path |
serve_at_root | False | When http_url has a path, mount routes at / instead of the path prefix |
public_url_scheme | http / https | Scheme for auto-generated URLs (http for localhost) |
Session isolation
| Parameter | Default | Description |
|---|---|---|
session_store | auto-created | Custom A2ASessionStore for per-context agent pooling |
session_manager_factory | file-backed | Factory (context_id) -> SessionManager |
max_active_contexts | 100 | Maximum concurrent context agents before LRU eviction |
strict_tool_policy | False | Reject stateful tools that are unsafe for multi-tenant A2A |
Request handler
| Parameter | Default | Description |
|---|---|---|
task_store | in-memory | Persistence backend for A2A task state |
queue_manager | None | Optional QueueManager for the request handler |
push_config_store | None | Push notification config store |
push_sender | None | Push notification sender |
enable_a2a_compliant_streaming | True | Stream content via artifact updates (A2A v1 compliant) |
enable_v0_3_compat | False | Accept legacy v0.3 JSON-RPC wire format |
A2AAgent options
| Parameter | Default | Description |
|---|---|---|
endpoint | required | Remote A2A server base URL |
name | from agent card | Name for this client agent |
description | from agent card | Description used when wrapping as a tool |
timeout | 300 | Request timeout in seconds (when no custom httpx_client) |
client_config | None | a2a.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)| Aspect | v0.3 | v1.0 |
|---|---|---|
| JSON-RPC method | message/send | SendMessage |
| Version header | optional | A2A-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 shape | flat task in result | result.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
| Variable | Default | Description |
|---|---|---|
ELSAI_A2A_SESSION_DIR | {tempdir}/elsai-a2a-sessions | Root 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.
Production S3 sessions (recommended convention)
These names are used in the S3 production example — set them in your deployment environment or secret store:
| Variable | Description |
|---|---|
A2A_SESSIONS_BUCKET | S3 bucket for per-context_id conversation history |
A2A_SESSIONS_PREFIX | Key prefix inside the bucket (default in examples: a2a/contexts/) |
AWS_REGION | AWS region for S3SessionManager and Bedrock-backed tools |
A2A_PUBLIC_URL | Public 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:
| Setting | Parameter | Default |
|---|---|---|
| Bind address | host | 127.0.0.1 |
| Port | port | 9000 |
| In-memory context pool | max_active_contexts | 100 |
| Stateful tool startup check | strict_tool_policy | False |
| Public endpoint | http_url | auto-detected (or set via A2A_PUBLIC_URL in production) |
See A2A API — Environment variables for the full API-level list.
Related
- A2A API reference —
A2AServer,A2ASessionStore, stream normalizer, and handler options - Sessions — A2A sessions —
context_id, file and S3 backing - Agents release notes — v0.3.0 A2A upgrade summary
Swarm not supported
A2A is not yet supported inside Swarm patterns. Use Graph or agent-as-tool instead.