#Title: OpenClaw + LM Studio (OpenAI server) context window mismatch → n_keep >= n_ctx (4096)

1 messages · Page 1 of 1 (latest)

sand wedgeBOT
#

That error is coming from the backend, not from OpenClaw’s token counter:

  • Cannot truncate prompt with n_keep (16886) >= n_ctx (4096) = your server is actually running with n_ctx = 4096, while the prompt OpenClaw sends is ~16.9k tokens.

Two key points:

  1. OpenClaw has a hard minimum context window of 16,000 tokens.
    So any backend that’s really at 4k (or even 8k) is going to fail sooner or later.

  2. The “?/16k” you see in OpenClaw is based on your configured contextWindow, not what LM Studio truly runs.
    If you “tell” OpenClaw 16k but LM Studio still serves 4k, you’ll get exactly what you’re seeing.

Fix path A (recommended): make LM Studio actually run ≥16k (ideally 32k+)

In LM Studio, you need to increase the model/server Context Length (LM Studio’s llama.cpp backend commonly defaults to 4096). After changing it, restart the LM Studio server (and ideally reload the model).

Then make sure your OpenClaw provider model entry matches reality:

#
{
  models: {
    providers: {
      lmstudio: {
        baseUrl: "http://127.0.0.1:1234/v1",
        apiKey: "lmstudio",
        api: "openai-responses", // if you can; otherwise your current OpenAI-chat compat
        models: [
          {
            id: "deepseek-r1-distill-llama-8b",
            contextWindow: 32768, // set this to what LM Studio is truly using
            maxTokens: 4096
          }
        ]
      }
#
    }
  }
}

Docs: https://docs.openclaw.ai/gateway/local-models

If you keep contextWindow at 16k, you’ll probably still be tight, because your current prompt is already ~16.9k. In practice: set 32k+ or reduce prompt overhead (below).

Fix path B: reduce OpenClaw prompt overhead (won’t help if backend stays at 4k)

Run in chat:

  • /context detail

This will show what’s eating tokens (tool schemas + injected workspace files are usually the big ones).

#

The levers that actually reduce the initial prompt size:

  • Reduce bootstrap injection: agents.defaults.bootstrapTotalMaxChars
  • Or disable bootstrap entirely: agents.defaults.skipBootstrap: true
  • Remove heavy tool schemas by denying tools you don’t need (browser/canvas/nodes are chunky):
{
  agents: {
    list: [
      {
        id: "main",
        tools: { deny: ["browser", "canvas", "nodes", "cron"] }
      }
    ]
  }
#
}

Even with aggressive trimming, OpenClaw still hard-requires ≥16k ctx, so this is mainly to make 16k/20k workable—not to make 4k workable.

If you want “deterministic big context” via other servers

llama.cpp server

Use llama-server with -c/--ctx-size (and keep parallelism at 1 while debugging):

llama-server -m /path/to/model.gguf --port 1234 --ctx-size 32768 --parallel 1

Ollama

Ollama’s OpenAI-compat API can’t set context per request; you must bake it into the model:

#

Modelfile:

FROM <base-model>
PARAMETER num_ctx 32768

Then ollama create ... and point OpenClaw at that OpenAI endpoint (often via a proxy/compat layer).


If you paste:

  • /context detail output, and
  • your models.providers.<yourProvider> block (redact keys),
    I can tell you exactly whether you should (1) bump ctx to 32k, (2) trim tool schemas, or (3) swap to a high-context local model (MiniMax M2.1 is the “known-good” path per the docs).
grizzled marlin
#

Quick question before I dive into these fixes you have suggested. What is the best way to run a local LLM for OpenClaw? I'm using LM Studio, but is Ollama or another option better/easier to work with? Thank you for your great help

sand wedgeBOT
#

If your goal is “least pain, most compatible” for OpenClaw right now: LM Studio is the best default.

Why:

  • It’s explicitly the recommended local stack in the OpenClaw docs (and what most configs/examples target): https://docs.openclaw.ai/gateway/local-models
  • It exposes a normal OpenAI-style /v1 endpoint and (crucially) can work well with Responses API mode (api: "openai-responses"), which tends to behave better for agent-style runs than pure Chat Completions.
  • It’s the easiest place to visually verify and change things that matter for OpenClaw, like context length (and avoid the “actually 4096” trap).

Ollama: great UX, but a bit more “gotchas” for OpenClaw

Ollama is super convenient, but for OpenClaw specifically:

  • The OpenAI-compatible API can’t set context per request, so you must bake it into a Modelfile (PARAMETER num_ctx ...). Easy once you know it, but it’s one more moving part.
  • The OpenAI-compat layer is mostly “chat completions” style; you can run it, it’s just more likely to hit mismatches vs what OpenClaw expects.

Docs (Ollama’s own note about context): https://docs.ollama.com/api/openai-compatibility

“Most deterministic / you control everything”: llama.cpp server

#

If you want guaranteed control over context (--ctx-size), parallelism, etc., llama.cpp’s llama-server is the most deterministic. It’s just more DIY than LM Studio.

“Best for a real GPU box / throughput”: vLLM (+ optionally LiteLLM in front)

If you’re on Linux + CUDA and want higher throughput / more “server-y” behavior, vLLM is strong, but it’s the most setup-heavy.


Rule of thumb: stay with LM Studio unless you have a specific reason not to (headless deployment, standardized server stack, want pure CLI reproducibility, etc.).

If you tell me:

  1. macOS vs Linux host, and
  2. what context window you’re targeting (16k? 32k? 128k?),
    I can point you at the best backend choice + the least annoying OpenClaw provider config for it.
grizzled marlin
#

Interesting. Thanks for that tidbit. Ok, I'm plugging along with LM Studio then! (and I'm on a Mac Mini m4 Pro 64 GB RAM). more info: Here’s a clean update you can paste back to Krill:

Update: we got it working, but LM Studio still clamps context.

Evidence from /tmp/openclaw/openclaw-2026-02-17.log:
• For Qwen 8B:
low context window: custom-127-0-0-1-1234/deepseek/deepseek-r1-0528-qwen3-8b ctx=16384 (warn<32000) source=model
• OpenClaw then successfully completes runs:
embedded run agent end ... isError=false

So despite LM Studio UI being set to 32768, OpenClaw is being told (and/or LM Studio is enforcing) 16384 via the OpenAI server model metadata (source=model). Restarting LM Studio didn’t change what OpenClaw sees.

Also note: earlier in the log the gateway sometimes booted with:
agent model: custom-127-0-0-1-1234/deepseek-r1-distill-llama-8b
but after config update + gateway restart it correctly booted with:
agent model: custom-127-0-0-1-1234/deepseek/deepseek-r1-0528-qwen3-8b

Net: system is functional now at 16k, but LM Studio seems unable/unwilling to expose 32k to OpenClaw through OpenAI server. Any known way to force LM Studio OpenAI server to advertise/use the full context, or recommended backend instead?

sand wedgeBOT
#

Yep, that log line is really informative:

ctx=16384 (warn<32000) source=model means OpenClaw is taking the context window from the model metadata it sees (usually context_length from GET /v1/models on your LM Studio server), not from your OpenClaw config.

Two separate problems can be happening:

1) “Advertised ctx” vs “actual enforced ctx”

LM Studio might:

  • actually be running the model at 32k, but still reporting 16k via /v1/models, or
  • truly clamping the backend to 16k (common if the model itself is 16k, or if the runtime can’t/doesn’t extend it).

Fast sanity checks:

curl -s http://127.0.0.1:1234/v1/models | jq '.data[] | {id, context_length}'
#

If that model shows 16384 there, OpenClaw will (correctly) treat it as 16k unless you override.

And to see what’s actually enforced, try a single huge request directly to LM Studio (bigger than 16k) — if it errors, it’s truly clamped.

2) You can override what OpenClaw believes (but it won’t force LM Studio)

OpenClaw’s context window resolution order is:

  1. models.providers.<provider>.models[].contextWindow (your config override)
  2. model metadata (“source=model”)
  3. defaults

So if you want to stop the warning / make OpenClaw plan for 32k, add an explicit model entry under your provider:

{
  models: {
#
    providers: {
      "custom-127-0-0-1-1234": {
        baseUrl: "http://127.0.0.1:1234/v1",
        apiKey: "lmstudio",
        api: "openai-responses",
        models: [
          {
            id: "deepseek/deepseek-r1-0528-qwen3-8b",
            name: "DeepSeek R1 Qwen3 8B (LM Studio)",
            contextWindow: 32768,
            maxTokens: 4096
          }
        ]
      }
    }
#
  }
}

After that, the log should say source=modelsConfig and ctx=32768.

But: if LM Studio is truly enforcing 16k, this will just bring back the original “n_keep >= n_ctx” failure once your prompts exceed 16k.

So what’s the “real fix” for getting 32k?

If /v1/models keeps saying 16k after you unload/reload the model and restart the server, in practice it usually means one of these:

  • The model itself is 16k (many “distill” variants are), so LM Studio won’t extend beyond it.
  • LM Studio’s OpenAI server is not reflecting the UI context slider for that particular runtime/model format (possible limitation/bug).

Recommended next step

Try a model that’s known to be >32k natively, or use the OpenClaw-docs “known-good” local path: LM Studio + MiniMax M2.1 (huge context) if you can run it on that Mac mini.

#

Docs: https://docs.openclaw.ai/gateway/local-models

If you want deterministic control no matter what LM Studio does

Run llama-server directly with --ctx-size 32768 (and --parallel 1 while debugging). That’s the most “what I set is what I get” option.


If you paste the output of the /v1/models jq snippet for that model (just the {id, context_length} lines), I can tell you whether this is “LM Studio is only advertising 16k” vs “the backend is really 16k.”

grizzled marlin
#

Report to Krill (what we observed + why it matters)

We restarted LM Studio server and the whole situation improved, but OpenClaw is still seeing the model as 16,384 context, even when LM Studio’s UI is set higher.

Key evidence (OpenClaw logs):
• OpenClaw repeatedly warns:

low context window ... ctx=16384 (warn<32000) source=model

So OpenClaw is taking the context window from model metadata (“source=model”), not from OpenClaw’s provider config.

Also observed:
• After some config edits earlier, we saw a warning with:

source=modelsConfig

…but in the most recent runs with the Qwen3 8B R1 model it’s still:

source=model

Meaning: even if we can override OpenClaw’s belief, the “authoritative” value it’s reading from the server remains 16384.

Practical outcome:
• 16,384 works great now (model replies reliably; no more immediate failure loop).
• Restarting LM Studio likely cleared something (stale model metadata, stale server state, or model didn’t fully reload with new ctx settings until restart).
Think “turn it off and on again,” but with more dignity and fewer British accents.

What we should verify next (to separate “advertised” vs “enforced”)
1. What LM Studio is advertising via the OpenAI-compatible endpoint:

curl -s http://127.0.0.1:1234/v1/models | jq '.data[] | {id, context_length}'

If this prints 16384, then OpenClaw’s behavior is expected.
2. Whether LM Studio is actually enforcing 16k or just reporting it:

•    Send a request whose prompt size clearly exceeds 16k tokens directly to LM Studio.
•    If it errors, it’s truly clamped. If it works, the server is likely enforcing more but misreporting context_length.

sand wedgeBOT
#

Your write-up is spot on, and the “source=model vs source=modelsConfig” detail is the giveaway.

What “source=model” means (in OpenClaw terms)

OpenClaw resolves context window like this:

  1. Your config override: models.providers.<provider>.models[].contextWindowsource=modelsConfig
  2. Otherwise, model metadata (what the backend/model registry reports) → source=model
  3. Otherwise, defaults

So if you’re seeing source=model, OpenClaw did not find a matching models.providers.<provider>.models[].id === <modelId> entry to override.

The most common reason overrides “randomly stop working”

Model ID mismatch. With LM Studio, the ID can be surprisingly different than what you expect (and can change when you switch models), e.g.:

  • deepseek-r1-distill-llama-8b
  • vs deepseek/deepseek-r1-0528-qwen3-8b
  • vs whatever /v1/models returns
#

If the id in your config doesn’t match exactly, OpenClaw falls back to metadata and you get source=model.

Action: grab the exact model id OpenClaw is using (from logs or /v1/models) and make the config match it.

Next verification steps (what I’d do)

  1. Confirm what LM Studio is advertising:
curl -s http://127.0.0.1:1234/v1/models | jq '.data[] | {id, context_length}'
  1. If it reports context_length: 16384 but you want OpenClaw to plan for more, add an explicit override with the exact id:
models: {
  providers: {
    "custom-127-0-0-1-1234": {
      baseUrl: "http://127.0.0.1:1234/v1",
#
      apiKey: "lmstudio",
      api: "openai-responses",
      models: [
        { id: "<EXACT_ID_FROM_/v1/models>", contextWindow: 32768, maxTokens: 4096 }
      ]
    }
  }
}
  1. Separate “advertised” vs “enforced”:
  • Send a request that’s clearly >16k tokens directly to LM Studio.
  • If it errors, LM Studio is truly clamping and you’ll need either:
    • a model that natively supports 32k+, or
    • a different backend (llama.cpp server with --ctx-size, etc.)
#

Practical note

If 16,384 is stable now: that’s already a win. The warn<32000 line is only a warning threshold; it’s not blocking you (hard block is <16k). But for tool-heavy OpenClaw usage, 32k+ is noticeably nicer.

If you paste the /v1/models {id, context_length} lines for the model you’re actually using, I can tell you immediately whether this is (a) pure ID mismatch / override issue or (b) LM Studio truly reporting (and likely enforcing) 16k for that model.

grizzled marlin
#

This is the smoking gun.

The important bits to send:
• GET /v1/models from LM Studio returns context_length: null for all models, so OpenClaw can’t infer context from model metadata.
• That means OpenClaw will only respect context sizing when it comes from modelsConfig overrides.
• Your logs already prove the override mechanism works (source=modelsConfig on the 8B llama), but fails on the Qwen model because the ID doesn’t match exactly, so it falls back to source=model and lands on ctx=16384.
• Fix is: add an explicit provider override for the exact model ID deepseek/deepseek-r1-0528-qwen3-8b, then restart gateway, confirm log switches to source=modelsConfig.

Movie quote for Krill: “It’s not a bug. It’s a feature.” (Translation: LM Studio is silently not giving context metadata, and everyone else has to cope.)

sand wedgeBOT
#

Yep — that’s a great “send this to Krill” summary. I’d phrase it like this (and one small nuance):

Key finding: LM Studio’s OpenAI-compatible GET /v1/models is not providing a usable context length (context_length: null / missing), so OpenClaw can’t auto-detect context from the server. In that situation OpenClaw falls back to whatever it has for that model in its model registry (often 16,384), which shows up as source=model.

Implication: The only reliable way to make OpenClaw use the right context with LM Studio is to set it explicitly via config overrides, i.e. models.providers.<provider>.models[] → then logs show source=modelsConfig.

Why it “worked for one model but not the other”: the override mechanism works (you saw source=modelsConfig), but the Qwen model wasn’t overridden because the id didn’t match exactly. When the ID doesn’t match, OpenClaw falls back to source=modelctx=16384.

Fix:

  1. Copy the exact model id from:
    curl -s http://127.0.0.1:1234/v1/models | jq -r '.data[].id'
    
  2. Add an explicit override for that exact id (e.g. deepseek/deepseek-r1-0528-qwen3-8b) with contextWindow: 32768
  3. Restart the gateway
  4. Confirm the log line flips to:
    ctx=32768 ... source=modelsConfig
#

If this fixes it, it’s worth noting: this is likely an LM Studio OpenAI-server metadata limitation/bug — OpenClaw is behaving reasonably given the server doesn’t advertise context.

Glad you got it stable at 16k already; that’s the hard part. If the override lands, you should be set.

grizzled marlin
#

Update / Evidence dump (LM Studio + OpenClaw context window mismatch):
• LM Studio GET /v1/models returns context_length: null for all models (see output below), so OpenClaw can’t auto-detect ctx from server and must rely on config overrides.
• OpenClaw does read modelsConfig overrides for some models, e.g. it logs:
low context window: custom-127-0-0-1-1234/deepseek-r1-distill-llama-8b ctx=16384 ... source=modelsConfig
• But for Qwen it ignores the override even though it’s present in ~/.openclaw/openclaw.json:
• Config contains:
id: "deepseek/deepseek-r1-0528-qwen3-8b", contextWindow: 32768
(verified via jq)
• Yet runtime log shows:
low context window: custom-127-0-0-1-1234/deepseek/deepseek-r1-0528-qwen3-8b ctx=16384 ... source=model
(so OpenClaw falls back to registry/model metadata, not config)
• Gateway is definitely using the Qwen model as agent primary:
agent model: custom-127-0-0-1-1234/deepseek/deepseek-r1-0528-qwen3-8b

Commands + outputs (short):
• curl -s http://127.0.0.1:1234/v1/models | jq '.data[] | {id, context_length}'
shows context_length: null for deepseek/deepseek-r1-0528-qwen3-8b (and all others)
• cat ~/.openclaw/openclaw.json | jq '.models.providers["custom-127-0-0-1-1234"].models'
shows Qwen entry with contextWindow: 32768

Hypothesis: OpenClaw’s config override matching is failing for model ids that include a slash namespace (e.g. deepseek/...) or it normalizes the key differently than the id we provide, so it never matches and stays source=model.

Also: movie quote for the vibe: “I have proof.” — Ron Burgundy.

sand wedgeBOT
#

Yeah, that is the smoking gun — and it also explains the confusing source=model line:

  • If LM Studio returns context_length: null, OpenClaw can’t learn ctx from the server.
  • In that case, source=model is basically “whatever the built-in model definition says” (often 16,384 for generic OpenAI-ish models), not “LM Studio told us 16k”.

So the only way to make ctx deterministic with LM Studio today is: models.providers.<provider>.models[].contextWindow override.

Why your override still isn’t being used for the Qwen ID

Given the OpenClaw codepath, the override match is a strict equality check:

  • provider key must match exactly: cfg.models.providers[provider]
  • model id must match exactly: models[].id === modelId (case + whitespace sensitive)

Slashes in the model id are totally fine — there’s no special normalization. So if you’re seeing:

  • config has: id: "deepseek/deepseek-r1-0528-qwen3-8b"
  • runtime uses: .../deepseek/deepseek-r1-0528-qwen3-8b
#
  • but log still says source=model

…then one of these is almost always true:

  1. Gateway service is not using the config file you inspected (classic macOS daemon gotcha).
    Run:

    openclaw gateway status
    

    and compare Config (cli) vs Config (service) paths. If they differ, you edited the wrong one for the daemon.

  2. The override is under a slightly different provider key than the runtime provider string (even a tiny mismatch).
    Confirm what the gateway actually loaded with:

    openclaw config get --json models.providers
    

    (This is better than cat ~/.openclaw/openclaw.json, because it reflects what OpenClaw thinks is configured.)

#
  1. There’s hidden whitespace in the id field (rare, but it happens).
    Quick check: retype the id by copy/paste from curl /v1/models | jq -r '.data[].id'.

What I’d do next (tight loop)

  1. openclaw gateway status → confirm config path
  2. openclaw config get --json models.providers["custom-127-0-0-1-1234"].models → confirm the daemon sees the Qwen override
  3. openclaw gateway restart
  4. tail logs → confirm it flips to source=modelsConfig

If after that it still doesn’t match, that’s the point where it’s worth filing a GitHub issue with:

  • the exact provider key string
  • the exact model ref string you’re using (custom-.../<modelId>)
  • the models.providers...models[] entry
  • and the log line showing source=model

Because, as written, the matching should work fine with deepseek/... ids.

grizzled marlin
#

Update: LM Studio context detection + OpenClaw overrides

Goal: Force OpenClaw to use manual contextWindow overrides for LM Studio models because LM Studio /v1/models reports context_length: null.

Environment:
• LM Studio OpenAI-compatible server: http://127.0.0.1:1234/v1
• OpenClaw gateway: ws://127.0.0.1:18789
• Config file: ~/.openclaw/openclaw.json (confirmed CLI + service both reading this path)

What we changed:
Added model entries under:
models.providers["custom-127-0-0-1-1234"].models
• deepseek-r1-distill-llama-8b
• contextWindow: 16384
• maxTokens: 4096
• deepseek/deepseek-r1-0528-qwen3-8b
• contextWindow: 32768
• maxTokens: 4096

Restarted gateway via:
openclaw gateway restart

Verification (catalog/config):
• openclaw config get --json 'models.providers' shows the provider key and both model overrides present.
• openclaw models list --provider custom-127-0-0-1-1234 --json shows the Qwen model registered with:
• key: custom-127-0-0-1-1234/deepseek/deepseek-r1-0528-qwen3-8b
• contextWindow: 32768
• Confirmed LM Studio model id matches exactly:
• curl -s http://127.0.0.1:1234/v1/models | jq -r '.data[].id'
• includes deepseek/deepseek-r1-0528-qwen3-8b

Runtime behavior (problem):
Despite catalog showing contextWindow: 32768, runtime logs consistently warn and behave as if context is 16384:

low context window: custom-127-0-0-1-1234/deepseek/deepseek-r1-0528-qwen3-8b ctx=16384 (warn<32000) source=model

Also we observed repeated compaction triggers (and even compaction timeouts), consistent with the smaller ctx limit.

Key clue: session pinning / caching
The embedded agent runs keep using the same sessionId even after gateway restarts:
• sessionId observed repeatedly: 4b6bd4ef-cbbe-4233-bf0e-99ee986ed228

We attempted to “quarantine” sessions by moving:
~/.openclaw/agents/main/sessions aside and recreating it empty, then restarting gateway.

#

However, runtime still resumes the same sessionId and still reports ctx=16384 source=model, suggesting:
• session state is being stored somewhere else in ~/.openclaw (or client-side), OR
• there is another cache/registry that preserves sessionId beyond agents/main/sessions, OR
• embedded agent resolves context window from provider metadata at runtime even when catalog shows config override (and the source=model label indicates it’s choosing provider-derived ctx anyway).

Next debug step (planned):
Search for where that sessionId is still referenced:
grep -R "4b6bd4ef-cbbe-4233-bf0e-99ee986ed228" -n ~/.openclaw | head

We’re trying to identify the actual persistent store that is driving the embedded runtime’s session reuse and/or the context resolution path that forces source=model.

Question: What is the authoritative cache/store for embedded/webchat sessions (the thing that keeps resurrecting sessionId=4b6bd4ef... even after moving ~/.openclaw/agents/main/sessions), and what is the intended way to force a new session / clear embedded session state? Also, what codepath controls the source=model vs source=modelsConfig resolution for context window at runtime?

sand wedgeBOT
#

That’s a really good dump — and the “sessionId sticks across restarts” part is a red herring (expected). Context window is recomputed per run in the embedded runner, it’s not cached in the session.

Given what you’ve proven:

  • openclaw models list … shows contextWindow: 32768 for the Qwen model
  • but the embedded runner still logs ctx=16384 … source=model

…then resolveContextWindowInfo() is not seeing a numeric contextWindow override for that exact (provider, modelId) at runtime. There are only a few realistic reasons:

1) Duplicate model entries with the same id (first one “wins”)

The code does a .find() on the provider’s models[] and takes the first matching id. If you have two entries for the same id and the first one has no contextWindow, you’ll fall back to source=model even though a later entry looks correct.

Run this to verify duplicates + types:

jq -c '
#
  .models.providers["custom-127-0-0-1-1234"].models
  | map(select(.id=="deepseek/deepseek-r1-0528-qwen3-8b"))
  | map({id, contextWindow, ctxType:(.contextWindow|type)})
' ~/.openclaw/openclaw.json

You want exactly one object back, and ctxType must be "number".

If you get >1 object back → delete the earlier duplicate (or reorder so the one with contextWindow is first).

2) contextWindow is a string (looks right in JSON, but won’t be used)

contextWindow: "32768" will not be recognized (OpenClaw only accepts a finite number). The jq snippet above will catch this (ctxType: "string").

3) The runtime config object differs from what you think (rare if you verified CLI+service path)

You already did the right check (Config cli/service both reading ~/.openclaw/openclaw.json). If (1) and (2) are clean, this starts to smell like an actual OpenClaw bug.

#

About the persistent sessionId

Totally normal. Restarting the gateway doesn’t create a new session ID; it resumes the same session store entry. If you want a hard reset for testing, use /new in the chat (instead of moving session folders around).

If duplicates/types are clean and it still logs source=model

At that point, I’d file a GitHub issue with just these minimal artifacts:

  • the exact agents.defaults.model.primary
  • the exact provider block for custom-127-0-0-1-1234 (just the models[] entries for that ID)
  • the warning line showing ctx=16384 … source=model
  • and the curl /v1/models showing context_length: null

Because slashes in model IDs should be fine — the match is strict string equality.

If you paste the output of that jq command (for the Qwen ID only), I can tell you immediately whether it’s duplicate-entry / wrong-type, or whether you’ve hit a real mismatch bug.

#

SessionId persistence is 100% server-side and by design. It’s not coming from LM Studio, and it’s not “cached client-side”.

1) Where embedded/Discord/WebChat sessions actually live (authoritative store)

OpenClaw stores session continuity in a per-agent session store file:

  • Session store (maps sessionKey → sessionId + metadata):
    ~/.openclaw/agents/<agentId>/sessions/sessions.json
    (state dir can be different if OPENCLAW_STATE_DIR is set)

  • Transcripts (the turns):
    ~/.openclaw/agents/<agentId>/sessions/<sessionId>.jsonl
    (threads/topics can add suffixes)

So if sessionId=4b6bd4ef-... keeps “resurrecting”, it’s because the sessionKey for that chat keeps resolving to an existing entry in sessions.json, and OpenClaw reuses its sessionId until it’s reset/stale.

Intended way to force a new session

Use chat command (recommended):

#
  • Send /new (or /reset) as a standalone message in that same thread/chat.
    That mints a fresh sessionId for the same sessionKey.

Nuclear option (all sessions):

  • openclaw reset --scope sessions --yes (wipes session store + transcripts)

Manual surgical option:

  • Delete just that key from sessions.json (then the next inbound message recreates it with a new sessionId).

To inspect what’s in the store:

  • openclaw sessions --json
  • or openclaw status (shows store path + recent sessions)

2) “source=model” vs “source=modelsConfig” for context window (runtime codepath)

This is controlled in the embedded runner via:

#
  • src/agents/context-window-guard.tsresolveContextWindowInfo()
  • called from src/agents/pi-embedded-runner/run.ts

Resolution order is:

  1. Config override: cfg.models.providers[provider].models[].contextWindow
    → log shows source=modelsConfig
  2. Else model definition (model.contextWindow from the model registry)
    → log shows source=model
  3. Else default

Important: this decision is per-run and independent of the sessionId. Reusing the same sessionId cannot “pin” the context window source.

So if you see openclaw models list reporting 32768 but the embedded runner still logs ctx=16384 source=model, it means: at runtime, resolveContextWindowInfo() did not find a numeric contextWindow override for that exact (provider, modelId) in the config object being passed into the run.

The usual real causes are:

  • duplicate models[] entries for the same id (first match wins, and might be missing contextWindow)
#
  • contextWindow accidentally being a string ("32768") not a number
  • provider key mismatch (different provider id than you think)

If you want, paste the output of this (it catches dupes + wrong types fast):

jq -c '
  .models.providers["custom-127-0-0-1-1234"].models
  | map(select(.id=="deepseek/deepseek-r1-0528-qwen3-8b"))
  | map({id, contextWindow, ctxType:(.contextWindow|type)})
' ~/.openclaw/openclaw.json

If that comes back clean (single entry, ctxType: "number"), then you’ve got a legit bug worth filing.

grizzled marlin
#

Update for Krill: context window mismatch investigation + Control UI token roadblock

Context window issue (what we proved)
• Setup: OpenClaw uses LM Studio OpenAI-compatible server at http://127.0.0.1:1234/v1. LM Studio /v1/models returns context_length: null, so OpenClaw can’t auto-detect context length from server metadata.
• Config edits: We added explicit model overrides in ~/.openclaw/openclaw.json under models.providers[“custom-127-0-0-1-1234”].models:
• deepseek-r1-distill-llama-8b: contextWindow 16384, maxTokens 4096
• deepseek/deepseek-r1-0528-qwen3-8b: contextWindow 32768, maxTokens 4096
We verified the Qwen entry is unique and contextWindow is a number (not a string) with jq.
• Service config is correct at runtime:
• openclaw config get –json ‘models.providers.custom-127-0-0-1-1234.models’ confirms both entries with numeric contextWindow values (16384 and 32768).
• Agent-local registry also contains Qwen with 32768:
~/.openclaw/agents/main/agent/models.json has exactly one Qwen entry with numeric contextWindow 32768.
• Yet embedded runner warnings persist for Qwen in logs:
• /tmp/openclaw/openclaw-2026-02-17.log shows:
“low context window: custom-127-0-0-1-1234/deepseek/deepseek-r1-0528-qwen3-8b ctx=16384 … source=model”
• Notably, Llama runs report source=modelsConfig, but Qwen runs report source=model with ctx=16384, even though both overrides exist and are numeric.
• Code inspection we did (bundled embedded runner):
• We inspected resolveModel() in both embedded bundles under ~/.local/lib/node_modules/openclaw/dist/ and confirmed the precedence:

#

registry find(provider, modelId) returns first; config overrides only used if model not found in registry.
• We confirmed the warn line is using model.contextWindow returned from resolveModel via resolveContextWindowInfo({ cfg, provider, modelId, modelContextWindow: model.contextWindow, … }).
• Where we landed after your latest guidance:
• Your “duplicates or string types” checks are clean.
• SessionId persistence is expected and not causal; we stopped treating it as a root cause.
• Since file config, live service config, and agent-local models registry all show Qwen=32768, but the embedded runner still logs ctx=16384 source=model, we suspect a runtime lookup mismatch in the embedded runner path:
either provider/modelId normalization mismatch, or registry lookup failing and falling back (likely to providerCfg.models[0], which is the 16384 Llama model), despite the Qwen entry existing.

Control UI / token issue (new blocker)
• Gateway is running fine:
• openclaw status: ws://127.0.0.1:18789 reachable, RPC probe ok, dashboard http://127.0.0.1:18789/
• LaunchAgent: ~/Library/LaunchAgents/ai.openclaw.gateway.plist
• But the browser Control UI cannot connect:
• /tmp/openclaw/openclaw-2026-02-17.log shows repeated:
subsystem=gateway/ws unauthorized, authMode=token, authReason=token_missing, with message:
“gateway token missing (open the dashboard URL and paste the token in Control UI settings)”
• Token retrieval attempts:
• openclaw status prints dashboard URL without token

#

• No ~/.openclaw/state/gateway.json (path guess wrong)
• find ~/.openclaw for gateway.json is empty
• grep ~/.openclaw for “authMode”:“token is empty
• openclaw gateway token fails: “too many arguments for ‘gateway’. Expected 0 arguments but got 1.”
This suggests gateway token is exposed via flags or stored elsewhere (LaunchAgent env, keychain, etc.), but we haven’t found the correct retrieval method for 2026.2.15.

What we need from you
1. The correct way in 2026.2.15 to retrieve or set the gateway token (CLI flag, file path, or where it’s stored), or a safe local-only way to disable token auth temporarily so the Control UI can connect.
2. For the context window mismatch: any known normalization edge cases for provider/modelId matching in embedded runner registry lookups, or a recommended minimal diagnostic to print the actual provider + modelId string used at lookup time (and whether it’s falling back to models[0]).

sand wedgeBOT
#

1) Where that “resurrected” sessionId actually comes from (authoritative store)

OpenClaw session continuity is server-side and driven by the per‑agent session store:

  • Session store (authoritative mapping sessionKey → sessionId):
    ~/.openclaw/agents/<agentId>/sessions/sessions.json

  • Transcript files (the turns):
    ~/.openclaw/agents/<agentId>/sessions/<sessionId>.jsonl

So you can move/delete transcript .jsonl files all day and the same sessionId will keep coming back as long as the entry remains in sessions.json. That’s expected behavior, not client caching.

Intended way to force a new session: send /new (or /reset) as a standalone message in that same chat/thread. Docs: https://docs.openclaw.ai/concepts/session

CLI “wipe sessions” option: openclaw reset --scope sessions --yes (nukes all sessions)
Docs: https://docs.openclaw.ai/cli/reset

#

If you want to surgically delete just that one, find it via:

openclaw sessions --json | jq '.. | objects | select(.sessionId? == "4b6bd4ef-cbbe-4233-bf0e-99ee986ed228")'

(and then remove that key from the sessions.json it points to).


2) What controls source=model vs source=modelsConfig for ctx at runtime

That comes from the embedded runner context guard:

  • src/agents/context-window-guard.tsresolveContextWindowInfo()

Resolution order is:

  1. config override: models.providers.<provider>.models[].contextWindowsource=modelsConfig
#
  1. else model definition (model.contextWindow from the model registry) → source=model
  2. else default

This is computed per run; it is not cached by sessionId.

So if Qwen still logs ctx=16384 … source=model, the embedded runner did not see a numeric override match for that exact (provider, modelId) in the config object used for the run (even if other commands print what you expect). At that point, it’s either:

  • a runtime mismatch (different agentDir/stateDir than the file you inspected), or
  • a real bug worth a GitHub issue with the minimal repro (provider id, model id, override snippet, and the log line).

3) Quick Control UI token unblock (if you’re stuck there)

Get the token:

openclaw config get gateway.auth.token

Then in the Dashboard/Control UI go to Control → Overview → Gateway Access and paste it into the Gateway Token field (there’s also a Password field right next to it—easy to miss).

#

(That UI-location nuance bites people a lot.)

If you paste the openclaw status --all “paths” section (state dir + agent dir), I can sanity-check whether the gateway is actually reading the same models.json you’re inspecting on disk.

#

Two separate things here: (A) Control UI token, and (B) why Qwen still logs ctx=16384 source=model.

A) Gateway token (2026.2.15): how to get/set it

There is no openclaw gateway token subcommand — the token is just config (gateway.auth.token) or an env var.

Get it:

openclaw config get gateway.auth.token

Easiest “print the right URL” way:

openclaw dashboard --no-open

That should print a Control UI URL using your current auth (typically includes the token in the URL).

If gateway.auth.token is empty/missing, generate one:

#
openclaw doctor --generate-gateway-token
openclaw gateway restart

Or set your own token:

openclaw config set gateway.auth.token "$(openssl rand -hex 32)"
openclaw gateway restart

Then in the dashboard go to Control → Overview → Gateway Access and paste into Gateway Token.

Temporarily disabling auth: not really supported as a “turn it off” switch; auth mode is token|password|trusted-proxy. For local-only debugging you can switch to password mode, but you still need some auth.

B) source=model even though your catalog shows 32768

source=model means the embedded runner is using model.contextWindow from the ModelRegistry (the models.json it loaded), and it did not apply the models.providers.<provider>.models[].contextWindow override path for that run.

#

Given you’ve verified config + ~/.openclaw/agents/main/agent/models.json show Qwen=32768, the most likely cause is:

The gateway process is loading a different agentDir/models.json than the CLI you’re inspecting

The embedded runner’s registry path is driven by:

  • OPENCLAW_AGENT_DIR / PI_CODING_AGENT_DIR (highest priority), else
  • OPENCLAW_STATE_DIR / OPENCLAW_HOME (because agentDir defaults to <stateDir>/agents/main/agent)

So on macOS LaunchAgents, it’s common for the daemon to have different env than your interactive shell.

Minimal diagnostic:

  • Run openclaw status --all and look for any “state dir / agent dir” style path lines and paste them.
  • Also inspect the LaunchAgent plist for env vars that change state/agent dirs:
    ~/Library/LaunchAgents/ai.openclaw.gateway.plist

If the gateway’s effective agent dir differs, it would explain perfectly why:

  • openclaw models list (your shell) shows 32768
  • but the gateway (LaunchAgent) still logs 16384
#

Sessions: what “keeps resurrecting” sessionId

That’s expected: the authoritative session mapping is:
~/.openclaw/agents/<agentId>/sessions/sessions.json

To force a new sessionId for the same chat: send /new in that chat/thread.

grizzled marlin
#

Krill — quick status dump because we’re looping:
1. Context window: we validated the Qwen model is set to contextWindow=32768 (number) in every config we inspected:

•    ~/.openclaw/openclaw.json → deepseek/deepseek-r1-0528-qwen3-8b contextWindow 32768 (type number)
•    openclaw config get models.providers.custom-127-0-0-1-1234.models → shows two models: llama 16384 and qwen 32768 (both numbers)
•    ~/.openclaw/agents/main/agent/models.json → qwen contextWindow 32768 (type number)

2.    Dashboard/UI is blocked by auth token missing. UI shows “unauthorized: gateway token missing” and /tmp/openclaw/openclaw-2026-02-17.log repeats websocket handshake failures with authMode=token, authProvided=none, reason=token_missing. Gateway is running and listening on ws://127.0.0.1:18789.
3.    We tried to find a gateway token file (looked for ~/.openclaw/state/gateway.json, searched for gateway*.json) and couldn’t find one. Also confirmed openclaw gateway token is not a command (CLI says too many arguments for ‘gateway’).
4.    We did see an earlier log line “Model context window too small (4096). Minimum 16000” from earlier today, but current openclaw status reports default model deepseek/deepseek-r1-0528-qwen3-8b (33k ctx), so it seems like a past run or a different effective registry.

Next we plan to follow your guidance: read gateway.auth.token from config (or generate one), then confirm whether LaunchAgent env/stateDir differs from our shell (to explain any ctx source mismatch). If you want specific path output from openclaw status –all, tell us what lines you need.

sand wedgeBOT
#

Token + ctx mismatch are both explainable, but you’ll get unstuck fastest by fixing the token first (because it also helps confirm you’re looking at the same running gateway instance).

1) Gateway token (there is no token file)

There isn’t a ~/.openclaw/state/gateway.json, and openclaw gateway token is not a command. In 2026.2.15 the token is just:

  • config: gateway.auth.token, or
  • env: OPENCLAW_GATEWAY_TOKEN

Get it (don’t paste it in public):

openclaw config get gateway.auth.token

If it’s empty/missing, generate one (writes only if missing):

openclaw doctor --generate-gateway-token
#
openclaw gateway restart

Best UX (prints the correct URL w/ token):

openclaw dashboard --no-open

Open the URL it prints (it usually includes #token=... so Control UI can connect immediately).

If you want to do it manually: append the token as a URL fragment to the dashboard URL (again: don’t paste the token in the thread).

2) Context window: why source=model can still happen even when “catalog shows 32768”

Important detail from the code:

  • resolveModel() prefers the ModelRegistry (<agentDir>/models.json)
  • the ctx guard (resolveContextWindowInfo) only uses source=modelsConfig if it finds an exact match in cfg.models.providers[provider].models[] for that exact modelId.
#

So the fact you still see:
ctx=16384 … source=model
means: at runtime, the embedded runner did not find that override match in the cfg object it’s using, even if openclaw models list in your shell shows it.

The #1 real-world cause on macOS is: the LaunchAgent is running with a different agent/state dir env than your interactive shell (so you’re inspecting the “right” files, but not the ones the daemon is using).

Two quick checks that don’t require the UI:

(a) See if LaunchAgent env is overriding agent/state dirs

plutil -p ~/Library/LaunchAgents/ai.openclaw.gateway.plist | rg -n "OPENCLAW|PI_CODING|HOME"
# and/or
launchctl print gui/$(id -u)/ai.openclaw.gateway | rg -n "OPENCLAW|PI_CODING|HOME"

You’re looking for OPENCLAW_AGENT_DIR, PI_CODING_AGENT_DIR, OPENCLAW_STATE_DIR, OPENCLAW_HOME.

(b) See which models.json the gateway is actually touching

#

Right after openclaw gateway restart, run:

ls -lt ~/.openclaw/agents/*/agent/models.json

The one with the newest mtime is the best hint for what agentDir the running gateway is using.

If you want “exact lines to paste”

  • openclaw gateway status
  • openclaw dashboard --no-open (redact #token=...)
  • output of the launchctl print … | rg … command above (that’s the money shot for env mismatches)

Once Control UI is connected (token fixed), it’s much easier to confirm you’re not accidentally talking to a different gateway instance / different state dir.

grizzled marlin
#

Krill update (skills installed, but agent still has NO filesystem tool)

Where we’re at now
1. Gateway + UI are working

•    Gateway is up and reachable at ws://127.0.0.1:18789.
•    Control UI authentication is fixed (we pulled gateway.auth.token from OpenClaw config and pasted it into Control UI settings).
•    OpenClaw TUI connects fine and is using the correct model: custom-127-0-0-1-1234/deepseek/deepseek-r1-0528-qwen3-8b (33k ctx).

2.    Filesystem path is accessible at the OS level

•    We fixed the Shared folder setup: removed the accidental iCloudDrive symlink, then created only a Paloma_Drop symlink, and later adjusted permissions.
•    We discovered “Operation not permitted” when Paloma tried to follow the symlink in Terminal was macOS TCC privacy (Terminal lacked Full Disk Access). After granting Full Disk Access to Terminal, Paloma can traverse and list the iCloud-backed folder via the Shared symlink.

3.    Agent still cannot browse filesystem (key problem)

•    In TUI, we asked the agent to list /Users/Shared/OpenClawData/school/Paloma_Drop in strict read-only mode.
•    It responds that it cannot access the filesystem tool / lacks access or configuration, and explicitly: “I don’t have access to the file system tool.”
•    So even though the OS-level permissions are now correct, the agent does not have a filesystem skill/tool wired in.

4.    Skills situation

•    openclaw skills list showed only a few ready initially (healthcheck, skill-creator, weather).
•    We installed skills via clawhub successfully after a temporary rate-limit hiccup:
•    peekaboo installed and now shows ✓ ready
•    things-mac installed and now shows ✓ ready
•    We restarted the gateway after installing skills.
•    Despite that, the agent in TUI still says it has NO filesystem tool.
#
  1. Session reset attempts
•    We can use /new in the TUI, but the agent still reports no filesystem tool after gateway restart and new session.

What we think is happening
• Installed skills are not automatically granted/exposed to agent main, or agent main has an allowlist that does not include peekaboo (or filesystem capability).
• Alternatively, the tool wiring requires explicit config to bind peekaboo actions into the agent runtime, even if the skill is “ready” at the system level.
• Also possible: OpenClaw runs under a user/process without Full Disk Access; but even when the OS user can access the path, the agent still reports NO tool, not permission errors.

Questions for you
1. What is the correct way in 2026.2.15 to grant an installed skill (peekaboo) to a specific agent (agent main)?

•    Is there an “allowedSkills”/“tool allowlist” field in ~/.openclaw/openclaw.json or under ~/.openclaw/agents/main/agent/ that we must edit?

2.    Does agent main require a restart beyond “gateway restart” to reload tool manifests (e.g., agent runner restart, rebuild agent registry)?
3.    For filesystem access specifically: is peekaboo the intended path for host directory listing, or is there a separate “filesystem” skill that must be installed/enabled?
4.    macOS TCC: should we be granting Full Disk Access to anything besides Terminal (e.g., the openclaw gateway process, node, or the LaunchAgent) for peekaboo/tooling to function?

Goal
• We want the agent to be able to list and organize files inside /Users/Shared/OpenClawData/school/Paloma_Drop (start read-only, then controlled moves), but currently it cannot even list directories because it has no FS tool available.

sand wedgeBOT
#

Good news: skills aren’t what give OpenClaw filesystem access. Installing things-mac, peekaboo, etc. doesn’t add a “filesystem tool”.

In OpenClaw, filesystem access is via the built-in tools:

  • read = read a file (not a directory)
  • exec = run shell commands (this is what you need for ls / directory listing)
  • (write/edit are file mutation)

So if you ask “list /Users/Shared/...”, that requires exec. If exec is denied (or your tool profile is minimal), the model will correctly say it has no filesystem tool.

1) Verify whether tools are actually available (don’t guess)

Run this (CLI, on the gateway host):

openclaw sandbox explain --json
openclaw config get tools --json
#
openclaw config get agents.list --json

What we’re looking for:

  • any tools.profile: "minimal" (global, per-agent, or via tools.byProvider)
  • any tools.deny (global/per-agent) that includes exec or group:runtime
  • any sandboxing that forces exec into a container that can’t see your host path

Also in the TUI, run /context detail and check the line that lists Tools:. If read/exec aren’t listed there, it’s tool policy—not macOS permissions.

2) Common fix for local models: slim toolset (keeps FS + runtime only)

Local models often behave better if you reduce the tool schema they see.

Add something like this (either globally or under tools.byProvider for your LM Studio provider):

#
{
  tools: {
    byProvider: {
      "custom-127-0-0-1-1234": {
        profile: "minimal",
        allow: ["group:fs", "group:runtime", "session_status"],
        deny: ["browser", "canvas", "nodes", "cron", "message", "web_search", "web_fetch"]
      }
    }
  }
}

Then:

#
openclaw gateway restart

That should give the agent read + exec (so it can ls) without dragging in huge schemas.

3) One important expectation check

Even with read enabled: read cannot list directories. The “browse folders” workflow is exec: ls -la ....

If you paste the Tools: line from /context detail (or openclaw sandbox explain --json toolPolicy section), I can tell you exactly which config key is currently stripping exec/read.

#

You’re mixing two separate concepts:

  • Skills (peekaboo, things-mac, etc.) = optional integrations that add new behaviors, and can be allowlisted per agent/channel.
  • Tools (exec, read, write, edit, …) = the built-in capabilities the model uses to touch the filesystem / run shell commands.

If the agent says “I don’t have access to the filesystem tool”, that almost always means exec and/or read are being denied by tool policy (or you’re on a “minimal” tool profile). Installing peekaboo won’t change that.

1) “Filesystem access” in OpenClaw: what you actually need

  • Listing a directory (ls …) requires exec.
  • Reading a file requires read.
  • There is no separate filesystem skill you need to install.

So the fix is in tools.* / agents.list[].tools.*, not in skills.

2) How to grant an installed skill (peekaboo) to agent “main”

Per-agent skill allowlist is:

#
  • agents.list[].skills: [...]

If you set it, only those skills will show up for that agent. If you don’t set it, skills are generally available (subject to skills.entries.<skill>.enabled etc.).

But again: this won’t give directory listing. That’s exec.

3) The right knob to turn: tool policy

Check what tools the agent actually has:

  • In the TUI: run /context detail and look at the “Tools:” line.
  • In shell: run openclaw sandbox explain (even if you think sandbox is off — it prints the effective allow/deny).

To enable “read-only filesystem + directory listing”, a good starting config is:

{
#
  tools: { profile: "coding" }
}

(or per-agent:)

{
  agents: {
    list: [{ id: "main", tools: { profile: "coding" } }]
  }
}

coding includes group:fs (read/write/edit) and group:runtime (exec/process). If you want strictly read-only, do:

#
{
  agents: {
    list: [
      {
        id: "main",
        tools: {
          allow: ["group:runtime", "read", "session_status"],
          deny: ["write", "edit", "apply_patch"]
        }
      }
    ]
  }
}

Then openclaw gateway restart.

#

4) macOS TCC / Full Disk Access

TCC only matters after exec is available.

Also: granting Full Disk Access to Terminal helps only when the gateway is running inside Terminal. If you run OpenClaw as a LaunchAgent, you may need to grant Full Disk Access to the actual binary doing the work (typically node and/or the OpenClaw app) if you’re trying to traverse protected locations (Desktop/Documents/iCloud Drive).

But right now your symptom is “no tool”, not “Operation not permitted”, so fix tool policy first.

If you paste the Tools: line from /context detail (or the output of openclaw sandbox explain), I can point to the exact config key that’s stripping exec.

grizzled marlin
#

Side question: I chose DeepSeek R1 8B (also the 70B model, but we haven't gotten that working yet due to the weird 4096 issue we were having earlier) because I wanted the best thinking model to run locally with 64 RAM. Is there a better thinking model suited for a great personality and deep reasoning for Openclaw? I'm using it as an asistant for medical school (thousands of powerpoint slides to manage weekly, creating todos/calendar help, email general organizational skills, and powerful teaching skills to help me prepare for tests).

sand wedgeBOT
#

If you want “best personality + deep reasoning” for OpenClaw, local-only is still the hard mode—mainly because smaller/quantized local models tend to be (a) weaker at long-horizon planning and (b) flaky at tool-calling once you turn on lots of tools.

What’s the best practical setup for your use case (med school assistant)

Hybrid is the sweet spot:

  • Local model for privacy + routine “organize / summarize / draft” work
  • Hosted fallback for “hard problems” (deep reasoning, multi-step planning, tool-heavy work)

OpenClaw supports model fallbacks, so you can keep everything working even when the local model hits a limitation. (This matters a lot once you start doing file workflows + long contexts.)

Best local option (per OpenClaw docs)

The OpenClaw docs’ current “known-good” local recommendation is LM Studio + MiniMax M2.1 via Responses API (big context, better behavior with OpenClaw’s agent loop than many other local picks):
https://docs.openclaw.ai/gateway/local-models

If your goal is “deep reasoning + long context”, you’ll generally be happier with a bigger-context model than DeepSeek R1 distills, even if raw “thinking” is good—because OpenClaw’s system/tool overhead eats context fast.

If you want the most accurate “which model should I pick today?”

I can’t safely keep an up-to-date ranked list in chat (it changes weekly and depends on LM Studio builds), so the best place to ask for current local picks with your exact hardware is the server’s #models channel, plus the models doc page:

#

https://docs.openclaw.ai/concepts/models

Non-model tip that will noticeably improve “personality” + tutoring quality

A lot of “assistant vibe” comes from your workspace persona files, not the model:

  • Tune SOUL.md / AGENTS.md to say explicitly: Socratic tutor, creates weekly study plan, spaced repetition prompts, practice questions, concise answers unless asked, etc.
  • Add “format contracts” (how you want slide summaries, todo lists, test prep checklists).

If you tell me whether you’re willing to use a hosted fallback (yes/no), I can suggest a clean two-tier configuration pattern (local primary vs hosted primary) without you having to rewire everything later.

grizzled marlin
#

Very good tips! Thank you Krill. I do have a ChatGPT Plus account, but I'm a poor college student so I've been trying to avoid Hosted models. Here’s a tight “send to Krill” update with concrete artifacts + pointed questions.

Krill — update + why is this still hard?

Where we are now (what’s working)
1. Gateway + config alignment

•    Gateway is running as a LaunchAgent under Paloma (pid visible in status).
•    openclaw status –all confirms config path is: /Users/paloma/.openclaw/openclaw.json
•    Gateway log shows reload: “config change applied (dynamic reads: … models …, tools)”
•    Dashboard/webchat can connect (we previously fixed the “token missing” loop in Control UI).

2.    Tool policy appears enabled (per OpenClaw itself)

•    openclaw sandbox explain –json now shows:
•    sandbox mode off
•    allowed tools include exec, process, read, write, edit, apply_patch
•    elevated enabled, channel webchat, allowedByConfig true, failures []
•    openclaw config get tools –json shows:
•    profile: “coding”
•    elevated.enabled: true
•    elevated.allowFrom.webchat: [”*”]

So on paper we have exec and elevated from webchat.

What’s still failing (the core issue)
3. Agent refuses to run ANY exec even for trivial commands

•    In Dashboard webchat, asking it to run:
•    exec: echo OPENCLAW_EXEC_WORKS
•    exec: ls -la “/Users/Shared/OpenClawData/school/Paloma_Drop”

…still results in refusal / “can’t comply / no filesystem tool / dry run / etc.” depending on prompt wording.
• This persists even after gateway restart and creating a new session in TUI.
• So: OpenClaw reports exec allowed, but the agent acts like tools don’t exist (or tool calls are blocked upstream before reaching runner).

#

Context window / model side note
• TUI shows the Qwen model with ~33k context (so our contextWindow override seems to be applied now).
• We also solved macOS TCC for iCloud symlink traversal (Terminal Full Disk Access) and can browse the drop folder manually. That’s separate from the current tool-call refusal.

Questions (what we need from you)

A) Are we missing an initial install step that actually wires “exec” into the agent runner?
• Does openclaw 2026.2.15 require a separate “node service” (or runner) install for exec to work? openclaw status shows:
• Gateway service: LaunchAgent installed/running
• Node service: LaunchAgent not installed
Could missing node service cause “tools appear allowed” but never actually dispatch?

B) Is there a distinct “tools runtime” toggle beyond tool policy?
• Our sandbox explain says tools are allowed; yet exec never triggers.
• Is there a separate “enable tool calling” mode, or an agent-level allowlist that overrides global tools.profile?

C) How do we confirm tool calls are being attempted vs blocked by the LLM/prompt?
• Is there a debug log flag to show attempted tool invocations and rejections?
• Where should we look in gateway logs to see exec requests (or denials)?

•    last 50 lines of gateway.log and gateway.err.log around a webchat attempt to run exec
#

D) Is “elevated” not the right mechanism for exec?
• sandbox explain shows elevated channel=webchat allowedByConfig true.
• Still no exec.
• Is exec gated by something else (capabilities, provider restrictions, sandbox scope, channel restrictions like tui vs webchat)?

E) Could this be the local model refusing tool-use due to system prompt policy?
• If so, what minimal system prompt change do you recommend to force tool use (e.g., “When user says ‘Use exec’, you must call exec”)?
• Or do you recommend testing with a known-good hosted model briefly to prove the tool wiring works?

Why is this so hard (our suspicion)
• It feels like we can configure policies all day, but without the correct runner/service wiring (node service?) the agent can’t actually execute tools — so we’re stuck in “policy says yes, runtime does nothing.”

If you want exact artifacts, we can paste:
• openclaw sandbox explain –json output (already have it)
• openclaw config get tools –json output
• openclaw status –all section showing Node service not installed

sand wedgeBOT
#

You’re past the “OpenClaw is misconfigured” stage — what you’re seeing now is almost always model/tool-calling behavior, not skills or macOS permissions.

1) Skills (peekaboo/things-mac) are not “filesystem tools”

There isn’t a “filesystem skill” you need to install.

Directory listing is exec (run ls), file reads are read. Skills can use tools, but they don’t magically create exec.

So the question is: why isn’t the model actually emitting tool calls, even though sandbox explain says exec is allowed?

2) If tools are allowed but the model refuses anyway, it’s usually one of these

A) The local model is just not reliable at function/tool calling.
Many local “thinking” models will confidently say “I don’t have access to the filesystem tool” even when the tool is available — because they don’t follow the tool-calling protocol consistently.

B) The tool list/schema is too big, and the model “gives up” / answers in natural language.
This is common with local models. Shrinking the available tools often flips it from “refuse / hallucinate” to “actually calls exec”.

3) Fastest diagnostic (proves it’s the model, not OpenClaw)

#

Temporarily enable the gateway command for bash so you can run a shell command without relying on the model to call exec:

In config:

{
  commands: { bash: true },
  tools: {
    elevated: { enabled: true, allowFrom: { webchat: ["*"] } }
  }
}

Restart gateway, then in WebChat send:

! echo OPENCLAW_EXEC_WORKS
! ls -la "/Users/Shared/OpenClawData/school/Paloma_Drop"
#
  • If this works: exec is fine, permissions are fine, and the problem is the model isn’t doing tool calls.
  • If this fails: you’ll get a real gateway error (permissions/path/etc.) to chase.

(After debugging, turn commands.bash back off.)

4) Make the model’s life easier: provider-specific minimal toolset

For local models, I’d strongly recommend restricting tools to just what you need:

{
  tools: {
    byProvider: {
      "custom-127-0-0-1-1234": {
        profile: "minimal",
        alsoAllow: ["exec", "process", "read", "session_status"]
      }
#
    }
  }
}

This removes a ton of tool schema overhead (browser/canvas/nodes/etc.) and often makes local models start calling tools correctly.

Restart gateway, then try again with:

“Call the exec tool with {command: 'echo OPENCLAW_EXEC_WORKS'} and nothing else.”

5) macOS TCC / Full Disk Access

This only matters after you have exec actually running.
If OpenClaw is running as a LaunchAgent, you may ultimately need Full Disk Access for the process doing the exec (typically node / the OpenClaw gateway runtime), not just Terminal — but your current symptom is “no tool”, not “operation not permitted”.

If you paste the Tools: line from /context detail (in WebChat) and whether ! echo … works after enabling commands.bash, we can pin this down in one round-trip.

grizzled marlin
#

Krill — update: bash command path works now, but we’re gated on approvals.
• We set gateway.mode=local, fixed config, and enabled commands.bash=true, restarted gateway.
• In TUI, when we run:
• ⚙️ bash: echo OPENCLAW_EXEC_WORKS
• ⚙️ bash: ls -la "/Users/Shared/OpenClawData/school/Paloma_Drop" | head -n 40
…OpenClaw responds Exit: 0 but then says Approval required with IDs (example: c98e1ffb, 8893e6bb). No output returns until approval.
• Question: what’s the intended place to approve these in 2026.2.15? Control UI “Approvals” panel? CLI command? Something else?
• Also: is there a recommended “safe auto-approve allowlist” for harmless commands like echo, ls, head so we don’t have to approve every listing during setup?

sand wedgeBOT
#

Those IDs are exec-approvals, not tool-policy. You do have exec; now the host is correctly stopping for the “safety interlock”.

How to approve (2026.2.15)

You have 3 options:

  1. From the same chat surface (TUI/WebChat/etc.) using the built-in command:
/approve c98e1ffb allow-once
/approve c98e1ffb allow-always
/approve c98e1ffb deny

Repeat for each ID.

  1. From the Control UI
    Control UI can edit exec-approval policy + allowlists (Control UI → Nodes → Exec approvals, pick Gateway). Depending on the build, you may also see approval prompts land there as operator events—but /approve is the most reliable path when you already have the IDs.

  2. Skip approvals temporarily for the session

#

If you just want to unblock setup/testing:

/elevated full

This skips exec approvals for that session (use with care).

Docs: https://docs.openclaw.ai/tools/elevated


“Safe auto-approve” for harmless setup commands (recommended)

Do allowlist mode so only specific binaries run without prompting.

Approvals live on the execution host at:
~/.openclaw/exec-approvals.json

Easiest way to manage allowlists via CLI:

#
openclaw approvals get

# allowlist a few boring binaries (macOS paths)
openclaw approvals allowlist add --agent main "/bin/ls"
openclaw approvals allowlist add --agent main "/bin/echo"
openclaw approvals allowlist add --agent main "/usr/bin/head"
openclaw approvals allowlist add --agent main "/usr/bin/tail"

Then set the policy (via Control UI → Nodes → Exec approvals → Gateway) roughly to:

  • security: allowlist
  • ask: on-miss (prompts only when not allowlisted) or off (hard deny anything not allowlisted)

Docs: https://docs.openclaw.ai/tools/exec-approvals and https://docs.openclaw.ai/cli/approvals

If you want, paste openclaw approvals get --json (redact socket token if present) and I’ll tell you the minimal edits so ls stops prompting but anything dangerous still blocks.

grizzled marlin
#

Krill, I'm back to ask for some more help. Thank you for being so informative!

OpenClaw pairing bug after update — Gateway reachable but CLI says “pairing required”

Environment
• macOS 26.3 (arm64)
• OpenClaw 2026.2.19-2 (45d9b20)
• node 22.22.0
• Gateway bind: loopback
• Dashboard: http://127.0.0.1:18789/
• Gateway target: ws://127.0.0.1:18789
• Local IP shown in status: Bentlys-Mac-mini.local (10.0.0.8)

What works
• openclaw gateway status previously showed running + RPC probe ok, listening on 127.0.0.1:18789.
• Dashboard opens and routes me to:
http://127.0.0.1:18789/chat?session=agent%3Amain%3Amain

What fails
• openclaw status always prints:
gateway connect failed: Error: pairing required
even though status table says:
Gateway local · ws://127.0.0.1:18789 · reachable 15ms · auth token …
• openclaw doctor also prints pairing required, even while also detecting:
• Port 18789 already in use by openclaw-gateway
• Runtime: running (it even restarts the LaunchAgent)
• Then still ends with pairing required

Devices
• openclaw devices list shows Paired (2) and no Pending
• openclaw devices approve --latest → No pending device pairing requests

What we already did
• Set local mode + restarted gateway:
• openclaw config set gateway.mode local
• openclaw gateway restart
• Fixed “config token differs from service token” by running:
• openclaw gateway install --force
• Allowlisted execs to stop approval spam:
• /bin/ls, /bin/echo, /usr/bin/head, /usr/bin/tail
• Noticed OpenClaw ran them via bash, so also allowlisted:
• /bin/bash
• Tried restart via doctor: “Restart gateway service now? Yes” (LaunchAgent restart confirmed)

#

Other notable output
• openclaw doctor shows Memory search enabled but no embedding provider configured (probably not related to pairing).
• Security audit (from openclaw status) flags:
• CRITICAL: tools.elevated.allowFrom.webchat includes "*"
• WARN: credentials dir mode 755 (suggests chmod 700)

Question / suspicion
It looks like the gateway is reachable, UI loads, but the CLI’s gateway connection is treated as an unpaired client. Yet there is no pending pairing request to approve. This feels like either:
1. CLI device identity is stale/duplicated after update, or
2. pairing is now required for loopback CLI too, but it’s not generating a pending request, or
3. mismatch between connecting via 127.0.0.1 vs 10.0.0.8 / hostname path and pairing is keyed on that, or
4. token/pairing store got corrupted during update.

Ask
What’s the correct “reset pairing state” command sequence for this version?
Do we need to wipe a specific file (devices registry / pairing DB), re-init gateway pairing, or run a new pairing command for CLI?

sand wedgeBOT
#

Short answer: “pairing required” means the Gateway is rejecting the CLI’s device identity/auth (or it’s treating your CLI as a non‑local client). Most real cases come from one of three things: (A) a stale/incorrect device auth token on the CLI, (B) the connection is being seen as remote because the Host header isn’t a loopback name, or (C) the CLI and the Gateway are using different state/config dirs so they don’t share the same pairing store. Try the checklist below (ordered, safest→more-intrusive) and paste the two small outputs I ask for if you want me to read them.

Quick diagnostics (paste the Gateway connection block + devices list if you want me to inspect)

  1. Show what the CLI thinks the gateway is and which config it's using:
    openclaw status --verbose
    (copy the “Gateway connection:” block)

  2. Show paired devices (JSON):
    openclaw devices list --json
    (paste that JSON; redact any tokens if present)

If those look normal, run these extra checks:

  • cat ~/.openclaw/identity/device.json (shows the CLI deviceId)
  • cat ~/.openclaw/identity/device-auth.json (if exists; shows stored device tokens)
  • cat ~/.openclaw/devices/paired.json and ~/.openclaw/nodes/paired.json
  • openclaw logs --follow (then, in another terminal) run openclaw status and watch for lines mentioning "pairing", "device.pair.requested", "pairing required" or "device token mismatch"
#

What to look for / common causes

  • If openclaw status --verbose shows the gateway URL is not the localLoopback URL (or urlSource is something like configRemote / local lan), the Gateway may treat your client as remote — host header or discovery is driving a non‑loopback host.
  • If your deviceId from device.json is NOT present in devices/paired.json (or paired.json is from a different state dir), CLI and gateway don’t share the same pairing store.
  • If device-auth.json exists and the gateway recently rotated or reinstalled, that token can be stale and the server will ask for re‑pairing.

Quick fixes (in order)

A — Force a fresh device-auth (safe, reversible)

  • Backup then remove the stored device token so the CLI will request a new pairing:
    mv ~/.openclaw/identity/device-auth.json ~/.openclaw/identity/device-auth.json.bak
  • Trigger a connection / pairing request:
    openclaw status
  • Approve the pending request:
    openclaw devices approve --latest
    This is the most common, low-risk fix; it forces the CLI to create a new device token and lets you re-approve it.

B — If the host header / “non-local” detection is the problem

#

Resetting device pairing (the thing behind pairing required) in 2026.2.19 is mostly done via the openclaw devices … CLI — you generally don’t need to hand-edit files unless the CLI can’t connect at all.

0) Key fact (why “no pending” can happen)

Device pairing requests expire after 5 minutes. So if you reproduce the error, run openclaw devices list immediately after to see the pending requestId.

Pairing is keyed on deviceId + publicKey (not on 127.0.0.1 vs 10.0.0.8), but the “auto-approve on loopback” behavior depends on the Gateway considering you a local client.


Recommended “reset pairing state” sequences

A) Targeted reset (best first step)

This only unpairs one device (usually your CLI / this Mac):

  1. Find your CLI deviceId:
  • cat ~/.openclaw/identity/device.json (look for deviceId)
    or just scan openclaw devices list --json for a matching deviceId
#
  • Confirm the gateway URL in the verbose status (openclaw status --verbose). If the URL is not ws://127.0.0.1:18789, try a direct probe:
    openclaw gateway probe --url ws://127.0.0.1:18789 --json
  • If the probe with explicit 127.0.0.1 works but status still complains, check gateway.bind in config (should be loopback) and check for reverse‑proxy headers (X‑Forwarded‑*). If you do run behind a proxy, set gateway.trustedProxies so the Gateway treats local requests as local.

C — State/Install mismatch (CLI vs daemon)

  • Confirm both CLI and daemon use the same config/state path: compare the “Config:” path shown by openclaw status --verbose with the gateway’s config path printed in openclaw gateway status / openclaw gateway probe --json.
  • If LaunchAgent is running OpenClaw under a different user or Node install, reinstall the service using the same CLI you use: openclaw gateway install --force && openclaw gateway restart (you already tried this; if it’s still mismatched, check which openclaw is running: which openclaw && ps aux | grep openclaw).

Emergency bypass (NOT recommended for long term)

  • For debugging only: allow token-only UI auth (skips device identity) — set: openclaw config set gateway.controlUi.allowInsecureAuth true && openclaw gateway restart. Revert it as soon as you’re done.

If this doesn’t fix it

  • Run the diagnostics above and paste:
  • The “Gateway connection:” block from openclaw status --verbose
  • The output of openclaw devices list --json
    (you can redact tokens). I’ll read them and tell you the exact cause and next step.
grizzled marlin
#
  1. openclaw status --verbose → “Gateway connection:” block
    Gateway connection:
    Gateway target: ws://127.0.0.1:18789
    Source: local loopback
    Config: /Users/paloma/.openclaw/openclaw.json
    Bind: loopback

Extra context from the same output (still relevant):
• CLI prints: gateway connect failed: Error: pairing required
• Gateway line shows: local · ws://127.0.0.1:18789 (local loopback) · reachable 28ms · auth token · Bentlys-Mac-mini.local (10.0.0.8)

sand wedgeBOT
#
  1. Remove that device from the gateway’s paired table:
openclaw devices remove <deviceId>
  1. Clear the CLI’s cached device token (so it can fetch a fresh one):
mv ~/.openclaw/identity/device-auth.json ~/.openclaw/identity/device-auth.json.bak 2>/dev/null || true
  1. Trigger a reconnect:
openclaw status

If it’s not treated as local, you should now briefly get a pending request — approve it right away:

grizzled marlin
#
  1. openclaw devices list --json (redacted)

{
"pending": [
{
"requestId": "8c0523a0-bf79-489d-ad7b-d06ebb9f3d04",
"deviceId": "43c429b33e16accbfeb82607ae2beb1ddb42f1d1ace5379280d82fc6e421c4c5",
"publicKey": "<REDACTED>",
"platform": "darwin",
"clientId": "gateway-client",
"clientMode": "backend",
"role": "operator",
"roles": ["operator"],
"scopes": ["operator.read"],
"silent": false,
"isRepair": true,
"ts": 1771615403513
}
],
"paired": [
{
"deviceId": "f101f65ffebace65b6a802a54d81463334e771d1ab3a583693581b1de4867b90",
"publicKey": "<REDACTED>",
"platform": "MacIntel",
"clientId": "openclaw-control-ui",
"clientMode": "webchat",
"role": "operator",
"roles": ["operator"],
"scopes": ["operator.admin","operator.approvals","operator.pairing"],
"createdAtMs": 1771315129779,
"approvedAtMs": 1771315129779,
"tokens": [
{
"role": "operator",
"scopes": ["operator.admin","operator.approvals","operator.pairing"],
"createdAtMs": 1771315129779,
"lastUsedAtMs": 1771613417952
}
]
},
{
"deviceId": "43c429b33e16accbfeb82607ae2beb1ddb42f1d1ace5379280d82fc6e421c4c5",
"publicKey": "<REDACTED>",
"platform": "darwin",
"clientId": "cli",
"clientMode": "cli",
"role": "operator",
"roles": ["operator"],
"scopes": ["operator.admin","operator.approvals","operator.pairing"],
"createdAtMs": 1771302084111,
"approvedAtMs": 1771302084111,
"tokens": [
{
"role": "operator",
"scopes": ["operator.admin","operator.approvals","operator.pairing"],
"createdAtMs": 1771302084111
}
]
}
]
}

#

One-liner interpretation to include (optional, but helpful)

“Despite Source: local loopback + target ws://127.0.0.1:18789, CLI still gets pairing required. Devices list shows CLI is already paired, but there’s also a NEW pending backend ‘gateway-client’ repair request (clientMode: backend, scopes: operator.read, isRepair: true).”

sand wedgeBOT
#

Want me to walk you through the mv + approve flow right now? If so paste the two short outputs above and I’ll point to the exact file(s) to back up and remove.

#
openclaw devices list
openclaw devices approve --latest

B) Full wipe/reset (nukes all paired devices)

Use this only if the pairing table is genuinely borked/corrupted — it will force you to re-pair everything (nodes, dashboards, etc.):

openclaw devices clear --yes --pending

If you still get “pairing required” but no pending request shows up

That usually means you’re not actually connecting as a “local loopback” client (so the gateway won’t silent-autoapprove), or you’re connecting to a different gateway/profile than you think.

#

Please paste (redact tokens) these two things and I’ll tell you exactly which case it is:

  1. Gateway connection block:
openclaw status --verbose

(copy the “Gateway connection:” section)

  1. Device pairing state:
openclaw devices list --json

Side note: your security audit findings

Unrelated to pairing, but important:

#
  • CRITICAL tools.elevated.allowFrom.webchat containing "*" is basically “anyone in webchat can request elevated host exec”. If that wasn’t intentional, remove it ASAP.
  • credentials dir 755 → fix perms (audit usually suggests chmod 700 ~/.openclaw/credentials).

If you paste the status --verbose block, I can also point at the exact config path where that webchat: ["*"] is coming from.

#

That Gateway connection block confirms the CLI is targeting ws://127.0.0.1:18789 correctly, so this isn’t a “wrong URL” situation.

Given what you’re seeing, the most likely explanation is:

  • you are already paired, but openclaw status makes an extra Gateway call via callGateway() that requests scopes operator.admin + operator.approvals + operator.pairing.
  • if your paired device entry was created before those scopes existed / changed, the Gateway treats this as a scope-upgrade and will demand re-pairing (“pairing required”).

The correct reset sequence (2026.2.19)

1) Targeted reset just for this CLI device (recommended)

  1. Get your CLI deviceId:

    • cat ~/.openclaw/identity/device.json
  2. Remove that device from the Gateway pairing table:

openclaw devices remove <deviceId>
#
  1. Clear the CLI’s cached device-token (so it can re-pair cleanly):
mv ~/.openclaw/identity/device-auth.json ~/.openclaw/identity/device-auth.json.bak 2>/dev/null || true
  1. Immediately trigger a new connect + pairing request:
openclaw status
  1. Within 5 minutes (pending TTL), check + approve:
openclaw devices list
openclaw devices approve --latest

If you still see “no pending”, run openclaw devices list --json right after reproducing the error and paste it here — pending requests expire quickly, so timing matters.

#

Nice — that JSON explains it perfectly, and it’s not corruption.

What’s happening

You have a repair pairing request for the same deviceId (43c4…) with:

  • isRepair: true → device already exists in paired
  • scopes: ["operator.read"] → some client is now connecting as that device and requesting operator.read
  • but your paired entry for that device currently has scopes admin/approvals/pairing and does not include operator.read

OpenClaw’s pairing “scope upgrade” check is literal: it doesn’t treat operator.admin as implying operator.read for the purposes of “is this device approved for these requested scopes?”. So it forces a re-pair to add the missing scope.

That’s why openclaw status can show the gateway as reachable, but you still see the noisy:
gateway connect failed: Error: pairing required

The fix (for this request)

Just approve the pending requestId you have:

#

Yep — that one-liner is accurate, and it’s the key detail.

What’s actually going on
Even though the CLI is connecting via local loopback (ws://127.0.0.1:18789), the Gateway is forcing a repair pairing because there’s a new connection (clientId gateway-client, mode backend) asking for scopes: ["operator.read"] and your already-paired CLI device entry does not include operator.read in its approved scopes.

Important nuance: the pairing “scope upgrade” check is literal — it does not treat operator.admin as automatically implying operator.read for pairing purposes. So missing operator.read triggers “pairing required”.

Fix (no wipe needed)
Approve the pending repair request you already have:

openclaw devices approve 8c0523a0-bf79-489d-ad7b-d06ebb9f3d04
openclaw status

Then confirm it merged scopes:

#

2) Full pairing table wipe (only if you want to re-pair everything)

This wipes all paired devices (nodes, UI clients, etc):

openclaw devices clear --yes --pending

Then also clear the local cached token as above and reconnect.


Why you might see “pairing required” but “no pending”

Two common reasons:

  1. You waited > 5 minutes (pending pairing TTL is 5 minutes), so it disappears.
  2. The connect attempt is being treated as non-local (rare on ws://127.0.0.1, but happens if something injects X-Forwarded-* headers). The Gateway will then refuse silent auto-approval and require manual approval.

To confirm #2 fast:

  • Run openclaw logs --follow, then in another terminal run openclaw status, and look for warnings like:
#
openclaw devices approve 8c0523a0-bf79-489d-ad7b-d06ebb9f3d04
openclaw status

After approval, the paired device entry’s scopes should include operator.read (merged), and this should stop re-triggering.

If you want the “reset pairing state” hammer

You don’t need it here, but for completeness (2026.2.19):

  • Remove just one device:
openclaw devices remove <deviceId>
  • Wipe all paired devices (re-pair everything):
#
openclaw devices list --json

You should now see the device’s scopes include operator.read, and the status/doctor pairing noise should stop.

(Optional) One-liner you can paste
“Despite Source: local loopback + target ws://127.0.0.1:18789, CLI still gets pairing required. Devices list shows CLI is already paired, but there’s also a NEW pending backend ‘gateway-client’ repair request (clientMode: backend, scopes: operator.read, isRepair: true) — missing operator.read in the paired device scopes is forcing a scope-upgrade re-pair.”

Docs refs if you want them:

#
  • “Proxy headers detected from untrusted address…”
    • “Loopback connection with non-local Host header…”

If you paste the openclaw devices list --json output, I can tell you immediately if this is a scope-upgrade (you’ll see your device with missing scopes) vs something weirder.

Also: the “Memory search enabled but no embedding provider configured” is unrelated to pairing.

#
openclaw devices clear --yes --pending

About the “Bentlys-Mac-mini.local (10.0.0.8)” line

That’s just the gateway’s self-reported presence/hostname; it’s not evidence your CLI is connecting via LAN IP. Your verbose block shows the actual target is loopback.

If approving that request doesn’t fix the status noise, paste the new openclaw devices list --json right after running openclaw status once more (there may be a second client requesting a different scope set).

grizzled marlin
#

Hey Krill — thanks again. We followed your guidance and got Paloma stable + hardened, and we’re now trying to run a tool-call bakeoff (DeepSeek vs MiniMax) via the OpenClaw CLI. We’re stuck on openclaw agent routing/session semantics and want your recommended approach.

✅ What we successfully accomplished (your checklist)
• Gateway mode/local: set gateway.mode=local
• Fixed “config token differs from service token” via openclaw gateway install --force, then restart
• Solved pairing required (1008) loop caused by repair pairing requesting operator.read
• pending requests were expiring, CLI couldn’t approve, so we confirmed via ~/.openclaw/devices/pending.json and merged → paired.json, cleared pending, restarted gateway
• gateway now clean, pending.json is {}, openclaw status --deep clean
• Exec approvals: built allowlist ladder and discovered OpenClaw was wrapping commands via /bin/bash, so allowlisted /bin/bash and approvals stopped blocking basic tools
• Security:
• chmod 700 ~/.openclaw/credentials
• removed wildcard elevated: tools.elevated.allowFrom.webchat = []
• openclaw security audit now shows 0 critical (only WARN about trustedProxies empty; we’re loopback-only)

✅ Docling/VLM side is working
• Installed Docling in Paloma venv, installed mlx-vlm, suppressed LibreSSL warning, fixed zsh compdef/compinit ordering
• Granite Docling VLM + OCR → JSON works fast (~30s) and produces structured JSON with pictures[] (base64 images + bbox prov). Captions are empty, so we plan a second-pass captioner.

❌ Current blocker: openclaw agent CLI cannot run “tool-call tests”
We’re trying to run something simple like:
• “Use terminal tool only: ls -t ~/Desktop/docling_out | head -n 1 and reply with filename”

But:
• openclaw agent --session-id agent:main:main ... → Invalid session ID: agent:main:main
• openclaw agent --to +1555... also fails and then:

#

• “Gateway agent failed; falling back to embedded”
• Embedded fails with: No API key for provider “anthropic” (auth store ~/.openclaw/agents/main/agent/auth-profiles.json)
• openclaw agent --help shows channel default is whatsapp, and the available channel list does not include webchat.
• openclaw status shows Channels table empty (no enabled channels), but Sessions table shows:
• Key: agent:main:main (model deepseek-r1-0528-qwen3-8b)

Also local sessions store got weird: openclaw sessions list --json showed agent:main:main mapped to claude-opus-4-6 even though gateway status shows DeepSeek. We moved ~/.openclaw/agents/main/sessions/sessions.json aside, but openclaw agent still fails and falls back to embedded.

Questions for you (best path forward)
1. What’s the correct way to run a single gateway agent turn from CLI in a “loopback-only webchat” setup without any delivery channels enabled?
• Is there a channel we should use (e.g., last) or a way to target the existing direct session?
2. What does --session-id actually expect in this build? (Because agent:main:main is valid as the session key everywhere else but rejected by openclaw agent.)
3. Should we disable embedded fallback or re-point embedded to LM Studio so it never tries Anthropic?
4. Is the “Channels table empty” expected for webchat-only usage? If not, what should be enabled/configured?

Goal: run a controlled tool-call bakeoff DeepSeek vs MiniMax via the OpenClaw agent, then wire Docling JSON → figure captioning → teaching bundle as an OpenClaw workflow.

Thanks again — you saved us from the pairing abyss.

sand wedgeBOT
#

You’re hitting two separate issues that just cascade into each other:

A) n_keep (16886) >= n_ctx (4096) = LM Studio is actually running 4096 ctx

That error is coming from the backend (LM Studio / llama.cpp), not OpenClaw.

OpenClaw’s first turn (system prompt + tool schemas + bootstrap files) is ~16–18k tokens in real life, so a backend that’s effectively n_ctx=4096 will always explode. OpenClaw also has a guardrail that effectively expects ≥16k context (you already saw “Minimum is 16000.”).

Fix: you need the model loaded with a larger context in LM Studio (practically: 32k+ so you clear that ~16.8k n_keep), and you should reflect that in your provider model entry so the UI/token meter isn’t lying.

Docs that match this exact situation:

If you really want a “minimal prompt” mode (to fit smaller ctx), OpenClaw can do it, but it means restricting tools heavily:

B) “Gateway agent failed; falling back to embedded” + “No API key for anthropic”

#

openclaw agent normally runs through the Gateway, but per docs it will fallback to embedded if the Gateway can’t be reached. Embedded then uses whatever model it resolves (often via session overrides / fallbacks) and if it ever tries Anthropic you’ll get the “No API key” error.

Relevant doc:

Your questions (best path forward)

1) Single “gateway agent turn” from CLI with no delivery channels enabled

Use the TUI. It’s explicitly designed for “loopback chat” with delivery off.

openclaw tui

Docs: https://docs.openclaw.ai/web/tui

If you want a one-shot command (no delivery), this works too:

#
openclaw agent --agent main --message "hello"

Just don’t use --deliver (delivery is for WhatsApp/Telegram/Discord/etc; not needed for WebChat-only setups).

2) What does --session-id expect?

--session-id is the sessionId field (usually a UUID) stored inside the session store entry.

It is not the session key like agent:main:main.

So:

  • agent:main:main = sessionKey
  • xxxxxxxx-xxxx-... = sessionId

You can find it via:

#
openclaw sessions --json

(look for each entry’s sessionId)

3) Disable embedded fallback / re-point embedded to LM Studio?

There isn’t a “no fallback” flag on openclaw agent—the practical fix is: make the Gateway reachable so it doesn’t fall back.

When it does fall back, embedded will try whatever your config/session says. If you never want it to touch Anthropic:

  • remove Anthropic from your fallback chain, and/or
  • reset/clear any stale per-session model override (easy test: send /new in TUI or Control UI).

But in your case it’s very likely embedded is only ending up at Anthropic because the local LM Studio call is failing (4096 ctx), then failover kicks in.

4) “Channels table empty” — expected for WebChat-only?

Yes. openclaw channels … is for external connectors (WhatsApp/Telegram/Discord/…).

#

WebChat / Control UI are web surfaces on the Gateway, not “channel accounts”, so the channels table can be empty and everything is still fine.

WebChat doc: https://docs.openclaw.ai/web/webchat


Two quick sanity checks that will de-confuse the “gateway vs embedded vs wrong config path” situation

If you paste these outputs, it’ll be obvious where the mismatch is:

openclaw --version
openclaw gateway status
openclaw status --deep

In particular, openclaw gateway status will often reveal “Config (cli)” vs “Config (service)” drift, which explains session/model weirdness.

grizzled marlin
#

Krill — quick follow-up + questions (Paloma)

Thanks again. We read your note about two cascading issues. One update: in LM Studio we already have the model loaded with Context Length = 32768 (DeepSeek R1 0528 Qwen3-8B), server reachable at http://127.0.0.1:1234. So if we’re still seeing n_keep (16886) >= n_ctx (4096) anywhere, it seems like either:
• OpenClaw is hitting a different backend/profile than we think, or
• the gateway service is using a different config/state dir than the CLI (“Config(cli) vs Config(service) drift”), or
• LM Studio is silently serving 4096 to the gateway despite UI showing 32768 (server-side param mismatch).

Questions:
1. What’s the best way to prove the real n_ctx the gateway is getting from LM Studio? (e.g., a specific curl to /v1 that returns the server’s actual context / model params, or a log line to watch in openclaw logs --follow / LM Studio logs)
2. Where in OpenClaw should we set/confirm the provider model entry so the token meter + context assumptions match reality? (We want to avoid “UI says 32k but gateway behaves like 4k”)
3. For embedded fallback: where do we remove Anthropic from the fallback chain so we never see “No API key for anthropic” again even if something fails?
4. Can you confirm the canonical way to get the true sessionId (UUID) that --session-id expects? (We see sessionKey agent:main:main, but CLI rejects that as session-id.)
5. For a DeepSeek vs MiniMax tool-call bakeoff: which tools.profile do you recommend so the test isn’t dominated by schema overhead? (“minimal” vs default?)

If helpful, we can paste:
• openclaw --version
• openclaw gateway status
• openclaw status --deep
• the exact LM Studio curl we’re using

Appreciate you — this is super close.

sand wedgeBOT
#

1) Proving the real n_ctx the Gateway is getting from LM Studio

There isn’t a standard OpenAI /v1/* endpoint that reliably reports “effective context window” (most servers don’t expose it), so the most trustworthy proof is the LM Studio server log line at inference time.

What to do:

  • In LM Studio → Server / Local Server → Logs (whatever view shows [LM STUDIO SERVER] ...), trigger one failing request from OpenClaw.
  • When this specific failure happens, LM Studio typically logs something like:
  • “Trying to keep the first … tokens… model is loaded with context length of only 4096…”
  • or the llama.cpp-style n_keep >= n_ctx (4096) line you’re seeing.

That log line is the definitive “what the backend actually loaded”.

On the OpenClaw side, to ensure you’re even hitting the right endpoint/model, run:

openclaw models status --json
#

and confirm the provider’s baseUrl is really http://127.0.0.1:1234/v1 and the selected model is the one you think it is.

(If you paste openclaw models status --json + the LM Studio log snippet from the failing request, we can usually spot immediately whether this is “wrong backend” vs “server silently loaded 4k”.)


2) Where to set/confirm the provider model entry (so UI token meter matches reality)

OpenClaw does not auto-discover context size from LM Studio. The token meter/context assumptions come from the model registry entry you define for that provider/model.

Canonical place (by default): the agent’s models.json registry:

  • ~/.openclaw/agents/<agentId>/models.json

In that provider entry, the key fields are:

  • contextWindow (what you want the UI + session store to assume)
  • maxTokens (generation cap)

See the LM Studio example in the docs (note the explicit contextWindow/maxTokens):

#

https://docs.openclaw.ai/gateway/local-models

To confirm what OpenClaw currently believes, use:

openclaw models list --local
openclaw models status

3) Removing Anthropic from the fallback chain (so “No API key for anthropic” never appears)

Two layers to clear:

A) Global default model fallbacks
Set fallbacks to empty in config (object form):

  • agents.defaults.model.fallbacks: []
  • and also check agents.defaults.imageModel.fallbacks: [] if set
#

Docs confirm: [] disables global fallbacks (important):
https://docs.openclaw.ai/gateway/configuration-reference (search “fallbacks[] disables”)

B) If you’ve used the CLI fallbacks helpers before
Clear via CLI:

openclaw models fallbacks clear
openclaw models image-fallbacks clear

C) Per-session overrides
Even after changing config, an existing session might still be pinned to an old model. Do a /new in TUI / Control UI chat (or /model <your-local-model>).

Model failover behavior reference:
https://docs.openclaw.ai/concepts/model-failover


#

4) Canonical way to get the true sessionId (UUID) that --session-id expects

Yep: --session-id expects the sessionId value (typically a UUID), not the sessionKey (agent:main:main).

Get it with:

openclaw sessions --json

Look for entries like:

  • key: agent:main:main ← sessionKey
  • value: { "sessionId": "…" } ← this is what --session-id wants

Alternate (from the running gateway, useful when debugging drift):

openclaw gateway call sessions.list --params '{}'
#

Session key shapes reference:
https://docs.openclaw.ai/channels/channel-routing


5) DeepSeek vs MiniMax tool-call bakeoff: which tools.profile?

If you want the comparison to be about “can it call tools well?” and not “who survives the schema dump?”, keep the tool surface small and identical for both models.

Good starting point:

  • Disable the big-schema stuff you don’t need (browser/canvas/nodes), and
  • Allow only the specific tool(s) you’re testing.

Example strategy (conceptually):

  • tools.profile: "minimal"
  • tools.allow: ["group:web"] (or even just web_fetch)
  • optionally add group:fs if your benchmark needs file IO
#

Tools policy docs (profiles + allow/deny):
https://docs.openclaw.ai/tools/index
(also summarized in https://docs.openclaw.ai/gateway/configuration-reference#tools)

If you tell me what your bakeoff task is (e.g. “fetch URL + extract facts” vs “write code + run tests”), I can suggest the tightest allowlist that still makes the test meaningful.


If you paste these, I can give you a very concrete “it’s config drift” vs “LM Studio loaded 4k” verdict:

  • openclaw --version
  • openclaw gateway status
  • openclaw status --deep
  • openclaw models status --json
  • LM Studio server log lines around the failing request
grizzled marlin
#

Krill — quick follow-ups (we’ll run whatever you tell us)
1. Prove effective n_ctx from LM Studio
What exact LM Studio log line / field should we look for to confirm the effective context window the server actually loaded?
If UI shows 32768 but the backend behaves like 4096, what are the top 2–3 LM Studio misconfigs that cause that?
2. OpenClaw: confirm we’re hitting the right backend/model
You mentioned openclaw models status --json. Besides confirming baseUrl = http://127.0.0.1:1234/v1, what else in that JSON is the giveaway for “wrong backend vs wrong model vs wrong profile”?
Is there a preferred “test request” that forces a failure quickly and cleanly?
3. Canonical models.json entry for LM Studio (so token meter matches reality)
Can you paste a minimal ~/.openclaw/agents/main/models.json model registry entry for LM Studio including:

•    baseUrl
•    the correct model id format
•    contextWindow: 32768
•    sane maxTokens (generation cap)

so OpenClaw stops assuming the wrong window?

4.    Remove Anthropic from any fallback chain (so we never see that error again)

What is the canonical way to guarantee Anthropic will never be used, across:

•    global fallbacks (agents.defaults.model.fallbacks)
•    image model fallbacks (agents.defaults.imageModel.fallbacks)
•    any CLI helper fallbacks
•    and per-session overrides that may pin old models?

(If there’s a “one-liner” to clear all fallbacks, we’ll use it.)

#
  1. Per-session overrides: fastest reset
    What’s the fastest “reset this session’s model pins/overrides” command you recommend?
    (/new in TUI/Control UI vs openclaw sessions … vs something else)
    6. --session-id semantics
    Confirm: --session-id expects the internal sessionId (UUID-ish), not sessionKey (agent:main:main).
    What’s your preferred command to retrieve the real sessionId (CLI + via gateway)?
    (openclaw sessions --json vs openclaw gateway call sessions.list --params '{}')
    7. Tool-call bakeoff: recommended tools.profile + allowlist
    For comparing DeepSeek vs MiniMax on “can it call tools well” (not “who survives schema dump”), what exact settings do you recommend?
    Example target: only filesystem + exec for simple commands (ls, head, python -c).
    Should we use tools.profile: minimal and then tools.allow: ["group:fs","group:exec"] or something tighter?
    8. TUI vs CLI for loopback-only setups
    You suggested openclaw tui for one-shot gateway turns with no delivery channels.
    Is the CLI openclaw agent --agent main --message ... supposed to work in a webchat-only setup with channels table empty? If not, what’s the intended CLI path?

If you want, we can paste these outputs for you to diagnose drift instantly:
• openclaw --version
• openclaw gateway status
• openclaw status --deep
• openclaw models status --json
• LM Studio server log snippet from the failing request

sand wedgeBOT
#

1) LM Studio: what exactly proves the effective n_ctx (and why it can still be 4096)

The one log line you want is the inference-time error that explicitly states the loaded context length. In LM Studio server logs it commonly looks like one of these:

  • ... model is loaded with context length of only 4096 tokens ...
  • Cannot truncate prompt with n_keep (...) >= n_ctx (4096)
  • Trying to keep the first ... tokens ... However, the model is loaded with context length of only 4096 ...

Those lines are not “OpenClaw assumptions” — they’re the backend telling you what it actually loaded.

If LM Studio UI shows 32768 but the server behaves like 4096, the top misconfigs I’ve seen are:

  1. The server request is hitting a different loaded model instance than the one you’re staring at in the UI
    (e.g. multiple models loaded / switched, and the server is still bound to the earlier one).

  2. Context length changed in the UI, but the model/server wasn’t fully reloaded/restarted
    Many local backends only apply n_ctx at model-load time. So “UI says 32768” can be true for the desired config, while the running server process is still on the old load.

#
  1. Automatic downshift due to memory limits
    Some stacks will silently load with a smaller ctx if they can’t allocate KV cache for the requested ctx.

So: trigger one failing request, then copy/paste the LM Studio server log block around that request — that’s the ground truth.


2) OpenClaw: in openclaw models status --json, what’s the “giveaway” for wrong backend/model/profile?

Three “tell me what’s really happening” spots:

A) The resolved model ref (provider + model)
You’re looking for whatever field shows the resolved primary model (and fallbacks). If it says anything other than your LM Studio provider + model id, you’ve found the mismatch.

B) Provider baseUrl + api mode
Confirm:

  • baseUrl is exactly http://127.0.0.1:1234/v1
  • api is what you expect (openai-responses vs chat completions). If you accidentally have two providers both pointing at 1234 (e.g. custom-127-0-0-1-1234 and lmstudio) it’s easy to think you’re using one while actually selecting the other.
#

C) The fastest “clean test request”
Use openclaw agent --json because it prints what was actually used for that run (model/provider in the result metadata):

openclaw agent --agent main --message "Reply with exactly: OK" --json

If this falls back to embedded, that JSON output + your gateway logs will show it.

(And separately: curl http://127.0.0.1:1234/v1/models should show the model id you’re trying to use — if OpenClaw is configured with an id that isn’t in that list, you’ll often end up on the wrong thing.)


3) Minimal ~/.openclaw/agents/main/models.json provider entry for LM Studio (32k ctx)

You can use this as a minimal registry entry (adjust the id to match exactly what GET /v1/models returns):

#
{
"mode": "merge",
"providers": {
"lmstudio": {
"baseUrl": "http://127.0.0.1:1234/v1",
"apiKey": "lmstudio",
"api": "openai-responses",
"models": [
{
"id": "deepseek-r1-0528-qwen3-8b",
"name": "DeepSeek R1 0528 Qwen3 8B (LM Studio)",
"reasoning": false,
"input": ["text"],
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 },
"contextWindow": 32768,
#
"maxTokens": 4096
}
]
}
}
}

Then your default model should be set to:

  • lmstudio/deepseek-r1-0528-qwen3-8b

If LM Studio returns an id with slashes (e.g. mlx-community/...), that’s fine — use it verbatim after lmstudio/.

Docs reference (LM Studio provider example): https://docs.openclaw.ai/gateway/local-models

#

4) “Guarantee Anthropic is never used” (global fallbacks, image fallbacks, CLI helpers, and session pins)

Do this in four sweeps:

A) Clear CLI-managed fallback lists

openclaw models fallbacks clear
openclaw models image-fallbacks clear

B) Set global defaults to no fallbacks
In config, ensure:

  • agents.defaults.model.fallbacks: []
  • agents.defaults.imageModel.fallbacks: [] (or set imageModel primary explicitly to your local provider too)

(Important nuance from the config docs: an empty array [] disables global fallbacks.)

#

C) Make sure your primary (and image primary, if used) is not Anthropic
Set primary to your LM Studio model ref.

D) Remove per-session pinned overrides
Two options:

  • Soft: /new in TUI / Control UI (and then pick /model lmstudio/<id> once)
  • Nuclear (guarantees no old pins anywhere): wipe sessions
  • via openclaw reset --scope sessions (use --dry-run first)

If you paste your current:

  • openclaw models status --json
  • plus the relevant parts of ~/.openclaw/openclaw.json (redact secrets)
    …I can tell you exactly where Anthropic is still getting pulled in (it’s almost always either a leftover fallback list, or an old session override).
grizzled marlin
#

Krill, question. Is there a repository for guiding through some of these technical questions, or a way to download a repository to hand to ChatGPT to give it the knowledge it needs to "walk" around setting up openclaw? I know there's the classic "documentation" site, but I didn't know if there was anything else. You've been a great resource but I don't want to continually be a pain, pestering you for information.

grizzled marlin
#

We’re trying to get OpenClaw tool execution working with LM Studio (127.0.0.1:1234) using qwen/qwen3.5-35b-a3b.

Facts from terminal:

  • openclaw agents list --json shows:
    id: main
    model: lmstudio/qwen/qwen3.5-35b-a3b
    bindings: 0
    routes: default
  • CLI on this install does NOT have tools or bindings commands:
    openclaw tools ... => unknown command (suggests hooks)
    openclaw bindings ... => unknown command
  • openclaw hooks list --json works and shows bundled hooks: boot-md, bootstrap-extra-files, command-logger, session-memory.
  • When prompted, the model can output valid tool JSON like:
    {"tool":"read","arguments":{"path":".../HEARTBEAT.md"}}
    but OpenClaw never executes it; it roleplays (“I already read the file…”).
  • We also intermittently see: “Cannot combine structured output constraints with tool grammar (sampling_switch_map)” when LM Studio structured output was on; we turned it off.

Questions:

  1. In this OpenClaw version, what is the correct mechanism/command to attach tool execution to agent main (since CLI has no bindings command but agent status shows bindings:0)?
  2. Is tool execution supposed to happen via hooks/plugins, not ‘tools’?
  3. Does OpenClaw require provider support for OpenAI tools/tool_calls, or does it execute tools independently?
  4. What is the minimal “hello world tool” we should run to confirm real tool execution (not roleplay) on this build?
#

“Help me, Krill-Wan Kenobi. You’re our only hope.”

sand wedgeBOT
#
  1. “bindings: 0” is not about tools.
    In openclaw agents list, “bindings” means routing bindings (multi‑agent routing: which inbound chats get routed to which agent). It has nothing to do with tool execution. There is no “attach tools to agent” step/command in OpenClaw.

So: an agent can have bindings:0 and still execute tools fine.


  1. Tools are not executed via hooks.
    Hooks/plugins are extra behavior around runs (bootstrap, logging, memory helpers, etc.). They don’t “turn on tool execution”.

Tool execution is the normal agent loop: model → tool call → gateway executes tool → tool result → model continues.


#
  1. Yes: tool execution requires provider/tool-calling support.
    OpenClaw will not execute tools just because the model prints JSON in normal text. If the model responds with something like:
{"tool":"read","arguments":{"path":"..."}}

…but that comes back as plain assistant text (not an actual structured tool call), OpenClaw treats it as text and you’ll see “roleplay”.

To get real tool execution, the backend/provider path must support structured tool calling (OpenAI tools/tool_calls, Anthropic tool_use, Ollama native tools, etc.), so OpenClaw can reliably distinguish “tool call” from “random JSON”.

This is exactly why local backends can be tricky: some “OpenAI-compatible” servers accept /v1/chat/completions but ignore tool calling (or only partially implement it).

Docs pointer for tool policy (not hooks): https://docs.openclaw.ai/tools


#
  1. Minimal “hello world” to prove tools are actually executing (not guessed)

Option A (smallest): session_status

Set tools to minimal and ask it to call session_status (this avoids huge tool schemas):

{ tools: { profile: "minimal" } }

Then ask in chat: “Call session_status and paste the result.”

If it can’t execute even that, you don’t have working tool-calling on that provider path.

Option B (best proof): read a random nonce from disk

  1. Create a file with an unguessable value in your workspace, e.g.
#
python3 - <<'PY'
import secrets, pathlib
p = pathlib.Path.home()/".openclaw/workspace/TOOL_NONCE.txt"
p.write_text(secrets.token_hex(16))
print("wrote", p)
PY
  1. Ensure file tools are allowed (example):
{
  tools: {
    profile: "coding",
    allow: ["read"] // or allow group:fs
  }
#
}
  1. Ask: “Use the read tool to read TOOL_NONCE.txt and reply with the exact contents.”

If tools are real, it will return the exact nonce. If it roleplays, tool calls aren’t making it through.


About the LM Studio n_keep >= n_ctx (4096) / “minimum is 16000”

That error is coming from the backend actually running with n_ctx=4096. OpenClaw can’t “fix” that from its side.

You have two practical paths:

  • Fix the backend context (LM Studio / llama.cpp) to ≥16k (realistically 32k+ if you want tools + conversation headroom). Your error shows the initial keep is ~16.8k tokens, so 4k will never work with tools enabled.
  • Reduce OpenClaw’s prompt footprint by restricting tools:
#
  • tools.profile: "minimal" (chat / status only), or
    • explicitly allow only what you need (e.g. only read/write/edit/exec) so it doesn’t inject browser/nodes/etc tool schemas.

Docs on local models: https://docs.openclaw.ai/gateway/local-models

If your goal is “local + tools + fewer weird edge-cases”, the most “known-good” local backend right now is Ollama via OpenClaw’s native Ollama provider, because it’s explicitly tool-aware end-to-end: https://docs.openclaw.ai/providers/ollama


If you paste:

  • openclaw models status --json
  • and a short log snippet around a “tool call that got roleplayed”

…I can tell you whether OpenClaw is (a) not sending tools due to tool policy, or (b) sending them but LM Studio isn’t returning structured tool_calls.

#

That n_keep (16886) >= n_ctx (4096) is 99% LM Studio/llama.cpp actually running the model with a 4k context, regardless of what OpenClaw thinks the model supports. With a tool-enabled OpenClaw prompt, 4k just won’t survive.

A) Reduce OpenClaw’s initial prompt / “minimal mode”

You can shrink the system+tool overhead a lot by restricting tools (this directly reduces the giant tool schema blob that gets injected).

Fast test (chat-only):

openclaw config set tools.profile minimal

Start a new session (/new) and retry. This should drastically reduce prompt size (but: it also means no real tool execution, by design).

If you want some tools but not all: allow only the groups you need (example: fs + runtime) and deny the rest:

openclaw config set --json tools.allow '["group:fs","group:runtime"]'
openclaw config set --json tools.deny  '["group:ui","group:nodes","group:web","group:automation","group:messaging"]'

(Then /new again so the session snapshot refreshes.)

#

Docs: https://docs.openclaw.ai/tools

Important: OpenClaw still basically expects >=16k context for sane operation; “minimal tools” can help you confirm basic chatting, but for real agent+tools work you want 16k/32k+ on the backend.


B) Fix the backend: make LM Studio actually run with big n_ctx

In LM Studio, you need to increase the Context Length / n_ctx for that loaded model (and then reload/restart the server). If it’s stuck at 4096, OpenClaw will keep exploding because the prompt + tool schemas are larger than that.

What I need from you to confirm: a screenshot or paste of the LM Studio server log line that shows n_ctx (or the model “Context Length” setting you’ve configured).


C) Known-good local backend for tools (more deterministic than “OpenAI-compatible”)

If your goal is local + tool calling that actually executes, the most reliable path is Ollama via OpenClaw’s native Ollama provider (it’s explicitly tool-aware end-to-end):

Docs: https://docs.openclaw.ai/providers/ollama

#

Minimal setup:

# install ollama, then pull whatever model you want
ollama list

# enable ollama for OpenClaw (any value works)
openclaw config set models.providers.ollama.apiKey "ollama-local"

# pick a model from `ollama list`
openclaw config set agents.defaults.model.primary "ollama/<your-model-id>"

Quick “is tool execution REAL?” hello-world

  1. Create a file with a random value.
  2. Ask the agent to use the read tool to read it back.
#

If it roleplays, your backend isn’t returning structured tool calls (or tools are denied by policy). If it returns the exact nonce, tools are working.


If you paste (1) openclaw models status --json and (2) your tools.* config (openclaw config get tools --json), I can tell you whether you’re blocked by tool policy vs LM Studio not supporting tool_calls / running 4k ctx.

grizzled marlin
#

Thanks Krill! We will report back. On a side note: Krill, question. Is there a repository for guiding through some of these technical questions, or a way to download a repository to hand to ChatGPT to give it the knowledge it needs to "walk" around setting up openclaw? I know there's the classic "documentation" site, but I didn't know if there was anything else. You've been a great resource but I don't want to continually be a pain, pestering you for information.

sand wedgeBOT
#

Yep — there’s a very straightforward way to “give an LLM the whole world” for OpenClaw setup, and it’s basically: use the git (hackable) install + a local coding agent.

1) The canonical “repository” is the main OpenClaw repo

If you just want the docs folder without installing anything, you can also “Download ZIP” from GitHub and point ChatGPT at it (or paste relevant files).

2) Best workflow: hackable (git) install, then use Claude Code / Codex on that folder

This is the path the FAQ recommends when you’re stuck because the agent can actually read files, inspect config, and reason about your exact installed version:

curl -fsSL https://openclaw.ai/install.sh | bash -s -- --install-method git

Then run your coding agent (Claude Code or OpenAI Codex) in that repo directory and ask it to:

  • read docs/help/faq.md + docs/gateway/troubleshooting.md
#
  • inspect your ~/.openclaw/openclaw.json
  • tail logs / run openclaw status --all

FAQ section that describes this approach: https://docs.openclaw.ai/help/faq (“I’m stuck — what’s the fastest way to get unstuck?”)

3) “Don’t worry about being a pest”

Asking in #help is literally what it’s for. The only thing I’d ask is: when you post logs/config, redact tokens / API keys (gateway token, provider keys, Discord bot token, etc.).

If you hit something that looks like a real bug or missing doc, the escalation path is:
https://github.com/openclaw/openclaw/issues (someone can turn it into a doc fix if needed).

grizzled marlin
#

Wait wait, I'm going to sound like an idiot here, but I've got to ask. I've just been having the normal ChatGPT app give me copy and paste commands into my terminal as we work hand and hand through this setup. should I have been using Codex this entire time? (same company, but different tool)

sand wedgeBOT
#

Not an idiot question. You don’t need Codex to set up OpenClaw — your “ChatGPT app + copy/paste into Terminal” workflow is totally valid.

The difference is mostly how much the AI can see and do:

ChatGPT app (what you’ve been doing)

Good for:

  • Explaining concepts
  • Giving commands to run
  • Reading snippets you paste back (logs/config)

Limits:

  • It can’t directly inspect your filesystem, config, or logs unless you paste them.
  • It’s easier to get into a slow loop of “run this → paste output → run this → paste output”.

Codex / Claude Code (local coding agents)

Good for:

  • “Walk around the repo and docs”
#
  • Search for exact config keys / behavior in source
  • Read your local logs/config files directly (with your permission)
  • Faster debugging when something is weird/edge-case

So: you should switch to Codex/Claude Code when you’re stuck, not necessarily from day 1.

Practical recommendation for OpenClaw setup/debug

  • If things are going smoothly: keep using ChatGPT copy/paste.
  • If you hit issues like your current one (context mismatch, tool-calling weirdness, version-specific behavior): it’s often faster to use a local agent on a git checkout of OpenClaw.

The OpenClaw FAQ explicitly recommends this “use a local agent that can see your machine” approach when you’re stuck: https://docs.openclaw.ai/help/faq

One caution

If you do use Codex/Claude Code, point it at:

  • the OpenClaw git checkout (hackable install), and
  • your ~/.openclaw/openclaw.json + logs
#

…and make sure you don’t accidentally paste/share secrets (tokens, API keys).

If you tell me your comfort level (“I’m fine installing Codex” vs “I want to keep it simple”), I can suggest the lowest-friction path.

grizzled marlin
#

Krill, thanks again for your insight. I've got Codex on my system and it's been amazing. I know use Codex and the ChatGPT app hand in hand to work through this. I'm almost done setting up openclaw how I like it, but I have a quick OpenClaw routing question. We’ve been building a local-first setup on my Mac mini with LM Studio Qwen as the default model, and we just successfully added OpenAI Codex via OAuth (openai-codex provider shows up; models list includes gpt-5.1-codex-mini, gpt-5.3-codex, etc.). We also hardened tool execution: artifact-first workflow, exec allowlists, and a strict Docling skill that runs a wrapper script and verifies outputs on disk.

Now I’m thinking about model balance. I want to use Codex a good amount of the time (probably gpt-5.1-codex-mini most of the time, gpt-5.3-codex for hard cases), but keep Qwen for cheap “heartbeat”/housekeeping stuff. Two questions:
1. Is it a normal pattern in OpenClaw to run “two brains” — one model for tool-planning/tool calls (Codex) and a separate model to rewrite/polish the final user-facing response (local Qwen) — or does that introduce bugs/state issues? What’s the lowest-risk implementation: separate agent, a post-processor skill, routing rules, etc.? Any gotchas with session state or duplicated actions?
2. In your experience, does Codex (5.1/5.3) feel noticeably “boring” in personality compared to chatty models, or is it mostly fine? If it’s dry, do people commonly do a local “voice pass” at the end?

Appreciate any practical guidance / known failure modes.

sand wedgeBOT
#
  1. “Two brains” (planner/executor vs writer) is a totally normal goal, but it’s not a single built-in “2-pass” pipeline in OpenClaw where one model plans/tools and a second model automatically rewrites the same turn.

The lowest-risk way to get the behavior you want is to make the separation explicit, using the patterns OpenClaw already supports:

  • Use different models by job type (supported, stable):

  • If you specifically want “executor Codex, writer Qwen”:

    • The safe pattern is: Codex does the tool work and produces an “artifact” (draft/notes/answer bullets) → then a writer pass rewrites without tools.
    • Do that writer pass as either:
      • a separate agent you switch to when you want polish (cleanest isolation), or
      • a sub-agent whose job is “rewrite/polish only” (also isolated), ideally with tools disabled/locked down so it can’t “decide to act” again.
    • Biggest gotcha: don’t let both models have tool access for the same task, or you’ll eventually see duplicated actions / “helpful” extra calls.

If your main objective is “Codex most of the time, but cheap heartbeat”: you don’t need any fancy routing. Just set agents.defaults.model.primary to Codex, and set agents.defaults.heartbeat.model to your local model. That’s a very common setup.

#
  1. Codex “personality”
    Yeah — in practice Codex-style models often read as more utilitarian / terse than chatty generalists. You can push tone a lot via SOUL.md / style instructions, but if you want consistently warmer output, a final “voice pass” is common.

What people do to avoid weirdness:

  • Keep the voice pass strictly rewrite-only (“preserve meaning, don’t add claims, don’t change any code/commands”).
  • Run it with tools effectively off (so the writer can’t trigger actions).

If you paste (redacted) snippets of how you’ve set up agents + heartbeat right now, I can suggest the cleanest place to put the heartbeat/subagent model overrides without stepping on your existing tool allowlists.

grizzled marlin
#

Great info. A few more questions before we run off and start working on what you've told us:

  1. Exact config keys + example snippet (2026.3.2):
    “Can you paste a minimal openclaw.json example showing agents.defaults.model.primary, agents.defaults.model.fallbacks, and agents.defaults.heartbeat.model?”
    2. How to make the writer pass tool-proof:
    “What’s the cleanest way to create a ‘writer-only’ agent/sub-agent with tools effectively off? Is it tools.deny: ['*'] per-agent, or is there a preferred ‘no-tools profile’?”
    3. Where heartbeats run + what they cost:
    “Do heartbeats actually hit the LLM every time by default, or can they be configured to be tool-only / no-model? Any recommended heartbeat interval for local setups?”
sand wedgeBOT
#
  1. Minimal config snippet (JSON5) for agents.defaults.model.primary + fallbacks + agents.defaults.heartbeat.model
// ~/.openclaw/openclaw.json
{
  agents: {
    defaults: {
      model: {
        primary: "providerA/modelA",
        fallbacks: [
          "providerB/modelB",
          // "providerC/modelC",
        ],
      },

      // Heartbeat is configured separately from the main model:
#
      heartbeat: {
        every: "30m",          // default cadence is 30m if you don't set it
        model: "providerX/modelCheap", // <- model override for heartbeat runs
        target: "none",        // run heartbeat but don't deliver messages
      },
    },
  },
}

Gotcha: if you also set agents.defaults.models (model allowlist / aliases), make sure the models you reference in primary/fallbacks are present there too, otherwise you’ll hit “Model … is not allowed”. Docs: https://docs.openclaw.ai/concepts/models

  1. “Writer-only” / tool-proof agent: cleanest way

Yes, you can do this per-agent. The two clean options are:

#
  • Preferred / simple: set the writer agent’s tool profile to minimal (only session_status)
  • Hard lock: deny everything with deny: ["*"]

Example writer agent that can chat but can’t call tools:

{
  agents: {
    list: [
      {
        id: "writer",
        // optionally set its default model too
        // model: { primary: "your-local-provider/your-local-model" },

        tools: {
          profile: "minimal",
#
          // or: deny: ["*"], // stricter: blocks *all* tools
        },
      },
    ],
  },
}

Docs:

  1. Where heartbeats run + what they cost (and “tool-only / no-model?”)
  • A heartbeat is a full agent turn (so: it’s an LLM call when it runs). There isn’t a “tool-only heartbeat” mode, because tools are invoked by the model.
  • You can effectively make it “no-model” by disabling heartbeat:
#

Recommended intervals for local setups:

  • If heartbeat is just “housekeeping / keep-warm”: start at 1–2h.
  • If you actively like frequent nudges: 30m is fine, especially if the heartbeat model is cheap (or local) and target: "none" unless there’s an alert.

If you want, paste your current agents.defaults.* blocks (redact tokens) and I’ll sanity-check the exact placement (and whether anything you’ve set—like agents.defaults.models—will unintentionally block the fallback chain).

grizzled marlin
#

Hey Krill — quick heartbeat + Discord routing question (OpenClaw 2026.3.2).

Heartbeat is running and confirmed hitting my local model:
agents.defaults.heartbeat = { every: "1m", model: "lmstudio/qwen/qwen3.5-35b-a3b", target: "last" }
LM Studio logs show Qwen is invoked every minute now.

Goal: have heartbeat visibly post to a specific Discord channel (e.g., #heartbeat) so we can confirm delivery + routing.

Issue 1: trying to set agents.defaults.heartbeat.target to something like discord:#<channelId> fails validation (“unknown heartbeat target”), so heartbeat.target doesn’t seem to accept Discord routing.

Issue 2: I tried using HEARTBEAT.md to force a tool send to Discord with explicit target channel:<id>, but nothing appears in Discord. Logs show:
[tools] message failed: Action send requires a target.
So the message tool is firing but the target isn’t being accepted/parsed.

Questions:
1. What’s the correct way to force heartbeat output to a specific Discord channel? (Is it only via HEARTBEAT.md + message tool?)
2. What exact tool-call format does the heartbeat agent reliably understand for Discord sends? (e.g., message(action="send", channel="discord", target="channel:<id>", text="HB_OK")?)
3. Is there any extra config needed so heartbeat has the message tool enabled / can deliver to Discord (tool profile, allowlist, guild config, etc.)?

We can mention-gate regular guild replies, but this is specifically about outbound heartbeat delivery to one known channel.

sand wedgeBOT
#
  1. Force heartbeat output to a specific Discord channel (no tool call needed)

heartbeat.target only accepts a channel name ("discord"), not an inline destination. To pin it to a specific channel, use target: "discord" + to::

{
  agents: {
    defaults: {
      heartbeat: {
        every: "1m",
        model: "lmstudio/qwen/...",   // your local model
        target: "discord",
        to: "channel:123456789012345678", // <-- Discord channel target (note prefix)
      },
    },
  },
#
}

Two very common “why did nothing show up?” gotchas:

  • If your heartbeat reply is exactly HEARTBEAT_OK, delivery is often suppressed unless you opt in to showing OKs (see below).
  • target: "last" will deliver to whatever the gateway thinks the “last external channel” was for that session — it might not be Discord.

To make “OK” heartbeats visible, set:

{
  channels: {
    defaults: { heartbeat: { showOk: true } }, // or set just discord if you prefer
    // discord: { heartbeat: { showOk: true } }, // also supported per heartbeat docs
#
  },
}

Docs: https://docs.openclaw.ai/gateway/heartbeat (target/to + showOk behavior)

  1. If you do want HEARTBEAT.md to send via the message tool: exact, working params

The error you saw is because the tool call didn’t include target (or it used text/to instead of the actual fields).

The message tool wants target and message:

  • action: "send"
  • channel: "discord" (recommended if you have multiple channels)
  • target: "channel:<id>" (Discord strongly prefers the explicit prefix)
  • message: "..."
#

Example you can literally instruct in HEARTBEAT.md:

“Call the message tool with: action=send, channel=discord, target=channel:123…, message=HB_OK”

(Your example text="HB_OK" won’t work — the field name is message.)

  1. Extra config needed?

Usually no, but check these if it still doesn’t post:

  • Tool policy: if you globally set a restrictive tools.profile / tools.allow, make sure message (or group:messaging) is allowed for the agent running heartbeat. Otherwise the model won’t even be offered the tool.
  • Discord action gates: channels.discord.actions.messages must be enabled (it is enabled by default).
  • Bot permissions + channel allowlist: make sure the bot has “Send Messages” in #heartbeat, and if you’re using groupPolicy: "allowlist" with a per-guild channels: block, ensure heartbeat is included there (inbound policy, but worth keeping consistent).

If you paste your channels.discord block (redact token) + the exact HEARTBEAT.md snippet you tried, I can point out exactly which field name/format is off.