Solace Agent Mesh (SAM) is an event-driven framework for building distributed ecosystems of collaborative AI agents. Agents do the thinking — they have skills, tools, and LLM access. But agents don’t talk to users directly. That’s where entrypoints come in.
An entrypoint connects an external platform (a web UI, a Slack workspace, a terminal, a REST API) to the agent ecosystem. It translates platform-specific input into the A2A protocol, delivers it to agents via the Solace Event Broker, and translates agent responses back to the platform. Entrypoints make zero LLM calls. All intelligence lives on the agent side.
This post covers how entrypoints work architecturally, what the framework provides, and what you need to build when creating a custom one.
Where Entrypoints Fit
Every entrypoint is a peer. They all connect to the same Solace Event Broker and talk to the same agents. A user chatting in the WebUI and a user interacting through a custom entrypoint are indistinguishable from the agent’s perspective — both arrive as A2A tasks.
The A2A Message Flow
When a user sends a message through any entrypoint, here’s what happens:
┌──────────┐ ┌──────────────┐ ┌─────────────┐ ┌──────────────┐
│ External │────▶│ Entrypoint │────▶│ Solace │────▶│ Orchestrator │
│ Platform │ │ Adapter │ │ Broker │ │ Agent │
└──────────┘ └──────────────┘ └─────────────┘ └──────┬───────┘
│
┌──────────┐ ┌──────────────┐ ┌─────────────┐ │
│ External │◀────│ Entrypoint │◀────│ Solace │◀───────────┘
│ Platform │ │ Adapter │ │ Broker │ Streamed response
└──────────┘ └──────────────┘ └─────────────┘
- User input arrives on the external platform (HTTP request, Slack message, etc.)
- The entrypoint’s
prepare_task()converts it into aSamTaskwith asession_id - SAM framework publishes a JSON-RPC 2.0 request to the agent’s request topic
- The orchestrator processes the request, potentially delegating to specialist agents
- Streamed status updates arrive on the entrypoint’s status topic
- The final response arrives on the entrypoint’s response topic
- The entrypoint translates the response back to the external platform
The entrypoint only handles steps 1, 2, and 7. The framework handles everything in between.
Topic Structure
All communication flows through hierarchical Solace topics:
| Direction | Topic Pattern |
|---|---|
| Entrypoint → Agent | {namespace}/a2a/v1/agent/request/{agent_name} |
| Agent → Entrypoint (status) | {namespace}/a2a/v1/gateway/status/{gateway_id}/> |
| Agent → Entrypoint (response) | {namespace}/a2a/v1/gateway/response/{gateway_id}/> |
| Agent discovery | {namespace}/a2a/v1/discovery/> |
Entrypoints don’t manage these topics directly. The framework subscribes, publishes, and routes automatically based on your config.yaml.
What the Framework Handles vs What You Build
This is the key insight: the framework handles the hard distributed systems problems. You only build the platform-specific translation layer.
| Responsibility | Owner |
|---|---|
| Broker connection, reconnection, and health | Framework |
| A2A protocol (JSON-RPC 2.0 over Solace) | Framework |
| Topic routing and subscriptions | Framework |
| Agent discovery and registry | Framework |
| Artifact storage and retrieval | Framework |
| Embed resolution in responses | Framework |
| Conversation history per session | Framework |
Translate platform input → SamTask |
Your adapter |
| Translate agent response → platform output | Your adapter |
| User authentication / identity | Your adapter |
| Session ID management | Your adapter |
| Platform-specific UX (rendering, streaming) | Your adapter |
The Adapter Pattern
Every entrypoint implements the same adapter interface. Here are the key methods:
Required
async def prepare_task(self, external_input, endpoint_context) -> SamTask:
"""Convert platform-specific input into a SamTask.
This is the only truly required method. It takes whatever your platform
provides and produces a SamTask with:
- parts: List of SamTextPart, SamFilePart, SamDataPart
- session_id: Scopes conversation history and artifacts
- target_agent: Which agent to route to (usually OrchestratorAgent)
- is_streaming: Whether to receive streamed responses
"""
Lifecycle
async def init(self, context: GatewayContext) -> None:
"""Called once on startup. Start your listener (HTTP server, WebSocket, etc).
The GatewayContext gives you:
- context.handle_external_input(event) — submit a task to SAM
- context.list_agents() — discover available agents
- context.list_artifacts(response_context) — list session artifacts
- context.load_artifact_content(response_context, filename) — fetch artifact
- context.submit_feedback(feedback) — submit user ratings
- context.gateway_id — your unique entrypoint identifier
- context.namespace — the SAM namespace
"""
async def cleanup(self) -> None:
"""Called on shutdown. Stop your listener, release resources."""
Response Handlers (all optional)
async def handle_text_chunk(self, text: str, context: ResponseContext) -> None:
"""A streaming text chunk arrived. Accumulate or forward in real-time."""
async def handle_status_update(self, status_text: str, context: ResponseContext) -> None:
"""Agent is reporting progress (e.g., 'Searching database...')."""
async def handle_file(self, file_part: SamFilePart, context: ResponseContext) -> None:
"""Agent created a file artifact."""
async def handle_task_complete(self, context: ResponseContext) -> None:
"""Task is done. Render/send the final response."""
async def handle_error(self, error: SamError, context: ResponseContext) -> None:
"""Something went wrong. Show the error to the user."""
Authentication
async def extract_auth_claims(self, external_input, endpoint_context) -> AuthClaims:
"""Identify the user. Returns AuthClaims(id=..., source=...)."""
Sessions: The session_id Concept
Every task includes a session_id. This single string scopes two things on the SAM side:
- Conversation history — the agent sees all prior messages in that session
- Artifacts — files created by the agent are stored under that session
When a user sends a message with session_id="abc123", the agent automatically receives the full conversation history for abc123. When the user switches to session_id="xyz789", the agent picks up a completely different conversation — no code needed on the entrypoint side to manage history replay or context loading.
How you map your platform’s concept of “conversation” to a session_id is entirely up to you. It could be a browser session cookie, a chat thread, a user-chosen label mapped to an ID, or a deterministic string that survives restarts. The entrypoint decides the mapping. SAM handles the rest.
Session Persistence
SAM maintains conversation history server-side. If your entrypoint restarts, you just pass the same session_id and the agent resumes with full context. There’s no history replay, no context window management, no summarization needed on the entrypoint side.
However, SAM currently has no entrypoint-facing API to delete or expire sessions. Sessions persist indefinitely on the server side. Keep this in mind when designing your session strategy.
Artifacts: How Files Flow Through Entrypoints
Agents can create file artifacts (CSVs, images, code files, etc.) during task execution. These are stored in the artifact service, scoped by session_id.
Inbound (user uploads a file):
- Your adapter receives a file from the platform
- Create a
SamFilePart(name=filename, content_bytes=data, mime_type=type) - Include it in the
SamTask.partslist - SAM delivers it to the agent
Outbound (agent creates a file):
- Your
handle_file()callback receives aSamFilePart - It may contain
content_bytes(inline) or auri(artifact reference) - Display or store it as appropriate for your platform
Listing and downloading artifacts:
# List all artifacts in the current session
artifacts = await self.context.list_artifacts(response_context)
# Download a specific artifact
content = await self.context.load_artifact_content(response_context, "report.csv")
Artifacts are scoped by session. Switch sessions, and you see a different set of artifacts.
Configuration: Wiring an Entrypoint
Every entrypoint is configured in a config.yaml file:
apps:
- name: my_entrypoint_app
app_module: solace_agent_mesh.gateway.generic.app
broker:
broker_url: ${SOLACE_BROKER_URL, ws://localhost:8080}
broker_username: ${SOLACE_BROKER_USERNAME, default}
broker_password: ${SOLACE_BROKER_PASSWORD, default}
broker_vpn: ${SOLACE_BROKER_VPN, default}
app_config:
namespace: "${NAMESPACE}"
# Point to your adapter class
gateway_adapter: my_package.adapter.MyEntrypointAdapter
# Your adapter-specific configuration
adapter_config:
host: "0.0.0.0"
port: 8080
# Entrypoint ID (must be unique per instance)
gateway_id: ${MY_ENTRYPOINT_ID, my-ep-01}
# Artifact storage
artifact_service:
type: "filesystem"
base_path: "/tmp/samv2"
artifact_scope: namespace
# Agent to route to by default
default_agent_name: "OrchestratorAgent"
# Embed resolution (artifact references in text)
enable_embed_resolution: true
Key configuration points:
gateway_adapter— the Python import path to your adapter classadapter_config— passed to your adapter ascontext.adapter_config(validated against yourConfigModel)gateway_id— must be unique across all entrypoint instancesartifact_service— shared artifact backend (filesystem, S3, etc.)
Packaging as a Plugin
SAM discovers entrypoints as Python packages. Your pyproject.toml needs:
[tool.my_entrypoint_adapter.metadata]
type = "gateway"
[project]
name = "my_entrypoint_adapter"
version = "0.1.0"
dependencies = []
[tool.hatch.build.targets.wheel]
packages = ["src/my_entrypoint_adapter"]
Install with pip install -e ., then run with sam run config.yaml.
Example: A Minimal Entrypoint
Here’s a stripped-down entrypoint that reads from stdin and prints to stdout — the simplest possible implementation:
import asyncio
import sys
from solace_agent_mesh.gateway.adapter.base import GatewayAdapter
from solace_agent_mesh.gateway.adapter.types import (
AuthClaims, GatewayContext, ResponseContext,
SamError, SamTask, SamTextPart,
)
class MinimalAdapter(GatewayAdapter):
async def init(self, context: GatewayContext) -> None:
self.context = context
self._response_event = asyncio.Event()
self._response_text = ""
asyncio.create_task(self._loop())
async def cleanup(self) -> None:
pass
async def extract_auth_claims(self, external_input, endpoint_context=None):
return AuthClaims(id="user", source="minimal")
async def prepare_task(self, external_input, endpoint_context=None) -> SamTask:
return SamTask(
parts=[SamTextPart(text=external_input["text"])],
session_id=external_input.get("session_id", "default"),
target_agent="OrchestratorAgent",
is_streaming=True,
)
async def handle_text_chunk(self, text, context: ResponseContext):
self._response_text += text
async def handle_task_complete(self, context: ResponseContext):
print(self._response_text)
self._response_text = ""
self._response_event.set()
async def handle_error(self, error: SamError, context: ResponseContext):
print(f"Error: {error.message}", file=sys.stderr)
self._response_event.set()
async def _loop(self):
loop = asyncio.get_running_loop()
while True:
line = await loop.run_in_executor(None, input)
self._response_event.clear()
await self.context.handle_external_input({
"text": line,
"session_id": "default",
})
await self._response_event.wait()
That’s ~45 lines. The framework handles broker connections, A2A protocol, agent discovery, topic routing, artifact storage, and session history. Your adapter just translates between your platform and SamTask.
Existing Entrypoints
SAM ships with and supports several entrypoints:
WebUI (HTTP/SSE)
The built-in entrypoint that powers the SAM web interface. Runs an embedded FastAPI server with:
- Server-Sent Events for real-time streaming of agent responses
- Cookie-based sessions via Starlette SessionMiddleware
- File upload/download through REST endpoints
- Agent discovery endpoint for the UI to list available agents
- Artifact management with full CRUD operations
- Visualization flow for tracing A2A messages in real-time
Slack, MCP, REST, Event Mesh
Documented integration patterns for connecting SAM to additional platforms. Each follows the same adapter pattern — implement prepare_task() and the response handlers, and the framework handles the rest.
Summary
Entrypoints are SAM’s connection layer between external platforms and the agent ecosystem. They follow a simple pattern:
- Inbound: Translate platform input →
SamTaskviaprepare_task() - Outbound: Translate agent responses → platform output via
handle_*()callbacks - Sessions: A
session_idstring scopes conversation history and artifacts automatically - Everything else: The framework handles broker connections, A2A protocol, topic routing, agent discovery, artifact storage, and embed resolution
Building a custom entrypoint means implementing a handful of async methods. The framework does the heavy lifting.
For more information about Solace Agent Mesh, visit the official Agent Mesh GitHub repository
