# Telegram Gateway Operator Guide
This document is the operator and maintainer guide for the Rust Telegram integration in `claw-code-parity`.
It is written for:
- humans deploying the bot on Unraid or Docker
- future AI agents modifying the deployment
- reviewers trying to understand how the gateway, workers, and manifest fit together
## What Exists
There are three distinct runtime modes:
- `claw-telegram gateway serve`
- production gateway mode
- owns the Telegram bot token
- polls Telegram
- reads the manifest
- creates and reconciles worker containers through the Docker socket
- `claw-telegram standalone serve`
- legacy single-process mode
- one process handles Telegram and the runtime directly
- does not use the gateway/worker split
- `claw-profile-worker serve`
- internal worker process
- one worker per profile
- normally started by the gateway, not by end users
## Architecture
The gateway/worker model is the intended deployment.
Topology:
1. One Telegram bot token
2. One gateway process
3. One manifest file
4. One worker container per profile
Important invariants:
- one Telegram bot token must have one inbound owner
- the gateway is the only process that should poll Telegram
- workers do not talk to Telegram directly
- workers are selected by profile mapping from the manifest
- one profile owns one isolated worker container
- one profile may have multiple channel identities in the future
- today, only Telegram DM identities are implemented
## Core Files And State
Gateway manifest:
- default in-container path: `/appdata/profiles.json`
- default Unraid host path: `/mnt/user/appdata/claw-telegram-gateway/profiles.json`
Gateway runtime state:
- default in-container path: `/appdata/state`
- used for Telegram update offsets and downloaded/generated files staged by the gateway
Generated Unraid worker templates:
- live Unraid directory: `/boot/config/plugins/dockerMan/templates-user/`
Important Unraid fact:
- the current value of each config field is stored in the text body of each `...` element
- replacing the live XML with a blank repo copy can wipe stored values
## Which Template To Use
For the gateway/worker deployment, use the gateway template:
- [claw-telegram-gateway.xml](../rust/crates/claw-telegram/assets/unraid/claw-telegram-gateway.xml)
Do not confuse it with the legacy standalone template:
- standalone mode expects `CLAW_TELEGRAM_BOT_TOKEN`
- gateway mode expects `CLAW_GATEWAY_TELEGRAM_BOT_TOKEN`
If the container logs mention `CLAW_TELEGRAM_BOT_TOKEN`, the container is almost certainly starting in standalone mode instead of gateway mode.
## Required Gateway Settings
The gateway container needs:
- Telegram bot token
- `CLAW_GATEWAY_TELEGRAM_BOT_TOKEN`
- worker API bearer token
- `CLAW_WORKER_AUTH_TOKEN`
- worker image
- `CLAW_GATEWAY_WORKER_IMAGE`
- appdata mount
- host path usually `/mnt/user/appdata/claw-telegram-gateway`
- mounted to `/appdata`
- Docker socket mount
- `/var/run/docker.sock:/var/run/docker.sock`
- Unraid templates mount
- `/boot/config/plugins/dockerMan/templates-user:/unraid/templates-user`
- Docker network
- `claw_gateway`
- `ANTHROPIC_AUTH_TOKEN`
- Claude subscription OAuth token such as `sk-ant-oat...`
- the gateway now sends the extra Claude Code headers required for this path
- `ANTHROPIC_API_KEY`
- standard Anthropic API key
If you use `ANTHROPIC_AUTH_TOKEN`, the gateway workers add the Claude Code OAuth headers and agent identity prefix that `nanobot` also uses for subscription auth.
The gateway copies inherited auth env vars into worker containers according to `CLAW_GATEWAY_INHERITED_ENV`.
## First Boot
Expected first-boot behavior:
1. The gateway starts with `claw-telegram gateway serve`
2. If the manifest does not exist yet, the gateway creates an empty manifest automatically
3. The gateway connects to Telegram
4. Users are rejected until they are mapped in the manifest
The gateway does not auto-map Telegram users from chat messages.
## Manifest Model
The manifest is the source of truth for routing.
Each profile contains:
- `profile_id`
- optional `display_name`
- `worker`
- container name
- host state directory
- host workspace directory
- `channels`
- today: Telegram DM user IDs
Example:
```json
{
"version": 1,
"gateway": {
"worker_image": "git.wylab.me/wylab/claw-telegram:latest",
"worker_network": "claw_gateway",
"template_dir": "/unraid/templates-user",
"template_file_prefix": "claw-worker-",
"template_archive_dir": "/appdata/template-archive",
"inherited_env": ["ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_API_KEY"]
},
"worker_defaults": {
"bind_port": 8080,
"default_cwd": "/workspace",
"permission_mode": "workspace-write",
"model": "claude-opus-4-6",
"host_state_root": "/mnt/user/appdata/claw-workers",
"host_workspace_root": "/mnt/user/appdata/claw-workers"
},
"profiles": [
{
"profile_id": "makar",
"display_name": "Makar",
"worker": {
"container_name": "claw-worker-makar",
"host_state_dir": "/mnt/user/appdata/claw-workers/makar/state",
"host_workspace_dir": "/mnt/user/appdata/claw-workers/makar/workspace"
},
"channels": [
{
"channel": "telegram",
"kind": "dm",
"user_id": 239824268
}
]
}
]
}
```
Validation rules:
- profile IDs must be unique
- worker container names must be unique
- host state directories must be unique
- host workspace directories must be unique
- Telegram user IDs must be unique across all profiles
## Command Reference
The command surface is implemented in [rust/crates/claw-telegram/src/main.rs](../rust/crates/claw-telegram/src/main.rs) and [rust/crates/claw-profile-worker/src/main.rs](../rust/crates/claw-profile-worker/src/main.rs).
### `claw-telegram`
Top-level commands:
- `claw-telegram serve`
- alias for legacy standalone mode
- `claw-telegram standalone serve`
- starts the old monolithic Telegram bot
- `claw-telegram gateway serve`
- starts the gateway mode
- `claw-telegram workers reconcile`
- loads the manifest
- creates missing worker templates
- creates missing worker containers
- restarts stopped workers
- archives removed templates
- removes deleted managed containers
- `claw-telegram profiles add [--user-id ] [--display-name ]`
- creates a new profile
- derives worker container and host paths
- optionally attaches a Telegram user
- saves manifest
- reconciles workers
- `claw-telegram profiles remove `
- removes a profile from the manifest
- reconciles workers
- removes the container
- keeps host state/workspace paths
- `claw-telegram profiles merge `
- moves the source profile channel identities into the target profile
- removes the source profile
- reconciles workers
- keeps source state/workspace paths
- `claw-telegram profiles channel add telegram `
- attaches a Telegram DM identity to an existing profile
- saves manifest
- reconciles workers
- `claw-telegram profiles channel remove telegram `
- removes a Telegram DM identity from whatever profile currently owns it
- saves manifest
- reconciles workers
- `claw-telegram migrate-standalone-registry [registry-path]`
- imports old standalone registry data into the gateway manifest
- then reconciles workers
### `claw-profile-worker`
- `claw-profile-worker serve`
- starts one worker instance
- expects worker env vars to already be present
## Practical Operator Workflows
### Start the gateway
If the container entrypoint is configured correctly, Unraid should start:
```bash
claw-telegram gateway serve
```
### Add the first Telegram user
Example:
```bash
docker exec -it claw-telegram-gateway \
claw-telegram profiles add makar --user-id 239824268 --display-name "Makar"
```
What this does:
1. creates profile `makar`
2. maps Telegram user `239824268`
3. writes the manifest
4. reconciles worker state
5. creates the worker template and worker container
### Add another mapped user to an existing profile
```bash
docker exec -it claw-telegram-gateway \
claw-telegram profiles channel add makar telegram 123456789
```
### Remove a profile
```bash
docker exec -it claw-telegram-gateway \
claw-telegram profiles remove makar
```
### Merge two mistaken profiles
```bash
docker exec -it claw-telegram-gateway \
claw-telegram profiles merge old-profile makar
```
### Force a resync after manual manifest edits
```bash
docker exec -it claw-telegram-gateway \
claw-telegram workers reconcile
```
## Telegram Chat Commands
Once a Telegram user is mapped to a profile, the bot supports:
- `/start`
- `/help`
- `/status`
- `/new`
- `/cancel`
These are user-facing chat commands, not gateway administration commands.
## How Routing Works
When a Telegram DM arrives:
1. the gateway polls Telegram
2. the gateway reads `from.id`
3. the gateway loads the manifest
4. the gateway finds the profile whose `channels` include that Telegram user ID
5. the gateway ensures that profile's worker exists
6. the gateway sends the turn to that worker
7. the worker streams events back
8. the gateway renders those events back to Telegram
If no profile matches that Telegram user ID, the bot replies:
`You are not mapped to a profile in the gateway manifest.`
That message means the gateway is healthy enough to receive updates, but the sender is not registered yet.
## Worker Lifecycle
Workers are managed by the gateway through the Docker socket.
The gateway:
- creates host state/workspace directories
- writes a worker template XML for each profile
- creates the worker container from `CLAW_GATEWAY_WORKER_IMAGE`
- starts it
- health-checks it over HTTP
Current worker startup command:
- entrypoint: `claw-profile-worker`
- args: `serve`
## Unraid-Specific Notes
### Network mode
The gateway container must not run with `--net=none`.
Use:
- `claw_gateway` for the real deployment
Why:
- the gateway needs outbound HTTPS to Telegram
- the gateway also needs network reachability to worker containers
### Docker socket access
The gateway image currently runs as root by default.
Reason:
- the gateway must open `/var/run/docker.sock`
- the current worker bind-mount model also depends on writable host paths without an explicit `PUID`/`PGID` ownership flow
### Live template values
Unraid stores actual values in the live template file under:
- `/boot/config/plugins/dockerMan/templates-user/`
Do not blindly overwrite a configured live template with a blank repo copy after you have entered secrets.
### Gateway template vs standalone template
If you are deploying the gateway/worker architecture:
- use the gateway template
- do not use the legacy standalone template
If you see logs complaining about `CLAW_TELEGRAM_BOT_TOKEN`, you are almost certainly starting the standalone path, not the gateway path.
## Troubleshooting
### `You are not mapped to a profile in the gateway manifest.`
Cause:
- the sender's Telegram user ID is not attached to any profile
Fix:
- add the user ID with `profiles add` or `profiles channel add`
### `missing required environment variable CLAW_TELEGRAM_BOT_TOKEN`
Cause:
- the container started in standalone mode
- or the old standalone template is being used
Fix:
- ensure the gateway container starts with `gateway serve`
- ensure the template uses `CLAW_GATEWAY_TELEGRAM_BOT_TOKEN`
### `gateway manifest does not exist at /appdata/profiles.json`
Cause:
- older gateway build
Current behavior:
- first boot now auto-creates the manifest if it does not exist
### `Permission denied (os error 13)` when opening Docker socket
Cause:
- gateway container was running as an unprivileged user
Current behavior:
- published gateway image now runs as root by default so it can access `/var/run/docker.sock`
### `error sending request for url (.../getMe)`
Meaning:
- the Telegram token is loaded
- the HTTPS request itself failed before Telegram returned an API payload
Likely causes:
- container network mode is `none`
- DNS failure
- outbound 443 blocked
- TLS or host time issue
### Template values disappear after saving in Unraid
Likely causes:
- using a stale live template
- overwriting the live template file with a blank repo copy
- using a malformed template where variable fields are self-closing instead of carrying body text
Operational rule:
- the repo template is the seed
- the live template in `templates-user` is where Unraid persists actual values
## Notes For Future Maintainers And AI Agents
Do not change these assumptions casually:
- one Telegram bot token must have one gateway owner
- the manifest is the routing source of truth
- workers are per-profile and isolated by host directories
- the gateway template and worker templates are part of the operational surface, not just examples
- Unraid stores live config values in the live XML files, not only in the UI
- the published image must contain both `claw-telegram` and `claw-profile-worker`
- the gateway image must be able to talk to the Docker socket
If you modify commands, env vars, or manifest behavior:
- update this guide
- update the gateway template
- update the worker template generator
- keep the CLI help output and docs aligned