> ## Documentation Index
> Fetch the complete documentation index at: https://praison.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Local (Terminal) Channel

> Chat with your gateway agent from your own terminal — no tokens, no webhooks, no allowlist

The local channel lets you talk to your gateway agent from your own terminal — the same live session your Telegram or Discord users reach.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "Local Terminal Channel"
        Stdin[⌨️ stdin] --> Local[💻 LocalBot]
        Local --> Session[🧠 Shared Session]
        Session --> Agent[🤖 Agent]
        Agent --> Session
        Session --> Stdout[📺 stdout]
        Session -.same session.-> Remote[📱 Telegram / Discord]
    end

    classDef input fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef process fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef agent fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef output fill:#10B981,stroke:#7C90A0,color:#fff

    class Stdin,Stdout input
    class Local,Session process
    class Agent agent
    class Remote output
```

## Quick Start

**1. One command (CLI)**

The quickest way to try the local channel — no Python, no token:

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai bot run --platform local
```

Fields declared by the channel's descriptor are also CLI flags — e.g. `praisonai bot run --platform local --prompt "me> "`. `bot run` starts any registered platform by name. See [`praisonai bot run`](https://praison.ai/docs/features/messaging-bots#registered-but-verb-less-platforms-praisonai-bot-run) for the full flag list.

**2. Chat in two lines**

The local channel is owner-trusted — no token, no webhook, no setup.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent
from praisonai_bot.bots import BotOS

agent = Agent(name="assistant", instructions="Be helpful")
BotOS(agent=agent, platforms=["local"]).run()
```

**3. Custom prompt**

Set your own input prompt in Python or `gateway.yaml`.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent
from praisonai_bot.bots import BotOS

agent = Agent(name="assistant", instructions="Be helpful")
BotOS(agent=agent, platforms={"local": {"prompt": ">>> "}}).run()
```

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# gateway.yaml
channels:
  local:
    prompt: ">>> "
```

**4. Terminal + remote in one process**

One process, two surfaces, one shared session — chat locally while Telegram users reach the same agent.

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# gateway.yaml
channels:
  local: {}
  telegram: { token: "${TELEGRAM_BOT_TOKEN}" }
agents:
  assistant:
    instructions: "You are a helpful assistant."
```

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai-bot gateway start
```

> [!NOTE]
>
> `channels: local: {}` is legal without a `token:`. The `local` channel is on the gateway's **token-free allowlist** (`email`, `agentmail`, `signal`, `local`), so it starts cleanly at launch **and** after a `gateway reload` — the config validator never marks it *degraded* for a missing token. See [Degraded Channel Isolation](https://praison.ai/docs/features/gateway-degraded-channels.md).

***

## User Interaction

Type a line, get a reply. `Ctrl-D` exits cleanly; built-in commands start with `/`.

```
$ praisonai-bot gateway start
Local channel reading from stdin (Ctrl-D to exit; /stop cancels the current run)
you> hi
Hello! How can I help?
you> /status
Bot: assistant · channel: local · uptime: 12s · running
you> /new
Session reset. Send a message to start a new conversation.
you> ^D
Local channel stopped
$
```

The terminal also survives a config reload — a `gateway reload` keeps the `local` channel healthy instead of dropping it into a degraded state:

```
$ praisonai-bot gateway reload
INFO  Channel 'local' hot-reloaded (token-free)
you> still here?
Yes, still here.
```

***

## Testing / CI — Offline Harness

The `local` channel doubles as a credential-free, network-free test harness for a full bot turn. Pass any file-like `stdin`/`stdout` to drive the real hook → command → session → send path and assert the reply.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import asyncio
import io
from praisonaiagents import Agent
from praisonai_bot.bots import LocalBot

agent = Agent(name="assistant", instructions="Be helpful")

out = io.StringIO()
bot = LocalBot(
    agent=agent,
    stdin=io.StringIO("What's 2+2?\n"),  # one turn, then EOF
    stdout=out,
)

asyncio.run(bot.start())           # returns at EOF
assert "4" in out.getvalue()       # real reply, real pipeline
```

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant Test as 🧪 Test / CI
    participant In as 📥 io.StringIO (stdin)
    participant Bot as 💻 LocalBot
    participant Session as 🧠 BotSessionManager
    participant Agent as 🤖 Agent
    participant Out as 📤 io.StringIO (stdout)

    Test->>In: write "What's 2+2?\n"
    In->>Bot: readline()
    Bot->>Session: chat(local-operator, "What's 2+2?")
    Session->>Agent: run turn (optionally ScriptedModel)
    Agent-->>Session: "4"
    Session-->>Bot: "4"
    Bot->>Out: write("4\n")
    Test->>Out: assert "4" in getvalue()
    Note over Test,Out: sys.stdin / sys.stdout are not touched
```

What runs, end to end:

* `MESSAGE_RECEIVED` hook
* Built-in command dispatch (`/help`, `/status`, …)
* `BotSessionManager.chat` — the shared session, identity resolver, and admission ladder
* `MESSAGE_SENDING` / `MESSAGE_SENT` hooks
* `send_message` → your `stdout`

No channel token is needed, and `sys.stdin` / `sys.stdout` are never monkey-patched. The input prompt (`you> `) is written to the same `stdout`, so assert with `in` rather than equality.

The agent still calls its own model. For a **fully offline** run, swap the LLM for `ScriptedModel` from [Offline Testing](https://praison.ai/docs/features/offline-testing.md) — the two features compose directly.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import asyncio
import io
from praisonaiagents import Agent, ScriptedModel
from praisonai_bot.bots import LocalBot

model = ScriptedModel(["4"])
agent = Agent(instructions="You are a math bot.", llm=model)

out = io.StringIO()
bot = LocalBot(agent=agent, stdin=io.StringIO("What's 2+2?\n"), stdout=out)
asyncio.run(bot.start())
assert "4" in out.getvalue()
```

### Which mode should I use?

| Scenario | How to run `LocalBot` |
| - | - |
| Chatting at your own terminal | `BotOS(agent=agent, platforms=["local"]).run()` — defaults use `sys.stdin`/`sys.stdout` |
| Scripted demo / recorded transcript | `LocalBot(agent=agent, stdin=io.StringIO("..."), stdout=open("transcript.txt", "w"))` |
| Unit test / CI assertion | `LocalBot(agent=agent, stdin=io.StringIO("..."), stdout=io.StringIO())` + `ScriptedModel` for a fully offline run |

***

## How It Works

Each line of stdin becomes one agent turn through the shared `BotSessionManager`, and the reply is written to stdout.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User
    participant LocalBot
    participant Session as Shared Session
    participant Agent

    User->>LocalBot: types a line (stdin)
    LocalBot->>Session: chat(local-operator, text)
    Session->>Agent: run turn
    Agent-->>Session: reply
    Session-->>LocalBot: response
    LocalBot-->>User: writes to stdout
    Note over LocalBot,Session: same session id is reachable<br/>as deliver="local:local"
```

The read loop runs on a dedicated daemon thread so other channels keep running while it waits for input.

### When does it exit?

| Signal | Behaviour |
| - | - |
| `Ctrl-D` / EOF | Clean end of the terminal session |
| `/stop` | Cancels the *current* agent run, keeps the read loop alive |
| `/new` | Resets the local session; next line starts a new conversation |
| Blank line | Ignored |

***

## Built-in Commands

| Command | Description |
| - | - |
| `/status` | Show bot status and info (agent, uptime, running) |
| `/new` | Reset conversation session |
| `/help` | Show the help message (including any custom commands) |
| `/stop` | Cancel the current agent run |

***

## Configuration Options

The local channel is token-free and declares a single config field.

| Option | Type | Default | Description |
| - | - | - | - |
| `prompt` | `str` | `"you> "` | Terminal input prompt |

Constructor kwargs on `LocalBot`: `token=""` (unused, kept for parity), `agent`, `config`, `prompt`, `stdin`, `stdout`.

Two of those are Python-constructor-only — they are **not** `gateway.yaml` fields:

| Kwarg | Type | Default | Description |
| - | - | - | - |
| `stdin` | file-like | `None` → `sys.stdin` | Where the read loop reads lines from. Any object with a `.readline()` method works (`io.StringIO`, a `pytest` fixture, a pipe). |
| `stdout` | file-like | `None` → `sys.stdout` | Where the prompt and outbound replies are written. Any object with `.write()` + `.flush()` works. |

When left as `None`, the fallback to `sys.stdin` / `sys.stdout` is looked up on **each call**, not cached at construction — so swapping `sys.stdout` after building the bot is still picked up.

> [card]
>
> Python adapter class
>
> [Open](https://praison.ai/docs/sdk/reference/python/classes/LocalBot)

> [card]
>
> The capability flags a local channel declares
>
> [Open](https://praison.ai/docs/sdk/reference/python/classes/PlatformCapabilities)

***

## Capabilities

The local channel declares a TTY-honest capability set so shared engines degrade correctly.

| Capability | Value | Why |
| - | - | - |
| `supports_edit` | `False` | A printed TTY line is not edited in place; streaming falls back to plain appends |
| `supports_typing` | `False` | No "typing…" indicator in a terminal |
| `needs_rate_limit` | `False` | No provider quota to respect |
| `accepts_webhooks` | `False` | Pull-based, not push |
| `supports_media` | `False` | Text only |

The channel's `system_prompt_hint` tells the agent about these constraints: *"You are replying in a local terminal: keep replies plain text; there is no markdown rendering, no message editing and no rate limit."*

> [card]
>
> How capabilities drive uniform chunking, streaming, and rate limiting
>
> [Open](https://praison.ai/docs/features/bot-platform-capabilities.md)

***

## Trust Model

The local operator is owner-trusted: it bypasses pairing and allowlists because it's your own machine — which is why no token is needed. Remote channels, by contrast, require pairing before a stranger can talk to your agent.

* `local` is on the gateway's **token-free allowlist** (`email`, `agentmail`, `signal`, `local`) — a `local` channel without a `token:` is fully healthy, **not** degraded, both at launch and after hot reload.

The terminal maps to a stable `local-operator` user id and `local` chat id, so it resolves to one durable session across restarts.

***

## Cross-Channel Continuity

A message begun in the terminal continues on a remote platform under one resolved session id via the shared identity resolver and delivery router.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant Terminal as 💻 Terminal
    participant Resolver as 🧠 Identity Resolver
    participant Agent
    participant Telegram as 📱 Telegram

    Terminal->>Resolver: message (local-operator)
    Resolver->>Agent: resolved session id
    Agent-->>Telegram: reply on the same session
    Note over Resolver,Telegram: StoreBackedIdentityResolver +<br/>DeliveryRouter unify both surfaces
```

> [card]
>
> Gateway Session Continuity
>
> [Open](https://praison.ai/docs/features/gateway-session-continuity.md)

> [card]
>
> Cross-Platform Mirror
>
> [Open](https://praison.ai/docs/features/cross-platform-mirror.md)

### `deliver="local"` — routing outbound to the terminal

Any scheduled job, hook, or agent tool that emits `deliver="local:local"` writes to the terminal exactly like `deliver="telegram:12345"` writes to Telegram.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
await botos._delivery_router.deliver("local:local", "proactive-ping")
```

> [card]
>
> Proactive Delivery
>
> [Open](https://praison.ai/docs/features/proactive-delivery.md)

> [card]
>
> Scheduler Delivery
>
> [Open](https://praison.ai/docs/features/scheduler-delivery.md)

***

## Common Patterns

1. **Dev / smoke-test** a gateway agent before wiring any remote channel.
2. **Single-user personal agent** on your own box — no bot registration.
3. **Start-here, continue-there** — chat locally at your desk, keep the same session going from your phone via Telegram.
4. **Proactive terminal notice** — a scheduled job that pings `deliver="local:local"` prints straight into the operator's terminal.
5. **Offline harness in tests/CI** — assert that a real bot turn (hook → session → agent → send) produces the expected reply, with no token, no network, and no `sys` monkey-patching.

***

## Best Practices

> [accordion] Prefer gateway start for cross-channel continuity
>
> `praisonai-bot gateway start` joins the gateway's live multiplexed session, identity resolver, and delivery router. `praisonai chat` runs a *separate* process with a *separate* session and does not.

> [accordion] Use /new to reset — not restart
>
> Resetting the session is cheaper than restarting the process, and any other running channels stay live.

> [accordion] Keep replies plain text in local-only agents
>
> A TTY has no markdown rendering. If you also serve remote channels from the same agent, the `system_prompt_hint` already tells the agent this.

> [accordion] Ctrl-D is the clean way to exit
>
> The read loop is intentionally not supervised (`supervised_inbound = False`), so EOF ends cleanly and the process exits without a reconnect storm.

> [accordion] Prefer injected streams over monkey-patching sys
>
> Tests that reassign `sys.stdin`/`sys.stdout` leak into the rest of the process (and other tests). The `stdin`/`stdout` kwargs give you the same reach into the read-and-write path without that cost — a cancelled test cannot leave the process in a half-swapped state.

***

## Related

> [card] [Gateway & Control Plane](https://praison.ai/docs/gateway.md)
>
> The gateway the local channel joins

> [card] [Channels Gateway](https://praison.ai/docs/features/channels-gateway.md)
>
> Connect agents to every built-in channel

> [card] [Multi-Channel Bots](https://praison.ai/docs/features/multi-channel-bots.md)
>
> Run local + remote channels together

> [card] [Bot Chat Commands](https://praison.ai/docs/features/bot-commands.md)
>
> `/status`, `/new`, `/help`, `/stop`

> [card] [Offline Testing (ScriptedModel)](https://praison.ai/docs/features/offline-testing.md)
>
> Pair with `ScriptedModel` for a fully offline bot turn


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.