#how can i configure web search api?

1 messages · Page 1 of 1 (latest)

dusty swiftBOT
#

To help others find answers, you can mark your question as solved via Right click solution message -> Apps -> ✅ Mark Solution

arctic vergeBOT
#

Yes — openclaw configure --section web is a supported (and recommended) way to set up web search.

What it does:

  • Prompts you for a Brave Search API key
  • Stores it in your config at: tools.web.search.apiKey (in ~/.openclaw/openclaw.json)
  • web_fetch does not need an API key (it’s usually enabled by default)

Docs:

Two quick gotchas to check if it still “can’t web search” after that:

  1. If you use tool allowlists, make sure web tools are allowed (either group:web or web_search + web_fetch).
  2. If you set the key via env var instead (BRAVE_API_KEY in ~/.openclaw/.env), you typically need a openclaw gateway restart so the service picks up the env.
#

If you run openclaw configure --section web and it says the section doesn’t exist, paste openclaw --version (older versions may not have that wizard section).

formal dagger
#

{
tools: {
web: {
search: {
enabled: true,
apiKey: "BRAVE_API_KEY_HERE", // optional if BRAVE_API_KEY is set
maxResults: 5,
timeoutSeconds: 30,
cacheTtlMinutes: 15,
},
},
},
}
where it locates?

arctic vergeBOT
#

That tools.web.search... block lives in your OpenClaw config file (JSON5).

Default location

On the Gateway host (the machine actually running openclaw gateway), it’s usually:

  • ~/.openclaw/openclaw.json

If you’re using a profile, it becomes:

  • ~/.openclaw-<profile>/openclaw.json

If you set OPENCLAW_CONFIG_PATH, then it’s whatever that env var points to.

How to see the exact file your Gateway is using

Run on the Gateway host:

#
openclaw gateway status

That output shows Config (cli) vs Config (service), which is the #1 “why is my edit not taking effect?” clue.

How to check the value quickly (no editing)

openclaw config get tools.web.search.apiKey
openclaw config get tools.web.search

Env-var alternative location (if you used BRAVE_API_KEY)

If you didn’t put it in config and used env instead: for a daemon/service install, the recommended place is:

  • ~/.openclaw/.env (on the Gateway host)
formal dagger
#

i mean, which level of json struct it locates?that is, which is its parent key?

arctic vergeBOT
#

It’s a top-level key in openclaw.json.

So the nesting is:

  • toolswebsearch (and/or fetch)

Example (valid JSON5 for ~/.openclaw/openclaw.json):

{
  // ...other config...

  tools: {
    web: {
      search: {
        enabled: true,
#
        apiKey: "BRAVE_API_KEY_HERE",
        maxResults: 5,
        timeoutSeconds: 30,
        cacheTtlMinutes: 15,
      },
      fetch: {
        enabled: true,
      },
    },
  },
}

If you prefer CLI instead of editing the file:

#
openclaw config set tools.web.search.apiKey "BRAVE_API_KEY_HERE"
openclaw config set tools.web.search.enabled true

And to confirm where it ended up:

openclaw config get tools.web.search
formal dagger
#

openclaw config get tools.web.search

openclaw config get tools.web.search outputs
🦞 OpenClaw 2026.3.1 (2a8ac97) — WhatsApp automation without the "please accept our new privacy policy".

{
"enabled": true,
"apiKey": "OPENCLAW_REDACTED",
"maxResults": 5,
"timeoutSeconds": 30,
"cacheTtlMinutes": 15
}

is this successfully configured?

arctic vergeBOT
#

Yes — that output means it’s configured correctly:

  • "enabled": true
  • "apiKey": "__OPENCLAW_REDACTED__" ✅ (redaction is normal; it means a key is present)

To confirm it works end-to-end, do one quick live test:

  1. Make sure the tool is allowed (only matters if you use tool allowlists):

    • your allowlist should include group:web or web_search + web_fetch
  2. In chat, explicitly ask it to use the tool, e.g.

    • “Use web_search to look up Brave Search API Data for Search plan and give me 3 results with URLs.”

If it’s going to a Gateway running as a service and you don’t see it picking up changes, do a restart once:

#
openclaw gateway restart
formal dagger
#

how can i show and config l allowlists?

arctic vergeBOT
#

Tool allowlists are controlled by these config fields:

  • Global: tools.profile, tools.allow, tools.deny
  • Per-agent: agents.list[...].tools.profile, agents.list[...].tools.allow, agents.list[...].tools.deny

Show your current allowlist/denylist

Run:

openclaw config get tools.profile
openclaw config get tools.allow
openclaw config get tools.deny

If you have multiple agents and want to check per-agent overrides:

#
openclaw config get agents.list

Configure (example: allow web tools)

The easiest/safest way is to allow the group:

  • group:web expands to web_search + web_fetch

If you already have tools.allow set

You must add group:web to that array (don’t overwrite and accidentally remove other tools). For example:

openclaw config set tools.allow '["group:web","group:messaging","group:fs"]' --strict-json

(Adjust the list to match what you want enabled.)

#

If you want to allow just the two tools

openclaw config set tools.allow '["web_search","web_fetch"]' --strict-json

Important gotcha

If you set tools.allow, OpenClaw is in “allowlist mode” — anything not listed becomes unavailable to the agent. So don’t set tools.allow unless you mean to restrict tools.

Docs (tool policy + groups): https://docs.openclaw.ai/tools (see tools.allow / tools.deny and group:web).

formal dagger
#

🦞 OpenClaw 2026.3.1 (2a8ac97)
The only crab in your contacts you actually want to hear from. 🦞

Config path not found: tools.profile

🦞 OpenClaw 2026.3.1 (2a8ac97) — No $999 stand required.

Config path not found: tools.allow

🦞 OpenClaw 2026.3.1 (2a8ac97)
If you can describe it, I can probably automate it—or at least make it funnier.

Config path not found: tools.deny

arctic vergeBOT
#

That’s normal — it just means you haven’t set any tool allow/deny policy yet.

openclaw config get <path> errors with “Config path not found” when that key doesn’t exist in your config file. Since tools.profile, tools.allow, and tools.deny are optional, missing = defaults apply:

  • tools.profile: default is effectively full (no restriction)
  • tools.allow: unset (not in allowlist mode)
  • tools.deny: unset

How to “show” what you currently have

This will show your whole tools object (including the tools.web.search you already set):

openclaw config get tools

How to configure an allowlist (if you want one)

Example: allow only web tools (web_search + web_fetch):

#
openclaw config set tools.allow '["group:web"]' --strict-json

Or allow web + file tools:

openclaw config set tools.allow '["group:web","group:fs"]' --strict-json

Important gotcha

If you set tools.allow, you are in restrictive allowlist mode — tools not listed become unavailable to the agent. (So don’t set it unless you actually want to restrict.)

Docs: https://docs.openclaw.ai/tools (see tools.allow / tools.deny and group:web).

formal dagger
#

openclaw config get tools

🦞 OpenClaw 2026.3.1 (2a8ac97)
I'm basically a Swiss Army knife, but with more opinions and fewer sharp edges.

{
"web": {
"search": {
"enabled": true,
"apiKey": "OPENCLAW_REDACTED",
"maxResults": 5,
"timeoutSeconds": 30,
"cacheTtlMinutes": 15
},
"fetch": {
"enabled": true
}
}
}

but when i prompt "use web_search tool, tell me the news totay", it didn't really search!

arctic vergeBOT
#

Your config is fine — but OpenClaw doesn’t force tool use just because you mention the tool name. The model can still decide to answer from “general knowledge” unless you make it strict (or you turn on verbose and verify the call happened).

1) Force it (better prompt)

Try something like:

“Use web_search (freshness: pd, count: 5) for top news headlines today and return the 5 result URLs first, then summarize.”

Requiring URLs first usually makes it actually call the tool.

2) Verify whether web_search was called (debug)

In this Discord thread, temporarily enable verbose so OpenClaw will show tool calls:

  1. Send this as a standalone message:
    /verbose on
    
    (or /verbose full if you also want tool outputs)
#
  1. Then send your web prompt again.

If web_search is being used, you should see a separate bubble like web_search: ....

After testing, turn it back off:

/verbose off

3) If you still see no web_search bubble

Then something is preventing tools from being offered to the model in that session (most commonly: you’re on a model/provider that isn’t reliably tool-calling, or there’s an agent-specific tool profile).

To check for agent-specific tool policy, paste:

openclaw config get agents.defaults.tools
openclaw config get agents.list
#

And if you can, also paste /model status from the chat (it should show which model/session you’re on).

formal dagger
#

oh, how can i prompt it in Chinese

arctic vergeBOT
#

你可以直接用中文“强制它真的去搜”,关键是把要求写得很明确(尤其是 先输出链接/结果列表)。

中文模板(推荐)

模板 A:先给结果列表 + 再总结

请使用 web_search 工具搜索:今天的国际新闻 头条
调用参数请尽量用:freshness="pd", count=5, country="CN", search_lang="zh", ui_lang="zh-CN"
先输出 5 条搜索结果(标题 + URL + snippet),然后再用中文总结 5 条要点。不要凭记忆编造。

模板 B:指定具体主题(更容易触发工具)

请用 web_search 搜索:2026年3月4日 科技新闻 头条(freshness=pd,count=5)。
先列出链接,再总结。

如果它还是“不搜”

在这个 Discord 线程里先发(单独一条消息):

/verbose on
#

然后再发上面的中文提示。你应该会看到一条 web_search: ... 的工具调用气泡;看不到的话就说明这次模型没有调用工具。调试完记得关掉:

/verbose off
formal dagger
#

Config path not found: agents.defaults.tools

arctic vergeBOT
#

That message just means your config file doesn’t currently contain agents.defaults.tools (it’s optional), not that anything is broken.

So right now:

  • you have tools.web.search configured ✅
  • but you don’t have any agent-specific tool policy set under agents.defaults.tools (so it’s using the global defaults)

If you want to see what is set under agents.defaults, run:

openclaw config get agents.defaults

If you want to add an agent-default tool allowlist (example: ensure web tools are allowed), you can create it like this:

#
openclaw config set agents.defaults.tools.allow '["group:web"]' --strict-json

(Only do this if you actually want allowlist mode for that agent — it will restrict other tools.)

For your “it didn’t really search” issue: the fastest next step is still to turn on tool visibility and confirm whether a web_search: call happens:

  1. send /verbose on in the chat with your bot
  2. ask again with “先列出 5 个结果链接…”
  3. if there’s no web_search: bubble, paste /model status (some models just ignore tool instructions).
formal dagger
#

openclaw config get agents.defaults
and there is no tool under this. Shall I configure web tool in it?

arctic vergeBOT
#

No — you don’t need to put “web tool config” under agents.defaults.

There are two different things:

  1. Tool configuration (API keys / behavior)
    This is what you already did, and it belongs here:

    • tools.web.search.*
    • tools.web.fetch.*
  2. Tool policy (which tools the agent is allowed to use)
    This is what agents.defaults.tools is for (allow / deny / profile), and it’s optional.
    If you don’t set it, the agent uses the global defaults (which are basically “full tools” unless you set an allow/deny list elsewhere).

So unless you specifically want to restrict tools (security hardening), leave agents.defaults.tools unset.

If your goal is “why doesn’t it actually call web_search?”, adding agents.defaults.tools usually won’t fix that — better to verify with /verbose on whether a web_search: tool call happens, and if not, it’s typically a prompt/model/tool-following issue.

formal dagger
#

is tavily web search api supported?

arctic vergeBOT
#

Not currently (as of 2026.3.x).

web_search supports these providers out of the box: Brave (default), Perplexity, Gemini (grounding), Grok, Kimi — no Tavily option in the built-in config (tools.web.search.provider) docs.
Docs: https://docs.openclaw.ai/tools/web

If you really want Tavily, you’d need to add it via a custom tool/plugin/skill (or a small wrapper service that exposes an OpenClaw-supported search provider shape).

If you want it supported natively, the right path is a feature request on GitHub:
https://github.com/openclaw/openclaw/issues

formal dagger
#

{
"id": "local",
"name": "local",
"workspace": "",
"agentDir": "",
"model": {
"primary": "qwen/qwen3.5:235b"
}
其中的primary可以通过命令行配置吗

arctic vergeBOT
#

可以的。

方式 1:用 openclaw models set(最简单,改“默认 agent”的默认模型)

openclaw models set qwen/qwen3.5:235b

这通常会把默认模型写回配置里(等价于改默认 agent 的 agents.defaults.model.primary 一类的设置)。

方式 2:精确修改你贴的那个 agent(id: "local")的 model.primary

因为 agents.list 是数组,你需要先找到 local 在第几个:

openclaw config get agents.list

假设你看到 localagents.list[0],那就:

#
openclaw config set agents.list[0].model.primary "qwen/qwen3.5:235b"

改完如果没生效,重启一下网关最稳:

openclaw gateway restart

如果你把 agents.list 的输出贴出来(把敏感信息打码即可),我也可以告诉你应该用哪个 index。

formal dagger
#

:50.557Z [agent/embedded] [context-diag] pre-prompt: sessionKey=agent:local:feishu:group:oc_4f6ac0f13c19c875a4a9ff3b24d32a3c messages=181 roleCounts=assistant:91,toolResult:80,user:10 historyTextChars=323798 maxMessageTextChars=41685 historyImageBlocks=0 systemPromptChars=27933 promptChars=881 promptImages=0 provider=qwen-brain/qwen3.5:9b-q8_0 sessionFile=/home/sun/.openclaw/agents/local/sessions/49bda36d-71d8-4c9f-b963-b195c4942c60.jsonl
:50.602Z [agent/embedded] embedded run compaction start: runId=266bfd60-b2aa-4b18-82b6-370ed18c1102
2026-03-05T10:53:49.753+08:00 typing TTL reached (2m); stopping typing indicator

分析这段日志,我是否该限制上下文长度?该如何限制?

arctic vergeBOT
#

这段日志的关键信息是:

  • messages=181(历史很长)
  • toolResult=80(工具输出很多)
  • historyTextChars=323798(进模型前的历史文本已经 32 万字符级别)
  • 然后立刻出现 embedded run compaction start(OpenClaw 已经判断上下文太大,开始自动压缩/总结

所以答案是:你现在确实需要控制上下文膨胀,否则小上下文/本地模型很容易变慢、跑不动、或者频繁压缩。

你该怎么“限制上下文长度”(推荐做法)

1) 设定一个合理的上下文上限:agents.defaults.contextTokens

如果你用的是本地/自建模型,OpenClaw 有时拿不到准确的 context window,就会让会话越堆越大才触发压缩。最有效的办法是手动给它一个上限(设置成你模型实际支持的上下文,比如 8k/16k/32k 等)。

命令行配置示例(以 16k 为例):

#
openclaw config set agents.defaults.contextTokens 16000 --strict-json

你应该把 16000 换成你模型服务实际的上下文长度(不确定就先 16000 或 32000,观察 compaction 是否更及时)。

2) 减少“超大工具输出”进入上下文(常见元凶)

你日志里 maxMessageTextChars=41685 暗示某条消息(常是 toolResult)特别长。对 web 抓取尤其常见。

可以把 web_fetch 的最大抓取长度调小点,例如:

openclaw config set tools.web.fetch.maxChars 20000 --strict-json

3) 操作习惯:主动 /compact/new

  • 话题切换就 /new(最干净)
  • 感觉上下文变臃肿时 /compact
#

如果你愿意把下面两条也贴一下(不含敏感信息即可),我可以更精确建议你该把 contextTokens 设多少:

openclaw models status
openclaw config get agents.defaults.model
formal dagger
#

Qwen3.59B的模型

arctic vergeBOT
#

明白,你用的是 Qwen3.5 9B(而且还是 q8 量化)。这种模型在本地跑的时候,“上下文能开多大”主要取决于你用的推理引擎/启动参数(不完全是模型本身),而且上下文越大越慢、越吃内存。

建议怎么设 contextTokens(实用取向)

如果你不确定你那边到底开了多少上下文,先从 8k 或 16k 开始最稳:

  • 机器比较一般 / 想更快:8192
  • 机器还行 / 想保留更多上下文:16384

命令行直接配:

openclaw config set agents.defaults.contextTokens 8192 --strict-json
# 或
openclaw config set agents.defaults.contextTokens 16384 --strict-json

另外一个很关键的参数:reserveTokensFloor

#

你贴的日志里已经在自动 compaction。对于 9B 这种小上下文模型,建议把 compaction 预留输出的 token 下限调小点(不然可能“老是觉得快满了”而频繁压缩)。

比如设成 1500~2500:

openclaw config set agents.defaults.compaction.reserveTokensFloor 2000 --strict-json

改完怎么验证效果

  • 观察日志里 pre-prompt ... historyTextChars=... 是否明显变小
  • 看 compaction 触发是否变得更“合理”(不会每两句就压缩,也不会堆到爆炸才压缩)

如果你告诉我你是用 Ollama / llama.cpp / vLLM / LM Studio 哪个在跑 Qwen3.5 9B,我还能给你一个“怎么查实际 context window/最大 n_ctx”的具体方法,然后把 contextTokens 精确对齐。

formal dagger
#

怎么查看openclaw的实时日志?我原来是nohup输出到文件里,但是restart之后就不继续输出了

arctic vergeBOT
#

nohup ... > file 这种方式只会抓住你那次启动进程的 stdout/stderr。你后来用 openclaw gateway restart(或服务)重启之后,新的进程已经不是从那个 nohup 启动的了,所以当然不会继续写到原来的文件。

下面是更“OpenClaw 正统”的实时日志看法:

1) 最推荐:OpenClaw 自带日志跟随

在网关机器上直接跑:

openclaw logs --follow

如果网关 RPC 挂了(openclaw logs 连不上),用 FAQ 里的兜底方式:

tail -f "$(ls -t /tmp/openclaw/openclaw-*.log | head -1)"

默认文件日志在:/tmp/openclaw/openclaw-YYYY-MM-DD.log

#

2) 如果你是以服务方式跑的(Linux systemd user)

实时跟随:

journalctl --user -u openclaw-gateway.service -f

如果你用了 profile,单位名会变成 openclaw-gateway-<profile>.service(不确定就先跑 openclaw gateway status 看它提示)。

3) macOS(launchd)的话

日志一般在:

  • ~/.openclaw/logs/gateway.log
  • ~/.openclaw/logs/gateway.err.log

(profiles 会在 ~/.openclaw-<profile>/logs/...

如果你告诉我你是 Linux 还是 macOS、以及是不是用了 --profile,我可以给你一条最准确的 journalctl/tail 命令。

formal dagger
#

03:06:27 debug diagnostic {"subsystem":"diagnostic"} run registered: sessionId=49bda36d-71d8-4c9f-b963-b195c4942c60 totalActive=1
03:06:27 debug agent/embedded {"subsystem":"agent/embedded"} embedded run prompt start: runId=4d32318a-0e56-496b-b3bb-8e80ad964d5a sessionId=49bda36d-71d8-4c9f-b963-b195c4942c60
03:06:27 debug agent/embedded {"subsystem":"agent/embedded"} embedded run compaction start: runId=4d32318a-0e56-496b-b3bb-8e80ad964d5a
03:06:52 debug cron {"module":"cron","storePath":"/home/sun/.openclaw/cron/jobs.json"} {"nextAt":1772711229203,"delayMs":60000,"clamped":true} cron: timer armed
03:07:52 debug cron {"module":"cron","storePath":"/home/sun/.openclaw/cron/jobs.json"} {"nextAt":1772711229203,"delayMs":60000,"clamped":true} cron: timer armed
03:08:27 info typing TTL reached (2m); stopping typing indicator
03:08:52 debug cron {"module":"cron","storePath":"/home/sun/.openclaw/cron/jobs.json"} {"nextAt":1772711229203,"delayMs":60000,"clamped":true} cron: timer armed
03:09:52 debug cron {"module":"cron","storePath":"/home/sun/.openclaw/cron/jobs.json"} {"nextAt":1772711229203,"delayMs":60000,"clamped":true} cron: timer armed
03:10:52 debug cron {"module":"cron","storePath":"/home/sun/.openclaw/cron/jobs.json"} {"nextAt":1772711229203,"delayMs":60000,"clamped":true} cron: timer armed

arctic vergeBOT
#

从这段日志看,核心点只有一个:

  • embedded run compaction start 出现了,但后面一直没有看到类似 compaction complete / prompt done / llm response 的日志
  • 然后只有 cron 在每分钟打点 + typing TTL reached (2m)(这只是“正在输入”指示器 2 分钟到期了,不会取消任务

这通常意味着:这次 compaction 的 LLM 请求还没跑完(要么很慢,要么卡住/后端挂了)。

先判断:是“慢”还是“卡死”

  • 如果你用的是本地 Qwen3.5 9B q8,面对你之前那种 historyTextChars=323798 的超长历史,compaction 可能真的要跑很多分钟。
  • 如果等了很久(比如 10+ 分钟)还是没有任何后续 LLM 请求/返回日志,那更像是后端推理卡死/超时/内存不够

建议你同时看一下你推理后端(qwen-brain 对接的服务,比如 LM Studio / llama.cpp / vLLM / Ollama)的控制台日志,那里往往会直接报 OOM 或超长上下文错误。

立刻止血(当前这次卡住)

  • 在对应的群聊里发一句 stop(abort 触发词),或直接 /new 开新会话绕开这次卡住的 run
  • 还不行就 openclaw gateway restart(会中断当前 run)
#

从根上减少 compaction 卡住:让它更早压缩、不要堆到 30 万字符

对 9B 本地模型我强烈建议你把这两个值配上:

openclaw config set agents.defaults.contextTokens 8192 --strict-json
openclaw config set agents.defaults.compaction.reserveTokensFloor 2000 --strict-json
openclaw gateway restart
  • contextTokens 会让 OpenClaw 更早判断“该压缩了”,避免堆积到超大历史才触发
  • reserveTokensFloor 对小上下文模型要调小,不然策略会很别扭

如果你告诉我你 qwen-brain 背后到底是 LM Studio / llama.cpp / vLLM / Ollama 哪个,我可以给你一条“怎么查实际上下文上限(n_ctx/max_model_len)”的命令,把 contextTokens 精确对齐。

formal dagger
#

⚠️ Agent failed before reply: All models failed (2): qwen-brain/qwen3.5:9b-q8_0: Model context window too small (8192 tokens). Minimum is 16000. (unknown) | siliconflow/Pro/zai-org/GLM-5: Model context window too small (8192 tokens). Minimum is 16000. (unknown).
Logs: openclaw logs --follow

arctic vergeBOT
#

这个报错很明确:OpenClaw 不允许 context window < 16000 tokens(硬性下限),而你现在的配置让它变成了 8192,所以主模型和 fallback 都被直接 block 了。

最常见原因:你之前设置了
agents.defaults.contextTokens = 8192
(这个字段是“上限 cap”,会把原本更大的上下文窗口压到 8192)。

立刻修复(推荐)

contextTokens 调到 >= 16000,比如先用 16000:

openclaw config set agents.defaults.contextTokens 16000 --strict-json
openclaw gateway restart

如果你想稍微留点余量,也可以 20000/24000 之类(但不要 <16000)。

或者直接撤销这个 cap(恢复默认上下文窗口估算)

#
openclaw config unset agents.defaults.contextTokens
openclaw gateway restart

修好后再看 compaction/卡住问题,我们再继续用 reserveTokensFloorweb_fetch.maxChars、以及会话 /new//compact 来控制“上下文膨胀”。

formal dagger
#

debug agent/embedded {"subsystem":"agent/embedded"} embedded run agent start: runId=2c457be6-6eae-4ec1-b79d-18857e5e90e0

为什么在这个卡了很久

arctic vergeBOT
#

这行:

embedded run agent start: runId=...

本身只表示 “这一轮 run 开始了”。如果它之后很久都没继续往下打印(比如看不到 context-diag pre-prompt / compaction start / provider request / reply),通常是卡在下面两类地方之一:

最常见原因 1:在等本地模型返回(Qwen3.5 9B + 16k 上下文会很慢)

你现在把最小上下文拉到了 16000(OpenClaw 硬性要求),对本地 9B q8 来说:

  • prompt 很长时(尤其会话历史多、工具 schema 多),推理可能要跑好几分钟才出首 token
  • Feishu/Discord 的 typing 指示器还会 2 分钟 TTL 到期停掉,看起来就像“卡死”,但其实还在跑

验证方法:

  • 同时看一下你 qwen-brain 后端(LM Studio / llama.cpp / vLLM / Ollama)的控制台日志,看它有没有收到请求、是否在跑、有没有 OOM。

最常见原因 2:自动 compaction 在跑(长会话会先总结)

#

如果这一轮触发了 compaction,你应该在后面看到:
embedded run compaction start ...
compaction 对本地小模型也会非常慢(而且可能不止一次调用)。

你可以立刻做的 3 个排查动作

  1. 在一个全新会话测试一下速度:发 /new,然后只发一句很短的问题。
    • 如果新会话很快:就是“旧会话太长/compaction 太重”导致的慢。
  2. 把 web_fetch 输出再压小(避免 toolResult 进上下文太大):
    openclaw config set tools.web.fetch.maxChars 20000 --strict-json
    openclaw gateway restart
    
  3. 如果你想让它更快失败而不是卡很久(方便定位后端错误),可以临时把超时调小,比如 180 秒:
    openclaw config set agents.defaults.timeoutSeconds 180 --strict-json
    openclaw gateway restart
    
#

(确定没问题后再调回去/或 unset)

如果你能补一段“agent start 之后”的日志(再往后 30~60 行),尤其是有没有出现 compaction start 或任何 provider= 的请求日志,我就能更准确判断它到底卡在哪一步。

formal dagger
#

我要如何让agent记住和它对话多的用户,增加熟悉程度

arctic vergeBOT
#

要让 OpenClaw “越聊越熟”,核心是把用户信息从一次性上下文变成落盘的长期记忆,并且避免多用户互相污染。

下面按从简单到进阶给你一套可落地的做法:

1) 先确保“每个用户都有自己的会话上下文”(最重要)

这样同一个群里不同人不会混在一锅粥里。

检查/设置:

openclaw config get session.scope
openclaw config set session.scope "per-sender" --strict-json

per-sender 意味着同一个群里每个人都有独立 session,更容易“熟悉”。)

2) 让它“记住你”:把偏好写进记忆文件(落盘)

OpenClaw 的记忆是工作区里的 Markdown 文件:

#
  • memory/YYYY-MM-DD.md:日记型记忆(每天)
  • MEMORY.md:长期记忆(只会在主私聊 main session 自动加载;群里默认不会加载,这是为了避免隐私泄漏)

所以如果你是在群里想让它记住你,建议把内容写到 memory/YYYY-MM-DD.md,并且加上明确的用户标识,例如:

“请把下面内容写进记忆:
【用户:Rinascere / Feishu】我更喜欢中文回复;回答尽量简洁;我主要问的是 OpenClaw 配置问题。”

你也可以手动编辑工作区里的 memory/2026-03-05.md 之类文件。

3) 让它能“想起来”:启用/使用 memory_search(进阶但很关键)

如果你希望它隔几天也能想起你的偏好,而不是只靠“今天/昨天会读到的日记”,就需要让它在对话时用 memory_search 召回旧记录(需要配置 memorySearch/embedding,见文档)。

文档:https://docs.openclaw.ai/concepts/memory

4) 多用户场景的提醒(避免串味)

如果你的 bot 会被很多人用,不要把每个人的偏好都写进共享的 MEMORY.md(群里也不会加载),更不要把敏感信息记下来。更稳的做法是:

#
  • session.scope: per-sender 保持上下文隔离(上面第 1 条)
  • memory/YYYY-MM-DD.md 记录偏好,并在记录里写清楚 “用户是谁/渠道是谁”

如果你告诉我:你是在 群聊里想记住多人,还是在 DM 私聊里想记住你自己,我可以给你一个更具体的“推荐记忆格式”(比如每个用户一段固定模板),让它更容易稳定召回。

formal dagger
#

39:12 debug agent/embedded {"subsystem":"agent/embedded"} embedded run agent start: runId=ff14b9c8-9d7a-4829-bfde-a4628154a7ee
03:39:30 debug cron {"module":"cron","storePath":"/home/sunrise/.openclaw/cron/jobs.json"} {"nextAt":1772711229203,"delayMs":60000,"clamped":true} cron: timer armed
03:39:34 debug agent/embedded {"subsystem":"agent/embedded"} embedded run tool start: runId=ff14b9c8-9d7a-4829-bfde-a4628154a7ee tool=web_search toolCallId=call_w2ux1428
03:39:34 debug agent/embedded {"subsystem":"agent/embedded"} embedded run tool end: runId=ff14b9c8-9d7a-4829-bfde-a4628154a7ee tool=web_search toolCallId=call_w2ux1428
03:39:39 debug agent/embedded {"subsystem":"agent/embedded"} embedded run tool start: runId=ff14b9c8-9d7a-4829-bfde-a4628154a7ee tool=web_search toolCallId=call_xc6jbqjy
03:39:40 error [tools] web_search failed: fetch failed
03:39:40 error [tools] web_search failed: fetch failed
03:39:40 debug agent/embedded {"subsystem":"agent/embedded"} embedded run tool end: runId=ff14b9c8-9d7a-4829-bfde-a4628154a7ee tool=web_search toolCallId=call_xc6jbqjy

web_search failed: fetch failed是怎么回事

arctic vergeBOT
#

web_search failed: fetch failed 不是“API key 不对”的那种 401/403,而是更底层的:网关进程在发 HTTP 请求时,连 Brave 的接口都没请求成功(DNS/网络/被墙/沙箱断网/证书等原因)。

结合你日志里同一轮里连着两次 web_search,第一次没报错、第二次直接 fetch failed,更像是 网络不稳定或有时能通、有时不通

最常见原因排查(按优先级)

1) 你是不是把群聊跑在 sandbox 里、并且 sandbox 禁网了?

如果你启用了 Docker sandbox,而且 network: "none",那所有外网请求都会 fetch failed

检查:

openclaw config get agents.defaults.sandbox

2) 网关机器本身能不能访问 Brave API(最关键)

在网关机器上跑(不要把 key 贴到 Discord):

#
curl -I https://api.search.brave.com/

再测真实搜索(建议先在本机终端里用环境变量,不要直接把 key 写命令里):

# 先在本机 shell 里 export BRAVE_API_KEY=...
curl -sS "https://api.search.brave.com/res/v1/web/search?q=test" \
  -H "Accept: application/json" \
  -H "X-Subscription-Token: $BRAVE_API_KEY" | head

如果这里都不通,那就是网络/DNS/防火墙/地区阻断问题。

3) 如果你在国内/公司网络,Brave 可能不稳定或被拦

这种情况下建议直接换 web_search provider(比如 Gemini/Kimi/Perplexity),避免被 Brave 卡住。文档里有配置示例:
https://docs.openclaw.ai/tools/web

#

你先回我两点就能基本定性了:

  1. openclaw config get agents.defaults.sandbox 的输出里 network 是什么?
  2. curl -I https://api.search.brave.com/ 能不能连上(返回状态码还是超时/失败)?
formal dagger
#

我在设备b上能连上api,且a能连上b,那么有办法通过什么(系统)配置让a连上api吗

arctic vergeBOT
#

可以的,而且 OpenClaw 的 web_search 会自动走系统代理环境变量(只要你给网关进程设置了 HTTP_PROXY / HTTPS_PROXY / ALL_PROXY)。

你现在的条件是:B 能上网、A 能连 B —— 那最实用的方案就是让 A 通过 B 的代理出网

方案 A(推荐):在 B 上提供一个 HTTP/SOCKS 代理,A 用环境变量指向它

  1. 确保 B 上有代理服务(例如 clash/v2ray/squid/tinyproxy 等),并且 A 能访问到 B 的代理端口(最好只在内网/Tailscale 上开放,别公网裸奔)。

  2. 在 A(运行 OpenClaw Gateway 的那台)写入:

  • ~/.openclaw/.env 里加(示例端口自己改):
HTTPS_PROXY=http://<B的IP或主机名>:7890
HTTP_PROXY=http://<B的IP或主机名>:7890
# 可选:不走代理的地址
NO_PROXY=127.0.0.1,localhost,::1
#
  1. 重启网关:
openclaw gateway restart

然后再试 web_search,你之前的 fetch failed 大概率就没了。

方案 B:不想在 B 开端口,用 SSH 动态 SOCKS(高级)

如果 A 能 ssh 到 B,可以在 A 上建一个 SOCKS5 隧道,然后让网关走它(需要你能把这个隧道长期保持,比如 autossh):

ssh -N -D 1080 user@B

然后给 OpenClaw 进程设置:

#
ALL_PROXY=socks5h://127.0.0.1:1080

方案 C:如果你用 Tailscale,直接把 B 设为 Exit Node

这是“系统层面最干净”的做法:A 所有流量都走 B 出口(对应用透明)。


你告诉我两点我就能给你最准确的一条命令/配置:

  1. A/B 是什么系统(Linux/macOS/Windows/WSL)?
  2. B 上现在有没有现成的代理端口(比如 7890/1080)可用?还是需要我给你一个最轻量的 tinyproxy/squid 配法?
formal dagger
#

为什么是7890端口呢,可以改吗

arctic vergeBOT
#

7890 只是我举的一个常见示例端口(很多人用 Clash/Mihomo 时 HTTP 代理默认就是 7890),不是 OpenClaw 强制的。

当然可以改,规则很简单:

  • 你代理服务监听什么端口(比如 8888、3128、1080…)
  • OpenClaw 就在环境变量里填同样的端口

例如你把 B 的 HTTP 代理开在 8888,那么 A 上就写:

HTTPS_PROXY=http://B:8888
HTTP_PROXY=http://B:8888

如果你用的是 SOCKS5 代理(常见 1080),就用:

#
ALL_PROXY=socks5h://B:1080

端口怎么改取决于你在 B 上跑的代理软件(Clash/squid/tinyproxy/v2ray 等)配置;OpenClaw 这边不关心端口是多少,只要能连通就行。

formal dagger
#

整理一下哈!b设备需要export HTTPS_PROXY=ipc:portc 来通过代理访问外网,b的ip是<ipb>,那么我需要在a和b上分别怎么设置来生效?

arctic vergeBOT
#

整理得很清楚。你现在的拓扑是:

  • B 这台机器想上外网,需要走上游代理 CipC:portC),所以在 B 上你会 export HTTPS_PROXY=...
  • A 不能直接连到 C,但 A 能连到 B
  • 目标:让 A 的 OpenClaw 也能通过 C 上网(用于 web_search

最省事、最稳的做法是:在 A 上用 SSH 把 “到 C 的代理端口” 通过 B 转发到本地,然后让 OpenClaw 走本地代理。


方案(推荐):A →(ssh)→ B →(tcp)→ C,把 C 的代理端口转到 A 本地

第 1 步:在 A 上建立端口转发(保持运行)

在 A 上运行(把 user/ip 填好):

#
ssh -N -L 7890:ipC:portC user@ipB

含义:A 本地的 127.0.0.1:7890 会被转发到 B 能访问的 ipC:portC

7890 只是示例,你可以换成任意 A 上没占用的端口,比如 18080。

注意:这条 ssh 命令要一直保持运行(否则代理就断了)。想常驻可以用 autossh 或 systemd user service。

第 2 步:在 A 上给 OpenClaw 设置代理环境变量

编辑 A 上的 ~/.openclaw/.env(或你服务的环境变量),加入:

HTTPS_PROXY=http://127.0.0.1:7890
HTTP_PROXY=http://127.0.0.1:7890
#
NO_PROXY=127.0.0.1,localhost,::1

然后重启 OpenClaw 网关让它读到 env:

openclaw gateway restart

第 3 步:B 上需要怎么设?

如果你的目标只是让 A 通过 B 转发到 C:B 不需要设置 OpenClaw 的代理 env。
B 只要满足两点即可:

  1. A 能 ssh 到 B
  2. B 能访问 ipC:portC(也就是 C 这个代理本身)

你在 B 上设置 HTTPS_PROXY=ipC:portC 主要是让 B 自己的应用走代理;端口转发这条链路本身不依赖它(B 只是在转发 TCP 到 C)。

#

怎么验证成功

在 A 上先验证代理通不通(不要贴 key 到群里):

curl -I https://api.search.brave.com/ --proxy http://127.0.0.1:7890

再让 OpenClaw 跑一次 web_search,看日志里是否还出现 fetch failed


如果你告诉我:A→B 是用 ssh 连接的吗?以及 C 是 HTTP 代理还是 SOCKS5,我可以把上面的 HTTP_PROXY/ALL_PROXY 写成你那种代理类型的“最正确版本”。

formal dagger
#

ssh -N -L 7890:ipC:portC user@ipB用autossh怎么做呀

arctic vergeBOT
#

autossh 做这种“本地端口转发 + 自动重连”,核心就三点:免密 SSH、ExitOnForwardFailure、KeepAlive

1) 先确保 SSH 免密

在 A 上要能直接连 B 不用输密码(用 key):

ssh user@ipB

如果还要输密码,先把公钥拷到 B(示例):

ssh-copy-id user@ipB

2) autossh 一条命令(推荐)

#
autossh -M 0 -f -N \
  -L 7890:ipC:portC user@ipB \
  -o ExitOnForwardFailure=yes \
  -o ServerAliveInterval=30 \
  -o ServerAliveCountMax=3

解释:

  • -L 7890:ipC:portC:就是你原来的转发
  • -f -N:后台运行、不执行远端命令
  • -M 0:关闭 autossh 的监控端口(很多环境开不了监控端口时用这个最省心),改用 keepalive 来判断断线重连
  • ExitOnForwardFailure=yes:如果转发没建立成功就直接退出,避免“假在线”
  • ServerAlive*:SSH 心跳,断了能被 autossh 发现并重连

3) 怎么确认它在跑

在 A 上试一下代理端口是否可用:

#
curl -I https://api.search.brave.com/ --proxy http://127.0.0.1:7890

4) 建议(稳定性)

如果你打算长期跑,最好把这个 autossh 放进 systemd user service(或用 tmux 常驻)。你说下 A 是 Linux 还是 macOS,我可以给你一份最短的 unit 文件。

formal dagger
#

如果ssh的用户要输入密码,怎么办

arctic vergeBOT
#

autossh 要实现“断线自动重连”,必须是非交互式登录。也就是说:如果每次都要手动输 SSH 密码,它就没法在后台自动重连(重连时没人帮它输入)。

正解:改成 SSH Key 登录(推荐)

在 A 上生成密钥(一路回车即可;如果你给 key 设了 passphrase,后面用 ssh-agent 解决):

ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519

把公钥复制到 B(会让你输一次密码,这是最后一次):

ssh-copy-id -i ~/.ssh/id_ed25519.pub user@ipB

验证免密:

#
ssh user@ipB

能直接登录不再要密码后,再跑 autossh ... 就能真正自动重连了。

如果你给 key 设了 passphrase

ssh-agent 预先解锁一次:

eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519

之后 autossh 就不需要再问 passphrase。

不推荐但可选:sshpass

#

可以用 sshpass 把密码塞给 ssh,但非常不安全(密码会出现在进程列表/脚本里),我不建议在生产环境用。


如果你贴一下 A 是 Linux 还是 macOS、以及你能不能在 B 上加 authorized_keys(有权限吗),我可以给你最短的按平台步骤。

#

Connection refused 说明一件事:你这台(A)机器能连到那个 IP,但对方的 22 端口上没有 SSH 服务在监听(或者被安全组/防火墙直接拒绝了)。所以 ssh-copy-id 才会失败。

先别纠结 ssh-copy-id,你得先做到 手动 ssh 能连上

1) 先确认 ssh 到底走哪个端口

在 A 上先试:

ssh -v yanjie.shen@120.48.157.2

如果你们服务器不是 22 端口(很多会改成 2222/22022),就用正确端口:

ssh -p 2222 yanjie.shen@120.48.157.2
#

一旦你找到“能连上的端口”,ssh-copy-id 也要带同样的端口:

ssh-copy-id -p 2222 -i ~/.ssh/id_ed25519.pub yanjie.shen@120.48.157.2

2) 如果你确定就是 22,但还是 refused

那就是 对方没开 sshd防火墙/安全组没放行

在 B(120.48.157.2)上检查:

  • Linux(Ubuntu/Debian 常见):
    sudo systemctl status ssh
    sudo ss -ltnp | grep ':22'
    
    没有的话需要安装/启动 sshd,并放行端口 22。
#

3) 快速判断是“被拒绝”还是“被拦截”

  • refused:通常是 没服务监听
  • timeout:通常是 网络/防火墙拦了

你能贴一下你平时从 A 连 B 用的那条 ssh ... 命令吗(把密码/私钥路径打码即可)?我就能直接告诉你 ssh-copy-id 应该怎么写。

formal dagger
#

ssh -v这条命令是通的

arctic vergeBOT
#

那就奇怪了:如果同一台机器上 ssh -v user@ip 能连通ssh-copy-id 本质也是调用 ssh,正常不该再出现 Connection refused

最常见的解释其实是:你能 ssh 通,是因为用了 非默认端口/跳板机(ProxyJump)/特定配置,但 ssh-copy-id 那次实际走的是 22 端口直连(所以才显示 port 22: Connection refused)。

你先确认一件事

把这条命令的前几行输出贴一下(重点看 “Connecting to … port …”):

ssh -v yanjie.shen@120.48.157.2

然后按“和 ssh 一模一样的参数”跑 ssh-copy-id

如果 ssh 实际用的是别的端口(例如 2222)

ssh-copy-id -p 2222 -i ~/.ssh/id_ed25519.pub yanjie.shen@120.48.157.2
#

如果 ssh 需要跳板机(ProxyJump)

ssh-copy-id -o ProxyJump=bastionUser@bastionHost -i ~/.ssh/id_ed25519.pub yanjie.shen@120.48.157.2

备选:不用 ssh-copy-id,手动把 key 追加进去(只要 ssh 能通就一定能做)

cat ~/.ssh/id_ed25519.pub | ssh yanjie.shen@120.48.157.2 \
  'mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys'

你把 ssh -v 里显示的端口号/有没有 ProxyJump 发我,我就能给你精确的那条 ssh-copy-id 命令。

formal dagger
#

why /status 不起作用?我是不是忘了什么?

arctic vergeBOT
#

/status 不起作用一般就这几种原因(按出现频率排序):

1) 你不是“单独一条消息”发的

大多数命令必须作为独立消息发送(整条消息只有 /status)才会被网关当命令处理。

✅ 正确:

/status

❌ 容易不生效:

帮我看看 /status 为啥不行

(这种“夹在句子里”的 inline shortcut 只有在你是 allowlisted/authorized sender 时才会触发。)

#

2) 你对“命令”不在授权名单里(最隐蔽)

如果你配置过 commands.allowFrom,那命令/指令只认它,没在名单里的人发 /status 会被当普通文本甚至静默忽略

检查:

openclaw config get commands.allowFrom
openclaw config get commands.useAccessGroups

如果你不小心配错了,可以先临时取消它(恢复用频道 allowlist/pairing 来判断权限):

openclaw config unset commands.allowFrom
openclaw gateway restart

3) 你把命令解析关掉了

检查:

#
openclaw config get commands.text

打开:

openclaw config set commands.text true --strict-json
openclaw gateway restart

你是在 Feishu 群里用 /status 吗?如果是的话,你试一下“只发一条 /whoami(或 /id)”能不能回(它跟 /status 一样属于命令体系)。能的话我们就沿着授权/格式排查;不能的话多半是 commands.allowFrom / commands.text 的问题。

formal dagger
#

解释这个jobs.json
{
"version": 1,
"jobs": [
{
"id": "54870799-7011-48c8-92ee-fd88c71b68d8",
"agentId": "local",
"name": "午饭提醒",
"description": "午饭提醒",
"enabled": true,
"deleteAfterRun": true,
"createdAtMs": 1772538429197,
"updatedAtMs": 1772624859566,
"schedule": {
"kind": "every",
"everyMs": 86400000,
"anchorMs": 1772538429197
},
"sessionTarget": "isolated",
"wakeMode": "now",
"payload": {
"kind": "agentTurn",
"message": "干饭!"
},
"delivery": {
"mode": "announce",
"channel": "feishu",
"to": "thor",
"bestEffort": false
},
"state": {
"nextRunAtMs": 1772711229203,
"lastRunAtMs": 1772624829203,
"lastRunStatus": "ok",
"lastStatus": "ok",
"lastDurationMs": 30363,
"lastDelivered": false,
"lastDeliveryStatus": "not-delivered",
"consecutiveErrors": 0
}
}
]
}

arctic vergeBOT
#

这是 OpenClaw 网关的 Cron 调度器存储文件~/.openclaw/cron/jobs.json)。它分两块:

  • jobs[]:静态配置(你创建的任务是什么、怎么跑)
  • state:运行时状态(上次跑了没、下次什么时候跑、投递成功没)

下面按字段解释你这条「午饭提醒」:

顶层

  • version: 1:jobs.json 的格式版本
  • jobs: [...]:任务列表

job 配置(静态部分)

  • id:任务唯一 ID(后面用来 openclaw cron run/edit/runs
  • agentId: "local":用哪个 agent 来跑(你的 local
  • name/description:名字/描述
  • enabled: true:启用
  • deleteAfterRun: true跑完后删除(一般更适合一次性 --at 任务;你这是每天重复的任务,通常建议设成 false,否则语义很怪)
#

schedule(什么时候跑)

"schedule": { "kind": "every", "everyMs": 86400000, "anchorMs": 1772538429197 }
  • kind: "every":固定间隔
  • everyMs: 86400000:每 24 小时一次
  • anchorMs:锚点时间(从这个时间开始每 24 小时触发一次)
    • 这意味着它会每天在“你创建它的那个时间点附近”触发,而不是严格中午 12 点
    • 如果你想“每天 12:00”,更推荐用 kind:"cron" + 时区

sessionTarget / payload(跑什么)

  • sessionTarget: "isolated":在独立的 cron:<jobId> 会话里跑(不污染主聊天上下文)
  • payload.kind: "agentTurn" + message: "干饭!":相当于“让 agent 收到一句‘干饭!’并生成输出”

delivery(把结果发到哪)

#
"delivery": { "mode":"announce", "channel":"feishu", "to":"thor", "bestEffort": false }
  • mode: "announce":把这次 run 的输出“投递”到指定渠道
  • channel: "feishu":投递到飞书
  • to: "thor":目标(你这里像是一个别名/目录名;是否能投递成功取决于 OpenClaw 能不能解析它)
  • bestEffort: false:投递失败会被当成失败(至少会记录 delivery 失败)

state(运行时状态)

  • nextRunAtMs:下次计划触发时间
  • lastRunAtMs:上次触发时间
  • lastRunStatus / lastStatus: "ok":任务执行本身 OK
  • lastDurationMs: 30363:上次耗时 ~30s
  • lastDelivered: false + lastDeliveryStatus: "not-delivered":重点——上次没有成功投递到飞书(可能原因:to 不可解析/不可达、渠道配置问题、或者这次 run 没产生可投递内容)

#

我建议你下一步怎么查“为什么 not-delivered”

用 CLI 看运行记录(会告诉你投递失败原因):

openclaw cron runs --id 54870799-7011-48c8-92ee-fd88c71b68d8 --limit 20

如果你把这段 runs 的输出(打码敏感信息)贴出来,我可以直接告诉你是 to: "thor" 目标不对,还是飞书投递链路哪里没配好。

formal dagger
#

或者我该如何通过feishu聊天的prompt来设置一个定时job?

arctic vergeBOT
#

可以的——前提是你的 agent 这次真的会调用 cron 工具(有些本地模型对“创建任务”这种工具调用不稳定,所以我给你一个“尽量不出错”的提示词模板)。

你在飞书里直接发下面这段(建议整段复制,信息越结构化越容易成功):


飞书提示词模板(创建每天定时提醒)

请帮我创建一个 OpenClaw Cron 定时任务(不是口头记住,是实际写入 cron/jobs.json 的那种)。
要求:

  1. 名称:午饭提醒
  2. 计划:每天 12:00(时区 Asia/Shanghai)
  3. 运行方式:sessionTarget = isolated(不要污染主会话)
  4. payload:只发送一句话「干饭!」(不要额外解释)
  5. 投递:用 delivery.mode = announce发送到当前这个飞书会话(优先用“last route/当前会话”这种方式,不要写死一个不确定的 to
  6. deleteAfterRun:false(我要每天都提醒)
    创建完成后请回复:jobId + 下次运行时间。
#

如果它“只答复但没真的创建 job”

那说明这次模型没调用 cron.add(或被工具 allowlist 拦了)。这时你可以:

  1. 在同一个会话先开 /verbose on 再发上面的模板(能看到是否真的执行了 cron 工具)
  2. 或者直接用 CLI 创建(最稳)

如果你愿意,把你飞书这边 /verbose on 后的那几行日志贴出来(看到有没有 tool=cron),我可以帮你判断是“模型没调工具”还是“投递 target 没解析对”。

formal dagger
#

{"version": "1.0", "cronJobs": [
{
"jobId": "lunch_reminder_1515_thu",
"jobName": "午饭提醒",
"schedule": {"type": "time_of_day", "hour": 11, "minute": 45},
"timezone": "Asia/Shanghai",
"mode": "isolated_session",
"isolatedSession": {
"message": "吃饭啦!",
"deliveryMode": "announce",
"targetRoute": "current_feishu_session"
},
"deleteAfterRun": false
}
]}

它创建了这个,正确吗

arctic vergeBOT
#

不对——你贴的这段:

{"version":"1.0","cronJobs":[... "schedule":{"type":"time_of_day"...}]}

不是 OpenClaw cron 的真实存储/配置格式,OpenClaw 不会识别 cronJobs / time_of_day / isolated_session 这些字段。它更像是模型“按你的描述编了一个看起来合理的 JSON”,但不等于真的创建了 job

怎么判断它到底有没有真的创建成功?

在网关机器上跑:

openclaw cron list

或者直接看真实存储文件(你之前那个格式):

#
cat ~/.openclaw/cron/jobs.json

真实的 job 结构会长这样(关键是 version: 1 + jobs: [...],以及 schedule.kind = at|every|cron):

  • schedule.kind: "cron" + expr: "45 11 * * *"(对应你说的每天 11:45)
  • timezone/tz: "Asia/Shanghai"
  • sessionTarget: "isolated"
  • payload.kind: "agentTurn"

如果你想“每天 11:45 提醒吃饭”,在 OpenClaw 里正确的 schedule 表达是

  • cron 表达式:45 11 * * *
  • 时区:Asia/Shanghai

顺便:如何投递到“当前飞书会话”

OpenClaw 需要一个真实的 Feishu target(通常是 oc_xxx 群 chat_id 或 ou_xxx 用户 open_id),而不是 current_feishu_session 这种描述性字符串。

#

获取当前群/用户 ID 的最稳方法是:

  • 在飞书里 @mention 一下 bot
  • openclaw logs --follow 里会打印 chat_id(群通常 oc_xxx

如果你愿意,把 openclaw cron list 的输出贴一下(或 ~/.openclaw/cron/jobs.json 里那条 job 的片段),我可以帮你确认是不是已经按正确 schema 写进去了,以及 to 应该填什么才会投递成功。

formal dagger
#

.openclaw/cron/jobs.json和.openclaw/workspace/cron/jobs.json,哪个会生效?

arctic vergeBOT
#

生效的是 $OPENCLAW_STATE_DIR/cron/jobs.json,也就是默认:

  • ~/.openclaw/cron/jobs.json

~/.openclaw/workspace/cron/jobs.json 不会被 cron 调度器读取(除非你手动把 cron.store 配置成指向它)。

你其实已经在日志里看到过真实生效路径了,例如你之前那行:

{"module":"cron","storePath":"/home/xxx/.openclaw/cron/jobs.json"}

怎么确认你这台机器当前到底用哪个

openclaw config get cron.store
openclaw logs --follow   # 里面会打印 storePath
#

补充:如果你用了 profile 或改了 OPENCLAW_STATE_DIR,路径会变成 ~/.openclaw-<profile>/cron/jobs.json 之类。

formal dagger
#

l/cron07:09:52 error [tools] read failed: ENOENT: no such file or directory, access '/home/sun/.openclaw/workspace-local/cron/jobs.json'
07:09:52 error [tools] read failed: ENOENT: no such file or directory, access '/home/sun/.openclaw/workspace-local/cron/jobs.json'
奇怪,为什么有这两条呢;
以及openclaw cron list输出是ID Name Schedule Next Last Status Target Agent ID Model
54870799-7011-48c8-92ee-fd88c71b68d8 午饭提醒 every 1d in 5h 19h ago ok isolated local - ,
对应的是
/home/sun/.openclaw/cron/jobs.json,但它里面并没有规定"targetRoute": "oc_形如的群组id,怎么办

arctic vergeBOT
#

这两条 [tools] read failed ... workspace-local/cron/jobs.json 不是 cron 调度器在读 job 文件,而是某次 agent 运行里调用了 read 工具去读一个它以为存在的路径:

  • 它去读的是:/home/sun/.openclaw/workspace-local/cron/jobs.json
  • 但这个文件本来就不存在 → 所以 ENOENT

一般发生在你在聊天里让 agent “去读 cron/jobs.json” 之类时:read 工具默认是围绕 workspace 的文件读写,而 cron 的真实存储在 state dir~/.openclaw/cron/jobs.json)。
要看 cron,就用你现在做的 openclaw cron list(不要让 agent 用 read 去读)。


关于投递到飞书群(oc_xxx)的问题

OpenClaw 的 cron schema 没有 targetRoute 这种字段。要投递到某个飞书群,需要在 job 里设置的是:

  • delivery.channel: "feishu"
  • delivery.to: "oc_xxx" (群 chat_id)

你现在的 job 里 delivery.to"thor",所以很可能解析不到正确的群,导致 “not-delivered/lastDelivered=false” 这类问题。

#

1) 先拿到群的 chat_id(oc_xxx

在那个飞书群里 @mention 一下 bot,然后在网关机器上看:

openclaw logs --follow

日志里会出现 chat_id: oc_...

(飞书文档也写了这个方法: https://docs.openclaw.ai/channels/feishu#get-groupuser-ids

2) 更新 cron job 的投递目标

拿到 oc_xxx 后:

openclaw cron edit 54870799-7011-48c8-92ee-fd88c71b68d8 --announce --channel feishu --to "oc_xxx"

然后手动跑一次测试:

#
openclaw cron run 54870799-7011-48c8-92ee-fd88c71b68d8

如果你把日志里那段 chat_id=oc_... 贴出来(打码其他敏感信息即可),我可以帮你确认 --to 到底应该填哪个值(群/私聊会不同:群是 oc_...,用户是 ou_...)。

formal dagger
#

openclaw cron run 54870799-7011-48c8-92ee-fd88c71b68d8成功了!我想把它改成特定的时间,怎么做( "createdAtMs": 1772538429197,
"updatedAtMs": 1772695596991, 这个我看不懂。。)

arctic vergeBOT
#

cron run 能跑通就很好了 ✅

你看到的这两个字段:

  • createdAtMs: 这个 job 创建的时间(Unix 时间戳,毫秒)
  • updatedAtMs: 这个 job 最后一次被修改的时间(同样是毫秒时间戳)

它们只是元数据,不用你手动改,也跟“几点提醒”没关系。


想改成“固定每天某个时间点”,正确做法:把 schedule 改成 cron 表达式

你现在的 job 用的是 schedule.kind = "every"(每 24h,从 anchor 时间开始算),所以它会每天在“创建时刻附近”触发。

改成固定时间(比如每天 11:45,上海时区):

#
openclaw cron edit 54870799-7011-48c8-92ee-fd88c71b68d8 \
  --cron "45 11 * * *" \
  --tz "Asia/Shanghai"

openclaw cron listNext 是否变成你期望的时间。

常用例子

  • 每天 12:00:--cron "0 12 * * *"
  • 工作日 12:00:--cron "0 12 * * 1-5"

如果你告诉我你想设置的“具体时间”(比如每天 12:10 还是工作日 12:10),我可以直接给你一条可复制的 cron 表达式。

formal dagger
#

我如何通过openclaw新建一个cron

arctic vergeBOT
#

用 CLI 新建 cron 任务就是 openclaw cron add ...(创建完用 openclaw cron list 看、用 openclaw cron run <id> 测)。

下面给你 2 个最常用模板,你直接改时间/内容/投递目标就行:


A) 创建“一次性提醒”(比如 20 分钟后提醒)

(主会话 system event)

openclaw cron add \
  --name "20分钟后提醒" \
  --at "20m" \
  --session main \
  --system-event "提醒:该去干饭了" \
  --wake now \
#
  --delete-after-run

B) 创建“每天固定时间提醒”(推荐用 cron 表达式)

(独立会话 isolated + announce 投递)

比如每天 11:45(上海时区)提醒“吃饭啦”:

openclaw cron add \
  --name "午饭提醒" \
  --cron "45 11 * * *" \
  --tz "Asia/Shanghai" \
#
  --session isolated \
  --message "请只回复:吃饭啦!" \
  --announce \
  --channel feishu \
  --to "oc_xxx"

oc_xxx 要换成你飞书群的 chat_id(在群里 @mention bot,然后 openclaw logs --follow 里能看到)。


创建后常用检查命令:

openclaw cron list
#
openclaw cron run <jobId>
openclaw cron runs --id <jobId> --limit 20

你想建的 cron 是“一次性提醒”还是“每天固定时间”?投递到飞书 群(oc_xxx) 还是 私聊(ou_xxx)?我可以把命令给你填成最终可直接跑的版本。

formal dagger
#

6df52c25-2869-4044-be1b-25a2d10fce62 晚饭提醒 cron 30 17 * * 1-5 @ Asia/Sha... in 1h - idle isolated - -
54870799-7011-48c8-92ee-fd88c71b68d8 午饭提醒 cron 45 11 * * 1-5 @ Asia/Sha... in 20h 34m ago ok isolated local -

arctic vergeBOT
#

看起来已经配置对了 ✅(这就是 openclaw cron list 里该有的样子):

  • 晚饭提醒cron 30 17 * * 1-5 @ Asia/Shanghai
    → 工作日 17:30 触发,Next in 1hStatus idle 只是表示还没跑过(所以 Last-

  • 午饭提醒cron 45 11 * * 1-5 @ Asia/Shanghai
    → 工作日 11:45 触发,Last 34m ago / ok 说明刚刚跑过一次没问题;并且它已经 pin 到 agentId=local

两个小建议(按需):

  1. 晚饭提醒的 Agent ID 是 -(没 pin,走默认 agent)。如果你也想固定用 local
openclaw cron edit 6df52c25-2869-4044-be1b-25a2d10fce62 --agent local
  1. 如果你想确认“有没有真的投递到飞书”,用:
#
openclaw cron runs --id 6df52c25-2869-4044-be1b-25a2d10fce62 --limit 20

需要我帮你把“晚饭提醒”也配置成投递到同一个 oc_xxx 群吗?你贴一下你现在晚饭提醒那条 job 在 jobs.json 里的 delivery 段就行。

formal dagger
#

成功了!还有哪些类似的常用方便小功能呢,我也想用?

arctic vergeBOT
#

爽,既然 cron + 投递都跑通了,下面这些也是 OpenClaw 里最常用、最“省事”的小功能(按实用度排序):

1) 常用聊天指令(调试/控制会话)

  • /status:看当前会话/agent 状态
  • /new/reset:开新会话(卡住/上下文太乱时神器)
  • /compact:把长对话压缩总结,减少上下文爆炸
  • /model:切换当前会话模型(用 /model status 看当前在用哪个)
  • /verbose on|off:临时打开工具调用可视化(排查 web_search/cron 投递特别有用)

文档: https://docs.openclaw.ai/tools/slash-commands

2) Heartbeat(“每隔一段时间自动看看有没有事”)

适合做:每日简报、待办提醒、定期检查某个状态。
原理是写一个 HEARTBEAT.md,然后配置心跳间隔。

文档: https://docs.openclaw.ai/gateway/heartbeat

#

3) Web 小工具(查资料 / 抓网页摘要)

  • web_search:搜索(你已经配好了)
  • web_fetch:抓网页正文转成 markdown(不需要 API key)

文档: https://docs.openclaw.ai/tools/web

4) 子任务/并行(sub-agents)

让它“后台跑一段时间”,跑完再回报结果,适合耗时任务或并行查资料。
文档: https://docs.openclaw.ai/tools/subagents

5) 设备能力(Nodes + Browser)

如果你有 macOS 节点/浏览器接管需求:

  • nodes:在你的电脑上跑命令/截图/录屏等
  • browser:自动点网页、填表(更强但也更容易被网站反爬)

文档: https://docs.openclaw.ai/nodeshttps://docs.openclaw.ai/tools/browser

#

如果你说下你最想自动化的 1-2 个场景(比如“每日信息汇总”“定时提醒+检查某网站”“自动整理群消息”),我可以直接给你一个最短的配置/提示词模板。另:这条线程已经很长了,接下来如果要展开新功能建议另开一个 thread,方便后人搜索。

formal dagger
#

还有别的吗!

arctic vergeBOT
#

再来一波更“日常用得到”的(不展开到写插件那种):

6) Dashboard / TUI(比纯聊天更好用的控制台)

  • openclaw dashboard:打开控制面板(看会话、配置、连接状态)
  • openclaw tui:终端里的聊天/调试界面(看模型、token、deliver 状态很直观)

文档:
https://docs.openclaw.ai/web/dashboard
https://docs.openclaw.ai/web/tui

7) Access Control(别让陌生人随便使唤你的 bot)

  • DMs 默认 pairing(配对码批准)
  • 群聊可以设置 requireMention / allowFrom / groupPolicy
  • 命令权限可单独控:commands.allowFrom

文档: https://docs.openclaw.ai/channels/feishu (你用飞书的话这页很关键)

#

8) Memory / 记忆(让它“越聊越懂你”)

  • 记忆文件就是 workspace 里的 Markdown(memory/YYYY-MM-DD.md + MEMORY.md
  • 想“更会想起来”就配 memory_search(语义检索)

文档: https://docs.openclaw.ai/concepts/memory

9) /export-session(把某次对话导出成 HTML 方便留档/排查)

在卡住或要复盘时特别好用。
文档: https://docs.openclaw.ai/tools/slash-commands

10) Doctor / Status(排障一键包)

  • openclaw status --all
  • openclaw doctor
  • openclaw logs --follow

文档(FAQ):https://docs.openclaw.ai/help/faq

#

你现在更想自动化哪类事:提醒类信息汇总类(日报/新闻)、还是 “帮我做事”(跑命令/查网页/填表)?我可以按你的场景给你一套最小可用配置。

formal dagger
#

subsystem":"gateway/channels/feishu"} [feishu] failed to add typing indicator: AxiosError: Request failed with status code 400

什么情况

arctic vergeBOT
#

这是 飞书插件在“加正在输入提示”时失败了

Feishu 这边的 typing indicator 不是像 Telegram 那样的“typing…”,而是通过接口给对方那条消息 加一个 reaction(表情) 来模拟正在输入。日志里的:

failed to add typing indicator ... status code 400

表示飞书 API 拒绝了这次 reaction 请求(常见原因:消息 id 不可用/消息太旧/权限不允许加 reaction 等)。这不影响正常回复,只是“正在输入提示”加不上而已。

最简单的处理:关掉 typing indicator(推荐)

直接在网关机器上设置:

openclaw config set channels.feishu.typingIndicator false --strict-json
openclaw gateway restart

如果你有多账号,也可以只关某个账号:

formal dagger
#

我有办法换一个indicator吗

arctic vergeBOT
#

目前不行——飞书这个 “typing indicator” 在插件里是写死的,用的是消息 reaction 的 emoji 类型:

const TYPING_EMOJI = "Typing"

配置层面只有 开/关

  • channels.feishu.typingIndicator: true|false

而没有类似 channels.feishu.typingEmoji 这种可换的选项。

另外,你现在报的是 HTTP 400(API 拒绝 reaction),就算换 emoji 大概率也还是会失败,所以最实用的做法还是 关掉 typingIndicator

如果你真的很想自定义,只能:

  1. 改插件源码(把 TYPING_EMOJI 换成别的 Feishu 支持的 emoji 类型)然后从源码运行/自建插件
  2. 或提一个 feature request: https://github.com/openclaw/openclaw/issues
formal dagger
#

我想每天早上10点通过web_search在google上搜索”今日人工智能大新闻“,然后整理出结果发布到群里,怎么做

arctic vergeBOT
#

可以做,而且建议用 cron + isolated job(每次跑都是全新会话,不会越跑上下文越大),然后让 job 的 prompt 强制调用 web_search

你说的“在 Google 上搜索”,在 OpenClaw 里对应的是把 web_search provider 切到 Gemini(Google Search grounding),而不是去爬 google.com


0) 先准备两样东西

  1. 飞书群的 chat_id(长这样:oc_xxx
    在群里 @mention 一下 bot,然后:

    openclaw logs --follow
    

    里会看到 chat_id.

  2. 确保网关能出网(你前面已经在搞代理了,web_search 先手动测通一次最好)。


#

1) 把 web_search 切到 “Google 搜索(Gemini grounding)”

在网关机器上配置(不要把 key 发到群里):

  • 推荐放到 ~/.openclaw/.env

    GEMINI_API_KEY=你的key
    
  • 然后在 openclaw.json 里指定 provider(命令行):

    openclaw config set tools.web.search.provider "gemini"
    openclaw gateway restart
    

文档(Gemini grounding):https://docs.openclaw.ai/tools/web#using-gemini-google-search-grounding


#

2) 新建每天 10:00 的 cron job(发布到飞书群)

oc_xxx 换成你的群 chat_id:

openclaw cron add \
  --name "AI大新闻日报" \
  --cron "0 10 * * *" \
  --tz "Asia/Shanghai" \
  --session isolated \
  --message "请务必先调用 web_search 搜索:今日 人工智能 大新闻(freshness=pd,count=10,search_lang=zh,ui_lang=zh-CN)。先列出10条结果的标题+URL,再整理成5条要点(中文),每条要点后附来源链接。不要凭记忆编造。" \
  --announce \
  --channel feishu \
  --to "oc_xxx"

#

3) 先手动跑一次测试 & 排查投递

openclaw cron list
openclaw cron run <jobId>
openclaw cron runs --id <jobId> --limit 20

如果你愿意,把你群的 oc_xxx(或者打码后)和你希望“输出格式”(要不要带链接、要不要按重要性排序、要不要只保留 3 条)告诉我,我可以把 --message 那段 prompt 调得更稳定、更像你要的“日报模板”。

formal dagger
#

如果我调用的是brave api,它会默认怎么搜索?需要我额外配置吗

arctic vergeBOT
#

用 Brave API 的话,web_search 默认就是走 Brave Search 的“普通网页搜索”(结构化返回:标题/URL/snippet),不做“AI 总结”(总结还是由模型来做)。

默认行为(你不额外配置时)

  • provider:如果你没显式设置 tools.web.search.provider,OpenClaw 会自动检测到 BRAVE_API_KEY / tools.web.search.apiKey 就优先用 Brave
  • 返回条数:用 tools.web.search.maxResults(默认 5
  • 地区/语言:如果你不传 country / search_lang / ui_lang(或文档里的 language),就用 Brave 的默认(通常跟它的默认区域/你的出口 IP 有关)。
  • 缓存:默认缓存 15 分钟(cacheTtlMinutes

你需要额外配置吗?

基本不用:有 key + enabled: true 就能搜。

但如果你希望结果更贴合中文/国内语境,建议在 prompt 里让它调用时带参数,例如:

  • country: "CN"
  • search_lang: "zh"
  • ui_lang: "zh-CN"
  • freshness: "pd"(只要最近一天/当天新闻)
formal dagger
#

发生了奇怪的状态,我明明只发了一遍prompt,但是chat回复了两条相同的回复,怎么回事

arctic vergeBOT
#

这种“我只发了一次,但收到了两条一模一样的回复”,在 OpenClaw/飞书里最常见就两类原因:

1) 同一条入站消息被处理了两次(最常见)

典型触发:

  • 你其实跑着 两个 OpenClaw Gateway 实例(同一个 Feishu bot 配置),它们都在订阅飞书 WS 事件 → 每条消息各处理一次 → 回两次
  • 或者飞书侧/网络抖动导致事件重放,而你的实例没去重成功

怎么确认:
在网关机器上 openclaw logs --follow,找到那条入站消息(会有飞书的 message_id/chat_id),看是不是出现了两次“收到同一 message_id → runId 不同”。

如果确实是重复处理:

  • 确保只有一台机器在跑这个 Feishu bot(同一 appId/appSecret 只跑一个 gateway)
  • openclaw gateway status,确认没有第二个实例/第二个 profile 也在跑

2) 飞书的 streaming/card 输出导致重复发送

飞书默认是用卡片流式更新,有些情况下会出现“卡片最终内容 + 最终文本消息”看起来像重复。

#

**快速验证/规避:**先把飞书 streaming 关掉试试:

openclaw config set channels.feishu.streaming false --strict-json
openclaw config set channels.feishu.blockStreaming false --strict-json
openclaw gateway restart

你这个重复是发生在:

  1. 正常聊天回复,还是 cron job 的 announce 投递
  2. 用的是 飞书群 还是 私聊

再贴一下日志里那条入站消息附近的 10~20 行(包含 message_id/runId),我就能一眼判断是“重复处理”还是“重复发送”。

dense pecanBOT
#

This thread is getting very long and answers may not be accurate due to the large context. Please start a new thread for any different problems/topics. @arctic verge please sum up the answer to the initial message and the conversation briefly.

formal dagger
#

openclaw cron run d7299eda-a666-4757-82cd-74f734e4a5b3

🦞 OpenClaw 2026.3.1 (2a8ac97) — Turning "I'll reply later" into "my bot replied instantly".


◇ Doctor changes ─────────────────────────────────────────────╮
│ │
│ Moved channels.feishu single-account top-level values into │
│ channels.feishu.accounts.default. │
│ │
├──────────────────────────────────────────────────────────────╯

◇ Doctor ──────────────────────────────────────────────╮
│ │
│ Run "openclaw doctor --fix" to apply these changes. │
│ │
├───────────────────────────────────────────────────────╯


Error: gateway timeout after 30000ms
Gateway target: ws://127.0.0.1:18789
Source: local loopback
Config: /home/sunrise/.openclaw/openclaw.json
Bind: loopback

arctic vergeBOT
#

你这段输出里其实有 两件事

1) 关键错误:gateway timeout after 30000ms

openclaw cron run ... 需要先连上正在运行的 Gateway(ws://127.0.0.1:18789)。现在是 连不上/连上但没响应,所以 30s 超时。

先在同一台机器上跑这两个命令看网关到底在不在:

openclaw gateway status
openclaw logs --follow

常见原因:

  • Gateway 根本没跑(或崩了)
  • 你 CLI 用的 state/config 路径跟服务用的不一致(gateway status 会显示 Config (cli) vs Config (service)
  • 18789 端口被别的东西占了但不是 OpenClaw
#

如果 gateway status 显示没跑,直接:

openclaw gateway restart
# 或者 openclaw gateway start

2) Doctor 提示:Feishu 配置需要迁移(不是超时的直接原因,但建议修)

它提示你现在用的是老式的 channels.feishu 单账号顶层字段,需要迁移到:

channels.feishu.accounts.default

按提示执行:

openclaw doctor --fix
#
openclaw gateway restart

你把 openclaw gateway status 的输出贴一下(尤其是 Listening / RPC probe / Config(service)),我就能判断是“网关没启动”、还是“配置/profile 指错”、还是“端口被占用”。

formal dagger
#

{
"id": "d7299eda-a666-4757-82cd-74f734e4a5b3",
"agentId": "local",
"name": "每日总结",
"enabled": true,
"createdAtMs": 1772709031193,
"updatedAtMs": 1772711046262,
"schedule": {
"kind": "cron",
"expr": "50 19 * * 1-5",
"tz": "Asia/Shanghai"
},
"sessionTarget": "isolated",
"wakeMode": "now",
"payload": {
"kind": "agentTurn",
"message": "请根据今日群内所有聊天内容,生成当日工作总结,并提醒大家好好休息,享受夜生活"
},
"delivery": {
"mode": "announce",
"channel": "feishu",
"to": "oc_3c8252b52dfd55b692ec683127e6573f"
},
"state": {
"nextRunAtMs": 1772796000000,
"lastRunAtMs": 1772710928650,
"lastRunStatus": "ok",
"lastStatus": "ok",
"lastDurationMs": 117612,
"lastDelivered": true,
"lastDeliveryStatus": "delivered",
"consecutiveErrors": 0,
"runningAtMs": 1772711602047
}
}
]

为什么我用openclaw cron run调用能发送,但是在预定的时间没有发送?

arctic vergeBOT
#

最常见原因其实就一句话:cron 只能在 Gateway 进程“活着”的时候跑。如果你在预定时间那会儿 Gateway 挂了/重启了/卡住了(你前面确实遇到过 gateway timeout + doctor 迁移提示),那次就会直接错过,不会自动“补发”,除非你手动 openclaw cron run

另外,你这条 job 的 state 里有个很关键的字段:

"runningAtMs": 1772711602047

这表示:当你查看这份 jobs.json 时,这个 job 正在运行中(还没结束/还没投递完成)。本地模型生成 + web_search 有时要 1-2 分钟甚至更久,你如果在 19:50 刚过就去看群里,可能只是它还在跑。

建议你这样确认到底发生了什么

  1. 看 run 记录(最直接):
openclaw cron runs --id d7299eda-a666-4757-82cd-74f734e4a5b3 --limit 20

这里会告诉你:那次是否真的触发了、是否投递、失败原因是什么。

#
  1. 确认 Gateway 是否在预定时间持续运行:
openclaw gateway status
openclaw logs --follow

如果你想“就算 Gateway 当时重启/短暂离线,也尽量别错过”

唯一办法是:保证 Gateway 24/7 常驻运行(systemd/launchd 服务),cron 才可靠。

你把 openclaw cron runs --id ... 的输出贴一下(打码即可),我可以帮你判断是:

  • 当时 Gateway 不在 → 任务根本没触发
    还是
  • 任务触发了但跑很久/投递失败(比如目标 oc_xxx / 网络 / 代理问题)
formal dagger
#

不可能,gateway是一直活者的

arctic vergeBOT
#

如果 Gateway 确实一直活着,那“没按你以为的时间发”通常只剩 3 种可能,而且都能用日志/cron runs 一锤定音:

A) 它其实按 另一个时间在跑(时区/系统时间不对,或你看错 Next)

Cron 计算完全依赖 网关机器的系统时间 + schedule.tz。只要系统时间/时区有偏差,就会“看起来没发”。

请你在网关机器上贴一下这三个输出:

date
timedatectl 2>/dev/null || true
openclaw cron list

另外你 jobs.json 里有 nextRunAtMs,可以直接换算成你本机看到的时间:

#
date -d "@$((1772796000000/1000))"

(把你那条 job 的 nextRunAtMs 换进去)

B) 它在预定时间触发了,但跑太久/卡住了,你以为“没发”

你之前 state 里出现过 runningAtMs,这就表示那一轮已经开始跑了,只是还没结束/还没投递。用本地模型 + web_search 时,跑几分钟很常见。

C) 它跑了,但没有产生可投递内容(所以没发)

比如输出是空/NO_REPLY/类似 heartbeat 的“无内容”,announce 会跳过投递。


直接给你最快的定位命令(最关键)

把下面这条的输出贴出来(最近 10 条就够):

#
openclaw cron runs --id d7299eda-a666-4757-82cd-74f734e4a5b3 --limit 10

里面会明确显示每次 run 的时间、状态、是否 delivered、以及失败原因。只要这条一出来,我们就能确定到底是 A/B/C 哪一种。

formal dagger
arctic vergeBOT
#

调不调 proxy 取决于:运行 web_search 的那个进程(通常是 Gateway)当时看到的环境变量,不是取决于“你现在这个 Discord 里发的消息”。

1) 这些 proxy 变量到底从哪来?

OpenClaw 的环境变量来源是(优先级大致是):

  1. 进程本身的环境变量(你用终端启动 openclaw gateway 时,终端里 export 的变量会继承进去)
  2. 当前工作目录的 .env
  3. 全局兜底的 ~/.openclaw/.env(也就是 state dir 里的 .env

并且:.env 不会覆盖进程里已经存在的同名变量。

文档(环境变量加载):https://docs.openclaw.ai/help/faq#how-does-openclaw-load-environment-variables

2) 所以“是不是终端环境变量决定的”?

  • 如果你是用终端前台跑 Gatewayopenclaw gateway):是的,Gateway 会继承你当前终端的环境变量。
  • 如果你是用 systemd/launchd 服务跑 Gateway:不是。服务通常不继承你 shell 的环境变量,所以要把 proxy 写到 ~/.openclaw/.env(或者写进服务环境),然后 openclaw gateway restart
#

3) HTTP_PROXY / HTTPS_PROXY / NO_PROXY 怎么影响行为?

  • 请求是 https://... → 优先看 HTTPS_PROXY
  • 请求是 http://... → 看 HTTP_PROXY
  • 命中 NO_PROXY 的 host(逗号分隔)→ 直连,不走代理
  • SOCKS 代理一般用 ALL_PROXY=socks5h://...

小提醒:你写的 NO_PROXY=...,:::::: 看起来像打错了;通常 IPv6 loopback 是 ::1,可以写:

NO_PROXY=127.0.0.1,localhost,::1

如果你想我帮你确认“当前 Gateway 到底有没有读到这些 proxy”,你贴一下你是用 服务跑还是 终端前台跑 Gateway(以及 openclaw gateway status 的关键几行)我就能判断该把 .env 放哪、要不要重启。

formal dagger
#

如何确认当前gateway有没有读到proxy

arctic vergeBOT
#

确认“Gateway 进程有没有读到 proxy”的最靠谱办法是:看正在运行的 Gateway 进程的环境变量(因为 web_search 是否走代理就是看 HTTP_PROXY/HTTPS_PROXY/ALL_PROXY/NO_PROXY 这些 env)。

下面按 Linux 常用方式给你一套最短检查法(别把整份环境变量贴出来,可能有 key):

方法 1(最准):直接看进程 /proc/<pid>/environ

  1. 先找到 Gateway 的 PID:
pgrep -af "openclaw.*gateway" || pgrep -af openclaw
  1. 假设 PID 是 12345,查看它实际拿到的 proxy 相关 env:
tr '\0' '\n' < /proc/12345/environ | grep -E '^(HTTP_PROXY|HTTPS_PROXY|ALL_PROXY|NO_PROXY)='

只要这里能看到你在 ~/.openclaw/.env 里写的值,就说明 Gateway 确实读到了

#

方法 2(行为验证):故意把代理端口写错看报错是否变成“连代理失败”

比如把 HTTPS_PROXY 临时改成一个不存在的端口(例如 127.0.0.1:9),重启 gateway,然后跑一次 web_search

  • 如果错误变成类似 “connect ECONNREFUSED 127.0.0.1:9”,说明它确实在走代理
  • 如果还是原来的直连错误,说明没读到/没生效

小提醒

如果 Gateway 是以 systemd/launchd 服务跑的,终端里 export 的变量不一定会进服务进程;要用 ~/.openclaw/.envopenclaw gateway restart

你告诉我 pgrep -af ... 找到的那行进程命令长什么样(打码也行),我也可以帮你判断你当前到底是前台跑、还是服务跑。

formal dagger
#

如何新增一个model

arctic vergeBOT
#

“新增一个 model”在 OpenClaw 里通常有 3 件事(看你想达到哪一步):

1) 先让 OpenClaw 能用这个 provider(有凭据/连接)

  • 云端 API:配 API key / OAuth(openclaw models auth … 或把 key 放到 ~/.openclaw/.env
  • 本地/自建:需要把对应 provider 的 baseUrl / 连接方式配好(看你用的是哪类后端)

先跑:

openclaw models status
openclaw models list

确认你的目标模型是否已经出现在列表里。

2) 把它设成默认(最常见需求)

openclaw models set provider/model
#

3) 让它出现在 /model 选择器里(如果你启用了 allowlist)

最省事的方法是用向导:

openclaw configure --section model

里面可以选默认模型 + 勾选允许的模型(agents.defaults.models allowlist)。


你现在想加的 model 是:

  1. 云端的(Anthropic/OpenAI/OpenRouter/…)还是
  2. 本地的(Ollama / LM Studio / vLLM / llama.cpp)?

告诉我你是哪一种、以及“provider/model”长什么样(不用贴 key),我就能给你一套最短的配置/命令。

formal dagger
#

我想加vllm的

arctic vergeBOT
#

加 vLLM 很直接:它在 OpenClaw 里就是一个 OpenAI-compatible /v1 端点,provider 叫 vllm

下面给你两种方式(推荐先用“自动发现”,最省事):

方式 1(最省事):自动发现 vLLM 的 models

前提:你的 vLLM 跑在默认地址 http://127.0.0.1:8000/v1

  1. 启动 vLLM(确保这些接口能访问到):
curl http://127.0.0.1:8000/v1/models
  1. 运行 Gateway 的那台机器上设置(服务模式建议写到 ~/.openclaw/.env):
VLLM_API_KEY=vllm-local

如果你的 vLLM 没有鉴权,VLLM_API_KEY 随便填一个值就行,它只是让 OpenClaw “启用 vllm provider + 去探测 /v1/models”。

#
  1. 重启 gateway:
openclaw gateway restart
  1. 看 OpenClaw 是否发现了 vLLM 模型:
openclaw models list
  1. 把默认模型切到 vLLM 的某个 model id(用你 /v1/models 返回的那个 id):
openclaw models set vllm/<your-model-id>

文档: https://docs.openclaw.ai/providers/vllm

#

方式 2(更可控):显式写 provider 配置(vLLM 不在本机/想固定 contextWindow)

~/.openclaw/openclaw.json 里加:

{
  models: {
    providers: {
      vllm: {
        baseUrl: "http://<vllm-host>:8000/v1",
        apiKey: "${VLLM_API_KEY}",
        api: "openai-completions",
        models: [
          {
            id: "your-model-id",
#
            name: "My vLLM model",
            contextWindow: 32000, // 至少要 >= 16000(OpenClaw 硬下限)
            maxTokens: 4096,
            input: ["text"],
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
          },
        ],
      },
    },
  },
}

你现在的 vLLM 是跑在 同一台机子(127.0.0.1:8000)还是 另一台主机?你把 curl http://.../v1/models 的输出里 id 那行贴一下(不用贴任何 key),我帮你把 openclaw models set vllm/... 的值直接填对。

formal dagger
#

400 "auto" tool choice requires --enable-auto-tool-choice and --tool-call-parser to be set

(APIServer pid=1) INFO: 127.0.0.1:35262 - "POST /v1/chat/completions HTTP/1.1" 400 Bad Request

什么情况

arctic vergeBOT
#

这是 vLLM OpenAI server 在拒绝 OpenClaw 发过去的“工具调用(tool calling)”请求

OpenClaw 发 /v1/chat/completions 时,只要当前会话有工具(web_search/cron/…),它会带上类似:

  • tools: [...]
  • tool_choice: "auto"

而 vLLM 报错说:你没用参数开启 tool_choice=auto 的支持:

"auto" tool choice requires --enable-auto-tool-choice and --tool-call-parser to be set

所以 vLLM 直接 400 了。


解决办法有两条路(选一条)

#

方案 A:让 vLLM 支持工具调用(推荐如果你真想用本地模型跑工具)

启动 vLLM OpenAI server 时加上它要求的参数,例如(示意):

python -m vllm.entrypoints.openai.api_server \
  --model <你的模型> \
  --enable-auto-tool-choice \
  --tool-call-parser <parser-name>

<parser-name> 要按你的模型选(不同模型用不同 parser)。这一点要看 vLLM 文档/你模型的推荐配置。

方案 B:让 OpenClaw 对 vLLM “不发工具”,只做纯聊天(最快能跑通)

openclaw.json 里把 vLLM 的工具集压到最小(这样就不会发 tool_choice:auto):

#
{
  tools: {
    byProvider: {
      vllm: { profile: "minimal" } // 只保留 session_status 等最少工具
    }
  }
}

然后重启 gateway。
代价是:用 vLLM 时就不能 web_search/cron/browser/nodes 这些工具了(但纯聊天可用)。


你想要哪种目标:

  1. vLLM 也能跑 web_search / cron 这种工具(走方案 A)
#

还是
2) vLLM 只当一个便宜的“聊天/兜底模型”(走方案 B)?

你告诉我你 vLLM 跑的是什么模型(模型名即可,不用 key),我可以帮你更具体地判断该用哪个 tool-call-parser

formal dagger
#

sudo docker run -d --runtime nvidia --gpus all
--net=host
-v ~/model:/root/models
--name vllm-qwen
ghcr.io/nvidia-ai-iot/vllm:latest-jetson-thor
vllm serve /root/models/Qwen3-8B
--served-model-name qwen3-8b
--gpu-memory-utilization 0.5
--max-model-len 16384
--trust-remote-code

docker我是这么启动的,需要怎么改参数

arctic vergeBOT
#

这个 400 的原因就是:OpenClaw 发了 tool_choice: "auto"(因为它把工具 schema 一起发给 vLLM 了),但你启动的 vLLM 没开启 tool-calling 支持

Qwen3-8B,按 vLLM/Qwen 的说明,一般用 hermes parser 就行。你只需要在 vllm serve ... 后面加上:

  • --enable-auto-tool-choice
  • --tool-call-parser hermes

你这条 docker 命令改成这样(核心是最后两行)

先停掉旧容器(如果你 OK 重建):

sudo docker rm -f vllm-qwen

然后用新参数启动:

sudo docker run -d --runtime nvidia --gpus all \
#
  --net=host \
  -v ~/model:/root/models \
  --name vllm-qwen \
  ghcr.io/nvidia-ai-iot/vllm:latest-jetson-thor \
  vllm serve /root/models/Qwen3-8B \
  --served-model-name qwen3-8b \
  --gpu-memory-utilization 0.5 \
  --max-model-len 16384 \
  --trust-remote-code \
  --enable-auto-tool-choice \
  --tool-call-parser hermes

然后看下日志确认不再报 tool_choice:

#
sudo docker logs -f vllm-qwen

补充:如果你其实只想把 vLLM 当“纯聊天模型”,不需要工具调用,也可以不改 vLLM,改 OpenClaw 把 vLLM 的工具关掉(tools.byProvider.vllm.profile="minimal")。但你现在这个需求显然是想要工具可用,所以建议按上面把 vLLM 开 tool-calling。

formal dagger
#

vllm.exceptions.VLLMValidationError: You passed 12289 input tokens and requested 4096 output tokens. However, the model's context length is only 16384 tokens, resulting in a maximum input length of 12288 tokens. Please reduce the length of the input prompt. (parameter=input_tokens, value=12289)
(APIServer pid=1) INFO: 127.0.0.1:39092 - "POST /v1/chat/completions HTTP/1.1" 400 Bad Request

arctic vergeBOT
#

这是 上下文长度(context window)刚好撞线 的典型报错。

你现在 vLLM 设置的是:

  • --max-model-len 16384(总上下文 16384 tokens)
  • OpenClaw 这次请求里 想要输出 4096 tokens
  • 所以 vLLM 计算:最大允许输入 = 16384 - 4096 = 12288
  • 但你实际输入是 12289(只超了 1 个 token)→ 直接 400

这说明:你把模型开到 16k 了,但 OpenClaw 默认给的输出预算太大/你的系统 prompt + 工具 schema + 历史稍微一长就会踩线。


解决思路(选一个或组合)

方案 A(推荐):给自己留余量——把 vLLM 的上下文开大一点

如果显存允许,直接把:

#
--max-model-len 16384

提高到比如 24576 / 32768(看你模型和显存能不能撑住)。

方案 B(更通用):让 OpenClaw 对 vLLM 少要点输出 tokens

最稳的做法是给 OpenClaw 写 显式 vLLM provider 配置,把这个模型的 maxTokens 调小(比如 1024 或 2048),这样 vLLM 就会允许更长的输入。

示例(在 ~/.openclaw/openclaw.json 里配置 vLLM 模型时):

models: {
  providers: {
    vllm: {
      baseUrl: "http://127.0.0.1:8000/v1",
      apiKey: "${VLLM_API_KEY}",
#
      api: "openai-completions",
      models: [
        {
          id: "qwen3-8b",
          name: "Qwen3 8B (vLLM)",
          contextWindow: 16384,
          maxTokens: 1024, // 👈 关键:把输出上限调小,给输入留空间
          input: ["text"],
          cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
        },
      ],
    },
  },
}
#

然后重启 gateway。

方案 C:减少输入(系统 prompt/工具/历史)

  • /new 开新会话
  • /compact 让历史变短
  • 或者对 vLLM 限制工具(工具 schema 也占 token)

你想走哪条?
如果你更倾向 方案 B,把你现在 models.providers.vllm 这一段(打码 key)贴一下,我帮你把 maxTokens/contextWindow 写成最适合你这个 16k 配置的值(一般 1024/2048 会舒服很多)。

formal dagger
#

如果我在docker里运行vllm,宿主机运行openclaw,那么模型能看到~/.openclaw的设定吗

arctic vergeBOT
#

不会“直接看到”。

你这个架构是:

  • OpenClaw 在宿主机:读 ~/.openclaw/openclaw.json~/.openclaw/.env、workspace(AGENTS.md、memory 文件等),然后把它们拼成 system prompt + messages + tools schema
  • vLLM 在 Docker 容器里:它只是一个 HTTP 推理服务/v1/chat/completions),只接收 OpenClaw 发过来的请求体,不会去读宿主机的 ~/.openclaw/*

所以:

  • vLLM 容器 不会读取/访问 宿主机的 ~/.openclaw(除非你特意 -v ~/.openclaw:... 挂载进去,而且 vLLM 也不会用到它)。
  • 但“模型看到的内容”= OpenClaw 发送过去的 prompt 内容。也就是说:只要 OpenClaw 把配置/记忆/AGENTS.md 的内容注入到 prompt 里,模型就能“看到”这些文本(通过请求体),并不是通过文件系统读取。

如果你的目标是“让模型能用 OpenClaw 的记忆/工具”,关键在于 OpenClaw 这边的配置与工具权限,跟 vLLM 容器是否能访问 ~/.openclaw 基本没关系。

formal dagger
#

安全限制:
无法访问 MEMORY.md(长期记忆文件,仅在主会话中加载)

为什么模型会恢复这个

arctic vergeBOT
#

这是 设计如此的安全限制,不是“模型自己突然学会拒绝了”。

在 OpenClaw 里:

  • MEMORY.md 被当作 长期、私密记忆
  • 默认规则是:只在主会话 / 私聊(main private session)加载
  • 群聊/频道/线程 里,为了防止把你的私密记忆泄露给群里其他人,OpenClaw 会阻止访问 MEMORY.md

所以当你在群里问到某些需要“长期记忆”的东西时,模型/工具链会返回类似:

“无法访问 MEMORY.md(长期记忆文件,仅在主会话中加载)”

你看到的就是这个保护机制的提示。

想要“群里也能用到固定信息”怎么办?

  • 可以公开的、群共享的规则/背景放到 AGENTS.mdUSER.md(它们会在所有会话注入)
  • 或者写到 memory/YYYY-MM-DD.md / 自建一个 memory/feishu-oc_xxx.md 这种文件,让群会话去读(不要放敏感信息)
#

想用 MEMORY.md 里的私密偏好/背景?

私聊/主会话 里问 bot(DM),那边才允许加载长期记忆。

如果你告诉我你是在飞书还是私聊触发到这条提示,以及你希望它“记住”的具体是什么内容,我可以建议你该放到 AGENTS.mdmemory/*.md 还是 MEMORY.md

formal dagger
#

12:00:24 info [error]: [
AxiosError: timeout of 10000ms exceeded
at RedirectableRequest.handleRequestTimeout (/usr/local/lib/node_modules/openclaw/node_modules/axios/lib/adapters/http.js:908:13)
at RedirectableRequest.emit (node:events:508:28)
at Timeout._onTimeout (/usr/local/lib/node_modules/openclaw/node_modules/follow-redirects/index.js:221:12)
at listOnTimeout (node:internal/timers:605:17)
at processTimers (node:internal/timers:541:7)
at Axios.request (/usr/local/lib/node_modules/openclaw/node_modules/axios/lib/core/Axios.js:46:41)
at processTicksAndRejections (node:internal/process/task_queues:104:5) {
isAxiosError: true,
code: 'ECONNABORTED',
config: {
transitional: [Object],
adapter: [Array],
transformRequest: [Array],
transformResponse: [Array],
timeout: 10000,
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
maxBodyLength: -1,
env: [Object],
validateStatus: [Function: validateStatus],
headers: [Object [AxiosHeaders]],
method: 'get',
url: 'https://open.feishu.cn/open-apis/bot/v3/info',
params: {},
allowAbsoluteUrls: true,
data: undefined
},
request: Writable {
_events: [Object],
_writableState: [WritableState],
_maxListeners: undefined,
_options: [Object],
_ended: true,
_ending: true,
_redirectCount: 0,
_redirects: [],
_requestBodyLength: 0,
_requestBodyBuffers: [],
_eventsCount: 3,
_onNativeResponse: [Function (anonymous)],
_currentRequest: [ClientRequest],
_currentUrl: 'https://open.feishu.cn/open-apis/bot/v3/info',
_timeout: null,
Symbol(shapeMode): true,
Symbol(kCapture): false
}
}
什么情况

arctic vergeBOT
#

这条日志是 Feishu 插件在做“账号探活/健康检查”(请求 GET https://open.feishu.cn/open-apis/bot/v3/info)时 10 秒超时 了:

  • code: ECONNABORTED
  • timeout: 10000
  • url: .../bot/v3/info

它通常不会直接影响你“能不能收发消息”(很多时候只是状态探针失败会刷日志),但如果一直这样,说明网关到飞书 OpenAPI 的网络确实不太通畅/太慢。

最常见原因

  1. 网关这台机器访问 open.feishu.cn 很慢/被阻断(网络、DNS、出口、TLS、中间盒子)
  2. 你全局开了 HTTPS_PROXY,结果飞书 OpenAPI 也被强制走代理,而代理对 open.feishu.cn 很慢或不通
  3. 你其实是 Lark 国际版租户,但域名/配置没切到 lark(一般会是别的错误码,但也可能表现为超时)

你可以马上做的两步排查

A) 在网关机器上测试连通性(不需要 token)

#
curl -I https://open.feishu.cn/ --max-time 10

如果这个都连不上/很慢,那就是网络层问题。

B) 如果你设置了全局代理,先把飞书域名加入 NO_PROXY(推荐)

~/.openclaw/.env 里把 NO_PROXY 改成类似这样(保留你已有的项):

NO_PROXY=127.0.0.1,localhost,::1,open.feishu.cn

然后重启 gateway:

openclaw gateway restart
#

这样 飞书 API 走直连,web_search 之类继续走代理(通常更稳定)。


如果你跑的是 Lark 国际版,再加上:

NO_PROXY=open.larksuite.com

并确认 channels.feishu.domain: "lark"

你把 curl -I https://open.feishu.cn/ --max-time 10 的输出贴一下(只要状态码/耗时即可),我就能判断是“直连慢”还是“代理导致慢”。

formal dagger
#

"providers": {
"vllm": {
"baseUrl": "http://127.0.0.1:8000/v1",
"apiKey": "vllm-local",
"api": "openai-completions",
"models": [
{
"id": "qwen3.5-9b",
"name": "qwen3.5-9b",
"contextWindow": 32768,
"maxTokens": 4096,
"input": ["text"],
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 },
},
],
},

sudo docker run -d --runtime nvidia --gpus all
--net=host
-v ~/model:/root/models
--name vllm-qwen
ghcr.io/nvidia-ai-iot/vllm:latest-jetson-thor
vllm serve /root/models/Qwen3.5-9B
--served-model-name qwen3.5-9b
--gpu-memory-utilization 0.5
--max-model-len 32768
--enable-auto-tool-choice
--tool-call-parser hermes

那么为什么vllm会报错(APIServer pid=1) ERROR 03-06 13:08:07 [serving.py:311] vllm.exceptions.VLLMValidationError: You passed 28673 input tokens and requested 4096 output tokens. However, the model's context length is only 32768 tokens, resulting in a maximum input length of 28672 tokens. Please reduce the length of the input prompt. (parameter=input_tokens, value=28673)

arctic vergeBOT
#

这不是“配置没生效”,而是 vLLM 的硬性校验在正常工作:

input_tokens + max_tokens(你请求的输出上限) ≤ context_length

你现在的组合是:

  • vLLM:--max-model-len 32768context_length = 32768
  • OpenClaw(vllm provider 里):maxTokens: 4096 → 请求里会带 max_tokens = 4096
  • 所以 vLLM 计算:最大允许输入 = 32768 - 4096 = 28672
  • 但你实际输入是 28673(又是超 1 token)→ 直接 400

也就是说:只要你的 prompt 再长一点点,就会踩线,这是必然的。


最推荐的修法:把 vLLM 的 maxTokens 调小,给输入留余量

比如改成 2048(甚至 1024 更稳):

#
openclaw config set models.providers.vllm.models[0].maxTokens 2048 --strict-json
openclaw gateway restart

这样最大允许输入会变成:

  • 32768 - 2048 = 30720(立刻多出 2048 tokens 的空间,不会再“差 1 token”)

4096 输出上限对大多数聊天/工具任务来说其实偏大,调小通常没副作用,反而更稳定。


其次:减少输入(否则再大上下文也会被你填满)

你这个 input_tokens=28673 已经非常长了,说明你把很多历史/工具输出塞进去了。可以配合:

  • /new/compact
  • 限制 vLLM 的工具集合(工具 schema 也占 tokens)
  • 控制 web_fetchmaxChars 等,减少 toolResult 进入上下文
#

如果你把 出错那次的 pre-prompt context-diag(里面会有 historyTextChars/systemPromptChars/maxMessageTextChars)贴一下,我可以帮你判断主要是“历史太长”还是“工具 schema 太多”,然后给一个更合适的 maxTokens/工具限制建议。

formal dagger
#

是不是vllm改成36864就行了

arctic vergeBOT
#

把 vLLM 的 --max-model-len 提到 36864 确实能“治标”——在你保持 maxTokens=4096 不变时:

  • 新的最大允许输入 = 36864 - 4096 = 32768
  • 你这次输入 28673 就不会再因为“差 1 token”被拒了

但有两个前提 + 一个现实建议:

前提 1:模型/后端真的支持这么大的上下文

不是所有模型都能稳定跑到 36k(有的需要 RoPE scaling/特定配置),而且上下文越大越吃显存、越慢。你得看 Qwen3.5-9B 在你这套 vLLM/权重下是否 OK。

前提 2:OpenClaw 这边的 contextWindow 要跟着对齐(别写得比 vLLM 还大)

你如果把 vLLM 改成 36864,建议同时把 OpenClaw 的:

models.providers.vllm.models[0].contextWindow
#

也改成 36864(保持一致)。

现实建议(更稳):还是把 maxTokens 调小

就算你把上下文加到 36864,只要你 prompt 继续增长,还是会再次撞线。maxTokens 从 4096 调到 2048/1024,会让系统更稳定、也更省显存/更快。

所以:

  • 你想快速不报错:可以试 36864
  • 你想长期稳定:建议 maxTokens 调小 +(必要时)再加大 max-model-len

如果你愿意,我建议你先做最小改动:把 maxTokens 改到 2048(通常立刻就稳定了),再看还需不需要上 36864。

formal dagger
#

vllm.exceptions.VLLMValidationError: You passed 32805 input tokens and requested 4096 output tokens. However, the model's context length is only 36900 tokens, resulting in a maximum input length of 32804 tokens. Please reduce the length of the input prompt. (parameter=input_tokens, value=32805)

      "name": "qwen3.5-9b",
        "contextWindow": 32768,
        "maxTokens": 4096,
        "input": ["text"],
        "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }, 
      },
    ],

怎么可能!明明设置了32768,哪来的32805?!

arctic vergeBOT
#

这个现象其实“正常”,关键点在于:

  1. models.providers.vllm.models[].contextWindow 不是硬上限
    它主要是 OpenClaw 用来估算上下文大小、决定什么时候 compaction/截断的“参考值”,不是“超过就绝对不发请求”。
    真正的 token 数,最终还是 vLLM 用自己的 tokenizer 算出来的

  2. 你现在报错的根因仍然是同一个:离 max input 差 1 token
    vLLM 说得很明确:

  • 模型 context = 36900
  • 你请求输出 = 4096
  • 所以最大允许输入 = 36900 - 4096 = 32804
  • 你实际输入 = 32805(就多 1 个 token)→ 400

你看到的 “32805” 并不是凭空来的,而是 vLLM 真实 tokenizer 计算的结果;而 OpenClaw 的估算/压缩没做到“严格小于 32804”。


#

怎么彻底解决(推荐)

✅ 把 maxTokens 调小(给输入留缓冲)

你现在 maxTokens=4096 太容易把 input 空间挤爆。改成 2048 或 1024,立刻就会稳定很多:

openclaw config set models.providers.vllm.models[0].maxTokens 2048 --strict-json
openclaw gateway restart

这样最大允许输入会直接变成:

  • 36900 - 2048 = 34852(瞬间多出 2k tokens 的余量,不会再“差 1 token”)

另外:把 contextWindow 对齐到你 vLLM 实际的 --max-model-len

你现在 vLLM 实际是 36900,但 OpenClaw 里还写 32768,会让它的“估算/决策”更混乱。建议保持一致(但即便一致,也还是要留缓冲,别把 maxTokens 拉太满)。


#

一句话总结:不是 32805 不可能,而是你把输出上限设得太大,导致输入边界特别窄;再加上估算/分词差异,必然会偶尔多出几十个 token。 调小 maxTokens 是最稳的。

formal dagger
#

2048一般够不够

arctic vergeBOT
#

一般 够用,尤其是你现在这个场景(群里总结、web_search 摘要、提醒类输出):

  • 2048 output tokens 通常可以写出一篇挺长的总结(大概相当于几千字级别的中文内容,具体跟写法有关)
  • 而且你现在经常撞 “input+output 超上下文” 的线,调小 maxTokens 会让系统 稳定性大幅提升

什么时候 2048 可能不够?

  • 一次性要吐很长的代码/长文(比如完整 README、长篇报告、几十段日志逐行分析)
  • 或者你明确希望它“一条消息写完非常长的内容”

这时你可以:

  • 先用 2048 保稳
  • 需要长输出时再临时调大到 3072/4096(但要接受输入空间变小、更容易撞上下文)
  • 或者让它分段输出(更适合群里)

建议:先把 maxTokens 设 2048 跑一天/跑几次 cron,总结类任务基本不会卡;真遇到不够再加。

formal dagger
#

以及我感觉模型在输出think的内容!怎么让它不输出呢

#

就是回复对话的时候

arctic vergeBOT
#

你说的“输出 think 内容”,一般有两种情况,处理方式不一样:

情况 1:OpenClaw 在“显示推理内容”(Reasoning)模式

特征:会单独发一条以 Reasoning: 开头的消息,或者你之前开过 /reasoning on / /verbose full

解决:在同一个会话里各发一条(必须单独一条消息):

/reasoning off
/verbose off

情况 2:模型自己把 <think>...</think> 当普通文本吐出来(开源模型常见)

特征:think 内容混在正常回复里,通常带 <think> 标签。

解决思路:

  • 在提示词里强制要求“不要输出 think/链路推理,只给最终答案”
#
  • 最好把这条规则写进你的工作区 AGENTS.md(让每次对话都生效)

可以加一段到 AGENTS.md 里,比如:

“Never output chain-of-thought. If you generate <think>...</think> internally, remove it from the final reply and output only the final answer.”


你现在看到的是哪一种?
是出现了 Reasoning: 这种前缀,还是 <think>...</think> 标签混在正文里?我可以按你实际那种给你最精准的设置位置/配置法。

formal dagger
#

他回答:用户正在发送一些问候消息,现在需要。。。

arctic vergeBOT
#

明白了——那就是 模型自己把 <think>…</think> 当普通文本吐出来(不是 OpenClaw 的 /reasoning 功能)。

这种情况下,OpenClaw 没有一个“全局自动过滤 <think> 标签”的开关(至少当前版本没有),最实用的办法是 在系统提示/AGENTS.md 里强约束它别输出。

推荐做法:写进 AGENTS.md(长期生效)

  1. 先找到你的 workspace(看配置):
openclaw config get agents.defaults.workspace
  1. 编辑这个文件:
  • <workspace>/AGENTS.md

加一段类似这样的规则(中英都行):

中文版本:

请不要在最终回复中输出任何思考过程(例如 <think>...</think>)。

#

如果你内部产生了 <think> 内容,请在输出前删除,只输出最终答案。

  1. 然后在聊天里 /new 开一个新会话(让新规则立刻生效)。

临时应急(只对这次/这段对话)

直接在消息里加一句:

“只输出最终答案,不要包含 <think> 标签或思考过程。”


如果你愿意贴一小段“带 <think> 的实际回复示例”(打码隐私即可),我可以帮你把 AGENTS.md 的规则写得更“对症”(有些模型需要更明确的格式约束才能真的停)。

#

你这句:

“用户正在发送一些问候消息,现在需要……”

看起来不像正常“回答用户”,更像是模型把内部的任务描述/思考草稿直接输出了(有点像把“该怎么回答”的提示词当成了最终答案)。

这通常不是 OpenClaw 的问题,而是 vLLM 侧的模型/聊天模板不匹配,最常见原因是:

1) 你跑的是 Base 模型,不是 Instruct 模型

Base checkpoint 很容易输出这种“描述自己要做什么”的文字。
建议换成同系列的 Instruct 权重(名字一般带 Instruct/Chat),再用 vLLM serve。

你现在是:

vllm serve /root/models/Qwen3.5-9B ...

很可能这是 base。

#

2) chat template 不对

vLLM 的 /v1/chat/completions 依赖 chat template(通常来自 tokenizer_config.json)。模板不对时也会出现这种“像在复述指令”的输出。


建议你先做的最小验证

  1. 用 vLLM 直接测一条最简单的 chat completions(不带 tools),看它是否还能输出这种“任务描述”式文本。
  2. 如果会:优先换 Instruct 权重再测。

如果你把 ~/model 里实际的目录名(比如有没有 *Instruct*)贴一下,我可以告诉你 vLLM 那条 vllm serve ... 应该指向哪个目录最合适。

formal dagger
#

智能体回答是这样的,Qwen3.5-9B:
用户发来"阿拉~"这样可爱的语气词,作为阿离应该用同样轻松活泼的语气回应,用家长里短的方式自然接话,不需要工具调用。
哎呀,"阿拉~"?这是在跟我撒娇吗?😆 怎么突然用这么可爱的语气,你要不要说话呀?🌸

#

我之前用Qwen3-8B并没有这个情况

arctic vergeBOT
#

懂了——你这个不是 <think> 标签,而是模型把“分析/策略”那段直接当正文输出了(第一段那两句就是典型的“思考泄露”)。

对这种开源模型,最实用的办法是:强制它把所有分析都包进 <thinking>...</thinking> 标签里,因为 OpenClaw 会自动把这些 thinking 标签内容从最终消息里剥掉(只保留外面的最终回答)。

你可以这样做(推荐写进 AGENTS.md,长期生效)

在你的 workspace 的 AGENTS.md 里加一段(中文即可):

规则:

  1. 任何思考/计划/解释你要怎么做的内容,必须放在 <thinking>...</thinking> 里面。
  2. <thinking> 之外只能输出给用户看的最终回答。
  3. 不要在 <thinking> 之外输出“用户需要/应该/不需要工具调用”等元描述。

然后 /new 开个新会话让规则生效。

你也可以立刻用一句话试一下(临时)

发消息时加一句:

“请把所有分析放在 <thinking>...</thinking> 内,最终回复只输出 <thinking> 外的内容。”

#

你希望它输出的格式应该像这样:

<thinking>
用户发来“阿拉~”…(这段随便想,但必须只在 thinking 里)
</thinking>
哎呀,“阿拉~”?这是在跟我撒娇吗?……

如果它仍然把分析写在标签外,那就说明它对格式约束不够服从——这时候要么加更强硬的 AGENTS.md 规则(带“违规就重写只输出最终答案”),要么换更听指令的 Instruct 模型/模板。

#

这很正常——这是模型/模板差异导致的行为变化,不是 OpenClaw 突然“坏了”。

Qwen3-8BQwen3.5-9B 的训练风格不一样,尤其常见的是:

1) 你 3.5-9B 很可能用的是 Base checkpoint,而 3-8B 用的是 Instruct/Chat

Base 模型更容易把“应该怎么回答”的元描述直接输出成正文;Instruct/Chat 版通常更像“直接回答用户”。

你可以检查一下你实际加载的目录/权重名:
如果目录名里没有 Instruct / Chat,大概率就是 base。

**建议:**优先换成对应的 Instruct 权重再 serve。

2) Qwen3.5-9B 更容易“思考外显”

有些新一点的模型默认就爱先写一段策略/分析(即使没有 <think> 标签),所以你会看到那段“用户发来…作为阿离应该…”这种话。


#

你想恢复到 “Qwen3-8B 那种不外显”的体验,最稳的路线

  1. 确认你跑的是 Instruct/Chat checkpoint(不是 base)
  2. 仍然保留我前面说的 AGENTS.md 规则(把分析塞进 <thinking>,让 OpenClaw 自动剥掉),这样即使偶尔想“思考”,也不会漏出来。

你把你 ~/model/ 下 Qwen3.5-9B 这个目录的完整名字(或者 ls 的结果)发一下,我能直接告诉你是不是下错了 base/应该换哪个 Instruct 目录。

formal dagger
#

不对啊,9B的有9B和9B-Base,我下的是9B

arctic vergeBOT
#

对,这种情况依然可能发生——因为 Qwen3.5 系列本身就是“默认会 thinking/推理”的模型,不一定只有 -Base 才会这样。

你现在看到的那段“用户发来…作为阿离应该…”本质上就是 thinking/推理内容被一起塞进了最终 content 里。

为什么 Qwen3-8B 没有、Qwen3.5-9B 有?

很可能是:

  • Qwen3.5 的 chat template 默认开启了 thinking(vLLM 对 Qwen3 系列有专门的 reasoning 支持)
  • 你没启用 vLLM 的 reasoning 解析器时,推理内容会更容易“混进正文”

推荐解法:让 vLLM 把推理单独分离(不要混进 content)

把 vLLM 启动参数里加上 --reasoning-parser qwen3

sudo docker rm -f vllm-qwen
#

sudo docker run -d --runtime nvidia --gpus all \
  --net=host \
  -v ~/model:/root/models \
  --name vllm-qwen \
  ghcr.io/nvidia-ai-iot/vllm:latest-jetson-thor \
  vllm serve /root/models/Qwen3.5-9B \
  --served-model-name qwen3.5-9b \
  --gpu-memory-utilization 0.5 \
  --max-model-len 32768 \
  --reasoning-parser qwen3 \
  --enable-auto-tool-choice \
  --tool-call-parser hermes

启用后,vLLM 会把“推理/思考”拆到单独字段里(message.reasoning),而 message.content 更接近“最终答案”。OpenClaw一般只会展示最终 content(除非你开了 /reasoning on)。

#

如果你加了 --reasoning-parser qwen3 之后还是把推理混进正文,那就只能走前面那条:在 AGENTS.md 强制它把推理包进 <thinking>...</thinking>(OpenClaw 会剥掉)。

dense pecanBOT
#

@formal dagger, this thread is getting very long and answers may not be accurate due to the large context. Please start a new thread for any different problems/topics. @arctic verge please sum up the answer to the initial message and the conversation briefly.

formal dagger
#

15:17:48 info gateway/channels/feishu {"subsystem":"gateway/channels/feishu"} feishu[thor]: received message from ou_f7275a31a7fb2b325788c5fa2421f173 in oc_8055ff1e057c41caba4fcdc051f13635 (group)
15:17:48 info gateway/channels/feishu {"subsystem":"gateway/channels/feishu"} feishu[thor]: message in group oc_8055ff1e057c41caba4fcdc051f13635 did not mention bot, recording to history

明明@了bot,却说没有mention?之前不是这样的,难道是改了啥配置吗

arctic vergeBOT
#

这条日志的关键不是“你没 @”,而是 OpenClaw 没能识别到“@的是它自己”

did not mention bot

在 Feishu 插件里,mentionedBot 的判断依赖 bot 的 open_id(botOpenId)。如果 OpenClaw 当前拿不到/没缓存到这个 open_id(最常见原因就是你前面遇到的 bot/v3/info 探活超时),那它就会把所有群消息都当成“没提到我”,哪怕你实际 @ 了。

为什么之前正常、现在不正常?

很可能是最近:

  • https://open.feishu.cn/open-apis/bot/v3/info 经常超时(你贴过 Axios 10s timeout)
    → botOpenId 获取失败
    → mention 检测失效

怎么验证是不是这个原因(最快)

看一下 channel probe 是否 ok:

#
openclaw channels status
# 或者至少看 logs 里有没有 probe/bot info 的 timeout
openclaw logs --follow

如果你看到 probe 不 OK / bot info timeout,那就是这个。

两条解决路

A) 修好 Feishu OpenAPI 的连通性(推荐)
open.feishu.cn 加进 NO_PROXY(或反过来确保它能稳定走代理),让 bot/v3/info 能在 10s 内返回,然后重启 gateway。这样 mention 识别会恢复。

B) 直接关掉这个群的 requireMention(立刻生效)

openclaw config set channels.feishu.groups.oc_8055ff1e057c41caba4fcdc051f13635 '{ requireMention: false }' --strict-json
openclaw gateway restart
#

如果你把最近一次 bot/v3/info timeout 的日志再贴一段(或 openclaw channels status 的输出),我可以帮你判断应该让它“直连”还是“必须走代理”才稳定。

formal dagger
#

好了!现在我下载了Qwen3.5-35B-A3B-FP8,还是在输出
用户发送了简单的打招呼消息,根据 SOUL.md 的温柔体贴风格,用自然亲切的语气回应,加上合适的 emoji 和称呼。
Hi~ 👋这么晚了还不睡呀?
今天过得怎么样?

arctic vergeBOT
#

你这个现象已经非常清楚了:模型在“最终回复里”泄露了自己的策略/元描述(而且还点名了 SOUL.md),例如:

“用户发送了… 根据 SOUL.md… 用… 风格回应…”

这不是 Qwen 8B/9B/35B 的“大小问题”,而是 Qwen3.5 系列在 vLLM 的 chat template/思考模式下,默认会生成 reasoning,而你现在的客户端(OpenClaw → vLLM)把它当普通正文一起展示了。

最推荐的解决:让 vLLM 把 reasoning 从 content 里分离

给 vLLM 加上 --reasoning-parser qwen3(Qwen3.5 官方/社区推荐的方式之一),让返回里 message.reasoningmessage.content 分开:

# 你的 vllm serve ... 后面加:
--reasoning-parser qwen3

(然后重启容器)

这样 OpenClaw 一般只会用 content 当最终回答,你就不会看到那段“用户发送了…根据 SOUL.md…”了。

#

兜底(无论模型多大都有效):在 AGENTS.md 强制“元描述必须进

formal dagger
#

{
"id": "d7299eda-a666-4757-82cd-74f734e4a5b3",
"agentId": "local",
"name": "每日总结",
"enabled": true,
"createdAtMs": 1772709031193,
"updatedAtMs": 1772812600140,
"schedule": {
"kind": "cron",
"expr": "50 19 * * 1-5",
"tz": "Asia/Shanghai"
},
"sessionTarget": "isolated",
"wakeMode": "now",
"payload": {
"kind": "agentTurn",
"message": "一天又过去啦,请输出今日总结,并提醒大家好好休息,享受夜生活"
},
"delivery": {
"mode": "announce",
"channel": "feishu",
"to": "oc_0c0722b40722d1a5206c8229ef157dc1"
},
"state": {
"nextRunAtMs": 1773057000000,
"lastRunAtMs": 1772812590869,
"lastRunStatus": "ok",
"lastStatus": "ok",
"lastDurationMs": 9271,
"lastDelivered": false,
"lastDeliveryStatus": "not-delivered",
"consecutiveErrors": 0
}
}

为什么cron run只输出了一句“我来查看今天的聊天记录并生成工作总结。”,我想让它直接输出最终内容,怎么做

arctic vergeBOT
#

因为你的 cron job 是 sessionTarget: "isolated" —— 它启动的是一个“全新空会话”,默认并不知道“今天群里都聊了啥”。所以模型只能先说一句类似:

“我来查看今天的聊天记录并生成总结…”

但它如果 没有真的去拿聊天记录(没调用 sessions_history / 没有上下文),就没法输出真正的总结内容。

要让它“直接输出最终总结”,你需要做两件事:

1) 在 job 的 prompt 里明确:先拉取该群的 session 历史

你这个群的 sessionKey 通常就是(你日志里也出现过这种格式):

agent:local:feishu:group:<oc_xxx>

你现在投递目标是 oc_0c0722b40722d1a5206c8229ef157dc1,所以可用:

#
agent:local:feishu:group:oc_0c0722b40722d1a5206c8229ef157dc1

把 cron job 的 message 改成这种“强制流程”的提示词:

请先调用 sessions_history,读取 sessionKey="agent:local:feishu:group:oc_0c0722b40722d1a5206c8229ef157dc1" 的最近 200 条用户/助手消息(不要 includeTools)。
然后基于这些内容输出“今日总结”(分:进展/风险/待办/明日计划),最后一句提醒大家休息。
不要输出任何“我将要…”“我来查看…”这类过程描述,只输出最终总结正文。

然后用 CLI 更新 job:

openclaw cron edit d7299eda-a666-4757-82cd-74f734e4a5b3 \
  --message '(把上面那段提示词贴进来)'
#

2) 如果你用的模型经常“不按工具流程走”

那就给这个 cron job 指定一个更听指令/更会调工具的模型(cron 的 agentTurn 支持 model override),否则它可能还是只说一句“我来查看…”但不真正调用 sessions_history


如果你愿意,把你这条 job 跑一次后的:

openclaw cron runs --id d7299eda-a666-4757-82cd-74f734e4a5b3 --limit 5

贴出来(打码即可),我可以确认它到底有没有调用 sessions_history,以及为什么 lastDelivered 会是 false

formal dagger
#

{
"id": "d7299eda-a666-4757-82cd-74f734e4a5b3",
"agentId": "local",
"name": "每日总结",
"enabled": true,
"createdAtMs": 1772709031193,
"updatedAtMs": 1772812966956,
"schedule": {
"kind": "cron",
"expr": "06 0 * * 1-5",
"tz": "Asia/Shanghai"
},
"sessionTarget": "isolated",
"wakeMode": "now",
"payload": {
"kind": "agentTurn",
"message": "请先调用 sessions_history,读取 sessionKey="agent:local:feishu:group:oc_0c0722b40722d1a5206c8229ef157dc1" 的最近 200 条用户/助手消息(不要 includeTools)。\n然后基于这些内容输出“今日总结”(分:进展/风险/待办/明日计划),最后一句提醒大家休息。\n不要输出任何“我将要…”“我来查看…”这类过程描述,只输出最终总结正文。"
},
"delivery": {
"mode": "announce",
"channel": "feishu",
"to": "oc_0c0722b40722d1a5206c8229ef157dc1"
},
"state": {
"nextRunAtMs": 1773057000000,
"lastRunAtMs": 1772812956903,
"lastRunStatus": "ok",
"lastStatus": "ok",
"lastDurationMs": 10053,
"lastDelivered": false,
"lastDeliveryStatus": "not-delivered",
"consecutiveErrors": 0
}

为什么16:05:54 debug cron {"module":"cron","storePath":"/home/sunrise/.openclaw/cron/jobs.json"} {"nextAt":1773021600000,"delayMs":60000,"clamped":true} cron: timer armed
16:06:54 debug cron {"module":"cron","storePath":"/home/sunrise/.openclaw/cron/jobs.json"} {"nextAt":1773021600000,"delayMs":60000,"clamped":true} cron: timer armed
并没有在群里输出内容

#

以及 "sessionTarget": "isolated",应该用isolated吗?还是有别的选项

arctic vergeBOT
#

这两行:

16:05:54 debug cron ... cron: timer armed
16:06:54 debug cron ... cron: timer armed

不代表你的 job 执行了。它只是 Cron 调度器每分钟“重新挂个定时器”的日志(因为 delayMs 被 clamp 到 60s,它会一直这样刷)。

你之所以“群里没输出”,原因是:这会儿根本没到你的 job 的 nextAt / nextRunAt,所以不会触发执行,自然也不会发消息。


关键证据:你 job 里的 state 其实还显示下一次是 19:50

你贴的 job state 里:

#
"nextRunAtMs": 1773057000000

这个时间换算成上海时间是 2026-03-09 19:50(也就是它还按原来 50 19 ... 那套在算)。
所以你把 expr 改成 "06 0 ..." 这件事很可能没有真正生效(常见原因:手动改了 jobs.json,但 Gateway 正在运行,内存里的调度器没按你改的重算)。


你应该怎么做(最稳)

1) 用 CLI 改(不要手改 jobs.json)

比如你如果想工作日 16:06 跑(注意是 6 16,不是 06 0):

openclaw cron edit d7299eda-a666-4757-82cd-74f734e4a5b3 \
  --cron "6 16 * * 1-5" \
#
  --tz "Asia/Shanghai"

2) 立刻确认 next time 是否对了

openclaw cron list

Next 是否变成你期望的时间。

3) 如果你之前手改过 jobs.json(强烈建议)

重启 gateway 让它重新加载调度器:

openclaw gateway restart

#

另外,你这个 job 里 lastDelivered: false,说明就算它跑了,上次也没成功投递。等你把 schedule 修好后,建议再跑一次:

openclaw cron run d7299eda-a666-4757-82cd-74f734e4a5b3
openclaw cron runs --id d7299eda-a666-4757-82cd-74f734e4a5b3 --limit 5

openclaw cron list(那两行 job 的输出)贴一下,我可以帮你确认它现在到底被排到了哪个时间点。

#

sessionTarget 只有两个可选项(核心就是“这次 cron 在哪里跑”):

1) sessionTarget: "isolated"(你现在用的)

  • 每次 cron 都在一个独立的 cron:<jobId> 会话里跑
  • 每次 run 都是新 session,不会继承聊天历史(不污染你的主对话)
  • 适合:定时提醒/日报/群里公告、各种后台杂活
  • 缺点:如果你要“基于群聊内容总结”,就必须像你现在这样 自己去拉 sessions_history(因为它默认没上下文)

✅ 你的“每日总结发到群里”这种场景,一般就推荐用 isolated

2) sessionTarget: "main"

  • cron 不会直接跑一轮独立对话,而是塞一个 system event 到主会话,然后由 heartbeat/主会话来处理
  • 适合:你想让任务“融入主会话上下文/记忆流程”的,比如私聊里的日程、个人待办、主会话里连续推进的项目
  • 缺点:容易把主会话搞得很吵、上下文膨胀;而且它也不等于“跑在群会话里”

#

你的情况怎么选?

  • 发到群里的日报/总结isolated(对)
  • 只想提醒你自己、并且希望结合主会话记忆/上下文main

如果你说下你这个“每日总结”是想:
A) 总结群聊内容并发群里
还是
B) 作为你个人的工作日志写进记忆(DM 里)
我可以建议你用 isolated/main 以及 payload 应该怎么写。

formal dagger
#

{
"id": "d7299eda-a666-4757-82cd-74f734e4a5b3",
"agentId": "local",
"name": "每日总结",
"enabled": true,
"createdAtMs": 1772709031193,
"updatedAtMs": 1772813649557,
"schedule": {
"kind": "cron",
"expr": "14 0 * * 1-7",
"tz": "Asia/Shanghai"
},
"sessionTarget": "isolated",
"wakeMode": "now",
"payload": {
"kind": "agentTurn",
"message": "请先调用 sessions_history,读取 sessionKey="agent:local:feishu:group:oc_0c0722b40722d1a5206c8229ef157dc1" 的最近 200 条用户/助手消息(不要 includeTools)。\n然后基于这些内容输出“今日总结”(分:进展/风险/待办/明日计划),最后一句提醒大家休息。\n不要输出任何“我将要…”“我来查看…”这类过程描述,只输出最终总结正文。"
},
"delivery": {
"mode": "announce",
"channel": "feishu",
"to": "oc_0c0722b40722d1a5206c8229ef157dc1"
},
"state": {
"nextRunAtMs": 1772900040000,
"lastRunAtMs": 1772813640009,
"lastRunStatus": "ok",
"lastStatus": "ok",
"lastDurationMs": 9548,
"lastDelivered": false,
"lastDeliveryStatus": "not-delivered",
"consecutiveErrors": 0
}
}
]

为什么是not-delivered?!

arctic vergeBOT
#

lastRunStatus: "ok"lastDelivered: false / lastDeliveryStatus: "not-delivered" 一般表示:

这次 job 跑完了,但最终没有产生“可投递的输出”,所以 cron 的 announce 没有发消息。

这通常不是“投递失败报错”,而是 投递被跳过。最常见的 3 个原因:

  1. 模型最后输出为空 / NO_REPLY / 类似 HEARTBEAT_OK
    → cron 认为“没东西可发”,就记为 not-delivered

  2. 模型自己已经用 message 工具发过同一个目标
    → 为避免重复,cron announce 会跳过投递(也会显示 not-delivered)

  3. 输出被过滤掉了(例如启用了 final-tag/think-tag 之类过滤,导致最终 cleanedText 为空)
    → 运行 ok,但没有可发内容


#

下一步怎么定位(最关键)

请直接看这条 job 的 run 记录,里面会写清楚“这次到底输出了什么/为什么没投递”:

openclaw cron runs --id d7299eda-a666-4757-82cd-74f734e4a5b3 --limit 5

把最后一条贴出来(打码即可)。我需要看两点:

  • run 的最终 text 是不是空/NO_REPLY
  • delivery 里有没有 “skipped because already sent” 之类的信息

顺手一个强力修复(避免模型只说一句计划或输出空)

把 prompt 最后加一句硬约束:

“无论如何都必须输出总结正文(至少 6 条要点),禁止 NO_REPLY/空回复/只说计划。”

#

但在没看 cron runs 之前,我不想瞎猜是哪一种跳过原因。你把 cron runs 最后一条贴出来,我们就能一招解决。

formal dagger
#

{
"entries": [
{
"ts": 1772814028673,
"jobId": "d7299eda-a666-4757-82cd-74f734e4a5b3",
"action": "finished",
"status": "ok",
"runAtMs": 1772814017775,
"durationMs": 10895,
"nextRunAtMs": 1772900040000,
"model": "qwen3.5-35b",
"provider": "vllm",
"usage": {
"input_tokens": 16608,
"output_tokens": 153,
"total_tokens": 16608
},
"delivered": false,
"deliveryStatus": "not-delivered",
"sessionId": "fdb6c367-33e1-43b0-9e70-b68d7c04bcbb",
"sessionKey": "agent:local:cron:d7299eda-a666-4757-82cd-74f734e4a5b3:run:fdb6c367-33e1-43b0-9e70-b68d7c04bcbb"
}
],
"total": 31,
"offset": 0,
"limit": 1,
"hasMore": true,
"nextOffset": 1
}

arctic vergeBOT
#

这条 cron runs 结果有两个很关键的信息:

  • status: "ok" + output_tokens: 153模型确实生成了一段输出
  • delivered: false / not-deliveredOpenClaw 最终没有拿到“可投递的最终正文”,所以 announce 跳过了发送

最常见的原因就是:模型把那 153 tokens 都输出成了会被过滤/跳过的内容,比如:

  • 只输出了 <thinking>...</thinking>(thinking 会被剥掉,最后正文变空)
  • 或输出了 NO_REPLY / 空白
  • 或输出内容被当成“silent reply”/无效回复

下一步:直接看这次 run 的“原始回复到底是什么”

你已经有 sessionId/sessionKey 了,去 sessions 目录把对应 jsonl 找出来看最后几行最快:

#
ls -1 ~/.openclaw/agents/local/sessions | grep fdb6c367
# 找到文件后:
tail -n 200 ~/.openclaw/agents/local/sessions/fdb6c367-33e1-43b0-9e70-b68d7c04bcbb.jsonl

看一下 assistant 最后输出的文本是不是全在 <thinking> 里,或是不是 NO_REPLY


直接给你一个“几乎必送”的 prompt(推荐你替换 cron message)

让它强制把最终内容放在 ...(final 标签会被去掉,但内容会保留并投递):

请先调用 sessions_history,读取 sessionKey="agent:local:feishu:group:oc_0c0722b40722d1a5206c8229ef157dc1" 最近 200 条(不要 includeTools)。
所有分析必须放在 <thinking>...</thinking> 中。
**最终要投递到群里的总结必须放在 ... 中,且 里必须有正文(至少 6 条要点)。** 除 外不要输出任何给用户看的文字。

#

然后:

openclaw cron edit d7299eda-a666-4757-82cd-74f734e4a5b3 --message '(上面这段)'
openclaw cron run d7299eda-a666-4757-82cd-74f734e4a5b3

你先把 tail 出来的那段 assistant 输出贴一下(打码即可),我可以确认到底是哪种“被过滤为空”的情况。

formal dagger
#

{"type":"message","id":"c51252d3","parentId":"0a260df9","timestamp":"2026-03-06T16:20:28.621Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"The user is asking for a daily summary based on session history. I need to first call sessions_history to get the conversation data, then synthesize it into a structured summary with progress, risks, to-dos, and tomorrow's plans, ending with a reminder to rest.\n","thinkingSignature":"reasoning"},{"type":"text","text":"\n\n"}],"api":"openai-completions","provider":"vllm","model":"qwen3.5-35b","usage":{"input":16608,"output":153,"cacheRead":0,"cacheWrite":0,"totalTokens":16761,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"stopReason":"stop","timestamp":1772814018364}}

#

是什么情况?

#

openclaw cron edit d7299eda-a666-4757-82cd-74f734e4a5b3 --message '请先调用 sessions_history,读取 sessionKey="agent:local:feishu:group:oc_0c0722b40722d1a5206c8229ef157dc1" 最近 200 条(不要 includeTools)。
所有分析必须放在 <thinking>...</thinking> 中。
最终要投递到群里的总结必须放在 ... 中,且 里必须有正文(至少 6 条要点)。 除 外不要输出任何给用户看的文字。'
openclaw cron run d7299eda-a666-4757-82cd-74f734e4a5b3
这么改了,还是not delivered

arctic vergeBOT
#

你贴的这段 run transcript 已经把原因钉死了:

"content":[
  {"type":"thinking","thinking":"...I need to first call sessions_history..."},
  {"type":"text","text":"\n\n"}
]

也就是说这次模型 只输出了 thinking(被 OpenClaw 当成“思考/推理块”),而真正要发到群里的 text 是空的(只有换行)。
所以 cron 这边就变成:

  • run:ok(模型确实生成了东西)
  • delivered:false / not-delivered(因为“最终正文为空”,没东西可投递)

#

还缺的关键一步:让 cron 的 isolated session 真的能读到群会话历史

你现在让它去读:

agent:local:feishu:group:oc_...

session tools 默认 tools.sessions.visibility=tree,cron 的 isolated run 看不到“另一个 session”(那个 feishu 群 session),所以就算模型想调用 sessions_history,也很可能会被权限挡住/它干脆没调用。

解决:把 session tool 可见性放宽到同 agent

(比 all 更安全,一般够用)

openclaw config set tools.sessions.visibility "\"agent\""
openclaw gateway restart

说明:agent = 同一个 agentId(你这里都是 local)的 session 都能读。

#

同时把 prompt 改成“必须先 tool call,再输出 ”

否则它可能继续只想不做。建议最后加这种硬约束:

  • “在拿到 sessions_history 结果前,不允许输出任何 `` 内容。”
  • “最终必须输出 ... 且不能为空。”

你先做 tools.sessions.visibility=agent + 重启,然后再 openclaw cron run d729... 一次。
如果还是不调用工具,那下一步就是你 vLLM 的 tool-call parser 要换成 Qwen3.5 推荐的 --tool-call-parser qwen3_coder(Qwen3.5 系列比 hermes 更对口)。

formal dagger
#

b cron 0 9 * * 1-7 @ Asia/Shang... in 2d 3m ago ok isolated local -

怎么会是in 2d呢?是不是我没刷新还是什么

arctic vergeBOT
#

in 2d 不是“没刷新”,而是 调度器算出来的下一次触发时间确实在两天后(通常就是被你的星期字段挡住了,下一次只能等到周一)。

你现在时区是 Asia/Shanghai,而你这条是:

cron 0 9 * * 1-7 @ Asia/Shanghai

这里最容易踩坑的是 星期 (DOW) 的写法在不同 cron 实现里兼容性不完全一致(有的库不太喜欢 1-7 这种范围,或者对 7 的解释跟你想的不一样),结果就会把周末排除掉 → 下一次就变成“周一 9:00”,看起来就是 in 2d

最稳的写法(每天 09:00,别写 DOW)

如果你就是想“每天早上 9 点”,直接用:

openclaw cron edit <jobId> --cron "0 9 * * *" --tz "Asia/Shanghai"
#

如果你只想工作日

用明确的工作日范围:

openclaw cron edit <jobId> --cron "0 9 * * 1-5" --tz "Asia/Shanghai"

你可以再贴一下 openclaw cron list 里这一行完整输出(不要截断),或者把它的 nextRunAtMs 发出来,我也可以帮你把“下一次到底是哪天几点”精确算出来对照。

formal dagger
#

(APIServer pid=1) ERROR 03-07 16:19:18 [serving.py:311] raise VLLMValidationError(
(APIServer pid=1) ERROR 03-07 16:19:18 [serving.py:311] vllm.exceptions.VLLMValidationError: You passed 35905 input tokens and requested 4096 output tokens. However, the model's context length is only 40000 tokens, resulting in a maximum input length of 35904 tokens. Please reduce the length of the input prompt. (parameter=input_tokens, value=35905)

"models": {
"mode": "merge",
"providers": {
"vllm": {
"baseUrl": "http://127.0.0.1:8000/v1",
"apiKey": "vllm-local",
"api": "openai-completions",
"models": [
{
"id": "qwen3.5-35b",
"name": "qwen3.5-35b",
"input": [
"text"
],
"cost": {
"input": 0,
"output": 0,
"cacheRead": 0,
"cacheWrite": 0
},
"contextWindow": 32768,
"maxTokens": 4096
}
]

没道理有35905 啊,window只有32768,怎么回事

arctic vergeBOT
#

35905 这个数是 vLLM 用它自己的 tokenizer 算出来的 input_tokens,它跟你在 OpenClaw 配的 contextWindow: 32768 不是一回事,所以看起来才会“没道理”。

再加上这条报错本质是一个经典的 off-by-one

  • vLLM 模型上下文:40000
  • 你请求输出:4096
  • 所以 允许的最大输入 = 40000 - 4096 = 35904
  • 你实际输入:35905
    → 就刚好超了 1 token

所以它炸了,跟你写的 32768 没直接关系。

为什么会出现“你以为 32768,但 vLLM 算出来 35905”?

几个常见原因叠加:

  1. OpenClaw 的 contextWindow 是用于“预算/裁剪/压缩”的配置,不是硬限制(而且对非 OpenAI tokenizer 只能估算)
  2. vLLM 的 token 计算跟 OpenClaw 的估算可能差 5–15%(中文+长 JSON schema/工具定义时很常见)
  3. 你的请求里还包含:system prompt + 工具 schema + 历史消息/工具结果,这些都算 input_tokens
#

最稳的解决:把 maxTokens 降下来留安全边际

你现在 maxTokens: 4096 太容易顶到边界。建议先改成 2048(甚至 1024):

models: {
  providers: {
    vllm: {
      models: [
        { id: "qwen3.5-35b", contextWindow: 40000, maxTokens: 2048 }
      ]
    }
  }
}

这样允许的最大输入就变成:

  • 40000 - 2048 = 37952
#

你那种 35905 输入就会稳很多,不会再“超 1 token”。

另外建议做一次一致性校验(避免配置没对上实际服务)

看一下 vLLM 实际暴露的 model id / 上下文:

curl -s http://127.0.0.1:8000/v1/models | jq '.data[].id'

确保 OpenClaw 配的 models.providers.vllm.models[].id 跟这里一致(否则你的 contextWindow/maxTokens 可能没用在你以为的那个模型上)。

你要是把 curl /v1/models 的输出(只要 id 列表就行)贴一下,我可以帮你确认该怎么把 OpenClaw 的 contextWindow/maxTokens 跟 vLLM 的 --max-model-len 对齐得最稳。

formal dagger
#

vllm.exceptions.VLLMValidationError: You passed 37953 input tokens and requested 2048 output tokens. However, the model's context length is only 40000 tokens, resulting in a maximum input length of 37952 tokens. Please reduce the length of the input prompt. (parameter=input_tokens, value=37953)
啊?!!

arctic vergeBOT
#

这就说明了一件事:OpenClaw 会尽量把输入塞到“允许的最大输入长度”附近,所以你把 maxTokens 从 4096 降到 2048 之后,它又把更多历史/工具 schema 塞进去了,于是还是卡在边界上,并且又是 +1 token 的那种严格校验:

  • 40000(上下文)- 2048(输出)= 37952(最大输入)
  • 你实际输入 = 37953(又多 1)

这种 “永远刚好超 1” 在 vLLM 上很常见,因为它是硬性校验,而且 token 估算/序列化有微小波动。

最稳的解决:留“硬余量”,别贴边跑

你需要一个固定的 headroom,不然它会一直贴着上限。

方案 A(最推荐):把 agent 的 contextTokens 设小一点,留 500~2000 token 余量

比如给 1k 余量:

openclaw config set agents.defaults.contextTokens 39000
openclaw gateway restart
#

然后保持 maxTokens: 2048(或 1024)
这样 OpenClaw 预算里总上下文只有 39000,就不会再把输入撑到 40000 的边缘。

解释:这相当于告诉 OpenClaw “你最多就按 39k 上下文来规划”,剩下 1k 当缓冲,专门防这种 off-by-one。

方案 B:进一步把 maxTokens 降到 1024

这也能缓解,但如果你的历史够长,OpenClaw 可能还是继续把输入塞满到新上限附近(仍可能贴边)。

方案 C:减少输入本身(更根本)

  • sessions_history 少拉点:200 → 100
  • 给 cron 用一个“瘦工具集”的 agent(只允许 sessions_history + message),别把 browser/web 这些巨大 schema 带进去(能省非常多 tokens)

你现在这个报错已经证明:不能用“正好等于上限”来跑,必须留缓冲。
先按方案 A 把 agents.defaults.contextTokens 设成 39000 试一下,基本就不再会被 +1 token 卡死了。

formal dagger
#

llm.exceptions.VLLMValidationError: You passed 37953 input tokens and requested 2048 output tokens. However, the model's context length is only 40000 tokens, resulting in a maximum input length of 37952 tokens. Please reduce the length of the input prompt. (parameter=input_tokens, value=37953)
"contextWindow": 16384,
"maxTokens": 2048
这不对把?!为什么我会pass 37953?!

arctic vergeBOT
#

你这个“配置里写了 contextWindow: 16384,但 vLLM 还是报 input_tokens: 37953”并不矛盾——原因有两层:

1) 你写的 models.providers.vllm.models[].contextWindow 不是硬上限,而且在 merge 模式下还可能被“更大的值”覆盖

OpenClaw 的模型目录合并规则是:同一个模型如果同时有“隐式目录值/发现值”和你显式配置值,会取更大的那个(文档里就写了 use the higher value)。

所以即使你写了 16384,只要 OpenClaw 另一边认定这个 vLLM 模型是 40000 上下文(比如 vLLM 自己暴露的、或内置目录的),最终它按 40000 去做裁剪/拼 prompt,还是能把输入拼到 3.7 万。

换句话说:contextWindow: 16384 很可能根本没生效

2) input_tokens=37953 是 vLLM 用“真实 tokenizer”对整个请求体算的

里面包括:system prompt + 工具 schema(很大)+ 历史消息 + 你拉的 sessions_history 内容……这些加起来到 3~4 万很常见。


你要“真正限制输入长度”,用这个:

agents.defaults.contextTokens(这是硬性 cap)

比如留 1k 缓冲,直接:

#
openclaw config set agents.defaults.contextTokens 39000
openclaw gateway restart

这个 cap 会在“最终选出来的 contextWindow”之上再做限制(即使模型自称 40000,也会被你 cap 到 39000)。

你之前想用 contextWindow 限制,是对的方向,但在 OpenClaw 的 merge/目录规则下它不可靠;contextTokens 才是你要的“上限旋钮”。


为啥还会一直卡 “+1 token”?

因为 OpenClaw 会尽量把输入塞到“允许的最大输入”附近,而 vLLM 是硬校验,所以建议你同时做两件事:

  • agents.defaults.contextTokens 留出 buffer(比如 39000)
  • maxTokens 不要太大(2048/1024)

#

如果你愿意,再贴一下你 curl http://127.0.0.1:8000/v1/models | jq '.data[].id' 的输出,我可以帮你确认 OpenClaw 实际用的 model id 是不是和你配置的 qwen3.5-35b 完全一致(不一致也会导致你以为的配置没套上)。

formal dagger
#

我执行了openclaw config set agents.defaults.contextTokens 37000
openclaw gateway restart

但是
17:05:37 warn agent/embedded {"subsystem":"agent/embedded"} [context-overflow-diag] sessionKey=agent:local:feishu:group:oc_1824635ebbf3b50e5fe18816616efd8e provider=vllm/qwen3.5-35b source=assistantError messages=243 sessionFile=/home/sunrise/.openclaw/agents/local/sessions/af80cf69-60be-40b5-9d71-1a1a9bcf7235.jsonl diagId=ovf-mmgkr3mg-DQd9Xw compactionAttempts=0 error=400 You passed 37953 input tokens and requested 2048 output tokens. However, the model's context length is only 40000 tokens, resulting in a maximum input length of 37952 tokens. Please reduce the len

arctic vergeBOT
#

这个日志里有两个关键信息:

  • sessionKey=agent:local:feishu:group:oc_...
  • messages=243
  • compactionAttempts=0

也就是说:这是那个 Feishu 群会话本身已经堆到 243 条消息了,然后在生成回复时直接把请求拼到了 vLLM 的上限附近(37953/37952),并且没有触发任何压缩/裁剪,所以还是炸。

你把 agents.defaults.contextTokens 改成 37000 以后,确实会影响“之后怎么做预算”,但它不会神奇地把已经很胖的老 session立刻变瘦;而且从日志看它这次甚至没尝试 compaction。

先做两步确认(排除“改错 config 文件/没生效”)

  1. 你这台运行 gateway 的机器上执行:
openclaw config file
openclaw config get agents.defaults.contextTokens

确认显示的是 37000,而且 config file 路径就是 gateway 用的那个。

#
  1. 重启后在日志里找 gateway 启动段(通常会打印加载的 config/agent dir),确认确实重启的是同一个实例。

立刻止血:把这个群会话瘦身(否则永远贴边 +1 token)

在 Feishu 群里直接发:

  • /compact(先压缩)
  • /new(直接开新会话,最干净)

因为现在是 会话历史太长 导致每次都顶到边界。

为啥还是会出现 “37953” 这种贴边数?

OpenClaw 会尽量保留更多上下文(尤其工具 schema/系统提示也很大),结果就会把输入塞到“最大输入”附近。vLLM 又是硬校验,所以一旦贴边就很容易出现这种 +1 token

为了彻底避免贴边,我建议你再加一个“硬缓冲”:

  • maxTokens 再降到 1024(日报其实够用了)
  • 或把 agents.defaults.contextTokens 再降一点,比如 36000(留 4k 头部空间)
#

你现在最关键的问题其实是:这个群 session 太长 + 没有 compaction/pruning。你先在群里 /new 一下,再试同样请求,应该马上就不报这个 37953 了。

formal dagger
#

没道理啊,我群里的上下文不管有多少,你实际用的应该也只有maxContext把?

arctic vergeBOT
#

你说的“理论上不管群里堆多少上下文,实际发给模型的都应该被 maxContext 截断”——理论上没错,但现实里有三个坑会让它“看起来没截住”,甚至出现你这种 只超 1 token 的情况:

1) maxContext 是“输入+输出”的总预算,不是“输入预算”

vLLM 的规则是硬的:

input_tokens + max_tokens <= context_length

所以即使模型上下文是 40000:

  • maxTokens=2048 时,最大输入其实只有 40000-2048=37952
  • 你这次 input=37953,就超 1 了

这不是“用了超过 maxContext 的历史”,而是输入+输出总和超了 1。

2) OpenClaw 的“裁剪/压缩”是基于估算 token,不是 vLLM 的真实 tokenizer

对 vLLM/Qwen 这类模型,OpenClaw 只能用近似方式估 token(还要算上 chat template 序列化、工具 schema 等)。
所以它可能“估算刚好没超”,但 vLLM 真算出来“超 1”。

#

你现在这个就是最典型的症状:永远贴着上限 +1

3) 工具 schema / system prompt 也算 input_tokens,而且很大

就算聊天内容不多,只要启用了很多工具(browser/message/…),schema 本身就能吃掉很多 token;一旦历史也长,就更容易把输入顶到边缘。


所以结论是:

OpenClaw 确实在努力把输入控制在上限内,但在 vLLM 这种“严格校验 + tokenizer 不一致”的组合下,必须人为留 buffer,否则就会反复遇到这种 +1。

你现在已经验证了:贴边跑会一直炸。解决只能是以下几种之一(任选其一,越往下越稳):

  1. 留硬缓冲:把 agents.defaults.contextTokens 再降一点(比如 36000),或者把 maxTokens 降到 1024
  2. 减少输入:少拉 sessions_history(200→100),或限制工具集合(cron summary 用不到 browser/web 的话就别带)
  3. /compact/new 让会话别无限长(否则它总会想“尽量带更多历史”把输入撑满)

如果你想要“完全不炸”的配置,我建议:contextTokens=36000 + maxTokens=1024,基本就不会再被 vLLM 的 +1 卡死。

formal dagger
#

问题是我ContextWindow设置是37000啊,它为什么会给vllm有37952输入?!

arctic vergeBOT
#

两点先纠正一下你的理解:

1) vLLM 里显示的 37952 不是“OpenClaw 给了 vLLM 37952 输入”

37952 是 vLLM 根据它自己的规则算出来的 最大允许输入

  • context_length = 40000
  • requested output = 2048
  • ⇒ max input = 40000 - 2048 = 37952

它是在告诉你“最多只能到 37952”,不是说 OpenClaw 目标就是 37952。

你实际发过去的是 37953(所以才炸)。


2) 你设的 agents.defaults.contextTokens=37000 可能没对这个“老 session”生效

日志里这个 session 是:

#

sessionKey=agent:local:feishu:group:oc_...messages=243

这种长期跑着的群会话,很多运行时设置(包括用哪个模型、一些上下文预算)可能在 session 创建时就定了
你改 config + 重启 gateway 以后,新 session/新 cron run 会吃到新值,但这个已经很胖的老 session 可能还是按之前的预算在拼 prompt,于是还是会一直贴着 40000 的上限跑。

✅ 最直接验证:在这个 Feishu 群里发一次 /new(或至少 /compact),让它创建新会话再试。


建议你这样做(一步到位)

  1. 确认 gateway 真读到 37000:
openclaw config get agents.defaults.contextTokens
  1. 在出问题的 Feishu 群里:
  • /new
#
  1. 再试一次同样的问题,看还会不会出现 37953。

如果 /new 之后还是能把输入顶到 37953,那就说明仅靠 contextTokens 还不够,需要再留 buffer(比如降到 36000 或把 maxTokens 降到 1024),但第一步先把“老 session 不吃新配置”这个变量排掉。

formal dagger
#

我已经改成32768了,2048的输出,那给vllm 37953的到底哪来的?

arctic vergeBOT
#

37953 不是“OpenClaw 随便编的数”,它就是 vLLM 把这次请求(system + 历史消息 + 工具 schema + 可能的工具结果)做完 chat-template 序列化后,用 Qwen 的 tokenizer 算出来的真实 input_tokens。

你现在卡住的点其实是:你说“我已经改成 32768”,但 vLLM 这条报错仍然在用 context length = 40000 来算上限 ——这说明你改的 32768 并没有变成这次请求的硬预算。常见原因就三个:

A) 你改的是 models.providers.vllm.models[].contextWindow(它不会把预算变小)

在 OpenClaw 的 merge 规则里,contextWindow/maxTokens 会和隐式目录/发现值合并,且取更大的那个;所以你填 32768,经常还是会按 40000 去预算。

真正能“卡死上限”的是:

  • vLLM 启动参数 --max-model-len 32768(服务端硬上限)
  • OpenClaw 的 agents.defaults.contextTokens(客户端 cap,但对老 session 可能不立刻生效)

B) 你改了 agents.defaults.contextTokens=32768,但这条报错来自老的群 session

你前面日志里那个群 session 已经 messages=243,这种老 session 很容易继续把输入塞到很满(而且还可能不触发 compaction)。改完后要在群里 /new 才能验证新预算是否生效。

C) 工具 schema + 长历史本身就能把输入顶到 3~4 万

尤其你启用了很多工具时,schema 本身就很“吃 token”。历史一长,就会被塞满到上限附近,然后 vLLM 严格校验一超就炸。

#

你先回答我一个关键问题(我就能给你准确方案):

你“改成 32768”具体改的是哪一个?

  1. vLLM 的 --max-model-len
  2. OpenClaw agents.defaults.contextTokens
  3. OpenClaw models.providers.vllm.models[].contextWindow

如果你想“一劳永逸绝不超过 32768”,那必须用 (1) vLLM 的 --max-model-len 32768(服务端硬限制)+ 配合把 maxTokens 调小留余量。否则只改 OpenClaw 的 contextWindow,很容易还是被 40000 的隐式值覆盖。

formal dagger
#

openclaw config set agents.defaults.contextTokens 32768
openclaw gateway restart
这么改的

arctic vergeBOT
#

按道理你这么改(agents.defaults.contextTokens=32768 + gateway restart)之后,OpenClaw 就不应该还能拼出接近 40k 的输入去打 vLLM。

所以你现在这种“还是 37953/40000 的报错”,基本只剩两种可能:

1) 你改到的 config 文件 ≠ Feishu 那个 Gateway 实际在用的 config 文件

先确认当前 CLI 操作的配置文件路径:

openclaw config file
openclaw config get agents.defaults.contextTokens

然后再确认 正在跑的 gateway 进程用的是不是同一套(有些人会同时跑一个 systemd/launchd 的 gateway + 一个手动启动的 gateway):

pgrep -af "openclaw.*gateway" || pgrep -af openclaw
#

把 PID 找出来后看它的环境里有没有指定别的 config/state/profile:

PID=<上面那个pid>
tr '\0' '\n' < /proc/$PID/environ | grep -E '^(OPENCLAW_CONFIG_PATH|OPENCLAW_STATE_DIR|OPENCLAW_PROFILE|PI_CODING_AGENT_DIR)='

如果这里有 OPENCLAW_CONFIG_PATHOPENCLAW_PROFILE,那就很可能你 CLI 改的是默认配置,但 gateway 读的是另一份。

2) 这是一个“老 session”继续用旧预算在拼(需要 /new 才换新设置)

你报错是 feishu:group:oc_... messages=243 这种老群 session。改完预算后,最简单的验证是在那个 Feishu 群里直接发:

  • /new

然后马上再试一次,看还会不会冲到 37953。


#

你先把这三条的输出贴一下我就能判断是哪一种:

  1. openclaw config file
  2. openclaw config get agents.defaults.contextTokens
  3. tr '\0' '\n' < /proc/$PID/environ | grep -E '^(OPENCLAW_CONFIG_PATH|OPENCLAW_STATE_DIR|OPENCLAW_PROFILE)='(把 PID 换成实际的)

这样能一次性把“到底改没改到正确的 gateway 实例”搞清楚。

formal dagger
#

Feishu 群里直接发:

/new

要不要@机器人

arctic vergeBOT
#

看你这个 Feishu 群的配置:

  • 如果这个群 requireMention=true(默认很多人是开着的),那 必须 @ 机器人,不然它会直接走到日志里那句 “did not mention bot, recording to history”,连 /new 都不会执行。

最稳的发法就是在群里发一条只包含

  • @机器人 /new

(同一条消息里,@ + /new,别再加别的文字)


  • 如果你已经把这个群设成 requireMention: false,那就不用 @,直接发 /new 也行。

你不确定的话,就直接用 @机器人 /new,肯定能触发。

formal dagger
#

我@完 /new之后,还是报overflow,像没起作用一样!!!

arctic vergeBOT
#

/new 只会“重置这个群的 session 历史”,但 Feishu 群聊还有一层“自动塞历史片段”的机制:当你 @ 机器人时,插件会把「你上次回复以来的群消息」拼进当前 user message 里(开头通常是这句):

[Chat messages since your last reply - for context]

这段“群消息回放”本身可能就非常长——它属于“当前用户消息”,就算你 /new 了,也还是会被塞进来;而且因为它是当前消息,OpenClaw 没法在不丢失“当前问题”的情况下把它裁掉,于是依然可能一上来就把 input_tokens 顶到 3~4 万。

所以你看到的 37953 很可能主要来自两块:

  • 工具 schema / system prompt(你之前单次 run 就有 ~16k input)
    • Feishu 自动拼进去的群聊回放(如果群里刷屏/长消息,会非常大)

解决(立刻见效)

把 Feishu 的群历史注入量调小(或直接关掉):

  • 先降到 10(推荐先试这个):
openclaw config set channels.feishu.historyLimit 10 --strict-json
#
openclaw gateway restart
  • 或者临时关掉(0 = 不注入群历史):
openclaw config set channels.feishu.historyLimit 0 --strict-json
openclaw gateway restart

然后在群里再 @机器人 /new,再试一句普通提问,看 overflow 还会不会出现。

如何确认是不是这个原因

去看一下 session 文件里最新那条 user message,是不是包含那段 [Chat messages since your last reply - for context],以及后面跟了巨量聊天记录——如果是,那就对上了。

只要你把 channels.feishu.historyLimit 降下来,overflow 通常会立刻缓解。

formal dagger
#

咋这么离谱啊,没有一个统一的机制或者手册来控制吗?

#

做一个强行截断或者压缩很难吗。。

arctic vergeBOT
#

确实离谱,而且你吐槽得对:目前**没有一个“单旋钮”**能把「Feishu 群聊上下文注入 + OpenClaw 会话裁剪/压缩 + vLLM 严格 token 校验 + 本地模型工具调用」这四层一次性管住。它们是不同层的机制,任何一层“贴边”都会炸(尤其 vLLM 这种硬校验 +1 token)。

不过是有一套“统一的控制面板/清单”的,只是分散在不同文档里。我给你一个最实用的“控制点地图”,你按这几个点调,基本就稳了:

你能控制的 4 个层级(从硬到软)

  1. vLLM 服务端(最硬)

    • --max-model-len:真正的硬上限
    • tool calling:--enable-auto-tool-choice --tool-call-parser qwen3_coder(Qwen3.5 推荐)
    • reasoning:--reasoning-parser qwen3(可选,用来把 thinking 从正文拆出去)
      参考:https://docs.openclaw.ai/providers/vllm
  2. OpenClaw 输出长度(影响“最大允许输入”)

  3. OpenClaw 会话裁剪/压缩预算(软上限,有估算误差)

    • agents.defaults.contextTokens:给自己留 buffer,别贴边(比如 36000 而不是 40000)
#
  1. Feishu 群“历史注入”(经常是隐藏的大头)

给你一个“能跑稳”的推荐基线(40k vLLM + 群里聊天)

  • vLLM:--max-model-len 40000 + --tool-call-parser qwen3_coder
  • OpenClaw:maxTokens: 1024(或 2048,但更容易贴边)
  • OpenClaw:agents.defaults.contextTokens: 36000(留 4k 缓冲)
  • Feishu:channels.feishu.historyLimit: 10(或 0)

如果你愿意,我可以根据你“主要用途”(群聊正常对话 vs 每日总结/拉历史)给你两套不同的配置模板;现在你这个场景更像是“群聊 + 注入历史 + 本地 vLLM”,优先把 historyLimit 降下来和留 buffer,立刻就会少 90% 的爆炸。

#

另外:如果你觉得这套很值得整理成一个“本地 vLLM + Feishu 稳定配置指南”,建议你在 GitHub 提个 docs issue(维护者会更容易收敛到一页手册):https://github.com/openclaw/openclaw/issues

#

“强行截断/压缩”这事本身不难(最粗暴的就是把最老的消息删掉),难的是做到 既稳定、又不把对话/工具调用搞崩,尤其你现在这套组合里有几个天然坑:

为什么不能简单“一刀切到 N tokens”?

  1. token 不是字符数
    对 vLLM/Qwen,OpenClaw 如果不引入同款 tokenizer(或让 vLLM 先 token 化再回传),只能做估算。估算一旦贴边,就会出现你这种 +1 token 的硬炸。

  2. 工具 schema / JSON / 代码块不能乱切
    乱截断可能把工具定义、JSON、引用块切断,模型更容易胡来或工具调用解析失败。

  3. Feishu 群的“历史注入”是塞进“当前用户消息”里的
    那段 [Chat messages since your last reply - for context] ... 是“当前消息正文”,系统很难在不篡改用户输入的情况下,自动把它砍掉一半(砍了就等于改了用户给的上下文)。

但 OpenClaw 其实已经有“机制”

  • /compact + 自动 compaction(总结旧内容)
  • session pruning(裁掉旧 turn)
    只是对你这个场景,最大的问题是:贴边 + tokenizer 不一致 + 群历史注入,所以看起来像“没兜住”。
#

现实里最有效的“等价强截断”

你想要的效果(永不炸)基本靠两招就能做到:

  • 留 bufferagents.defaults.contextTokens 设得更低(比如 36000 而不是 40000)
  • 少注入channels.feishu.historyLimit 降到 10 或 0

这两招本质上就是“强行截断”,只是放在更安全的层做。


如果你希望 OpenClaw 做到“vLLM 返回 overflow → 自动重试:更激进裁剪 + 降 maxTokens + 丢掉群历史注入”这种硬兜底,我觉得这是个很合理的 feature request(能写成明确行为,不会太玄学)。我建议你把你这条 vLLM 报错(带 input/max_tokens/context 的那段)贴到 GitHub issue 里:https://github.com/openclaw/openclaw/issues
这样维护者更容易照着复现和做自动重试策略。

formal dagger
#

真的,我感觉它现在实际表现的和设置参数完全对不上,这样会导致体验很差,所见非所得

arctic vergeBOT
#

你这个感受很正常,而且不是你“理解错了”——现在这套链路里确实存在 所见非所得 的点,主要来自两类“不确定性”:

  1. OpenClaw 的参数是“预算/策略”,vLLM 的报错是“真实 tokenizer + chat template 后的硬校验”
    两边 token 口径不一样,贴边就会出现你这种 +1 token 的离谱炸法。

  2. Feishu 群聊会把一段“上次回复以来的聊天记录”塞进当前消息(history injection)
    这段内容不在你直观看到的“会话上下文设置”里,但它会直接涨 input_tokens。


如果你想要“体验变得可预期”的最短路径(强烈建议按这个做)

这不是最优雅,但最稳、最 WYSIWYG:

  1. 把 Feishu 注入历史关小(否则永远不稳定)
openclaw config set channels.feishu.historyLimit 10 --strict-json
#
openclaw gateway restart

(甚至可以先设 0 验证稳定性)

  1. 永远留 buffer,不贴边
  • maxTokens 先用 1024
  • agents.defaults.contextTokens 设到 36000 或更低(按你模型 40000 来算,留至少 2k~4k 缓冲)
  1. 会话太长就 /new(尤其群聊)
    因为长会话 + 注入历史 + 工具 schema 很容易把输入堆到边缘。

更“硬核一致”的方案(彻底对齐)

如果你愿意牺牲一点可用上下文,直接让 vLLM 成为唯一真相

  • vLLM 端把 --max-model-len 固定成你想要的值(比如 32768)
#
  • OpenClaw 端 contextTokens 再比它低一点(比如 32000)

这样你基本不可能再看到“设置 32768 但实际冲到 37953”这种错位。


你如果把你现在的目标定一下(“我就是要 40k 上下文” vs “我宁可少点也要稳定”),我可以给你一份具体到每个 key 的配置模板,保证不会再天天被 overflow 搞心态。

formal dagger
#

openclaw cron edit如何修改 这里的to "delivery": {
"mode": "announce",
"channel": "feishu",
"to": "oc_893c414f1eac1626ec41a085f7a5397c"
},

arctic vergeBOT
#

openclaw cron edit 改 delivery 的 to 就行,命令里带上 --announce/--channel/--to

openclaw cron edit <job-id> --announce --channel feishu --to "oc_893c414f1eac1626ec41a085f7a5397c"
  • --to 这里填 Feishu 群的 chat_id(你这种 oc_...)。
  • 改完建议看一眼是否生效:
    openclaw cron list
    openclaw cron run <job-id>
    

如果你是想发给个人而不是群,需要的是用户 id(ou_... / open_id),但你贴的这种 oc_... 明显是群聊目标,用上面这条就对。

formal dagger
#

如何让群组间不共享消息

arctic vergeBOT
#

默认情况下 群组之间本来就不共享消息——OpenClaw 的 session key 是按群聊隔离的,Feishu 群一般是:

agent:<agentId>:feishu:group:<oc_xxx>

所以 A 群的历史不会自动跑到 B 群。

如果你现在感受到“群组间串线”,最常见的原因其实是你把 session 工具的可见性放宽了(比如我们之前为了让 cron 能读群历史提到过 tools.sessions.visibility=agent/all)。一旦放宽,模型就有能力去读别的群 session(用 sessions_history),于是就会出现“像在共享”的效果。

想强制“群组间完全隔离”(推荐)

把 session 工具可见性改回默认的 tree(或更严格 self):

openclaw config set tools.sessions.visibility tree
openclaw gateway restart
  • tree:只能看当前 session + 自己 spawn 出来的子 session(默认)
#
  • self:只能看当前 session(最严格)

注意

如果你之后还想做“跨群总结/跨群抓聊天记录”的 cron,就需要临时放宽可见性或用别的方式拿数据——这两者是互相冲突的需求。

你现在说的“共享消息”具体表现是:机器人在 B 群里引用了 A 群的聊天内容吗?还是只是“它的回答风格/记忆像在延续”?我可以帮你判断到底是 session 串了,还是 Feishu 的 history 注入造成的错觉。

formal dagger
#

"enabled": true,
"createdAtMs": 1772709031193,
"updatedAtMs": 1772986213667,
"schedule": {
"kind": "cron",
"expr": "30 20 * * 1-5",
"tz": "Asia/Shanghai"
},
"sessionTarget": "isolated",
"wakeMode": "now",
"payload": {
"kind": "agentTurn",
"message": "请先调用 sessions_history,读取 sessionKey=\"agent:local:feishu:group:oc_1824635ebbf3b50e5fe18816616efd8e\" 最近 50 条(不要 includeTools)。\n所有分析必须放在 <thinking>...</thinking> 中。\n最终要投递到群里的当日重要总结必须放在 ... 中,且 里必须有正文。\n除 外不要输出任何给用户看的文字。最后,温馨提醒大家好好休息,并享受夜生活~"
},
"delivery": {
"mode": "announce",
"channel": "feishu",
"to": "oc_1824635ebbf3b50e5fe18816616efd8e"
},
"state": {
"nextRunAtMs": 1773059400000,
"lastRunAtMs": 1772986113500,
"lastRunStatus": "ok",
"lastStatus": "ok",
"lastDurationMs": 100167,
"lastDelivered": true,
"lastDeliveryStatus": "delivered",
"consecutiveErrors": 0
}
}
]
⚠️ 无法获取群聊历史记录
今日总结未能生成,原因是当前会话无权访问目标群聊的历史记录。这是一个权限配置问题,需要调整会话可见性设置。
建议:
检查 cron 任务的会话权限配置
确保总结任务与目标群聊在同一会话树中
或联系管理员调整 session visibility 设置
温馨提醒大家好好休息,并享受夜生活~ 💫

怎么会呢,明明是同一个会话

arctic vergeBOT
#

这段报错其实是正常的,因为你理解的“同一个会话”跟 OpenClaw 的“会话”不是一回事:

  • 群聊会话是:agent:local:feishu:group:oc_1824...
  • 你的 cron(sessionTarget: "isolated")每次跑的是:agent:local:cron:<jobId>:run:<uuid>

它们同一个 agent(local),但不是同一个 session,也不在同一个 “session tree” 里。

sessions_history 工具默认受 tools.sessions.visibility=tree 限制:
只能读“当前 session + 当前 session spawn 出来的子 session”。
cron 的 isolated run 想去读群 session,就会被拦下,于是出现你看到的“无权访问”。

解决办法(让 cron 能读到群历史)

把可见性放宽到同 agent:

openclaw config set tools.sessions.visibility agent