Public Surface
Select agents and teams for bounded public execution while keeping runtime administration private.
PublicSurface serves an explicit selection of agents and teams to anonymous product clients. It limits accepted routes and inputs, checks shared PostgreSQL request quotas, and bounds execution. Selected workflows require verified credentials, which makes the same runtime useful for public chat and protected indexing jobs.
Select the public components
Set DATABASE_URL to a PostgreSQL database and OPENAI_API_KEY for the model before running this example:
from os import environ
from agno.agent import Agent
from agno.db.postgres import PostgresDb
from agno.os import AgentOS
from agno.os.public import PublicSurface, RateLimit
db = PostgresDb(db_url=environ["DATABASE_URL"])
assistant = Agent(id="assistant", name="Assistant", model="openai:gpt-5.5")
agent_os = AgentOS(
id="product-assistant",
db=db,
agents=[assistant],
public=PublicSurface(
agents=[assistant],
limits={"run": RateLimit(client_per_minute=10, global_per_minute=50)},
),
cors_allowed_origins=["https://your-product.example"],
)
app = agent_os.get_app()
if __name__ == "__main__":
agent_os.serve(app="public_agent:app")Use the same registered object in AgentOS.agents and PublicSurface.agents. The equivalent teams and workflows lists also require registered objects of the matching kind. Remote components and factories are not supported selections. Selecting a team does not expose separate public run routes for its members.
The runtime requires a synchronous PostgresDb and a stable explicit AgentOS.id or PublicSurface.namespace. All replicas using a quota namespace must share the database. AgentOS prepares the limiter during startup; /readyz checks that its database table is accessible.
Follow a public request
- The middleware matches the route against the selected surface. Administrative routes such as
/config,/info, sessions, traces, and knowledge management return 404. - It resolves the client identity and consumes the applicable PostgreSQL quota before reading the request body. Exhausted quotas return 429 with
Retry-After. - It enforces body size, accepted form fields, upload policy, and local execution capacity.
- The native AgentOS endpoint runs the selected component. The middleware bounds its duration and response size and sanitizes top-level failures.
Successful runs retain the native output, including selected-team member tool results and recovered failure details. Choose tools and component output with this public visibility in mind.
GET /agents and GET /teams return a small roster containing only the selected components' IDs, names, and descriptions. Health endpoints and configured interface routes also remain available. Interfaces keep their own authentication and request contracts.
Call the public run endpoint
curl http://localhost:7777/agents/assistant/runs \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "message=What can you help me with?" \
-d "stream=false"Public run forms accept message, stream, session_id, and background. Agent and team calls must keep background=false. A supplied session_id must be a UUID; use the returned session ID for later turns. Cancellation accepts the session ID on the component's run-cancel endpoint.
Public callers cannot override dependencies, session state, metadata, user IDs, output schemas, or query parameters. Those belong to the broader runtime API. Files are disabled unless you supply FileUploadLimits with explicit lowercase extension/MIME pairs.
Configure identity and limits
By default, identity comes from the connection's client IP, with IPv6 addresses grouped by /64; missing identity shares an unknown bucket. Behind a proxy, supply client_id(request) using the deployment's verified identity contract. It may be synchronous or asynchronous. Do not treat an arbitrary client-supplied forwarding header as a verified address.
CORS config controls which browsers may read responses; a product can additionally reject disallowed origins in its identity callback. The Docs Agent walkthrough shows where that deployment policy lives.
| Setting | Default | Scope |
|---|---|---|
max_body_bytes | 12 MiB | Agent/team request body |
max_run_seconds | 240 | Public run request duration |
max_output_bytes | 1 MiB | Public response output |
max_active_runs | 8 | Active run requests in each middleware process |
uploads | None | Files disabled unless explicitly configured |
limits | Built-in quotas | Shared per-client and global minute/day counters |
Quota buckets are run, cancel, mcp, and feedback. A limits dictionary replaces only the named buckets. RateLimit takes client_per_minute, global_per_minute, and optional client_per_day and global_per_day; omitting daily limits in an override removes them for that bucket. A custom feedback tool must explicitly consume its feedback quota.
Shared rate counters do not make active-run capacity global. Each replica has its own execution capacity, and cross-replica cancellation needs the runtime's separate coordination configuration.
Serve a focused MCP API
To include MCP, set PublicSurface(mcp=True, ...) and configure explicit tools:
from agno.os import MCPConfig
def service_description() -> str:
"""Describe this public service."""
return "An assistant for product questions."
mcp = MCPConfig(
tools=[service_description],
default_tools=False,
lifecycle_tools=False,
stateless=True,
instructions="Use service_description to learn what this service provides.",
allowed_hosts=["your-product.example"],
)Pass this mcp configuration to AgentOS together with the public surface. Public MCP requires disabled default and lifecycle tools and stateless=True. Authentication, when configured on MCP, still applies. See MCP discovery and transport for Server Cards, host validation, and client connections.
Keep synchronization protected
Register a workflow, select it in PublicSurface.workflows, and configure an internal_service_token for trusted deployment calls. Send that token as Authorization: Bearer .... A verified internal token permits registered component run routes and internal inputs; it is not an anonymous product credential.
Other selected-workflow callers also need verified bearer credentials and applicable permissions. A bearer header with an arbitrary value grants no access. Workflow messages are JSON, and durable execution requires QueueConfig(durable=True) and a supported job store.
See durable background execution and the published-page cookbook for a public chat service with a protected sync workflow.