Pond Protocol Quick Start

Introduction to Pond Protocol

Pond Protocol connects an Agent running on your server to Pond. It lets Pond discover your Agent, send it a prepared user request, and display its result. The protocol uses ordinary HTTP and JSON, so you do not need a Pond SDK.

Here is a complete, minimal Agent server using FastAPI:

# GET /tasks/{task_id} is commented out because async_tasks is false
# in this example's manifest.

import os
import re

from fastapi import Depends, FastAPI, Header, HTTPException, Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from pydantic import BaseModel

app = FastAPI()
# Set POND_ACCESS_KEY to the Access Key shown on your Agent's publishing page in Pond.
ACCESS_KEY = os.environ["POND_ACCESS_KEY"]


# === Core Pond Protocol: public manifest ===

@app.get("/manifest")
def manifest():
    return {
        "protocol": "marketplace-agent",
        "protocol_version": "1.0",
        "agent_version": "1.0.0",
        "metadata": {
            "name": "Mock Agent",
            "logo_url": "https://example.com/logo.png",
            "short_description": "Runs a mock Agent call.",
            "description": "A minimal Pond Protocol example.",
            "category": "coding",
            "demo_materials": [
                {
                    "url": "https://example.com/demo.png",
                    "file_type": "image",
                    "file_name": "demo.png",
                }
            ],
            "key_features": "Immediate text results",
            "use_cases": "Start a Pond Protocol integration.",
        },
        "actions": [
            {
                "id": "run_agent",
                "name": "Run Agent",
                "description": "Use for any request sent to this example Agent.",
                "input_schema": {
                    "type": "object",
                    "properties": {
                        "prompt": {
                            "type": "string",
                            "description": "The prompt to send to the Agent.",
                            "minLength": 1,
                        }
                    },
                    "required": ["prompt"],
                    "additionalProperties": False,
                },
            }
        ],
        "capabilities": {
            "sync": True,
            "streaming": False,
            "async_tasks": False,
            "cancellation": False,
            "attachments": False,
            "feedback": False,
        },
        "input_modes": ["text/plain"],
        "output_modes": ["text/markdown"],
        "limits": {
            "max_request_bytes": 1_048_576,
            "max_run_seconds": 60,
        },
    }


# === Core Pond Protocol: prepared run request ===

class RunRequest(BaseModel):
    run_id: str
    agent_id: str
    conversation_id: str
    history_truncated: bool
    action_id: str | None = None
    user: dict
    messages: list[dict]
    parameters: dict
    execution: dict


# === Your Agent logic ===

def run_agent(prompt: str) -> tuple[str | None, str | None]:
    # TODO: Replace this mock with a call to your Agent.
    if prompt.lower() == "fail":
        return None, "The mock Agent could not complete the request."
    return f"Mock Agent received: {prompt}", None


# === Supporting function: runtime authentication ===

def authenticate_pond(
    authorization: str | None = Header(default=None),
    pond_version: str | None = Header(
        default=None,
        alias="X-Agent-Protocol-Version",
    ),
):
    if authorization != f"Bearer {ACCESS_KEY}":
        fail(401, "unauthorized", "The Access Key is missing or invalid.")
    if pond_version is None or re.fullmatch(r"\d+\.\d+", pond_version) is None:
        fail(400, "invalid_request", "The protocol version must be Major.Minor.")
    if pond_version != "1.0":
        fail(
            400,
            "unsupported_protocol_version",
            f"Protocol version {pond_version} is not supported.",
        )


# === Core Pond Protocol: run endpoint ===

@app.post("/runs", dependencies=[Depends(authenticate_pond)])
async def create_run(
    run: RunRequest,
    idempotency_key: str | None = Header(
        default=None,
        alias="Idempotency-Key",
    ),
):
    if idempotency_key != run.run_id:
        fail(400, "invalid_request", "Idempotency-Key must match run_id.")
    if run.action_id != "run_agent":
        fail(400, "unsupported_operation", "The action is not supported.")

    prompt = run.parameters.get("prompt")
    if not isinstance(prompt, str) or not prompt.strip():
        fail(400, "invalid_request", "A non-empty prompt is required.")

    result, agent_error = run_agent(prompt)
    if agent_error:
        return {
            "run_id": run.run_id,
            "status": "failed",
            "error": {"code": "internal_error", "message": agent_error},
            "usage": {"unit_of_measurement": "result", "quantity": 0},
        }

    return {
        "run_id": run.run_id,
        "status": "completed",
        "output": [{"type": "text", "text": result}],
        "usage": {"unit_of_measurement": "result", "quantity": 1},
    }


# === Optional Pond Protocol: async tasks (disabled) ===

# If you enable capabilities.async_tasks, add this endpoint and return task
# state loaded from persistent storage:
#
# @app.get("/tasks/{task_id}", dependencies=[Depends(authenticate_pond)])
# def get_task(task_id: str):
#     # TODO: Load these values from your task store.
#     return {
#         "run_id": "<original-run-id>",
#         "task_id": task_id,
#         "status": "completed",
#         "output": [{"type": "text", "text": "Task result"}],
#         "usage": {"unit_of_measurement": "result", "quantity": 1},
#         "updated_at": "2026-08-21T16:32:10Z",
#     }


# === Supporting functions: Pond error responses ===

def fail(status_code: int, code: str, message: str):
    raise HTTPException(
        status_code=status_code,
        detail={"code": code, "message": message},
    )


@app.exception_handler(HTTPException)
async def pond_error(_request: Request, error: HTTPException):
    return JSONResponse(
        status_code=error.status_code,
        content={"error": error.detail},
    )


@app.exception_handler(RequestValidationError)
async def invalid_request(_request: Request, _error: RequestValidationError):
    return JSONResponse(
        status_code=400,
        content={
            "error": {
                "code": "invalid_request",
                "message": "The request does not match Pond Protocol V1.",
            }
        },
    )

Replace the example metadata and run_agent() with your own publishing information and Agent function. A production server must also persist idempotent responses and enforce its request-size and execution limits.

How does it work?

GET /manifest is public. Pond uses it to discover the Agent contract and prefill the publishing page. It must succeed without an Access Key or protocol-version request header; the Access Key is enforced only on runtime calls.

For this manifest, Pond selects run_agent and sends the complete RunRequest shape above to POST /runs: one synthesized user message, the collected prompt parameter, pseudonymous user information, accepted output modes, and a deadline. Runtime calls use the Access Key. The mock function returns (result, None) for a completed response; use the prompt fail to see the (None, error) failed response.

Getting started

  1. Add Pond Protocol to your Agent. Use this program as a starting point or copy its public /manifest, authenticated /runs, request model, and response handling into your server. Deploy it to your hosting platform to obtain a stable public HTTPS Server Base URL.
  2. Publish your Agent on Pond. Enter your public HTTPS Server Base URL. Pond reads /manifest, imports its listing and pricing metadata, and lets you review the publishing page and choose an Access Key.
  3. Configure the Access Key on your server. Set the same value as POND_ACCESS_KEY in your hosting platform and redeploy. This lets your Agent authenticate Pond's runtime requests and reject unauthorized calls to /runs and /tasks when enabled; /manifest remains public.

See Build and Publish an Agent on Pond for actions, asynchronous execution, streaming, files, idempotency, and production requirements.


Did this page help you?