Skip to content

Gateway has no first-class local/terminal channel — you must stand up a remote bot just to talk to your own agent #4455

Description

@MervinPraison

Summary

The gateway multiplexes nine messaging platforms behind one process, one identity resolver, one delivery router and one session store — but there is no way to reach that running agent from your own terminal as a channel. Every built-in channel is a remote transport (Telegram, Discord, Slack, WhatsApp, Signal, Linear, Email, AgentMail, Webhook). To hold a conversation with your gateway agent you must first register a real remote bot: obtain a provider token, expose a webhook or run a poller, and configure allowlists — even when all you want is to try the agent locally, or run a single-user personal agent on your own box.

praisonai chat is not this: it spins up a separate process with its own session and does not join the gateway's live multiplexed session, identity, memory, delivery or pairing. So there is no supported way to (a) develop and smoke-test the gateway in seconds, or (b) start a conversation at your desk and continue it from your phone within one process and one session. A first-class local (terminal) channel closes both, and completes the "one agent, every surface, shared memory" promise the gateway is otherwise built for.

Current behaviour

The channel registry ships nine remote platforms and no local one:

# src/praisonai-bot/praisonai_bot/bots/_registry.py:60
_BUILTIN_PLATFORMS = {
    "telegram": _telegram_loader,
    "discord": _discord_loader,
    "slack": _slack_loader,
    "whatsapp": _whatsapp_loader,
    "linear": _linear_loader,
    "email": _email_loader,
    "agentmail": _agentmail_loader,
    "webhook": _webhook_loader,
    "signal": _signal_loader,
}

Everything a local channel needs already exists. The core protocol is a clean four-method contract with a declarative capability descriptor — a terminal adapter is a natural fit (live in-place edit in a TTY, no markdown dialect, no rate limit, no webhook):

# src/praisonai-agents/praisonaiagents/bots/base.py:341
class BasePlatformAdapter(ABC):
    async def connect(self, *, is_reconnect: bool = False) -> bool: ...
    async def disconnect(self) -> None: ...
    async def send(self, chat_id, content, *, reply_to=None, metadata=None): ...
    async def get_chat_info(self, chat_id) -> Dict[str, Any]: ...
# src/praisonai-bot/praisonai_bot/bots/botos.py:28  (docstring example)
botos = BotOS(agent=agent, platforms=["telegram", "discord"])   # no "local"

The registry is already extensible (register_platform(...) at _registry.py:422, praisonai.channels entry points, and single-file drop-in channels), and the shared machinery a local channel would slot into is fully built: StoreBackedIdentityResolver keys history on a resolved unified id, DeliveryRouter already resolves origin/<platform>/<platform>:<chat> targets, and _mirror.py mirrors cross-channel context. What is missing is simply a concrete terminal adapter and its registry entry — so none of the continuity, delivery or session infrastructure can be reached from the operator's own shell.

Consequence: the minimum viable way to converse with a gateway agent is "create a Telegram bot". There is no zero-setup local surface, and no single-process path for "start locally, continue on a remote channel".

Desired behaviour

A built-in local terminal channel, registered exactly like the others, that:

  • reads stdin / writes stdout with a small TTY renderer (in-place edit where the terminal allows, honouring PlatformCapabilities);
  • joins the same BotOS/gateway session, identity resolver, delivery router and mirror as every other channel, so a conversation begun in the terminal continues seamlessly on Telegram (and vice versa) under one resolved session id;
  • is owner-trusted by default (bypasses pairing/allowlists for the local operator), so it needs no tokens or config to work.

Then:

BotOS(agent=agent, platforms=["local"])          # chat in your terminal, no tokens
# gateway.yaml — local channel alongside remote ones
channels:
  local: {}
  telegram: { token: "..." }
praisonai-bot gateway start   # terminal channel live next to Telegram; deliver="local" already resolves

Layer placement

  • Primary layer: wrapper (praisonai-bot) — the gap is a concrete transport adapter plus a registry entry; the heavy I/O (TTY loop, stdin handling) belongs beside the other channel implementations, not in the protocol core.
  • Why not core: the core already provides everything needed (BasePlatformAdapter, PlatformCapabilities, identity/delivery/session protocols); adding a concrete terminal adapter with real I/O to core would violate the protocol-driven-core rule.
  • Why not tools: a channel is persistent gateway transport with its own lifecycle, not an agent-callable action taken mid-task.
  • Why not plugins: this is a transport, not a lifecycle guardrail, policy or cross-cutting hook.
  • Secondary touch (optional): core — only if a local value needs adding to any default platform enumeration; wrapper CLI onboarding (gateway onboard) to list the local channel; optionally a gateway chat convenience verb that attaches a terminal channel to a running gateway.
  • 3-way surface (CLI + YAML + Python): yes — Python BotOS(platforms=["local"]), YAML channels.local, CLI gateway start (and an optional gateway chat).

Proposed approach

  • Extension point: a new built-in channel adapter registered through the existing BotPlatformRegistry.
# src/praisonai-bot/praisonai_bot/bots/local.py  (new)
class LocalBot(BasePlatformAdapter):
    """Terminal (stdin/stdout) channel. Owner-trusted; no tokens.
    Joins the same session/identity/delivery as remote channels."""
    platform = "local"
    capabilities = PlatformCapabilities(
        supports_edit=True, supports_typing=False,
        markdown_dialect="plain", needs_rate_limit=False,
        accepts_webhooks=False,
    )
    async def connect(self, *, is_reconnect=False) -> bool: ...
    async def disconnect(self) -> None: ...
    async def send(self, chat_id, content, *, reply_to=None, metadata=None): ...
    async def get_chat_info(self, chat_id): ...

# src/praisonai-bot/praisonai_bot/bots/_registry.py
_BUILTIN_PLATFORMS = { ..., "local": _local_loader }

Resolution sketch

# Before (today) — to talk to your gateway agent at all:
#   1. Create a Telegram bot, copy the token
#   2. channels: { telegram: { token: "123:abc" } } + allowlist your user id
#   3. praisonai-bot gateway start
#   (praisonai chat exists but is a SEPARATE process/session — no gateway
#    continuity, identity, delivery, pairing or cross-channel mirror.)

# After (proposed):
botos = BotOS(agent=agent, platforms=["local"])   # converse immediately, zero setup
# or, alongside remote channels in one process:
#   channels: { local: {}, telegram: { token: "..." } }
# Start a thread in the terminal, reply to it later from Telegram — one
# resolved session, shared memory, because the local channel uses the same
# identity resolver + delivery router + mirror as every other channel.

Severity

High — this is a default-path ease-of-use and adoption gap. The lowest-friction way to converse with a gateway agent currently requires standing up an external bot with tokens and allowlists; there is no zero-setup local surface for development or single-user use, and no single-process path for cross-channel continuity between the terminal and a remote platform. It blocks no existing platform, so it is High rather than Critical.

Validation

Confirmed by reading:

  • src/praisonai-bot/praisonai_bot/bots/_registry.py_BUILTIN_PLATFORMS (line 60) lists nine remote platforms and no local/terminal/cli; ls bots/ shows no local adapter file. Registration is already extensible (register_platform at line 422; praisonai.channels entry points; single-file drop-in discovery).
  • src/praisonai-agents/praisonaiagents/bots/base.py:341BasePlatformAdapter four-method contract a terminal adapter would implement; PlatformCapabilities (protocols.py:71) already models the flags a TTY needs.
  • src/praisonai-bot/praisonai_bot/bots/botos.pyBotOS(agent=..., platforms=[...]) shortcut (class at line 77; example at line 28) has no local option.
  • Shared continuity infra a local channel would reuse but cannot today: StoreBackedIdentityResolver (bots/_identity.py), DeliveryRouter origin/<platform> grammar (bots/delivery.py), cross-channel mirror_to_session (bots/_mirror.py).

Metadata

Metadata

Assignees

No one assigned

    Labels

    claudeAuto-trigger Claude analysisdocumentationImprovements or additions to documentationenhancementNew feature or requestgateway

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions