Runtime contract
The same provider-neutral pipeline handles every supported channel:- An opaque webhook URL resolves exactly one project Channel.
- The official provider adapter verifies the untouched request.
- Polpo normalizes the message, identity, thread, reply context, and media.
- The Channel route selects an enabled agent.
- A stable Session preserves conversation history for that external user and thread.
- The agent response is delivered through the same official adapter.
Tool access
An agent can definechannels.allowedTools for every messaging Channel using
that agent. Each Channel Route can narrow access further with its own optional
allowedTools list. Omitting either policy inherits the upper-level policy; an
explicit empty list denies every tool at that level.
For an inbound Channel turn, effective access is:
allowedTools with disposition: "dispatch"
to apply a tenant-, site-, entitlement-, or grant-specific restriction. A
server-side client-tool handler may return allowedTools for the continuation.
Both are trusted, immutable restrictions and can never expand access.
If a Channel preflight starts a Project Loop, Polpo preserves the Session,
canonical history, user, scope, metadata, and trusted grants, then recalculates
the tool policy for Loop mode. The Channel and Route restrictions do not carry
into the Loop.
Trusted application identity
A provider user ID identifies a Slack, Telegram, Discord, or WhatsApp account; it does not prove which user, tenant, or resource that person can access in your application. Configure an optional server-side identity resolver when Channel turns need application-scoped authorization. Polpo calls the resolver after webhook verification and before route selection, Session creation, or model execution. Store the resolver bearer secret in an active project Connection, then update the Channel:Authorization: Bearer ...; the token is never stored in Channel config.
Production resolver endpoints must be public HTTPS URLs. Redirects, private
network destinations, oversized responses, invalid JSON, and timeouts fail
closed.
Resolver request:
channelId is the provider-neutral Chat SDK channel identifier.
installationId is the Polpo Channel resource UUID. Use installationId in
management and outbound paths such as POST /v1/channels/:installationId/send;
do not use the provider channel identifier in that URL.
Dispatch an authorized turn:
user is the stable application user. Optional scope.key and scope.version
partition the provider thread into an application context such as Personal,
Family, or Work. Changing either value creates or resumes a different Session;
returning to a previous pair resumes that pair’s Session without leaking history
across contexts.
Or consume a pairing/setup message before it reaches an agent:
reply to consume the event silently. A consumed event does not select a
route, create or resume a Session, invoke Memory, call a model, or run tools.
Dispatch metadata is available only through immutable custom-tool invocation
context and hidden bindings. Scope is available as invocation.scope.key and
invocation.scope.version; metadata remains under invocation.metadata. These
trusted values are not copied into model arguments. Private metadata is not copied into Session
metadata, Channel events, or loop checkpoints. A durable loop resume calls the
resolver again and verifies that the application user and scope still match.
Exact provider retries are deduplicated. A completed consume response is not
resolved or sent twice; a previously failed event can be reclaimed and
re-authorized.
Create a Channel
- Open an agent and select Channels.
- Select a provider and create or reuse its project Connection.
- Enter the provider channel, chat, application, or phone-number identifier.
- Configure message coordination, response delivery, typing, and audio behavior.
- Complete the provider webhook setup shown in the dashboard.
- Send a test message and inspect the Channel events.
Conversation behavior
Incoming messages can run concurrently, queue, debounce, burst, or drop according to the Channel policy. Burst mode combines rapid messages into one turn before agent execution. Queue and drop policies are bounded so one thread cannot consume unbounded memory. Response delivery is independent from inbound coordination:- Single keeps one logical response and splits only at provider hard limits.
- Conversational splits long output at semantic boundaries using the target length and maximum-message preference configured for the Channel.
Read and typing lifecycle
Read acknowledgement and typing are independent Channel policies:readReceipt is off or immediate. typing is off, immediate, or
before-delivery. The last mode emits typing immediately before each progress
or terminal delivery, which avoids presenting a short-lived typing indicator
during a long Project Loop. Providers without the corresponding capability
ignore the signal. Signal failures are observable but never change execution or
delivery results. Existing Channels without activity retain their legacy
typingEnabled behavior.
Acknowledge a long continuation
A server-side Channel client-tool handler can return an acknowledgement together with the tool result. Polpo delivers it before starting the configured Project Loop, records it in the canonical Session, and deduplicates provider delivery across retries and reconnects.acknowledgement accepts non-empty text and up to ten provider-neutral
actions. An acknowledgement is sent only after the handler succeeds. If its
delivery fails, Polpo does not start the Loop.
Present a Project Loop result
Project Loops can keep structured output for subsequent steps and expose a separate terminal presentation without an extra Agent step:data is persisted with the Loop run and remains available for inspection.
presentation.text is the only terminal text delivered by a Channel.
Missing bindings, an empty/non-string presentation, malformed actions, or an
unsafe URL fail the Loop deterministically instead of leaking serialized JSON
to the user.
Supported actions are:
open_url opens an HTTPS URL. postback re-enters the Channel as a typed action
event in the same provider thread. Adapters use native controls when available
and preserve an intelligible fallback on providers with fewer capabilities.
Reply while a Loop is active
For a fail-fast conversational policy, configure the Channel to process inbound turns concurrently and reject a new turn when the canonical Session already has a non-terminal Project Loop:scope.key and scope.version. Polpo handles the
conversational response; the application should retain its own domain lock for
the protected resource or working copy.
Media and audio
Official adapters normalize text, reply context, images, audio, video, and files. Polpo downloads media with scoped provider credentials, enforces per-file and per-turn limits, and passes supported content to the model. Unsupported or failed attachments are recorded without silently changing tenant routing. For application-managed assets, configure a trusted attachment handler. Polpo runs it after trusted identity resolution but before Session creation and model execution:multipart/form-data with a binary file part and a
JSON-encoded context part. context contains the stable idempotency key,
Channel/message descriptor, attachment metadata, and trusted invocation
user, metadata, and scope. Polpo authenticates the call with the selected
same-project Connection and sends the same key in the Idempotency-Key header.
The response contract is intentionally small and strict:
., _, :, /, or -. Only this reference enters model input. Bytes,
provider identifiers, bearer credentials, and trusted grants remain outside the
prompt. Count, MIME, declared and downloaded size, timeout, redirect, endpoint,
response-size, and schema violations fail closed before model execution. Without
an attachment handler, Polpo preserves its normal image, audio-transcription,
and workspace-file behavior.
With Deepgram configured, audio can be transcribed before agent execution. Voice
responses can use Deepgram or ElevenLabs. Response modality is explicit: text or
audio, never an accidental duplicate of both. Provider capabilities and selected
fallbacks are visible in the Channel configuration.
Outbound delivery
Send proactive text through the data API:idempotencyKey to deduplicate one logical send. Polpo returns the
prior delivery for a completed duplicate. If delivery fails, inspect the stored
outcome before retrying with a new key; the original key is never replayed
automatically because one or more provider messages may already have arrived.
Provider-specific targeting:
POST /v1/channels/management/{channelId}/test uses the same verified
installation and delivery path with a default test message. WhatsApp direct
message tests require a recipient:
POST /v1/channels/management/{channelId}/templates:
WhatsApp Cloud API
Create a Meta app with the WhatsApp product. Polpo needs:
Save these values through the WhatsApp Connection flow. Register the exact
opaque callback URL shown by Polpo in Meta, subscribe the app to WhatsApp message
webhooks, and use the same verify token. The GET challenge and signed POST body
are verified before project routing or agent execution.
The Channel remains
pending while this provider action is incomplete. Polpo
marks it active only after the opaque callback returns the exact Meta
challenge using the configured verify token.
Inbound routing is scoped by the opaque installation URL, project, Connection,
Channel, route, external user, and provider thread. Duplicate provider events are
deduplicated using shared transport state. Media downloads use the scoped Cloud
API token. Outbound sends go through the configured phone number ID and are
idempotently recorded in Channel events and deliveries.
delivery.completed currently means Meta accepted the outbound API request.
The official Chat SDK WhatsApp adapter pinned by Polpo does not yet expose
Meta statuses callbacks as normalized delivered, read, or failed receipts.
Do not interpret the stored status as proof that the recipient read the message.