English | 中文
This directory is a small runnable binary that implements an
OpenClaw-like shape on top of trpc-agent-go:
- A long-running gateway process (HTTP endpoints).
- An optional A2A surface for sub-agent and sandbox access.
- A real IM channel: Telegram (long polling).
- A stable session_id derived from DM (direct message) vs group chat.
- Skills support via the built-in skills tooling in
llmagent.
It is intended as a starting point for adding more channels (Enterprise WeChat, Slack, etc.) and hardening operational controls.
Detailed guide: OpenClaw Runtime guide (English) | OpenClaw Runtime 指南(中文)
If you want the shortest path from "nothing installed" to "OpenClaw is running", use the public install script:
curl -fsSL \
https://github.com/trpc-group/trpc-agent-go/releases/latest/download/openclaw-install.sh \
| bashThe installer uses the stdin profile by default, and that profile uses
the built-in mock model. That means the first run works without model
credentials or Telegram credentials.
The installer keeps the GitHub build's config and state under
~/.trpc-agent-go-github/openclaw by default.
If openclaw is not found after install, run the PATH commands printed
by the installer. For bash, the persistent form is:
grep -qxF 'export PATH="$HOME/.local/bin:$PATH"' "$HOME/.bashrc" || \
printf '\nexport PATH="$HOME/.local/bin:$PATH"\n' >> "$HOME/.bashrc"
. "$HOME/.bashrc"Then start OpenClaw:
openclawYou should see the local terminal channel start immediately. Type a short
message such as hello, then use /quit or /exit to stop.
More details: INSTALL.md | INSTALL.zh_CN.md | RELEASE.md | RELEASE.zh_CN.md
Run with a mock model (no external model credentials needed):
cd openclaw
go run ./cmd/openclaw -config ./openclaw.stdin.yamlNote: by default, OpenClaw uses -mode openai and -model gpt-5.
If you do not have model credentials, keep using -mode mock.
By default, OpenClaw runs the llm agent (the built-in llmagent),
which uses your model config and supports skills/tools.
If you have Claude Code installed locally and want OpenClaw to drive
messages through the Claude Code CLI, use claude-code:
cd openclaw
go run ./cmd/openclaw \
-agent-type claude-code \
-http-addr :8080YAML equivalent:
agent:
type: "claude-code"
claude_output_format: "stream-json"Notes:
- In
claude-codemode, OpenClaw'stools:section is not supported. model:is optional unless you enable model-backed features likesession.summary.enabledormemory.auto.enabled.
OpenClaw supports a YAML config file to avoid a long list of CLI flags.
- Pass
-config /path/to/openclaw.yaml, or - set
OPENCLAW_CONFIG=/path/to/openclaw.yaml. - If neither is set, OpenClaw also tries
~/.trpc-agent-go-github/openclaw/openclaw.yaml(only if the file exists).
CLI flags always override config file values.
The config file supports environment variable placeholders in the form ${NAME}.
Missing environment variables cause OpenClaw to fail fast with a config error.
OpenClaw now supports a native browser tool provider for real browser
automation.
Use it when the task needs:
- JS-heavy pages
- tabs and page navigation
- screenshots or page snapshots
- clicking, typing, selecting, or dialog handling
The provider exposes one browser tool and uses Playwright MCP under
the hood. That keeps the model-facing interface stable while preserving
image forwarding for screenshots.
By default, browser navigation blocks:
- loopback hosts such as
localhost - private-network IPs
file://URLs
You can relax or refine that policy with:
allowed_domains, blocked_domains, allow_loopback,
allow_private_networks, and allow_file_urls.
Example config:
tools:
providers:
- type: "browser"
config:
default_profile: "openclaw"
evaluate_enabled: false
allowed_domains: ["example.com"]
profiles:
- name: "openclaw"
transport: "stdio"
command: "npx"
args:
- "--yes"
- "@playwright/mcp@latest"
- "--headless"
- "--isolated"
- "--caps"
- "vision,pdf"
timeout: "5m"Runnable example: examples/browser_use/README.md
Browser-server example: examples/browser_server_use/README.md
The full browser plane scaffolding also lives in:
When debugging multi-step flows (especially Telegram "Processing..." messages), it helps to have a single place that captures what happened end-to-end.
OpenClaw includes an opt-in, file-based debug recorder that writes a per-request trace directory with:
- gateway requests/responses
- runner events
- Telegram message + attachment metadata
- (mode
full) attachment bytes (to reproduce multimodal issues)
Enable with CLI flags:
cd openclaw
go run ./cmd/openclaw -debug-recorderOr via YAML:
debug_recorder:
enabled: true
mode: "full" # "full" (default) or "safe" (no attachment bytes)
# dir: "<state_dir>/debug" # defaultTrace output location:
- default:
<state_dir>/debug - canonical layout:
<YYYYMMDD>/<HHMMSS>_<channel>_<request_id>/ - session index:
<by-session>/<session-or-user>/<YYYYMMDD>/<HHMMSS>_<message_id>/trace.json - files:
meta.json: trace start metadataevents.jsonl: event stream (one JSON object per line)result.json: trace end status + durationattachments/<sha256>: stored bytes (modefullonly)by-session/.../trace.json: pointer to the canonical trace dir
This repo ships two sample configs:
./openclaw.yamlfor Telegram../openclaw.stdin.yamlfor local terminal chat.
Example config:
app_name: "openclaw"
http:
addr: ":8080"
admin:
enabled: true
addr: "127.0.0.1:19789"
auto_port: true
agent:
# Short instruction text (optional).
instruction: |
You are a helpful assistant. Reply in a friendly tone.
Use browser for JS-heavy sites, page interactions, snapshots,
screenshots, downloads, uploads, and current-tab relay workflows.
# Optional: load and merge multiple markdown files into the system prompt.
# Files are read in alphabetical order.
# system_prompt_dir: "./prompts/system"
# Optional: enable an outer verification loop. Unsafe because it can
# execute host commands.
# ralph_loop:
# enabled: true
# max_iterations: 5
# verify:
# command: "go test ./..."
# timeout: "2m"
model:
mode: "openai"
name: "gpt-5"
openai_variant: "auto"
# Optional: knowledge backends used by knowledge search tools.
# This only configures the embedder and vector store. Content loading
# can be triggered separately at runtime.
knowledges:
providers:
- name: "docs"
max_results: 5
config:
embedder:
type: "openai"
model: "text-embedding-3-small"
dimensions: 1536
vector_store:
type: "inmemory"
tools:
# Optional; default is serial execution.
# When enabled and the model returns multiple tool calls in one step,
# OpenClaw executes them concurrently.
enable_parallel_tools: true
# Optional: override the built-in OpenClaw tooling guidance prompt.
# Leave unset to use the built-in default, or set to "" to disable it.
openclaw_tooling_guidance: ""
# Optional: reduce large parent model requests by exposing compact
# tool_search + dynamic_agent entrypoints when the direct tool surface
# exceeds the auto threshold. Default mode is auto; set
# defer_to_dynamic_agent_mode: on to force it on, or off to disable it.
defer_to_dynamic_agent_mode: auto # off|on|auto
defer_to_dynamic_agent_threshold_chars: 4000
# Optional: cap one dynamic_agent child call; 0 or unset disables it.
dynamic_agent_timeout: "180s"
# Optional: keep default direct tools on the parent agent.
# Set false for token-sensitive profiles that should expose only
# tool_search/dynamic_agent plus defer_direct_tools.
# defer_default_direct_tools: true
# Optional: keep a small set of tools directly callable by the parent agent.
# defer_direct_tools: ["exec_command"]
# Optional: default timeout for host exec_command calls when timeout_sec is
# omitted. Leave unset to keep the built-in host exec default.
# host_exec_default_timeout: "60s"
# Optional: configure fenced-code execution without exposing workspace_exec.
code_executor:
type: "sandbox" # sandbox; leave empty/unset to inherit enable_local_exec
auto_execute_code_blocks: true
sandbox:
workspace_root: "" # default: state_dir/sandbox
backend: "auto" # auto|linux-bubblewrap|macos-sandbox-exec
profile: "workspace_write" # workspace_write|read_only|disabled
network: "restricted" # restricted|enabled
default_timeout: "30s"
output_max_bytes: 1048576
shell_env:
inherit: "core" # all|core|none
apply_default_excludes: true
providers:
- type: "browser"
name: "browser-runtime"
config:
default_profile: "openclaw"
evaluate_enabled: false
server_url: "http://127.0.0.1:19790"
sandbox_server_url: "http://127.0.0.1:20790"
profiles:
- name: "openclaw"
description: "Managed Playwright profile from browser-server."
- name: "chrome"
description: "Current Chromium tab attached through relay."
channels:
- type: "telegram"
config:
token: "${TELEGRAM_BOT_TOKEN}"
streaming: "progress"
http_timeout: "60s"
session:
backend: "inmemory"
summary:
enabled: false
memory:
backend: "inmemory"
auto:
enabled: falseRun:
cd openclaw
go run ./cmd/openclaw -config ./openclaw.yamlNotes:
-
Duration fields use Go-style strings like
60s,10m,1h. -
For secrets (model keys, Telegram tokens), keep them out of version control. Prefer environment variables when available.
-
tools.code_executor.type: sandboxwirescodeexecutor/sandboxinto both fenced-code execution and OpenClawexec_commandwhile keeping the genericworkspace_exectool surface disabled. In this mode,exec_commandonly supports foreground non-interactive commands;write_stdinandkill_sessionare unavailable. -
Sandbox decision table:
Setting Fenced code blocks OpenClaw exec_commandInteractive follow-up Typical reason tools.code_executor.type: "sandbox"Runs in sandbox Runs in sandbox Not available ( write_stdin/kill_sessionare omitted)Need filesystem, network, timeout, or env isolation tools.code_executor.type: ""Runs on host when enable_local_exec: true; disabled whenfalseRuns on host Available for host exec_commandsessionsNeed host shell semantics or interactive shell workflows -
In sandbox mode, upload and memory variables still expose stable
OPENCLAW_*metadata, but host paths such asOPENCLAW_LAST_UPLOAD_PATH,OPENCLAW_SESSION_UPLOADS_DIR,OPENCLAW_MEMORY_FILE,OPENCLAW_USER_MEMORY_FILE, andOPENCLAW_CHAT_MEMORY_FILEare not automatically mounted into the sandbox. -
knowledgescurrently configures only embedder / vector store wiring. Loading documents into a knowledge base is a separate runtime action. -
Example
pgvectorknowledge config:knowledges: providers: - name: "trpc_agent_go" max_results: 5 config: embedder: type: "openai" model: "text-embedding-3-small" base_url: "${OPENAI_BASE_URL}" api_key: "${OPENAI_API_KEY}" dimensions: 1536 vector_store: type: "pgvector" url: "postgres://postgres:${PGPASSWORD}@localhost:5432/vectordb?sslmode=disable" table: "trpc_agent_go" index_dimension: 1536 enable_tsvector: true
Use identifier-safe table names such as
trpc_agent_go; do not use raw names liketrpc-agent-go. -
The sample Telegram config enables the native
browsertool against the local browser-server defaults. Whenserver_urlpoints athttp://127.0.0.1:19790,go run ./cmd/openclawnow probes that address and auto-startsopenclaw/browser-serverif it is not already running. The checkout must already haveopenclaw/browser-serverdependencies installed (npm installandnpx playwright install chromium), and the managed process logs are written under<state_dir>/debug/services/and surfaced in the admin Browser card. If auto-start cannot find the local browser-server checkout, setOPENCLAW_BROWSER_SERVER_DIRor start the server manually. Seeopenclaw/examples/browser_server_use/. -
The sample config in
./openclaw.yamlis ready to use withgo run ./cmd/openclaw -config ./openclaw.yaml. -
The sample config in
./openclaw.stdin.yamlis ready to use withgo run ./cmd/openclaw -config ./openclaw.stdin.yaml. -
The sample config in
./openclaw.stdin.sqlite.yamlis ready to use withgo run ./cmd/openclaw -config ./openclaw.stdin.sqlite.yaml. -
Plugin sections:
channelsconfigures channel plugins. The default binary ships with thetelegramandstdinchannel plugins; other channel types require a custom binary that imports them. Seeopenclaw/EXTENDING.mdandopenclaw/examples/stdin_chat/.tools.enable_parallel_toolstoggles parallel tool execution for one model step (optional).tools.providersandtools.toolsetswork out of the box for the built-in types shipped in this repo. Custom types still require a custom binary. Seeopenclaw/INTEGRATIONS.mdandopenclaw/EXTENDING.md.
Use runtime_profiles when one OpenClaw deployment serves multiple users,
tenants, or product surfaces that need different runtime behavior. A profile
can override prompt text, app name, model name, tool policy, knowledge policy,
workspace roots, credentials, skill visibility, isolation mode, runtime state,
and model request extras.
Static profiles can be configured in YAML:
tools:
toolsets:
- type: "mcp"
name: "tenant-alpha-tools"
config:
transport: "stdio"
command: "tenant-alpha-mcp"
runtime_profiles:
default: default
profiles:
default:
app_name: "default"
prompt:
instruction: "You are a helpful assistant."
tenant_alpha:
app_name: "tenant-alpha"
prompt:
instruction: "You are the tenant Alpha assistant."
workspace:
workdir: "/srv/openclaw/workspaces/tenant-alpha"
allowed_roots:
- "/srv/openclaw/workspaces/tenant-alpha"
skills:
roots:
- "/srv/openclaw/skills/tenant-alpha"
include: ["tenant-alpha-guide"]
knowledge:
indexes: ["tenant-alpha-faq"]
filter:
tenant: "tenant-alpha"
tools:
toolsets: ["tenant-alpha-tools"]
isolation:
mode: "profile_cache"
agent_cache: true
toolset_cache: true
selectors:
- profile_id: "tenant_alpha"
channels: ["wecom"]
users: ["replace-with-user-id"]
- profile_id: "tenant_alpha"
tenants: ["tenant-alpha"]Selectors are matched in order. All non-empty fields in a selector must match
the request before its profile_id is selected. When selectors are configured,
profile selection fails closed: a request with no matching selector, an
explicit profile that conflicts with the selected profile, or a selector that
points at a missing profile fails the run instead of silently falling back.
channels, users, and sessions match gateway metadata. tenants matches
the tenant_id field in the openclaw.runtime_profile request extension, so
custom channels or callers that use tenant selectors must pass an extension
like this:
{
"extensions": {
"openclaw.runtime_profile": {
"tenant_id": "tenant-alpha"
}
}
}Profile tools.toolsets selects from the globally configured tools.toolsets
entries by name. It does not create a toolset by itself; it hides tools that
do not come from the selected toolsets for that request. tools.include and
tools.exclude still apply to concrete tool names inside the selected
toolsets.
Profile knowledge.indexes selects from knowledges.providers by name.
Search tools for other knowledge providers are hidden for that request, and
knowledge.filter still applies as metadata filter criteria when querying the
selected knowledge provider.
Profile tools.credential_refs maps a concrete tool name or toolset name to
a credential reference. When credentials.allowed_refs is set, tools mapped to
other credential references are hidden for that request. Allowed credential
references are also copied into runtime state so custom tools can resolve
their own secrets through the deployment's credential provider.
When isolation.mode is profile_cache or service and app_name is not
set, OpenClaw uses the profile id as the run app name. That isolates session,
memory, and event filter keys for the profile. Deployments that need a
separate process or sandbox boundary can read the isolation fields from
runtime state and enforce that boundary in their custom runtime.
For custom OpenClaw binaries, load profiles from code by passing runtime
options to app.MainWithOptions:
func main() {
store := runtimeprofile.StoreFunc(func(ctx context.Context) (
runtimeprofile.Config,
error,
) {
return loadRuntimeProfilesFromDB(ctx)
})
os.Exit(app.MainWithOptions(
os.Args[1:],
app.WithRuntimeProfileStore(store, true),
))
}The store can read from a database, config center, or control plane. Returning
Selectors in the loaded runtimeprofile.Config gives code-loaded profiles
the same fail-closed selector semantics as YAML.
OpenClaw can publish a native A2A surface alongside the HTTP gateway.
This is the preferred way to attach a sandboxed OpenClaw runtime as a
sub-agent for another trpc-agent-go process.
YAML:
a2a:
enabled: true
host: "http://127.0.0.1:8080/a2a"
user_id_header: "X-User-ID" # optional
streaming: true
advertise_tools: false
name: "openclaw-sandbox"
description: "Sandbox agent for bundled skills and host binaries."CLI:
cd openclaw
go run ./cmd/openclaw \
-a2a \
-a2a-host http://127.0.0.1:8080/a2aNotes:
a2a.hostmust include a non-root path such as/a2a.- The A2A surface reuses the same OpenClaw runner, session service, memory service, skills, and tools as the gateway.
- By default, the agent card publishes one stable "OpenClaw sandbox"
skill instead of enumerating every tool. Set
advertise_tools: trueonly when your caller needs per-tool card metadata. - A runnable example is available in
./examples/a2a_subagent.
OpenClaw exposes subagents_spawn and the compatibility alias
sessions_spawn for delegated work. The mode argument controls whether the
main agent continues immediately or waits:
async: default. Start the subagent and return a run id immediately.sync: wait until the subagent reaches a terminal status, then return the result to the main agent.review: wait for the subagent result, then route the next user reply back to the same main-agent continuation point so the user can review before the main agent proceeds.
timeout_seconds limits the subagent run. wait_timeout_seconds only limits
how long subagents_spawn waits in sync or review mode.
isolation: "worktree" is opt-in for coding tasks that should not edit the
parent checkout directly. OpenClaw creates a managed Git worktree from the
current runtime profile workspace, runs the subagent with that worktree as its
default workspace, removes it when it stays clean, and preserves it when the
subagent leaves file or commit changes. The source workspace must be a clean
Git checkout so the isolated run starts from an explicit commit.
OpenClaw supports customizing the main agent's prompt with either:
- Inline config fields (
agent.instruction,agent.system_prompt), or - File-based prompts (
agent.*_files,agent.*_dir) to keep long prompts out of YAML.
CLI equivalents:
cd openclaw
go run ./cmd/openclaw \
-mode mock \
-agent-instruction "You are a helpful assistant." \
-agent-system-prompt-dir ./examples/stdin_chat/prompts/systemRalph Loop is an outer loop that reruns the agent until a verifiable completion condition is met (or until the maximum number of iterations is reached).
OpenClaw supports it only for agent.type: llm, because the claude-code
agent does not consume session history (so loop feedback would be ignored).
Ralph Loop is considered unsafe because it can execute a host command after each iteration.
YAML example:
agent:
ralph_loop:
enabled: true
max_iterations: 5
verify:
command: "go test ./..."
timeout: "2m"
env: ["CGO_ENABLED=1"]CLI example:
cd openclaw
go run ./cmd/openclaw \
-mode mock \
-agent-ralph-loop \
-agent-ralph-verify-command 'go test ./...' \
-agent-ralph-verify-timeout 2mHealth check:
curl -sS 'http://127.0.0.1:8080/healthz'Send one message via HTTP (webhook-style):
curl -sS 'http://127.0.0.1:8080/v1/gateway/messages' \
-H 'Content-Type: application/json' \
-d '{"from":"alice","text":"Hello"}'Stream one message via HTTP SSE:
curl -N 'http://127.0.0.1:8080/v1/gateway/messages:stream' \
-H 'Content-Type: application/json' \
-d '{"from":"alice","text":"Hello"}'The stream emits newline-delimited SSE events. Each data: payload is a
JSON StreamEvent with a stable type field:
run.startedrun.ignoredrun.progressmessage.deltamessage.completedrun.completedrun.error
Typical successful flow:
run.started- zero or more
run.progress - zero or more
message.delta message.completedrun.completed
run.progress is a low-frequency, system-generated status update. It is
meant for channels that want a short "still working" summary without
guessing from partial text. The first release uses stable stages such as:
preparingreading_documentreading_spreadsheetrunning_toolsummarizing
For in-process integrations, channel plugins can prefer
StreamMessage(...) when deps.Gateway also implements
registry.StreamingGatewayClient.
Send a multimodal message via HTTP:
- Use
textfor the main text message. - Use
content_partsfor additional inputs (images, audio, files, links, etc.).
Security note: for URL-based parts (audio.url, file.url, video.url),
the gateway downloads the content. By default, it blocks URLs that resolve
to loopback/private addresses to reduce SSRF risk. If you embed the gateway
server in your own program, you can adjust this via gateway options (for
example, gateway.WithAllowPrivateContentPartURLs(true) or
gateway.WithAllowedContentPartDomains(...)).
Example (text + image URL):
curl -sS 'http://127.0.0.1:8080/v1/gateway/messages' \
-H 'Content-Type: application/json' \
-d '{
"from": "alice",
"text": "What is in this image?",
"content_parts": [
{
"type": "image",
"image": {
"url": "https://example.com/image.png",
"detail": "auto"
}
}
]
}'Example (audio by URL):
curl -sS 'http://127.0.0.1:8080/v1/gateway/messages' \
-H 'Content-Type: application/json' \
-d '{
"from": "alice",
"content_parts": [
{
"type": "audio",
"audio": {
"url": "https://example.com/voice.wav"
}
}
]
}'Example (file by URL):
curl -sS 'http://127.0.0.1:8080/v1/gateway/messages' \
-H 'Content-Type: application/json' \
-d '{
"from": "alice",
"text": "Summarize this document.",
"content_parts": [
{
"type": "file",
"file": {
"url": "https://example.com/report.pdf"
}
}
]
}'If you send non-text inputs (image, audio, file, video), make sure
the configured model supports those input types.
Note: OpenAI Chat Completions does not support raw file inputs in the same
way as images/audio. OpenClaw persists inbound file and video parts to
stable host paths under the state directory, keeps those refs in session
history, and exposes them back to tools. In practice this means later turns
can still operate on the same upload with read_document,
read_spreadsheet, or exec_command
($OPENCLAW_LAST_UPLOAD_PATH, $OPENCLAW_LAST_UPLOAD_NAME,
$OPENCLAW_LAST_UPLOAD_MIME, $OPENCLAW_LAST_PDF_PATH,
$OPENCLAW_LAST_AUDIO_PATH, $OPENCLAW_LAST_VIDEO_PATH,
$OPENCLAW_LAST_IMAGE_PATH, $OPENCLAW_SESSION_UPLOADS_DIR) or
skill_run (host://... inputs staged into $WORK_DIR/inputs).
For common file-reading tasks, prefer the first-party tools:
read_document: stable reads for PDF, DOCX, and text-like uploads.read_spreadsheet: stable reads for XLSX and CSV uploads.
Use exec_command as the fallback for conversions, custom scripts, and
other host-side work that those file tools cannot satisfy.
OpenClaw uses the model/openai implementation with provider variants.
For OpenAI:
export OPENAI_API_KEY="your-api-key"
cd openclaw
go run ./cmd/openclaw \
-http-addr :8080By default, -model uses $OPENAI_MODEL if set, otherwise it falls
back to gpt-5.
You can override the OpenAI-compatible base URL with:
OPENAI_BASE_URL(environment), or-openai-base-url(CLI flag), ormodel.base_url(YAML config).
Some OpenAI-compatible gateways require additional HTTP headers. Set them with
OPENAI_HEADERS as space-, comma-, or newline-separated KEY=VALUE pairs:
export OPENAI_HEADERS="X-Example-User=alice X-Example-Agent=openclaw"Quote values that contain spaces or commas:
export OPENAI_HEADERS='X-Example-User=alice Authorization="Bearer token"'You can also configure them in YAML:
model:
headers:
X-Example-User: "alice"
X-Example-Agent: "openclaw"
X-Example-Token: "Bearer token-with-space"If you use DeepSeek directly, set DEEPSEEK_API_KEY together with the official
DeepSeek base URL:
export DEEPSEEK_API_KEY="your-api-key"
export OPENAI_BASE_URL="https://api.deepseek.com/v1"
cd openclaw
go run ./cmd/openclaw \
-mode openai \
-model deepseek-chat \
-http-addr :8080If you already use the OpenAI-compatible environment variables, this also works:
export OPENAI_API_KEY="your-api-key"
export OPENAI_BASE_URL="https://api.deepseek.com/v1"
cd openclaw
go run ./cmd/openclaw \
-mode openai \
-model deepseek-chat \
-http-addr :8080By default, -openai-variant is auto and is inferred from the configured
base URL host (OPENAI_BASE_URL, -openai-base-url, or model.base_url). For
custom proxies or other compatible endpoints, set -openai-variant
explicitly.
You can override it explicitly:
export OPENAI_API_KEY="your-api-key"
cd openclaw
go run ./cmd/openclaw \
-mode openai \
-openai-variant openai \
-model gpt-4o-mini \
-http-addr :8080OpenClaw uses Telegram long polling (getUpdates), so it does not
require a public HTTPS endpoint.
-
Talk to
@BotFather. -
Run
/newbot. -
Pick a bot name and a username (Telegram requires the username to end with
bot). -
Copy the bot token.
A Telegram bot cannot use getUpdates while a webhook is set.
If you ever configured a webhook for this bot, delete it:
curl -sS "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/getWebhookInfo"
curl -sS "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/deleteWebhook"If you add the bot into a group, Telegram privacy mode affects which messages the bot receives.
- With privacy enabled (default), the bot typically only receives messages
that mention it (for example
@mybot), commands (for example/start), and replies to the bot. - With privacy disabled, the bot can receive all group messages.
OpenClaw recommends mention gating (-require-mention) for groups, so keeping
privacy enabled is usually fine. If you want to disable privacy, use
@BotFather and run /setprivacy.
Add a Telegram channel to your config file:
channels:
- type: "telegram"
config:
token: "<YOUR_TELEGRAM_BOT_TOKEN>"
## Optional:
# streaming: "progress"
# proxy: "http://127.0.0.1:7890"
# http_timeout: "60s"
# max_retries: 3
# max_download_bytes: 20971520
# session_reset_idle: "24h"
# session_reset_daily: true
# on_block: "reset"Run:
cd openclaw
go run ./cmd/openclaw \
-mode mock \
-http-addr :8080 \
-config ./openclaw.yamlConfigure Telegram networking under the Telegram channel config::
proxy: HTTP proxy URL (optional)http_timeout: HTTP client timeout (optional; should be > 25s long polling)max_retries: retry count for transient failures (optional; default: 3)max_download_bytes: per-file download limit for inbound attachments (optional; default: 20971520 / 20 MiB)
To override the Telegram API base URL (for testing), set
OPENCLAW_TELEGRAM_BASE_URL.
To quickly validate your Telegram setup (token, webhook, pairing store):
cd openclaw
go run ./cmd/openclaw doctor -config ./openclaw.yamlOpenClaw can inspect common host-side file tooling requirements and can prepare a managed Python environment under the state directory for Python packages used by host tooling.
Inspect the default file-tool profiles:
cd openclaw
go run ./cmd/openclaw inspect depsInspect specific profiles or skill metadata:
cd openclaw
go run ./cmd/openclaw inspect deps \
-profile pdf,office \
-skill nano-pdfPreview the install plan:
cd openclaw
go run ./cmd/openclaw bootstrap deps \
-profile common-file-toolsApply the plan:
cd openclaw
go run ./cmd/openclaw bootstrap deps \
-profile common-file-tools \
-applySome specialized toolchains are intentionally opt-in. For example,
chess checks for a Stockfish-compatible UCI engine and installs Python
board-analysis helpers for chess-position tasks:
cd openclaw
go run ./cmd/openclaw bootstrap deps \
-profile chess \
-applyThe bootstrap command never runs automatically on startup. Startup logs may
print a suggested bootstrap deps command when optional file tools are
missing, but installation is always explicit. The managed Python environment
is created with access to the current system site-packages, so existing
packages such as pandas remain visible after bootstrap.
Official OpenClaw skill metadata can currently describe package-manager,
Go, npm, managed-Python, and asset download install actions. Explicit
-skill ... runs only plan the selected skills and no longer pull in the
default dependency profiles automatically. bootstrap deps --apply is
best-effort: user-space installs and downloads run first, while root-only
steps are reported as deferred instead of aborting the entire run. Download
actions store assets under <state_dir>/tools/<skill>/... and may link
selected executables into the managed toolchain bin directory when their
metadata declares links.
Open a chat with your bot (or add it into a group) and send:
- a text message, or
- a photo, document, audio, voice note, video, animation, or video note.
Inbound attachments are downloaded from Telegram and forwarded to the gateway
as multimodal content_parts. The uploaded files are also persisted under the
OpenClaw state directory so later turns in the same chat can keep working on
the same PDF, image, audio, or video without asking the user to upload again.
When a Telegram voice note can be transcribed locally, OpenClaw also injects
the transcript as the user instruction while keeping the original audio upload
available to tools and follow-up turns.
For host-side tools, OpenClaw injects stable attachment metadata such as
OPENCLAW_LAST_UPLOAD_PATH, OPENCLAW_LAST_UPLOAD_NAME,
OPENCLAW_LAST_UPLOAD_MIME, OPENCLAW_SESSION_UPLOADS_DIR, and
OPENCLAW_RECENT_UPLOADS_JSON, plus kind-aware shortcuts like
OPENCLAW_LAST_PDF_PATH, OPENCLAW_LAST_AUDIO_PATH,
OPENCLAW_LAST_VIDEO_PATH, and OPENCLAW_LAST_IMAGE_PATH, so the agent can
keep operating on the recent chat uploads without guessing local paths.
When the agent generates local output files under the current working
directory or the OpenClaw state directory, Telegram can send them back as
documents, images, audio, or video via the message tool. Replies that mention
generated files by local path are also sanitized to user-facing filenames, and
OpenClaw will try to send those generated files back to the current chat
automatically when it can resolve them safely.
By default, DMs are fail-closed and require pairing.
On the first DM, the bot replies with a 6-digit pairing code and will not process your message yet.
To approve a user, run:
cd openclaw
go run ./cmd/openclaw pairing approve <CODE> -config ./openclaw.yamlYou can also list pending pairing requests:
cd openclaw
go run ./cmd/openclaw pairing list -config ./openclaw.yamlAfter approval, the bot forwards inbound text to the gateway and sends the final reply back to Telegram.
To disable pairing (less safe), set the Telegram channel dm_policy to open:
channels:
- type: "telegram"
config:
dm_policy: "open"OpenClaw supports a few basic commands:
/help: show a short help message./cancel: cancel the current run for the same DM/thread session./resetand/new: start a new DM session (old data is kept)./forget: permanently delete your stored sessions, memories, and debug traces (DM only)./jobs: list scheduled jobs scoped to the current chat./jobs_clear: remove scheduled jobs scoped to the current chat. Future executions stop immediately. If a matching job is currently running, OpenClaw cancels that in-flight run and suppresses any pending delivery for this chat./persona: show the active persona preset for this chat and list the available presets./persona <id>: switch the active persona preset for this chat./personas: list the available persona presets.
On startup, OpenClaw also registers these commands with Telegram via
setMyCommands, so supported clients can show them in the slash-command
menu. If registration fails, the commands still work when typed manually.
Built-in persona presets:
default: keep the normal assistant behavior.girlfriend: warm, playful, and affectionate companion tone.concise: direct, brief, and action-first replies.coach: structured, pragmatic, and goal-oriented.creative: more imaginative, vivid, and idea-rich replies.
Examples:
/persona
/persona girlfriend
/persona default
You can also configure automatic DM session resets:
session_reset_idle: rotate the active DM session after it has been idle for the configured duration.session_reset_daily: rotate the active DM session when the date changes (local time).
To react to privacy/lifecycle events, configure:
on_block: what to do when a user blocks the bot (my_chat_memberupdates). Supported values:reset(default),forget,none.
OpenClaw can optionally use editMessageText to show a processing preview,
then replace it with the final answer.
Telegram streaming modes (Telegram channel config):
off: send the final answer as messages.block: send one "Processing..." message, then edit once to final.progress(default): keep editing the message while the model is running.
Outbound Telegram text now uses parse_mode: "HTML" by default:
- Markdown-ish model output is rendered into Telegram-safe HTML.
- Raw HTML from the model is escaped before sending.
- If Telegram rejects the formatted HTML, OpenClaw automatically retries with plain text.
To disable streaming:
channels:
- type: "telegram"
config:
streaming: "off"OpenClaw derives session_id based on whether the inbound message is a
DM (direct message, i.e. a private chat) or a group message:
- DMs:
threadis empty, so the session is per-user. The active DM session can be rotated via/reset(or automatically viasession_reset_*) and is persisted under<state_dir>/telegram/. - Groups:
threadis the chat ID, so the session is per-group. - Group topics: if Telegram provides
message_thread_id,threadbecomes<chat_id>:topic:<message_thread_id>, so each topic gets its own session.
OpenClaw stores the Telegram getUpdates offset on disk so restarts
can resume from the last processed update.
- Default state dir:
$HOME/.trpc-agent-go-github/openclaw - Override with:
-state-dir
On the first run (when no offset file exists), the poller drains pending
updates by default to avoid replying to very old messages. You can
disable this behavior with start_from_latest: false in the Telegram channel
config.
To only allow specific user IDs:
go run ./cmd/openclaw \
-mode mock \
-config ./openclaw.yaml \
-allow-users "123456789,987654321"The allowlist is matched against:
- Telegram: the numeric
from.id(as a string) - HTTP:
user_idif set, otherwisefrom
To find your Telegram from.id:
-
Do not run OpenClaw yet (or stop it), so your local process does not consume updates.
-
Send any message to your bot in Telegram.
-
Call
getUpdatesand look formessage.from.id:
curl -sS "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/getUpdates"To ignore group messages unless a mention pattern is present:
go run ./cmd/openclaw \
-mode mock \
-config ./openclaw.yaml \
-require-mention \
-mention "@mybot"Mention patterns can also be set in config via gateway.mention_patterns.
If -require-mention (or gateway.require_mention) is enabled and mention
patterns are empty, the gateway refuses to start.
To override patterns:
go run ./cmd/openclaw \
-mode mock \
-config ./openclaw.yaml \
-require-mention \
-mention "@mybot,/agent"By default, OpenClaw ignores all group messages (group_policy defaults to
disabled).
To enable groups (less safe), use:
channels:
- type: "telegram"
config:
group_policy: "open"To allowlist specific groups/topics, use:
channels:
- type: "telegram"
config:
group_policy: "allowlist"
allow_threads:
- "<chat_id>"
- "<chat_id>:topic:<message_thread_id>"You can discover chat_id and message_thread_id from getUpdates.
OpenClaw can optionally enable the local code execution tool for the agent. This is unsafe when exposed to external inputs (Telegram, webhook traffic).
It is disabled by default. To enable:
go run ./cmd/openclaw \
-mode openai \
-config ./openclaw.yaml \
-enable-local-execOpenClaw supports AgentSkills-style SKILL.md skill folders, and
borrows a few design ideas from OpenClaw:
- Multiple skill roots (workspace, managed, extra dirs) with precedence.
- Optional load-time gating via
metadata.openclaw.requires.*. {baseDir}placeholder substitution for better OpenClaw skill compatibility.
Local skills are the default place to teach OpenClaw reusable capabilities. When a user wants the agent to remember a workflow, connect to a tool or API, reuse an MCP server, follow a team process, or preserve a domain rule for future tasks, prefer creating or updating a skill instead of adding case-specific runtime logic.
Use memory for lightweight facts, preferences, and simple standing rules. Use a skill when the remembered item needs an operational workflow, tools, examples, references, or recovery paths.
Use application code and runtime config for stable boundaries such as permissions, secret handling, file access, validation, and lifecycle management. Use skills for the evolving context: when the capability should trigger, how to operate it, which examples matter, and how to recover from common failures.
OpenClaw vendors the upstream OpenClaw skill pack under openclaw/skills/
(see openclaw/skills/README.md for attribution and license).
It also includes a few simple example skills:
hello: write a small file toout/.envdump: dump environment info toout/env.txt.http_get: fetch a URL withcurlintoout/.
Skills are loaded from these locations (highest precedence first):
- Workspace skills:
-skills-root(default:./skills) - Project AgentSkills:
./.agents/skills - Personal AgentSkills:
$HOME/.agents/skills - Managed skills:
<state-dir>/skills - Installed bundled skills:
<state-dir>/bundled-skills - Repo bundled skills (when running from repo root):
./openclaw/skills - Extra dirs:
-skills-extra-dirs(comma-separated, lowest precedence)
If two skills have the same name, the one from the higher-precedence
location wins.
Prebuilt release installs refresh <state-dir>/bundled-skills on each
install or upgrade, while leaving <state-dir>/skills available for your
own managed skills.
If a skill's SKILL.md front matter contains metadata.openclaw, this
OpenClaw can filter the skill at load time based on the local environment:
metadata.openclaw.os(darwin/linux/win32)metadata.openclaw.requires.binsmetadata.openclaw.requires.anyBinsmetadata.openclaw.requires.envmetadata.openclaw.requires.config
Enable -skills-debug to log which skills are skipped and why.
Upstream OpenClaw supports providing per-skill environment variables and API keys via config. OpenClaw supports the same idea in YAML:
skills:
# Optional: restrict which bundled skills are enabled by default.
# Applies only to bundled skills under ./openclaw/skills.
allowBundled: ["gh-issues", "notion"]
# Watch mutable local skill roots and refresh automatically.
watch: true
watch_bundled: false
watch_debounce_ms: 250
tool_profile: "knowledge_only" # knowledge_only|full
load_mode: "turn" # once|turn|session
loaded_content_in_tool_results: true
max_loaded_skills: 0
skip_fallback_on_session_summary: true
# Optional: override default skills guidance text. Set to "" to disable it.
tooling_guidance: ""
# Optional: per-skill config (by skillKey or skill name).
entries:
gh-issues:
# Injected into metadata.openclaw.primaryEnv when present.
apiKey: "..."
# Injected into skill_run env (never overrides host env).
env:
GH_TOKEN: "..."OpenClaw defaults to materializing loaded skill bodies/docs into tool
result messages. This keeps the system prompt more stable while still
letting SkillLoadMode control how long loaded skill state survives.
tool_profile: "knowledge_only" is the default. It keeps skill_load,
skill_list_docs, and skill_select_docs, while hiding execution
tools such as skill_run. This is useful when you want skills to
provide guidance and supporting docs but keep actual execution on the
normal runtime tool surface.
Use tool_profile: "full" only when you explicitly want the built-in
skill execution tools to be visible to the model.
When skills.watch is enabled, changes under local filesystem skill
roots are picked up automatically after the watch debounce fires.
Official install flows should still prefer staged publish plus explicit
refresh after the final rename.
The built-in skills guidance is also more runtime-oriented by default:
the agent prefers skill-owned scripts when present and may use minimal
read-only probes such as --help or --version to verify external CLI
syntax before taking side effects.
Many OpenClaw skills use {baseDir} in commands (for example to run
scripts under scripts/). OpenClaw replaces {baseDir} in loaded
skill bodies/docs with the local skill folder path.
If you already have an OpenClaw skills directory, you can reuse it:
cd openclaw
go run ./cmd/openclaw \
-mode openai \
-model gpt-5 \
-skills-extra-dirs "/path/to/openclaw/skills"Note: OpenClaw skills often assume the OpenClaw tool surface. OpenClaw
enables the OpenClaw host tools for the default LLM agent so skills can
use exec_command, message, and cron, but it is not a full
OpenClaw replacement.
In a chat, you can ask the assistant to list and run skills. For example:
List available skills, then run the hello skill.
OpenClaw is intentionally small and "composition-first": it wires
existing trpc-agent-go building blocks together, instead of hiding
them behind a large framework.
It ships:
- A runnable binary:
go run ./cmd/openclaw - An importable library:
trpc.group/trpc-go/trpc-agent-go/openclaw/app
For enterprise/internal customization, the recommended pattern is to build your own "distribution binary" in another repo:
- Import
openclaw/app. - Enable internal-only plugins via anonymous imports (
import _ "..."). - Use a YAML config file to turn plugins on.
Go is a compiled language: a running binary cannot magically discover new Go packages at runtime.
The idiomatic "plugin" pattern in Go is:
- A shared registry package (
openclaw/registry). - Plugin packages call
registry.Register...(...)ininit(). - Your binary links plugins in by importing them (often anonymously).
OpenClaw supports these extension points:
- Channels: implement
openclaw/channel.Channeland register withregistry.RegisterChannel(type, factory). Enable via YAMLchannels: [...]. - Tool providers: register with
registry.RegisterToolProvider(type, factory). Enable via YAMLtools.providers: [...]. - ToolSet providers: register with
registry.RegisterToolSetProvider(type, factory). Enable via YAMLtools.toolsets: [...]. - Model types: register with
registry.RegisterModel(type, factory). Select viamodel.mode(-mode) and optionalmodel.config. - Session backends: register with
registry.RegisterSessionBackend(type, factory). Select viasession.backend(-session-backend) and optionalsession.config. - Memory backends: register with
registry.RegisterMemoryBackend(type, factory). Select viamemory.backend(-memory-backend) and optionalmemory.config. - Gateway run options: pass
app.WithGatewayRunOptions(...)orapp.WithGatewayRunOptionResolver(...)from a custom binary to inject request-scopedagent.RunOptionvalues. - Skills: no Go code needed; point
skills.extra_dirsat a folder.
For a step-by-step plugin authoring guide (with copy-paste templates),
see openclaw/EXTENDING.md.
See openclaw/examples/stdin_chat/ for a runnable reference
distribution binary:
main.goimportsopenclaw/app- It enables two plugins via anonymous imports:
openclaw/plugins/stdin(channel)openclaw/plugins/echotool(tool provider)
openclaw.yamlturns the plugins on
This is intentionally close to what an internal repo would do.
For skills, the simplest workflow is to keep a separate skills folder and point OpenClaw at it:
- Use
-skills-extra-dirs "/path/to/skills"(comma-separated), or - put skills under the managed directory:
<state-dir>/skills.
This allows an internal team to iterate on skill packs independently, without forking the gateway/channel code.
OpenClaw uses trpc-agent-go sessions to store conversation history
per session_id (derived from DM vs group/topic).
The session service is in-memory by default, so session history is cleared when the process exits.
It also enables an in-memory memory service and memory tools
(memory_add, memory_load, etc.) for the agent. Stored memories are
kept in process memory and are cleared when the process exits.
If you want a centralized store (for multi-instance deployments), you can switch session and memory backends to Redis:
cd openclaw
go run ./cmd/openclaw \
-mode mock \
-session-backend redis \
-session-redis-url "redis://127.0.0.1:6379/0" \
-memory-backend redis \
-memory-redis-url "redis://127.0.0.1:6379/0"The Redis key-space is still isolated by app_name and user_id. You
can override app_name with -app-name (or app_name in YAML) to
match your business identifier.
If you want local persistence across restarts (without running Redis), use the SQLite-backed backends.
By default, they store data in:
- Session
sqlite:<state_dir>/sessions.sqlite - Memory
sqlite:<state_dir>/memories.sqlite - Memory
sqlitevec:<state_dir>/memories_vec.sqlite
Example using session + memory SQLite:
cd openclaw
go run ./cmd/openclaw \
-mode mock \
-session-backend sqlite \
-memory-backend sqliteThe bundled sample config ./openclaw.stdin.sqlite.yaml also uses
SQLite for both session and memory.
memory.backend=sqlitevec is also supported for vector search. It uses
an OpenAI-compatible embedder and requires embedding credentials at
runtime.
SQLite-backed memory backends require a CGO-enabled build.
sqlitevec is additionally compiled behind the
openclaw_sqlitevec build tag so the default go build ./cmd/openclaw
path does not require the extra sqlite-vec header dependency.
OpenClaw also supports SQL backends already implemented in
trpc-agent-go:
- Session:
mysql,postgres,clickhouse - Memory:
mysql,postgres,pgvector(vector search via Postgres)
They are configured via session.config / memory.config. See
openclaw/INTEGRATIONS.md for copy-paste config examples.
The runner can enqueue background session summarization jobs after assistant replies.
Two related knobs:
session.summary: generate and store summaries in the session backend.agent.add_session_summary: prepend the latest summary to the model context (and only send incremental history after the summary).
To enable both:
cd openclaw
go run ./cmd/openclaw \
-mode openai \
-session-summary \
-session-summary-policy any \
-session-summary-events 20 \
-add-session-summaryThe runner can also enqueue background auto memory extraction jobs after assistant replies. When enabled, the memory service uses an LLM-based extractor to maintain user memories automatically.
Enable with:
cd openclaw
go run ./cmd/openclaw \
-mode openai \
-memory-auto \
-memory-auto-policy all \
-memory-auto-messages 20OpenClaw exposes a code-agent-first host tool surface for the default LLM agent, but it is unsafe when exposed to untrusted inputs.
The assistant gets:
exec_commandfor general host shell workwrite_stdinandkill_sessionfor interactive commandsmessagefor sending text, PDFs, images, audio, or video to the current chat or explicit targetscronfor future or recurring jobs
When OpenClaw finds a managed Python environment under
<state_dir>/toolchain/python, exec_command automatically prepends that
environment to PATH so host commands can use the installed Python packages
without changing prompts or shell commands.
To disable these tools explicitly:
go run ./cmd/openclaw \
-mode openai \
-model gpt-5 \
-config ./openclaw.yaml \
-enable-openclaw-tools=falseOnce enabled, you can ask the assistant to run a command, send to the current chat, or create a recurring job. For example:
Use exec_command to run: echo hello
If it is interactive, continue with write_stdin.
Create a cron job that reports system resources every minute to
this Telegram chat.
Cron jobs are persisted under the OpenClaw state directory, so they
continue after gateway restarts. In Telegram, /jobs and /jobs_clear
provide a direct way to inspect or clean up jobs for the current chat
without going through the model.
When admin.enabled is true (default), OpenClaw also starts a local
admin surface on admin.addr (default 127.0.0.1:19789). When
admin.auto_port is true (default), OpenClaw automatically moves to a
nearby free port if the preferred admin port is busy, and startup logs
print the actual admin URL.
The admin surface is intentionally broader than cron management. It now includes:
- runtime and host metadata
- gateway routes and JSON endpoints
- scheduled job inspection plus run/remove/clear actions
- exec session inspection
- persisted upload browsing with direct open/download links
- upload-session and media-kind filtered JSON views for multimodal traces
- session-indexed debug trace browsing with direct links to
meta.json,events.jsonl, andresult.json