This document explains what must change on the OpenClaw side so Function Router can receive a stable session identifier on every request.
OpenClaw Gateway already knows the WebSocket session id from chat.send.params.sessionKey, but older runtime bundles do not automatically forward that value to the OpenAI-compatible provider request sent to Function Router.
Without the patch:
- FR still works as a normal tool router
- FR often falls back to a default session identity
- session-aware features degrade:
fr_context_historyfr_context_preserve- AutoOpenClaw exact matching of
/v1/tool_historybysession_key
With the patch:
- FR receives a stable
x-openclaw-session-keyheader - FR can isolate Qwen context by session
- AutoOpenClaw can match tool history strictly by session
Patch OpenClaw if you want any of these:
- strict session isolation between conversations
- same-session Qwen context retention
- reliable AutoOpenClaw FR history matching by
session_key
If you do not patch OpenClaw, use this safer FR configuration instead:
"fr_context_history": { "enabled": false },
"fr_context_preserve": { "enabled": false }That compatibility mode still allows FR tool routing, but session-aware behavior is degraded.
Forward:
- source:
chat.send.params.sessionKey - target: HTTP header
x-openclaw-session-key
FR should then read that header and use it as the request session id.
The real runtime is usually in the global install, not the source repository:
/usr/local/nodejs/lib/node_modules/openclaw/dist/
OpenClaw bundle names change by version, so do not rely on one exact filename. Search for stable anchors such as:
activeSession.agent.streamFn = streamSimpleapplyExtraParamsToAgent(shouldInjectOllamaCompatNumCtx(pi-embedded-*.js- sometimes
reply-*.js - sometimes
plugin-sdk/dispatch-*.js
Inject the header immediately before the provider request is made by wrapping the existing streamFn.
const openclawSessionKeyHeaderValue = typeof params.sessionKey === "string" ? params.sessionKey.trim() : "";
if (openclawSessionKeyHeaderValue) {
const inner = activeSession.agent.streamFn;
activeSession.agent.streamFn = (model, context, options) => inner(model, context, {
...options,
headers: {
...options?.headers,
"x-openclaw-session-key": openclawSessionKeyHeaderValue,
},
});
}- smallest behavioral change
- no OpenAI-compatible schema changes
- preserves existing provider headers
- preserves auth headers
- keeps the session id transport explicit and easy to debug
Function Router should derive session id in this order:
x-openclaw-session-keyheader- body fields such as
sessionKey,session_key,sessionId,session_id - nested fallbacks like
metadataorextra_body - final fallback default
- Restart Function Router
- Restart OpenClaw Gateway
- Send one real OpenClaw request through Gateway
- Inspect FR logs
Expected result:
header x-openclaw-session-key=agent:main:... session_key=agent:main:...
If FR still logs session_key=default, the most likely cause is that you patched the wrong OpenClaw bundle for the current version.
Use this quick checklist:
- Send one real request through OpenClaw Gateway to FR
- Check FR logs or
/v1/tool_history - Look for one of these outcomes
Patched:
- FR logs show a non-empty
x-openclaw-session-key - derived
session_keyis a real value such asagent:main:... /v1/tool_historyentries record real per-requestsession_keyvalues
Not patched:
- FR logs show empty header values
- derived
session_keystaysdefault - repeated requests from different OpenClaw sessions collapse into the same fallback session bucket
A practical check is:
curl -s "http://127.0.0.1:18790/v1/tool_history?limit=5" | jq '.entries[] | {timestamp, session_key, user_message}'If recent entries consistently show real OpenClaw session ids, the patch is active. If they all stay default, OpenClaw is still unpatched or the wrong runtime bundle was modified.
If FR and AutoOpenClaw are updated but OpenClaw is not patched:
- exact session matching may fail
- AutoOpenClaw may need to fall back to older prompt-prefix-based history matching
- this fallback is only recommended together with:
fr_context_history=falsefr_context_preserve=false
Otherwise old history attribution noise can come back.