Build and Publish an Agent on Pond

Use Pond Protocol V1 to connect an Agent running on your own server. Pond discovers what the Agent supports, prepares a user request, calls the Agent once, and displays the result in the user's chat.

Pond Protocol is an HTTP and JSON contract. Start with synchronous text requests. Add actions, streaming, asynchronous tasks, or files only when your Agent needs them.

Quick start

Your Agent server needs:

  • A stable public HTTPS Server Base URL
  • A valid TLS certificate
  • GET /manifest for discovery
  • POST /runs for execution
  • GET /tasks/{task_id} only when asynchronous execution is supported
  • A secret Access Key configured identically in Pond and on the Agent server
  • Safe, human-readable English error messages

Pond appends the fixed paths to the Server Base URL. If the URL is https://agent.example.com, Pond discovers the Agent at https://agent.example.com/manifest and starts work at https://agent.example.com/runs.

EndpointRequired whenPurpose
GET /manifestAlwaysDescribes the Agent, optional actions, accepted inputs, outputs, and limits
POST /runsAlwaysStarts one prepared Agent execution
GET /tasks/{task_id}capabilities.async_tasks is trueReturns asynchronous progress and the terminal result

Pond Protocol V1 uses protocol version 1.0.

Add the manifest

Pond reads GET /manifest when an Agent is created, manually refreshed, or revalidated. The response must use a JSON media type and must not exceed 256 KiB.

This complete example describes a language Agent with two actions:

{
  "protocol": "marketplace-agent",
  "protocol_version": "1.0",
  "agent_version": "2026.08.21",
  "metadata": {
    "name": "Language Agent",
    "logo_url": "https://example.com/logo.png",
    "short_description": "Translates text and detects its language.",
    "description": "Processes text using focused language tools.",
    "category": "productivity_tools",
    "demo_materials": [
      {
        "url": "https://example.com/translation-demo.png",
        "file_type": "image",
        "file_name": "translation-demo.png"
      }
    ],
    "key_features": "<ul><li>Translation</li><li>Language detection</li></ul>",
    "use_cases": "<p>Translate customer messages or identify an unknown language.</p>",
    "setup_instructions": "Send text and describe the language operation you need.",
    "developer_x_url": "https://x.com/example",
    "developer_linkedin_url": "https://www.linkedin.com/in/example",
    "github_url": "https://github.com/example/language-agent",
    "faqs": [
      {
        "question": "Does the Agent preserve formatting?",
        "answer": "<p>Yes, when preserve_formatting is true.</p>"
      }
    ],
    "pricing_plans": [
      {
        "name": "Token Plan",
        "pricing_model": "pay_as_you_go",
        "amount_minor": 100,
        "usage_quantity": 1000,
        "usage_unit": "token",
        "description": "$1 per 1,000 tokens",
        "sort_order": 1
      },
      {
        "name": "Monthly Token Plan",
        "pricing_model": "subscription",
        "amount_minor": 1900,
        "usage_unit": "token",
        "billing_interval": "month",
        "included_units": 100000,
        "description": "$19 per month, including 100,000 tokens",
        "sort_order": 2
      }
    ]
  },
  "actions": [
    {
      "id": "translate_text",
      "name": "Translate Text",
      "description": "Use when the user wants supplied text translated into another language.",
      "input_schema": {
        "type": "object",
        "properties": {
          "text": {
            "type": "string",
            "description": "The source text to translate."
          },
          "target_language": {
            "type": "string",
            "description": "The language requested for the translated output.",
            "enum": ["English", "Spanish", "French", "Japanese"]
          },
          "preserve_formatting": {
            "type": "boolean",
            "description": "Whether to preserve the source text's paragraph and list formatting."
          }
        },
        "required": ["text", "target_language"],
        "additionalProperties": false
      }
    },
    {
      "id": "detect_language",
      "name": "Detect Language",
      "description": "Use when the user wants to identify the language of supplied text.",
      "input_schema": {
        "type": "object",
        "properties": {
          "text": {
            "type": "string",
            "description": "The text whose language should be identified.",
            "minLength": 1
          }
        },
        "required": ["text"],
        "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": 10485760,
    "max_attachment_bytes": 52428800,
    "max_run_seconds": 300
  }
}

The required top-level fields are:

FieldMeaning
protocolExactly marketplace-agent
protocol_versionExactly 1.0 for Pond Protocol V1
agent_versionA non-empty identifier for this Agent release
capabilitiesSupported synchronous, streaming, asynchronous, attachment, and reserved behavior
input_modesMedia types the Agent accepts
output_modesMedia types the Agent can return
limitsRequest, attachment, and execution limits

metadata, actions, and input_schema are optional. Unknown manifest fields are ignored for forward compatibility.

Increment agent_version whenever you change an action identifier, routing description, input contract, capability, media mode, or limit. agent_version identifies your release and is separate from the fixed Pond protocol_version.

Declare multiple actions(optional)

Add actions when one Agent supports multiple distinct operations. Omit it for one general-purpose operation.

Action fieldMeaning
idStable lowercase snake-case identifier, unique within the manifest; Pond sends the exact value in /runs.action_id
nameConcise action name used in developer-facing diagnostics
descriptionClear routing guidance describing when Pond should select the action
input_schemaOptional rules for structured values Pond should collect; when omitted, Pond sends an empty parameters object

Keep capabilities, input_modes, output_modes, and limits at the manifest top level. Pond Protocol V1 does not define per-action transport capabilities or media modes.

Understand input_schema

input_schema is a JSON Schema that describes the shape of the actual parameters object Pond will send to POST /runs. The schema contains rules, not user values.

For an Agent with actions, place input_schema inside each action that needs structured input. Do not also provide a top-level input_schema. For an Agent without actions, you may provide one top-level input_schema.

The translation action above uses:

{
  "input_schema": {
    "type": "object",
    "properties": {
      "text": {
        "type": "string",
        "description": "The source text to translate."
      },
      "target_language": {
        "type": "string",
        "description": "The language requested for the translated output.",
        "enum": ["English", "Spanish", "French", "Japanese"]
      },
      "preserve_formatting": {
        "type": "boolean",
        "description": "Whether to preserve the source text's paragraph and list formatting."
      }
    },
    "required": ["text", "target_language"],
    "additionalProperties": false
  }
}

Each field has a specific purpose:

Field or keywordMeaning and Pond behavior
type: "object"The resulting parameters value is a JSON object. Every Pond V1 input_schema uses object at its root.
propertiesDeclares the parameter names Pond may collect and send.
Property typeDefines the JSON value Pond must collect and validate, such as string, integer, number, boolean, array, or object.
Property descriptionExplains what the value means. Pond uses it to ask a useful follow-up question. Provide a clear description for every property.
enumRestricts a value to one of the listed choices. Pond uses those choices while clarifying and validating the request.
Validation constraintsStandard JSON Schema keywords such as minLength, maxLength, minimum, maximum, and items further constrain accepted values.
requiredLists values Pond must collect before calling the Agent. Other declared properties remain optional.
additionalProperties: falsePrevents Pond from sending undeclared keys. This value is required for Pond V1 input schemas.

Do not confuse these related objects:

ObjectPurpose
Manifest input_schemaRules describing structured input the Agent accepts
/runs.parametersActual user-specific values collected and validated using those rules
The embedded /runs request schemaRules for the complete request envelope, including action_id, messages, parameters, and execution

How Pond selects and calls an action

Before calling an Agent that declares actions, Pond:

  1. Compares the prepared user intent with every action's id, name, and description.
  2. Selects exactly one matching action.
  3. Reads that action's input_schema to identify required and optional values.
  4. Asks follow-up questions for missing required values.
  5. Validates the collected values against the selected schema.
  6. Creates one synthesized user-role instruction in messages.
  7. Calls POST /runs once with the selected action_id and validated parameters.

For the translation example, Pond constructs:

{
  "action_id": "translate_text",
  "parameters": {
    "text": "Hello world",
    "target_language": "Spanish",
    "preserve_formatting": true
  }
}

When actions is absent, Pond omits action_id and uses the optional top-level input_schema. When the applicable schema is absent, Pond sends parameters: {} and relies on the synthesized instruction in messages.

If no declared action matches the user's request, Pond does not call the Agent. If an Agent declares actions, its server must reject a missing or undeclared action_id before accepting execution by returning HTTP 400 with unsupported_operation. The server must also validate parameters against the selected action's input_schema.

Add listing and pricing metadata

metadata is optional and does not determine protocol compatibility. We strongly recommend including complete listing and pricing information so Pond can prefill the publishing page.

Pond validates listing fields independently. Invalid scalar fields are ignored, and valid demo materials and FAQ entries are retained item by item. Pricing is imported only when pricing_plans is non-empty and every plan is valid, preventing a partial commercial offer from being imported.

Metadata fieldAccepted value
nameNon-empty UTF-8; at most 255 characters and 255 UTF-8 bytes
logo_urlValid HTTP or HTTPS URL; at most 2,000 characters and 2,000 UTF-8 bytes
short_descriptionNon-empty UTF-8; at most 500 characters and 500 UTF-8 bytes
descriptionDetailed rich text; Pond sanitizes it before storage or rendering
categorysales, content, development, analytics, research, workflow, compliance, productivity_tools, agent_infrastructure, or others
demo_materialsItems containing HTTP(S) url, file_type, and non-empty file_name
key_featuresRich text describing the Agent's main features
use_casesRich text describing suitable uses
setup_instructionsSetup guidance
developer_x_urlValid X profile URL
developer_linkedin_urlValid LinkedIn profile URL
github_urlValid public github.com repository URL
faqsItems with non-empty question and answer
pricing_plansNon-empty collection imported only when every plan is valid

Remote metadata does not set the Access Key, Server Base URL, compliance confirmations, review state, or Pond-generated statistics. Missing publishing information must be entered manually before submitting the Agent for review.

Pricing plans

Pond's saved pricing configuration is the source of truth for billing. Manifest pricing plans prefill the publishing page but do not change a published price by themselves.

FieldMeaning and usage
nameNon-empty plan name shown to users
pricing_modelfree, pay_as_you_go, or subscription
amount_minorPrice in US cents; 100 means $1.00
usage_quantityFor pay_as_you_go, how many units amount_minor buys; defaults to 1
usage_unittoken, result, or other
custom_usage_unitConcrete unit such as report or minute; required when usage_unit is other
billing_intervalSubscription renewal interval; V1 supports month
included_unitsPositive allowance required for free and subscription plans
validity_daysOptional duration for a free plan; 0 means no expiry
descriptionOptional short explanation shown to users
sort_orderOptional positive display position
Natural-language (NLP) pricing (coming soon; no V1 field yet)Agent developers will be able to include a plain-text pricing description in the manifest. Pond will analyze it and convert it into structured pricing plans. Until this capability is announced, use metadata.pricing_plans.

Do not provide Pond plan UUIDs, currency, or internal billing fields. Pond generates or derives them when the imported draft is saved.

Configure authentication

Configure the same secret Access Key in Pond and on the Agent server. Pond sends it to every Pond Protocol endpoint:

Authorization: Bearer <creator-configured-access-key>
X-Agent-Protocol-Version: 1.0

The Agent server must verify the Bearer token before processing the request and return HTTP 401 unauthorized when it is missing, malformed, or incorrect. Never include the key in the manifest, URL, logs, errors, or example content.

The version header selects the exact Major.Minor protocol version. A missing or malformed version returns 400 invalid_request. A well-formed unsupported version returns 400 unsupported_protocol_version. Pond V1 supports exactly 1.0; 1.0.1 and 1.1 are not implied.

Receive a prepared run

Users first interact with Pond's Chat Interface. Pond may ask follow-up questions until it has the details required by the selected action and its input_schema. Only then does Pond send one prepared POST /runs request.

POST /runs
Authorization: Bearer <creator-configured-access-key>
Idempotency-Key: run_01JDEF
X-Agent-Protocol-Version: 1.0
Accept: application/json
Content-Type: application/json

The Idempotency-Key always equals the body run_id. The request does not contain the raw clarification transcript:

  • messages contains exactly one Pond-generated user-role instruction synthesizing what the Agent should do.
  • action_id identifies the selected manifest action and is omitted when the manifest has no actions.
  • parameters contains actual structured values validated against the applicable input_schema.
  • Relevant user uploads appear as file parts in messages only when capabilities.attachments is true.
Request fieldMeaning
run_idPond-created identifier for the complete logical execution; it matches the Idempotency-Key header
agent_idIdentifies the Pond Agent being called
conversation_idIdentifies the originating Pond chat for correlation; it does not grant access to the chat transcript
history_truncatedIndicates whether Pond shortened non-required supporting context while preparing the instruction; required inputs remain present
action_idExact selected manifest action; omitted when the manifest has no actions
userPond-generated pseudonymous user information
messagesExactly one synthesized user-role execution message
parametersActual structured values collected using the applicable input_schema
executionRequested response mode, accepted output modes, and deadline

This complete request selects the translation action:

{
  "run_id": "run_01JDEF",
  "agent_id": "agt_01JXYZ",
  "conversation_id": "chat_789",
  "history_truncated": false,
  "action_id": "translate_text",
  "user": {
    "id": "usr_pseudonymous_123",
    "locale": "en-US",
    "timezone": "America/Los_Angeles"
  },
  "messages": [
    {
      "id": "msg_01J001",
      "role": "user",
      "created_at": "2026-08-21T16:30:00Z",
      "parts": [
        {
          "type": "text",
          "text": "Translate 'Hello world' into Spanish and preserve its formatting."
        }
      ]
    }
  ],
  "parameters": {
    "text": "Hello world",
    "target_language": "Spanish",
    "preserve_formatting": true
  },
  "execution": {
    "mode": "sync",
    "accepted_output_modes": ["text/markdown"],
    "deadline_ms": 300000
  }
}

Pond creates run_id, conversation_id, and the prepared message ID. The Agent creates identifiers only for objects it creates, such as asynchronous tasks and output artifacts. Echo the same run_id in every response.

/runs request JSON Schema

This complete Draft 2020-12 schema defines the request envelope. It permits action_id because the schema cannot see the discovered manifest. The behavioral rule is stricter: omit action_id when the manifest has no actions, and require an exact declared action ID when it does.

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://docs.joinpond.ai/schemas/pond-run-request-v1.json",
  "title": "Pond Run Request V1",
  "type": "object",
  "required": [
    "run_id",
    "agent_id",
    "conversation_id",
    "history_truncated",
    "user",
    "messages",
    "parameters",
    "execution"
  ],
  "properties": {
    "run_id": {
      "description": "Pond-created identifier for one logical execution; matches Idempotency-Key.",
      "type": "string",
      "minLength": 1
    },
    "agent_id": {
      "description": "Identifier of the Pond Agent being called.",
      "type": "string",
      "minLength": 1
    },
    "conversation_id": {
      "description": "Identifier of the originating Pond chat for correlation.",
      "type": "string",
      "minLength": 1
    },
    "history_truncated": {
      "description": "Whether Pond shortened non-required supporting context.",
      "type": "boolean"
    },
    "action_id": {
      "description": "Selected manifest action; omitted when the manifest declares no actions.",
      "type": "string",
      "pattern": "^[a-z][a-z0-9_]{0,63}$"
    },
    "user": {
      "description": "Pond-generated pseudonymous user information.",
      "$ref": "#/$defs/user"
    },
    "messages": {
      "description": "Exactly one synthesized user-role execution message.",
      "type": "array",
      "minItems": 1,
      "maxItems": 1,
      "items": {
        "$ref": "#/$defs/message"
      }
    },
    "parameters": {
      "description": "Values collected and validated against the applicable manifest input_schema.",
      "type": "object"
    },
    "execution": {
      "description": "Requested response mode, accepted outputs, and deadline.",
      "$ref": "#/$defs/execution"
    }
  },
  "additionalProperties": false,
  "$defs": {
    "user": {
      "type": "object",
      "required": ["id", "locale", "timezone"],
      "properties": {
        "id": {
          "description": "Pond-generated pseudonymous user identifier.",
          "type": "string",
          "minLength": 1
        },
        "locale": {
          "description": "User locale such as en-US.",
          "type": "string",
          "minLength": 1
        },
        "timezone": {
          "description": "User timezone such as America/Los_Angeles.",
          "type": "string",
          "minLength": 1
        }
      },
      "additionalProperties": false
    },
    "message": {
      "type": "object",
      "required": ["id", "role", "created_at", "parts"],
      "properties": {
        "id": {
          "description": "Pond-created identifier for the prepared execution message.",
          "type": "string",
          "minLength": 1
        },
        "role": {
          "description": "Always user in Pond Protocol V1.",
          "const": "user"
        },
        "created_at": {
          "description": "RFC 3339 time when Pond created the prepared message.",
          "type": "string",
          "format": "date-time"
        },
        "parts": {
          "description": "Ordered synthesized text and relevant user-uploaded file parts.",
          "type": "array",
          "minItems": 1,
          "contains": {
            "$ref": "#/$defs/textPart"
          },
          "minContains": 1,
          "items": {
            "oneOf": [
              {
                "$ref": "#/$defs/textPart"
              },
              {
                "$ref": "#/$defs/filePart"
              }
            ]
          }
        }
      },
      "additionalProperties": false
    },
    "textPart": {
      "type": "object",
      "required": ["type", "text"],
      "properties": {
        "type": {
          "description": "Text-part discriminator.",
          "const": "text"
        },
        "text": {
          "description": "Non-empty synthesized instruction text.",
          "type": "string",
          "minLength": 1
        }
      },
      "additionalProperties": false
    },
    "filePart": {
      "type": "object",
      "required": ["type", "file"],
      "properties": {
        "type": {
          "description": "File-part discriminator.",
          "const": "file"
        },
        "file": {
          "description": "Relevant user upload available through a signed HTTPS URL.",
          "type": "object",
          "required": ["url", "name", "media_type"],
          "properties": {
            "url": {
              "description": "Signed HTTPS URL available through the execution deadline.",
              "type": "string",
              "format": "uri",
              "pattern": "^https://"
            },
            "name": {
              "description": "Original or Pond-assigned filename.",
              "type": "string",
              "minLength": 1
            },
            "media_type": {
              "description": "Input media type also declared in manifest input_modes.",
              "type": "string",
              "minLength": 1
            }
          },
          "additionalProperties": false
        }
      },
      "additionalProperties": false
    },
    "execution": {
      "type": "object",
      "required": ["mode", "accepted_output_modes", "deadline_ms"],
      "properties": {
        "mode": {
          "description": "Requested response mode supported by the manifest.",
          "enum": ["sync", "stream", "async"]
        },
        "accepted_output_modes": {
          "description": "Output media types Pond accepts for this run.",
          "type": "array",
          "minItems": 1,
          "uniqueItems": true,
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "deadline_ms": {
          "description": "Maximum execution duration in milliseconds from acceptance.",
          "type": "integer",
          "minimum": 1
        }
      },
      "additionalProperties": false
    }
  }
}

The Agent must additionally validate that:

  • action_id follows the discovered manifest rule.
  • parameters matches the selected action-level or top-level input_schema.
  • execution.mode is advertised by the corresponding capability.
  • accepted_output_modes overlaps the manifest output_modes.
  • deadline_ms does not exceed limits.max_run_seconds × 1000.
  • The request body and file parts stay within the declared limits.

Return a result

Start with synchronous execution. Add streaming or asynchronous execution only when needed.

ModeManifest capabilityRequest AcceptSuccessful transport
synccapabilities.syncapplication/jsonHTTP 200 terminal result
streamcapabilities.streamingtext/event-streamHTTP 200 SSE stream
asynccapabilities.async_tasksapplication/jsonHTTP 202 task acceptance

Synchronous

A completed result contains run_id, status, ordered output, and cumulative usage:

{
  "run_id": "run_01JDEF",
  "status": "completed",
  "output": [
    {
      "type": "text",
      "text": "Hola mundo"
    }
  ],
  "usage": {
    "unit_of_measurement": "token",
    "quantity": 42
  }
}

An accepted synchronous failure still returns HTTP 200, with status: "failed", a safe error object, and cumulative usage.

Streaming

Return HTTP 200 text/event-stream. The first event is run.started, followed by zero or more message.delta or artifact.ready events. End with exactly one run.completed or run.error event.

event: run.started
data: {"run_id":"run_01JDEF"}

event: message.delta
data: {"run_id":"run_01JDEF","part_index":0,"delta":"Hola "}

event: message.delta
data: {"run_id":"run_01JDEF","part_index":0,"delta":"mundo"}

event: run.completed
data: {"run_id":"run_01JDEF","output":[{"type":"text","text":"Hola mundo"}],"usage":{"unit_of_measurement":"token","quantity":42}}

Pond Protocol V1 does not define SSE replay, reconnection, or stream resumption.

Asynchronous

Accept the run with HTTP 202:

{
  "run_id": "run_01JDEF",
  "task_id": "task_agent_123",
  "status": "queued",
  "poll_after_ms": 2000
}
Acceptance fieldRequirement
run_idRequired; echoes the Pond run ID
task_idRequired stable ID created by the Agent server
statusRequired initial status, normally queued
poll_after_msOptional scheduling hint; it does not guarantee the exact next poll time

Pond polls GET /tasks/{task_id} with the same authorization and protocol-version headers until it receives a valid terminal result or reaches the deadline.

A running task may include progress:

{
  "run_id": "run_01JDEF",
  "task_id": "task_agent_123",
  "status": "running",
  "progress": {
    "percent": 60,
    "message": "Translating the text"
  },
  "updated_at": "2026-08-21T16:31:20Z"
}

A completed task contains ordered output, referenced artifacts when present, and cumulative usage:

{
  "run_id": "run_01JDEF",
  "task_id": "task_agent_123",
  "status": "completed",
  "output": [
    {
      "type": "text",
      "text": "Hola mundo"
    }
  ],
  "usage": {
    "unit_of_measurement": "token",
    "quantity": 42
  },
  "updated_at": "2026-08-21T16:32:10Z"
}
StatusTerminalRequired result
queuedNoTask identity and status
runningNoTask identity and status; optional progress
completedYesValid output, referenced artifacts, and usage
failedYesValid error and usage
expiredYesValid error and usage

Pond continues polling after queued or running. Network failures, malformed JSON, invalid media types, and other invalid responses that do not establish a terminal status are non-terminal, so Pond continues until a valid terminal result or the deadline.

A completed task must contain valid output, a failed or expired task must contain a valid error, and every artifact reference must resolve. If the Agent reports a terminal status with invalid required output or error, Pond stops polling immediately and records invalid_agent_response. Invalid usage remains non-blocking and does not change an otherwise valid terminal result into invalid_agent_response. If the deadline is reached first, Pond stops polling and records a timeout.

Report usage

Every terminal response produced after execution begins contains cumulative usage:

{
  "usage": {
    "unit_of_measurement": "token",
    "quantity": 10000
  }
}

unit_of_measurement is token, result, or other and must match the saved pricing plan. quantity is a non-negative integer for the complete logical run. When the plan uses other, Pond interprets it using the saved custom_usage_unit.

Usage is required on synchronous success and accepted failure, run.completed, run.error, and terminal asynchronous tasks. Request-level failures before execution begins do not include usage.

Missing, malformed, or mismatched usage does not hide an otherwise valid result. Pond records a metering error, uses zero variable units, and notifies the Agent creator.

Handle files

When capabilities.attachments is true, Pond may include relevant user uploads as file parts:

{
  "type": "file",
  "file": {
    "url": "https://files.marketplace.example/signed/input.png",
    "name": "input.png",
    "media_type": "image/png"
  }
}

The signed HTTPS URL does not require the Pond Access Key or another authorization header and remains available through the execution deadline.

An Agent-generated file artifact has one content source: an external HTTPS url or complete Base64-encoded bytes. It also has an Agent-created id, name, and media_type.

External URL:

{
  "id": "artifact_123",
  "type": "file",
  "file": {
    "url": "https://agent.example.com/output/report.pdf",
    "name": "report.pdf",
    "media_type": "application/pdf"
  }
}

Inline bytes:

{
  "id": "artifact_456",
  "type": "file",
  "file": {
    "bytes": "RXhhbXBsZSByZXBvcnQuCg==",
    "name": "report.txt",
    "media_type": "text/plain"
  }
}

Pond displays an external URL but does not fetch or copy it. The URL must be available to the intended user without Agent credentials. Pond decodes and stores inline bytes.

Completed synchronous and asynchronous results put complete definitions in top-level artifacts and use artifact_ref entries in output. Streaming sends each complete definition in artifact.ready before the terminal event. Every reference must resolve to exactly one artifact definition.

Return errors

Use a stable machine-readable code and a safe English message:

{
  "run_id": "run_01JDEF",
  "error": {
    "code": "invalid_input",
    "message": "The source text cannot be empty.",
    "details": {
      "field": "parameters.text"
    }
  }
}

code and message are required. details is optional. Never expose credentials, stack traces, database messages, or private infrastructure details.

The following statuses apply when a request is rejected before execution begins:

CodeHTTP statusMeaning
invalid_request400Malformed JSON or protocol validation failure
idempotency_conflict409The key was reused with a materially different request
unauthorized401Missing or invalid Access Key
invalid_input422Valid request containing input the Agent cannot process
unsupported_operation400Missing, unknown, or unsupported action or execution mode
unsupported_protocol_version400Unsupported Pond Major.Minor version
unsupported_content_type415Unsupported media type
task_not_found404Asynchronous task does not exist or is inaccessible
rate_limited429Agent server is rate-limiting requests
temporarily_unavailable503Agent server cannot currently process the request
internal_error500Unexpected Agent server failure

Pond handles an unknown Agent error code as internal_error. Pond may independently record gateway conditions such as agent_timeout, agent_unreachable, and invalid_agent_response; Agent servers must not return those Pond-only codes.

After execution is accepted, put the error in that mode's terminal response: synchronous HTTP 200 with status: "failed", streaming run.error, or a terminal failed/expired asynchronous task.

Make runs idempotent

Pond sends Idempotency-Key: <run_id> and does not intentionally retry POST /runs. Protect against unintended duplicates:

  • Coalesce concurrent requests with the same key and body.
  • Return the saved terminal synchronous result for a completed duplicate.
  • Return the original task_id and current state for an asynchronous duplicate.
  • Return HTTP 409 idempotency_conflict when the same key is reused with a materially different request.
  • Retain the mapping through at least the execution deadline.

Test and publish

Before submitting the Agent:

  1. Deploy the HTTPS server and configure the same Access Key in Pond and on the server.
  2. Confirm GET /manifest returns a valid V1 manifest and rejects missing or incorrect authentication.
  3. Review every listing and pricing value imported into the publishing page.
  4. Confirm Pond selects the correct action for representative user requests.
  5. Confirm Pond asks for missing required input_schema values before dispatch.
  6. Confirm /runs.action_id and /runs.parameters match the selected manifest action.
  7. Test every advertised execution mode and media type.
  8. Test safe failures before and after execution is accepted.
  9. Test user uploads and Agent-generated files when supported.
  10. Confirm every terminal result includes valid cumulative usage.
  11. Enter any required publishing information missing from the manifest.
  12. Submit the Agent for review.

Only advertise actions, capabilities, media modes, and limits that are deployed and ready for production traffic. Revalidate the Agent after changing any of them.

Appendix: Manifest validation schema

This appendix is for validation tooling. Agent developers do not need to copy, modify, or include this schema in GET /manifest. Write only the manifest described above, then use this schema with a JSON Schema-compatible editor, CI check, or validator to catch structural errors before submission. Pond can use the same schema when validating a submitted manifest.

The following complete JSON Schema uses Draft 2020-12. It defines which Pond Protocol V1 manifest fields are required or optional, the types and values they accept, and the rules for optional actions and input schemas.

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://docs.joinpond.ai/schemas/pond-agent-manifest-v1.json",
  "title": "Pond Agent Manifest V1",
  "type": "object",
  "required": [
    "protocol",
    "protocol_version",
    "agent_version",
    "capabilities",
    "input_modes",
    "output_modes",
    "limits"
  ],
  "properties": {
    "protocol": {
      "description": "Pond discovery protocol identifier.",
      "const": "marketplace-agent"
    },
    "protocol_version": {
      "description": "Exact Pond Protocol Major.Minor version.",
      "const": "1.0"
    },
    "agent_version": {
      "description": "Non-empty identifier for this Agent release.",
      "type": "string",
      "minLength": 1
    },
    "metadata": {
      "description": "Optional marketplace data. Invalid values are ignored during field-level import and do not invalidate the protocol manifest.",
      "type": "object",
      "additionalProperties": true
    },
    "actions": {
      "description": "Optional distinct operations Pond may select from user intent.",
      "type": "array",
      "minItems": 1,
      "uniqueItems": true,
      "items": {
        "$ref": "#/$defs/action"
      }
    },
    "input_schema": {
      "description": "Optional structured input contract for an Agent that does not declare actions.",
      "$ref": "#/$defs/inputSchema"
    },
    "capabilities": {
      "description": "Execution and content behavior supported by the Agent.",
      "$ref": "#/$defs/capabilities"
    },
    "input_modes": {
      "description": "Media types the Agent accepts from Pond.",
      "$ref": "#/$defs/mediaModes"
    },
    "output_modes": {
      "description": "Media types the Agent can return to Pond.",
      "$ref": "#/$defs/mediaModes"
    },
    "limits": {
      "description": "Request, attachment, and execution limits used before dispatch.",
      "$ref": "#/$defs/limits"
    }
  },
  "not": {
    "required": ["actions", "input_schema"]
  },
  "additionalProperties": true,
  "$defs": {
    "action": {
      "type": "object",
      "required": ["id", "name", "description"],
      "properties": {
        "id": {
          "description": "Stable unique action identifier returned in /runs.action_id.",
          "type": "string",
          "pattern": "^[a-z][a-z0-9_]{0,63}$"
        },
        "name": {
          "description": "Concise developer-facing action name.",
          "type": "string",
          "minLength": 1,
          "maxLength": 100
        },
        "description": {
          "description": "Routing guidance describing when Pond should select this action.",
          "type": "string",
          "minLength": 1,
          "maxLength": 1000
        },
        "input_schema": {
          "description": "Optional structured values Pond collects for this action.",
          "$ref": "#/$defs/inputSchema"
        }
      },
      "additionalProperties": false
    },
    "inputSchema": {
      "type": "object",
      "required": ["type", "properties", "additionalProperties"],
      "properties": {
        "type": {
          "description": "The /runs.parameters root is always a JSON object.",
          "const": "object"
        },
        "properties": {
          "description": "Named parameter definitions Pond may collect and send.",
          "type": "object",
          "additionalProperties": {
            "$ref": "#/$defs/valueSchema"
          }
        },
        "required": {
          "description": "Parameter names Pond must collect before dispatch.",
          "type": "array",
          "items": {
            "type": "string"
          },
          "uniqueItems": true
        },
        "additionalProperties": {
          "description": "Must be false so Pond never sends undeclared parameters.",
          "const": false
        }
      },
      "additionalProperties": true
    },
    "valueSchema": {
      "type": "object",
      "required": ["type", "description"],
      "properties": {
        "type": {
          "description": "Expected JSON value type.",
          "enum": ["string", "integer", "number", "boolean", "array", "object"]
        },
        "description": {
          "description": "Human-readable meaning Pond uses when collecting the value.",
          "type": "string",
          "minLength": 1
        },
        "enum": {
          "description": "Optional fixed choices accepted for this value.",
          "type": "array",
          "minItems": 1,
          "uniqueItems": true
        },
        "minLength": {
          "description": "Optional minimum string length.",
          "type": "integer",
          "minimum": 0
        },
        "maxLength": {
          "description": "Optional maximum string length.",
          "type": "integer",
          "minimum": 0
        },
        "minimum": {
          "description": "Optional inclusive numeric minimum.",
          "type": "number"
        },
        "maximum": {
          "description": "Optional inclusive numeric maximum.",
          "type": "number"
        },
        "items": {
          "description": "Schema for each value in an array parameter.",
          "$ref": "#/$defs/valueSchema"
        },
        "properties": {
          "description": "Nested definitions for an object parameter.",
          "type": "object",
          "additionalProperties": {
            "$ref": "#/$defs/valueSchema"
          }
        },
        "required": {
          "description": "Required keys for an object parameter.",
          "type": "array",
          "items": {
            "type": "string"
          },
          "uniqueItems": true
        },
        "additionalProperties": {
          "description": "Whether an object parameter may contain undeclared keys.",
          "type": "boolean"
        }
      },
      "additionalProperties": true
    },
    "importableMetadata": {
      "type": "object",
      "properties": {
        "name": {
          "description": "Agent name shown on Pond.",
          "type": "string",
          "minLength": 1,
          "maxLength": 255
        },
        "logo_url": {
          "description": "HTTP or HTTPS logo URL used on the publishing page.",
          "type": "string",
          "format": "uri",
          "pattern": "^https?://",
          "maxLength": 2000
        },
        "short_description": {
          "description": "Short Agent summary shown in listings.",
          "type": "string",
          "minLength": 1,
          "maxLength": 500
        },
        "description": {
          "description": "Detailed Agent description; Pond sanitizes rich text.",
          "type": "string",
          "minLength": 1
        },
        "category": {
          "description": "Pond marketplace category.",
          "enum": [
            "sales",
            "content",
            "development",
            "analytics",
            "research",
            "workflow",
            "compliance",
            "productivity_tools",
            "agent_infrastructure",
            "others"
          ]
        },
        "demo_materials": {
          "description": "Optional examples shown on the publishing page.",
          "type": "array",
          "items": {
            "$ref": "#/$defs/importableDemoMaterial"
          }
        },
        "key_features": {
          "description": "Rich text describing the Agent's main features.",
          "type": "string",
          "minLength": 1
        },
        "use_cases": {
          "description": "Rich text describing suitable use cases.",
          "type": "string",
          "minLength": 1
        },
        "setup_instructions": {
          "description": "Instructions users should follow before using the Agent.",
          "type": "string",
          "minLength": 1
        },
        "developer_x_url": {
          "description": "Developer's X profile URL.",
          "type": "string",
          "format": "uri",
          "pattern": "^https://(www\\.)?(x\\.com|twitter\\.com)/"
        },
        "developer_linkedin_url": {
          "description": "Developer's LinkedIn profile URL.",
          "type": "string",
          "format": "uri",
          "pattern": "^https://(www\\.)?linkedin\\.com/"
        },
        "github_url": {
          "description": "Public GitHub repository URL for the Agent.",
          "type": "string",
          "format": "uri",
          "pattern": "^https://github\\.com/[^/]+/[^/]+/?$"
        },
        "faqs": {
          "description": "Frequently asked questions shown on the publishing page.",
          "type": "array",
          "items": {
            "$ref": "#/$defs/importableFaq"
          }
        },
        "pricing_plans": {
          "description": "Structured pricing plans imported atomically into the publishing draft.",
          "type": "array",
          "minItems": 1,
          "items": {
            "$ref": "#/$defs/importablePricingPlan"
          }
        }
      },
      "additionalProperties": true
    },
    "importableDemoMaterial": {
      "type": "object",
      "required": ["url", "file_type", "file_name"],
      "properties": {
        "url": {
          "description": "Public HTTP or HTTPS demo asset URL.",
          "type": "string",
          "format": "uri",
          "pattern": "^https?://"
        },
        "file_type": {
          "description": "Demo asset presentation type.",
          "enum": ["image", "video", "pdf", "ppt", "pptx"]
        },
        "file_name": {
          "description": "Non-empty display filename.",
          "type": "string",
          "minLength": 1
        }
      },
      "additionalProperties": false
    },
    "importableFaq": {
      "type": "object",
      "required": ["question", "answer"],
      "properties": {
        "question": {
          "description": "FAQ question.",
          "type": "string",
          "minLength": 1
        },
        "answer": {
          "description": "FAQ answer; Pond sanitizes rich text.",
          "type": "string",
          "minLength": 1
        }
      },
      "additionalProperties": false
    },
    "importablePricingPlan": {
      "type": "object",
      "required": ["name", "pricing_model", "amount_minor", "usage_unit"],
      "properties": {
        "name": {
          "description": "Plan name shown to users.",
          "type": "string",
          "minLength": 1
        },
        "pricing_model": {
          "description": "How users are charged.",
          "enum": ["free", "pay_as_you_go", "subscription"]
        },
        "amount_minor": {
          "description": "Price in minor USD units; 100 means $1.00.",
          "type": "integer",
          "minimum": 0
        },
        "usage_quantity": {
          "description": "For pay-as-you-go pricing, the number of units purchased for amount_minor.",
          "type": "integer",
          "minimum": 1,
          "default": 1
        },
        "usage_unit": {
          "description": "Unit the Agent reports in terminal usage.",
          "enum": ["token", "result", "other"]
        },
        "custom_usage_unit": {
          "description": "Concrete unit required when usage_unit is other.",
          "type": "string",
          "minLength": 1
        },
        "billing_interval": {
          "description": "Subscription renewal interval.",
          "const": "month"
        },
        "included_units": {
          "description": "Positive allowance included with a free or subscription plan.",
          "type": "integer",
          "minimum": 1
        },
        "validity_days": {
          "description": "Optional free-plan validity; zero means no expiry.",
          "type": "integer",
          "minimum": 0
        },
        "description": {
          "description": "Optional plan explanation shown to users.",
          "type": "string"
        },
        "sort_order": {
          "description": "Optional positive display position.",
          "type": "integer",
          "minimum": 1
        }
      },
      "allOf": [
        {
          "if": {
            "properties": {
              "usage_unit": {
                "const": "other"
              }
            },
            "required": ["usage_unit"]
          },
          "then": {
            "required": ["custom_usage_unit"]
          },
          "else": {
            "not": {
              "required": ["custom_usage_unit"]
            }
          }
        },
        {
          "if": {
            "properties": {
              "pricing_model": {
                "const": "free"
              }
            },
            "required": ["pricing_model"]
          },
          "then": {
            "required": ["included_units"],
            "properties": {
              "amount_minor": {
                "const": 0
              }
            },
            "not": {
              "anyOf": [
                {
                  "required": ["usage_quantity"]
                },
                {
                  "required": ["billing_interval"]
                }
              ]
            }
          }
        },
        {
          "if": {
            "properties": {
              "pricing_model": {
                "const": "pay_as_you_go"
              }
            },
            "required": ["pricing_model"]
          },
          "then": {
            "properties": {
              "amount_minor": {
                "minimum": 1
              }
            },
            "not": {
              "anyOf": [
                {
                  "required": ["billing_interval"]
                },
                {
                  "required": ["included_units"]
                },
                {
                  "required": ["validity_days"]
                }
              ]
            }
          }
        },
        {
          "if": {
            "properties": {
              "pricing_model": {
                "const": "subscription"
              }
            },
            "required": ["pricing_model"]
          },
          "then": {
            "required": ["billing_interval", "included_units"],
            "properties": {
              "amount_minor": {
                "minimum": 1
              }
            },
            "not": {
              "anyOf": [
                {
                  "required": ["usage_quantity"]
                },
                {
                  "required": ["validity_days"]
                }
              ]
            }
          }
        }
      ],
      "additionalProperties": false
    },
    "capabilities": {
      "type": "object",
      "required": [
        "sync",
        "streaming",
        "async_tasks",
        "cancellation",
        "attachments",
        "feedback"
      ],
      "properties": {
        "sync": {
          "description": "Supports synchronous JSON terminal results.",
          "type": "boolean"
        },
        "streaming": {
          "description": "Supports Server-Sent Events streaming.",
          "type": "boolean"
        },
        "async_tasks": {
          "description": "Supports asynchronous task acceptance and polling.",
          "type": "boolean"
        },
        "cancellation": {
          "description": "Reserved in V1 and must be false.",
          "const": false
        },
        "attachments": {
          "description": "Accepts user-uploaded file parts from Pond.",
          "type": "boolean"
        },
        "feedback": {
          "description": "Reserved in V1 and must be false.",
          "const": false
        }
      },
      "anyOf": [
        {
          "properties": {
            "sync": {
              "const": true
            }
          }
        },
        {
          "properties": {
            "streaming": {
              "const": true
            }
          }
        },
        {
          "properties": {
            "async_tasks": {
              "const": true
            }
          }
        }
      ],
      "additionalProperties": false
    },
    "mediaModes": {
      "type": "array",
      "minItems": 1,
      "uniqueItems": true,
      "items": {
        "type": "string",
        "minLength": 1
      }
    },
    "limits": {
      "type": "object",
      "required": [
        "max_request_bytes",
        "max_attachment_bytes",
        "max_run_seconds"
      ],
      "properties": {
        "max_request_bytes": {
          "description": "Maximum /runs request-body size accepted by the Agent.",
          "type": "integer",
          "minimum": 1
        },
        "max_attachment_bytes": {
          "description": "Maximum size of one user-uploaded input attachment.",
          "type": "integer",
          "minimum": 1
        },
        "max_run_seconds": {
          "description": "Maximum execution duration accepted by the Agent.",
          "type": "integer",
          "minimum": 1
        }
      },
      "additionalProperties": false
    }
  }
}

The outer schema intentionally validates metadata only as an object. Pond evaluates its fields independently during publishing-page import: invalid scalar values and invalid demo or FAQ items are ignored, while an invalid pricing collection is omitted as a whole. The importableMetadata definitions document importable shapes but are not part of protocol compatibility.

The schema validates action objects but cannot enforce unique values of actions[].id. Action IDs must still be unique within the manifest.


Did this page help you?