pi-gateway Skill
A comprehensive guide for AI agents to understand, install, configure, and use pi-gateway.
Overview
pi-gateway is a multi-channel AI gateway that connects the pi coding agent to messaging platforms (Telegram, Discord, Feishu, WebChat). It handles RPC process management, message routing, session isolation, and provides a plugin system for extensibility.
+-------------------------------------------------------------------------+
| pi-gateway |
| |
| Telegram ----+ |
| | +------------+ +-------------+ +-------+ |
| Discord ------+--> | Plugin | --> | Session | --> | RPC | |
| | | System | | Router | | Pool | |
| Feishu ------+ +------------+ +-------------+ +-------+ |
| | | | | |
| WebChat -----+ Hooks/Tools Message Queue pi --mode rpc |
| | | | |
| Auth/Transform Backpressure stdin/stdout |
| |
+-------------------------------------------------------------------------+
Key Differentiator: All pi skills work seamlessly. No modification needed.
Installation
Prerequisites
- Bun >= 1.1.0
- Node.js >= 18 (for tooling)
- pi CLI (
npm install -g @mariozechner/pi-coding-agent) - LLM provider (API key or OAuth via
pi /login)
Steps
# Clone repository
git clone https://github.com/Dwsy/pi-gateway.git
cd pi-gateway
# Install dependencies
bun install
# Copy example config
cp pi-gateway.jsonc.example pi-gateway.jsonc
# Edit config with your tokens
# Set environment variables or edit pi-gateway.jsonc directly
export TELEGRAM_BOT_TOKEN="your-bot-token"
export DISCORD_TOKEN="your-discord-token"
# Start gateway
bun run start
# Or with verbose logging
bun run src/cli.ts gateway --verbose
# Health check
bun run src/cli.ts doctor
Build Standalone Binary
bun run build
# Output: dist/pi-gw
Configuration
Configuration file: pi-gateway.jsonc (JSONC format with comments)
Minimal Configuration
{
"gateway": { "port": 52134 },
"agent": { "model": "claude-sonnet-4" },
"channels": {
"telegram": {
"enabled": true,
"botToken": "${TELEGRAM_BOT_TOKEN}"
}
}
}
Full Configuration Schema
{
// Gateway settings
"gateway": {
"port": 52134, // HTTP/WebSocket port
"bind": "loopback", // "loopback" | "lan" | "auto"
"auth": {
"mode": "token", // "off" | "token" | "password"
"token": "secret" // Auth token (auto-generated if omitted)
}
},
// Agent settings
"agent": {
"model": "claude-sonnet-4",
"modelFailover": {
"primary": "claude-sonnet-4",
"fallbacks": ["gpt-4o", "gemini-pro"],
"maxRetries": 2,
"cooldownMs": 60000
},
"piCliPath": "pi",
"thinkingLevel": "high", // "off" | "minimal" | "low" | "medium" | "high"
"runtime": {
"agentDir": "~/.pi/gateway/runtime-agent",
"packageDir": "~/.pi/gateway/runtime-package"
},
"skillsBase": ["~/.pi/agent/skills"],
"skillsGateway": ["~/.pi/gateway/skills"],
"extensions": ["~/.pi/agent/extensions/role-persona/index.ts"],
"pool": {
"min": 1,
"max": 4,
"idleTimeoutMs": 300000
},
"appendSystemPrompt": "~/.pi/agent/APPEND_SYSTEM.md"
},
// Role capabilities (merge mode)
"roles": {
"mergeMode": "append",
"capabilities": {
"mentor": {
"skills": ["~/.pi/gateway/skills/mentor"],
"extensions": ["~/.pi/gateway/pi-extensions/mentor-tools/index.ts"],
"promptTemplates": ["~/.pi/gateway/prompts/mentor"]
}
}
},
// Channel configurations
"channels": {
"telegram": {
"enabled": true,
"botToken": "${TELEGRAM_BOT_TOKEN}",
"dmPolicy": "pairing", // "pairing" | "allowlist" | "open" | "disabled"
"allowlist": ["123456789"],
"streamingMode": "partial", // "off" | "partial" | "block" | "draft"
"accounts": {
"default": { "enabled": true },
"work": {
"enabled": true,
"botToken": "${WORK_BOT_TOKEN}",
"webhookUrl": "https://example.com/telegram/work",
"webhookSecret": "secret"
}
},
"groups": {
"123456789": {
"requireMention": true,
"role": "assistant"
}
}
},
"discord": {
"enabled": true,
"token": "${DISCORD_TOKEN}",
"streamingMode": "partial"
},
"webchat": {
"enabled": true,
"auth": {
"mode": "token",
"token": "webchat-secret"
}
}
},
// Cron scheduled tasks
"cron": {
"enabled": true,
"jobs": [
{
"id": "daily-report",
"schedule": { "kind": "cron", "expr": "0 9 * * *", "timezone": "Asia/Shanghai" },
"task": "Generate daily report",
"delivery": "announce", // "announce" | "direct" | "silent"
"resumeContext": false
}
]
},
// Plugin settings
"plugins": ["./plugins/my-plugin/index.ts"],
// Hooks
"hooks": {
"enabled": true,
"token": "webhook-secret",
"path": "/hooks"
}
}
Configuration Priority
Skills: roles.capabilities[role].skills -> agent.skillsGateway -> agent.skillsBase
Extensions: roles.capabilities[role].extensions -> agent.extensions
Prompt Templates: roles.capabilities[role].promptTemplates -> agent.promptTemplates
Environment Variables
Use ${VAR_NAME} syntax in config:
{
"channels": {
"telegram": {
"botToken": "${TELEGRAM_BOT_TOKEN}"
}
}
}
Channel Setup
Telegram
-
Create bot via @BotFather:
/newbot # Follow instructions, copy token -
Configure:
{ "channels": { "telegram": { "enabled": true, "botToken": "${TELEGRAM_BOT_TOKEN}", "dmPolicy": "pairing" } } } -
Approve DM pairing:
pi-gw pairing approve telegram ABCD1234
Discord
- Create bot at Discord Developer Portal
- Enable intents: Message Content, Guild Messages, DMs
- Generate invite URL with bot scope and permissions
- Configure:
{ "channels": { "discord": { "enabled": true, "token": "${DISCORD_TOKEN}" } } }
Feishu (Lark)
- Create app at Feishu Open Platform
- Configure permissions and event subscriptions
- Configure:
{ "channels": { "feishu": { "enabled": true, "appId": "${FEISHU_APP_ID}", "appSecret": "${FEISHU_APP_SECRET}" } } }
WebChat
Built-in web interface at http://localhost:52134:
{
"channels": {
"webchat": {
"enabled": true,
"auth": {
"mode": "token",
"token": "webchat-secret"
}
}
}
}
CLI Commands
# Start gateway
pi-gw gateway [--port N] [--verbose]
# Health check
pi-gw doctor
# Send message
pi-gw send --to telegram:123456789 --message "Hello"
pi-gw send --to telegram:default:123456789:topic:456 --message "Topic message"
pi-gw send --to discord:789012345 --message "Hello Discord"
# Show config
pi-gw config show
# Interactive setup
pi-gw onboard [--install-daemon]
# Pairing management
pi-gw pairing list
pi-gw pairing approve telegram ABCD1234 --account default
pi-gw pairing deny telegram ABCD1234
Agent Tools
pi-gateway provides these tools for pi agents:
send_message
Send text message to channel:
await send_message({
text: "Hello!",
replyTo: "message-id" // optional, for threaded reply
});
send_media
Send media file to channel:
await send_media({
path: "./image.png",
type: "photo", // "photo" | "audio" | "document" | "video" | "sticker"
caption: "Optional caption"
});
message
React, edit, delete, pin messages:
// React with emoji
await message({ action: "react", messageId: "123", emoji: "👍" });
// Edit message
await message({ action: "edit", messageId: "123", text: "Updated text" });
// Delete message
await message({ action: "delete", messageId: "123" });
// Pin message
await message({ action: "pin", messageId: "123" });
cron
Manage scheduled tasks:
// List jobs
await cron({ action: "list" });
// Add job
await cron({
action: "add",
id: "reminder",
schedule: { kind: "every", expr: "1h" },
task: "Check for pending items"
});
// Run job
await cron({ action: "run", id: "reminder" });
// Pause/resume
await cron({ action: "pause", id: "reminder" });
await cron({ action: "resume", id: "reminder" });
gateway
Gateway management:
// Reload config
await gateway({ action: "reload" });
// Restart gateway
await gateway({ action: "restart" });
session_status
Query current session's runtime status:
const status = await session_status();
// Returns: token usage, cost, model, context utilization
Plugin Development
Plugin Structure
my-plugin/
├── index.ts # Plugin entry point
├── types.ts # Type definitions (optional)
└── utils.ts # Helper functions (optional)
Plugin Entry Point
// my-plugin/index.ts
import type { GatewayPluginApi } from "pi-gateway";
export default function myPlugin(api: GatewayPluginApi) {
// Register channel
api.registerChannel({
name: "my-channel",
// ... channel implementation
});
// Register tool
api.registerTool({
name: "my_tool",
description: "My custom tool",
parameters: { /* JSON Schema */ },
execute: async (params, context) => {
return { result: "success" };
}
});
// Register hook
api.registerHook(["message_received"], async (context) => {
console.log("Message received:", context.message);
});
// Register HTTP route
api.registerHttpRoute("GET", "/my-endpoint", async (req, res) => {
res.json({ status: "ok" });
});
// Register WebSocket method
api.registerGatewayMethod("my.rpc", async (params) => {
return { result: "ok" };
});
// Register slash command
api.registerCommand("mycommand", async (context) => {
await context.reply("Command executed!");
});
// Register background service
api.registerService({
name: "my-service",
start: async () => { /* ... */ },
stop: async () => { /* ... */ }
});
}
Available Hooks
| Hook | Context | Description |
|---|---|---|
before_agent_start | { sessionKey, message, config } | Inject context before agent run |
agent_end | { sessionKey, messages, usage } | Inspect final messages after run |
message_received | { sessionKey, message, channel } | Inbound message from channel |
message_sending | { sessionKey, message, channel } | Outbound before send (mutable) |
message_sent | { sessionKey, message, channel } | Outbound after send |
before_tool_call | { sessionKey, tool, params } | Intercept tool parameters |
after_tool_call | { sessionKey, tool, params, result } | Intercept tool results |
tool_result_persist | { sessionKey, tool, result } | Transform before persist |
session_start | { sessionKey } | Session created |
session_end | { sessionKey } | Session ended |
before_compaction | { sessionKey } | Before context compact |
after_compaction | { sessionKey } | After context compact |
gateway_start | { config } | Gateway started |
gateway_stop | {} | Gateway stopping |
Plugin Configuration
Add to pi-gateway.jsonc:
{
"plugins": [
"./plugins/my-plugin/index.ts"
]
}
Architecture
Components
+------------------+
| Channels | Telegram, Discord, Feishu, WebChat
+--------+---------+
|
v
+--------+---------+
| Plugin System | 14 hooks, 8 registration APIs
+--------+---------+
|
v
+--------+---------+
| Session Router | agent:{id}:{channel}:{chat}
+--------+---------+
|
v
+--------+---------+
| Message Queue | Per-session serial, backpressure
+--------+---------+
|
v
+--------+---------+
| RPC Pool | Manage pi --mode rpc subprocesses
+--------+---------+
|
v
+--------+---------+
| pi agent | + skills
+------------------+
Session Key Format
agent:{agentId}:{channel}:{scope}:{id}
Examples:
agent:main:telegram:dm:123456789
agent:main:telegram:group:-100123456789
agent:main:telegram:topic:-100123456789:456
agent:main:discord:channel:789012345
agent:mentor:telegram:dm:123456789
Message Flow
1. User sends message on channel
2. Channel plugin receives and normalizes
3. Plugin hooks process (auth, transform)
4. Session router determines target session
5. Message queue serializes per-session
6. RPC pool routes to agent process
7. Agent processes with pi skills
8. Response flows back through queue
9. Plugin hooks process response
10. Channel sends to platform
Security
DM Policies
| Policy | Behavior |
|---|---|
pairing | Unknown users get 8-char code, admin approves |
allowlist | Only configured user IDs can interact |
open | Anyone can interact |
disabled | Block all DMs |
SSRF Guard
Prevents agents from accessing internal network resources:
{
"security": {
"ssrfGuard": {
"enabled": true,
"blockPrivateIPs": true,
"blockLocalhost": true,
"allowedHosts": ["api.example.com"]
}
}
}
Authentication
{
"gateway": {
"auth": {
"mode": "token",
"token": "your-secret-token"
}
}
}
HTTP API
| Endpoint | Method | Description |
|---|---|---|
/health | GET | Health check |
/api/sessions | GET | List active sessions |
/api/session/:key | GET | Get session details |
/api/cron/jobs | GET | List cron jobs |
/api/cron/jobs/:id | GET | Get cron job |
/api/cron/jobs/:id/run | POST | Trigger cron job |
/api/config | GET | Get config (secrets redacted) |
/hooks/wake | POST | Webhook trigger |
WebSocket API
Connect to ws://localhost:52134/ws
Methods
// List sessions
ws.send(JSON.stringify({ method: "session.list", params: {} }));
// Get session
ws.send(JSON.stringify({ method: "session.get", params: { key: "agent:main:telegram:dm:123" } }));
// Kill session
ws.send(JSON.stringify({ method: "session.kill", params: { key: "..." } }));
// Cron operations
ws.send(JSON.stringify({ method: "cron.list", params: {} }));
ws.send(JSON.stringify({ method: "cron.run", params: { id: "daily-report" } }));
ws.send(JSON.stringify({ method: "cron.pause", params: { id: "daily-report" } }));
// Config reload
ws.send(JSON.stringify({ method: "config.reload", params: {} }));
Troubleshooting
Cannot connect to Telegram
# Verify bot token
curl https://api.telegram.org/bot<TOKEN>/getMe
# Check environment variable
echo $TELEGRAM_BOT_TOKEN
# Check if bot is blocked by privacy mode
# BotFather -> Bot Privacy -> Disable
RPC pool exhausted
// Increase pool size in pi-gateway.jsonc
{
"agent": {
"pool": {
"min": 2,
"max": 8
}
}
}
Model rate limited
// Configure failover chain
{
"agent": {
"modelFailover": {
"primary": "claude-sonnet-4",
"fallbacks": ["gpt-4o", "gemini-pro"],
"maxRetries": 2,
"cooldownMs": 60000
}
}
}
Session not found
Check session key format:
- Telegram DM:
agent:main:telegram:dm:123456789 - Telegram Group:
agent:main:telegram:group:-100123456789 - Discord:
agent:main:discord:channel:789012345
WebSocket connection failed
# Check gateway is running
pi-gw doctor
# Check port
lsof -i :52134
# Use wss:// for HTTPS
wss://your-domain.com/ws
Authentication failed
# Check token in config
pi-gw config show
# For development, disable auth
# In pi-gateway.jsonc:
{
"gateway": {
"auth": { "mode": "off" }
}
}
Best Practices
Production Deployment
- Use process manager (systemd, pm2)
- Enable HTTPS with reverse proxy
- Set
dmPolicy: "pairing"or"allowlist" - Configure model failover
- Set up monitoring for RPC pool health
- Rotate tokens regularly
Performance
- Set appropriate pool size (
agent.pool.min/max) - Use webhook mode for Telegram (not polling)
- Configure message queue limits
- Enable caching for frequently used data
Security
- Never commit
pi-gateway.jsoncwith real tokens - Use environment variables for secrets
- Enable auth for WebChat and API
- Configure SSRF guard
- Use
dmPolicy: "pairing"for DM access control
File Structure
pi-gateway/
├── src/
│ ├── cli.ts # CLI entry point
│ ├── server.ts # Gateway core
│ ├── core/
│ │ ├── config.ts # Configuration system
│ │ ├── rpc-pool.ts # RPC process pool
│ │ ├── session-router.ts
│ │ ├── message-queue.ts
│ │ └── cron.ts # Cron engine
│ ├── plugins/
│ │ ├── types.ts # Plugin API types
│ │ ├── loader.ts # Plugin loader
│ │ └── builtin/ # Built-in channels
│ │ ├── telegram/
│ │ ├── discord/
│ │ ├── feishu/
│ │ └── webchat.ts
│ └── security/
│ ├── allowlist.ts
│ └── pairing.ts
├── docs/ # Documentation
├── docs-site/ # Nextra docs site
├── pi-gateway.jsonc.example
└── package.json
Related Resources
- GitHub: https://github.com/Dwsy/pi-gateway
- Documentation: https://dwsy.github.io/pi-gateway
- OpenClaw: https://github.com/openclaw/openclaw
- pi-mono: https://github.com/badlogic/pi-mono
- Bun: https://bun.sh