Skip to main content

cogno-gateway

Messaging transport edge for the Cogno cognitive pipeline — Telegram / WhatsApp (Evolution) / Web channel adapters behind a normalized, content-typed message model.

cogno-gateway is the sibling of cogno-vox: where vox converts audio ⇆ text, gateway converts channel messages ⇆ a normalized model. Pure transport — it verifies a webhook, parses the provider payload, fetches media, and sends a reply. It does not orchestrate the pipeline, read a database, or run an HTTP server.

Status: alpha — Telegram, WhatsApp (Evolution), and Web channels + the unit suite are in place.

webhook → verify + parse_inbound → [audio? fetch_media → vox STT] → PIPELINE (host)
        → reply → [vox TTS] → send

One port, pluggable channels

Every adapter satisfies the Channel protocol — verify · parse_inbound · fetch_media · send — so the host treats Telegram, WhatsApp, and Web uniformly:

from cogno_gateway import create_channel, ChannelConfig, OutboundMessage

tg = create_channel("telegram", ChannelConfig(token=bot_token, secret=hook_secret))
wa = create_channel("whatsapp", ChannelConfig(base_url=evo_url, token=apikey, instance="tenant1"))
web = create_channel("web")     # the cogno-cloud-ui {session_id, message} contract

msg = tg.parse_inbound(payload)                 # → InboundMessage (content-typed)
await tg.send(msg.sender, OutboundMessage(text="resposta"))   # auto-chunked

Rich, content-typed messages

InboundMessage.kind is a MessageKind: TEXT · IMAGE · AUDIO · VIDEO · DOCUMENT · LOCATION · REACTION · STICKER · INTERACTIVE. So a host handles reactions (emoji + target message id), media (a MediaRef resolved lazily via fetch_media — e.g. to feed audio to cogno-vox), quick-reply buttons (send OutboundMessage(buttons=[Button(...)]); the tap returns kind=INTERACTIVE with selection.id), replies, and plain text uniformly across channels.

1:1 by design — group fields reserved, groups still dropped

Every adapter drops group traffic at parse (Telegram group/supergroup, Evolution @g.us, WhatsApp Cloud a message carrying a group_id), so a host only ever receives 1:1 messages. InboundMessage already carries the fields a group needs — conversation_id (where to answer), author (who spoke), mentions_bot, reply_to_bot, author_is_bot — keyword-only and at the 1:1 default; no adapter fills them yet, and a 1:1 message prints and compares exactly as before. A Cloud group_id that names no group (absent, null, empty, blank) stays 1:1.

Markup per channel, decided once

Bold is written differently on every transport: *one asterisk* on WhatsApp, **two** in markdown, and nothing at all on a surface that renders no markup. Write markdown and the adapter converts on the way out (to_channel_markup) — the gateway is the only layer that knows which channel the text is going to. It is a delimiter swap, never a markdown renderer: arithmetic, globs and unpaired markers reach the contact exactly as written. Each cell of the table carries the reason it holds the value it does, so it can be turned when the surface changes.

Reaching the provider: two budgets, and a family that answers

A reply is lost the same way whether the provider refused it or the connection never finished being made. The second one had no code: every adapter passed ChannelConfig.timeout to httpx as a single number, which httpx spreads over connect, read, write and pool alike — so an address that never answers could spend the whole budget a slow provider was meant to have. The two are now separate: connect is short (5 s, COGNO_HTTP_CONNECT_TIMEOUT), read stays where you set it.

The address selection is explicit too, and it is not the happy-eyeballs you may assume is under you. anyio does race the TCP connect across families (RFC 6555) — but httpcore then runs the TLS handshake on the single stream that won that race, with no address left to go back for. An IPv6 address that completes the TCP handshake and then black-holes the TLS one wins the race precisely because it answered fast, and hangs there while the working IPv4 address is never tried. So cogno_gateway.net walks the families itself, across both halves of the bind: one address per family, IPv6 then IPv4, each attempt on the short connect budget, falling through on a dead connect or a dead handshake. COGNO_HTTP_IP_FAMILY=auto|ipv4|ipv6 (default auto) if you would rather decide. Certificates and SNI still name the provider — the address is chosen below the origin, never by rewriting the URL.

Decoupled from cognition & audio

The gateway imports neither cogno-anima nor cogno-vox. Inbound audio comes back as bytes (fetch_media) for the host to run through vox STT; a voice reply is just OutboundMessage(audio=tts_bytes). The host wires the two edges to the pipeline.

WhatsApp: two providers, one port

WhatsApp is pluggable — the host picks per tenant:

  • EvolutionChannel ("evolution") — Evolution API, unofficial (QR/Baileys), free, full-featured; good for dev/testing.
  • WhatsAppCloudChannel ("whatsapp_cloud") — the official Meta Cloud API, for production/compliance: HMAC webhook verification, free-form replies within the 24h service window, and template messages for proactive sends outside it (OutboundMessage(template=Template(...))).

Pairing a WhatsApp account (Evolution QR)

EvolutionChannel talks to an instance that already exists. cogno_gateway.provisioning is the half that brings one into existence: create it, hand back the QR to scan, poll the connection state, keep the return address alive, and tear it down.

from cogno_gateway import EvolutionWhatsAppProvisioner

prov = EvolutionWhatsAppProvisioner(
    base_url=evo_url, api_key=apikey,
    webhook_base="https://my.app",                       # the public URL YOU serve
    webhook_secret=secret,                               # → webhook headers.apikey
    instance_template="myapp_{account}",                 # your naming, not ours
    webhook_path_template="/webhook/whatsapp_evo-{account}")

conn = await prov.connect("acct-42")          # → WhatsAppConnection(qrcode_base64=…, status=…)
st   = await prov.status("acct-42")           # → WhatsAppStatus(state="open", webhook_ok=…)
cfg  = ChannelConfig(**prov.channel_credentials("acct-42"))   # → a send-capable EvolutionChannel

account is an opaque key you choose — what it means (a tenant, a workspace, one user), how its instance is named and which URL you serve are your decisions, so they arrive as templates rather than as assumptions baked in here. The defaults are the identity mapping.

status() also probes the return address, which the connection state says nothing about: an instance sits at "open" while every inbound message is delivered to a dead URL. It asks the provider where it will deliver (never our own store — that held the right URL all along), and re-points it when it is stale, but only after proving your public URL answers — an unguarded heal during an outage rewrites every account's webhook to a dead address. InMemoryWhatsAppProvisioner is the deterministic stub for dev/tests.

Registering a Telegram bot's webhook

TelegramChannel verifies X-Telegram-Bot-Api-Secret-Token on every delivery. Something has to have told Telegram to send one — that is cogno_gateway.telegram_provisioning, the Telegram twin of the WhatsApp half above.

from cogno_gateway import TelegramWebhookRegistrar, build_telegram_registrar

reg = TelegramWebhookRegistrar(webhook_base="https://my.app")   # the public URL YOU serve
ok, why = await reg.register(token=bot_token, webhook_key="telegram-acme", secret=hook_secret)
handle = await reg.bot_username(token=bot_token)                # "@acme_bot", or ""
ok, why = await reg.unregister(token=bot_token, drop_pending=True)

webhook_key is an opaque key you choose, rendered into {webhook_base}/webhook/{key}. Failures come back as (False, description) in Telegram's own wording — an admin click must surface the reason, never 500 and never claim "registered".

unregister is the half that is easy to leave out, and its absence is silent: switching a channel off on your side only stops you. Telegram keeps delivering to a URL that now 404s, retries, climbs pending_update_count, and replays the whole backlog the day the channel comes back. drop_pending decides what happens to the queue Telegram already holds (up to 24 h) — discard it and a message sent during a two-minute restart is lost; keep it and a day-old "can you book me for tomorrow at 9?" is answered as if it had just arrived. Nothing at this layer can tell the two apart, so the caller decides. build_telegram_registrar() returns a real registrar when COGNO_PUBLIC_URL (or COGNO_BASE_URL) is set and NullTelegramRegistrar otherwise — which reports why on every call instead of being a None the call site must remember to branch on.

Install

pip install cogno-gateway          # adapters talk to providers over httpx; no web framework
pip install -e ".[dev]"            # tests (provider calls are mocked — no network)

The Cogno ecosystem

cogno-gateway is one organ of Cogno — a family of small, composable, Apache-2.0 libraries that together form a complete conversational-agent platform. Each library owns a single concern and stays infra-agnostic; a host assembles them into a running agent:

The Cogno ecosystem

The open-source libraries are the organs; the host is the body that joins them. Our reference host — cogno-host, with its cogno-ui dashboard — is the private product layer, but it holds no special powers: everything it does rides on the public seams documented in each library's docs/HOST_INTEGRATION.md, so you can assemble a body of your own.

Test

pytest tests/unit -q

Metadata

Release files for cogno-gateway 0.1.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cogno-gateway 0.1.3
File Size Uploaded Provenance
cogno_gateway-0.1.3.tar.gz 61.7 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for cogno-gateway 0.1.3
File Interpreter ABI Platform
cogno_gateway-0.1.3-py3-none-any.whl Python 3 none any Details

Total release size: 128.0 kB

Release files / cogno_gateway-0.1.3.tar.gz

Download URL cogno_gateway-0.1.3.tar.gz
Size 61.7 kB
Tags Source
SHA-256 checksum
How to use checksums
61ecce9fdc6e7e238a29dc8bde1e3331f9d75e824a402fa23fac07d3439db97e
BLAKE2b-256 checksum
How to use checksums
53d5d621bbe4829607659e915782178f30c35a2a945701d6a9ca5cc054634abc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

Release files / cogno_gateway-0.1.3-py3-none-any.whl

Download URL cogno_gateway-0.1.3-py3-none-any.whl
Size 66.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
73576a3bec96fe3861c9298cb477cfb87fc4a3fa11ce9b31ae846dc2f2f45473
BLAKE2b-256 checksum
How to use checksums
4a45e6e6efa6d4c2db4a6bddcc15ff785b03995b22fa44b9c805f82602015e7b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

Security for cogno-gateway 0.1.3

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for cogno_gateway-0.1.3.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.3 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page