Skip to content

feat(android): Multiple nodes session context isolated from each other#53752

Merged
obviyus merged 5 commits intoopenclaw:mainfrom
lixuankai:main
Mar 28, 2026
Merged

feat(android): Multiple nodes session context isolated from each other#53752
obviyus merged 5 commits intoopenclaw:mainfrom
lixuankai:main

Conversation

@lixuankai
Copy link
Copy Markdown
Contributor

@lixuankai lixuankai commented Mar 24, 2026

Summary

My family have two Android Device connected to the same gateway.
Due to the same session agent:main:main, we can see everyone 's chat history.

Like this,

  1. I can see Find X7 Ultra chat history on OnePlus 13T
  2. I can see OnePlus 13T chat history on Find X7 Ultra

This is a bad experience. When communicating with OpenClaw, the context interferes with each other

Image

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

User-visible / Behavior Changes

Users can see independent sessions of multiple nodes at the top of chatSheetContent, and the context does not interfere with each other when communicating with OpenClaw

Security Impact (required)

  • New permissions/capabilities? (No)
  • Secrets/tokens handling changed? (No)
  • New/changed network calls? (No)
  • Command/tool execution surface changed? (No)
  • Data access scope changed? (No)

Repro + Verification

Environment

  • OS: Windows host
  • Runtime/container: Android Gradle app module
  • Model/provider: n/a
  • Integration/channel (if any): Android Node
  • Relevant config (redacted):none

Steps

  1. Build Android Kotlin sources and targeted Android unit tests.
  2. [device1] Install Android apk and connect openclaw gateway
  3. [device2] Install Android apk and connect openclaw gateway
  4. Two devices chatting with OpenClaw without interfering with each other

Expected

Two devices chatting with OpenClaw without interfering with each other

Actual

Matches expected.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

And We can see every node session on WebUI.
Like this
[agent:main:node-c682443feaa8]
[agent:main:node-6bc70a46c7ea]

image

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios: Multiple devices chat with OpenClaw without interfering with each other
  • Edge cases checked: NA
  • What you did not verify: NA

Compatibility / Migration

  • Backward compatible? (No)
  • Config/env changes? (No)
  • Migration needed? (No)

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: NA
  • Files/config to restore: NA
  • Known bad symptoms reviewers should watch for: NA

Risks and Mitigations

none

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 24, 2026

Greptile Summary

This PR scopes each Android device to its own session by deriving a unique key (agent:main:node-<shortDeviceId>) from the device identity, replacing the shared hardcoded "agent:main:main" key that caused multiple nodes on the same gateway to share conversation history. The gateway-side session machinery is unchanged; only the Android client changes how it picks its default session key.

Changes made:

  • NodeRuntime.kt: Adds generateNodeSessionKey() which reads the device's identity, takes the first 8 hex chars of its ID, and returns agent:main:node-<shortDeviceId>. This is used as the initial value of _mainSessionKey and as the reset target on disconnect. The key format starts with "agent:" so isCanonicalMainSessionKey (in SessionKey.kt) correctly treats it as canonical.
  • NodeRuntime.kt: Applies the session key immediately at ChatController construction via .also { it.applyMainSessionKey(...) }, fixing a gap where the operator-session ChatController was initialised before the key was set.
  • ChatController.kt: Removes the guard if (_sessionKey.value != "main") return from applyMainSessionKey, because the default key is no longer the literal "main". However, this removal also strips the protection against overriding a user-switched session on reconnect — see inline comment.

Issues found:

  • P1 (logic) — Removing the guard in ChatController.applyMainSessionKey regresses session-switch persistence: a user who switches to a non-default session and then experiences a connection drop will have their active session silently reset to the device default on reconnect.
  • P2 (style) — Using only 8 hex characters of the device ID gives a 32-bit namespace; consider increasing to 12–16 to avoid collisions in larger gateway deployments.

Confidence Score: 3/5

  • The core device-isolation fix is correct, but removing the session-switch guard in ChatController introduces a regression that disrupts users who manually navigate to non-default sessions during a connection blip.
  • The primary goal (per-device session isolation) is achieved correctly and the canonical-key detection works as expected. However, removing the if (_sessionKey.value != "main") return guard breaks session-switch persistence across reconnects — a real user-facing regression that affects anyone using the session-switching feature. That warrants holding at 3 until the guard is replaced with an equivalent that handles the new non-literal default key. The short device-ID prefix is a lower-priority style issue.
  • apps/android/app/src/main/java/ai/openclaw/app/chat/ChatController.kt — the applyMainSessionKey function needs a replacement guard to preserve manually-switched sessions across reconnects.

Comments Outside Diff (1)

  1. apps/android/app/src/main/java/ai/openclaw/app/chat/ChatController.kt, line 81-87 (link)

    P1 Removed guard breaks session-switch persistence across reconnects

    The old guard if (_sessionKey.value != "main") return prevented applyMainSessionKey from overriding a session the user had explicitly selected via switchSession. Removing it means: if a user switches to a non-default session (e.g. "work") and the gateway connection drops and reconnects, applyMainSessionKey will be called again with "agent:main:node-XXXXXXXX" and forcibly reset their active session back to the device default.

    The guard was removed because the default key is no longer the literal "main" — it's the device-specific generateNodeSessionKey() value. The fix is to track the canonical default key inside ChatController and only override when the current session is still at that default:

    fun applyMainSessionKey(mainSessionKey: String) {
        val trimmed = mainSessionKey.trim()
        if (trimmed.isEmpty()) return
        if (_sessionKey.value == trimmed) return
        // Only override if we're still on whatever key was last set as the "main" key,
        // i.e. the user has not manually switched away to a different session.
        if (_sessionKey.value != _lastAppliedMainKey) return
        _lastAppliedMainKey = trimmed
        _sessionKey.value = trimmed
        scope.launch { bootstrap(forceHealth = true, refreshSessions = true) }
    }

    where _lastAppliedMainKey is initialized to "main" (matching _sessionKey's initial value) and updated in load() as well.

    Without this, any user who is actively viewing an older session when a connection blip occurs will be silently redirected back to the node-default session.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/android/app/src/main/java/ai/openclaw/app/chat/ChatController.kt
    Line: 81-87
    
    Comment:
    **Removed guard breaks session-switch persistence across reconnects**
    
    The old guard `if (_sessionKey.value != "main") return` prevented `applyMainSessionKey` from overriding a session the user had explicitly selected via `switchSession`. Removing it means: if a user switches to a non-default session (e.g. `"work"`) and the gateway connection drops and reconnects, `applyMainSessionKey` will be called again with `"agent:main:node-XXXXXXXX"` and forcibly reset their active session back to the device default.
    
    The guard was removed because the default key is no longer the literal `"main"` — it's the device-specific `generateNodeSessionKey()` value. The fix is to track the canonical default key inside `ChatController` and only override when the current session is still at that default:
    
    ```kotlin
    fun applyMainSessionKey(mainSessionKey: String) {
        val trimmed = mainSessionKey.trim()
        if (trimmed.isEmpty()) return
        if (_sessionKey.value == trimmed) return
        // Only override if we're still on whatever key was last set as the "main" key,
        // i.e. the user has not manually switched away to a different session.
        if (_sessionKey.value != _lastAppliedMainKey) return
        _lastAppliedMainKey = trimmed
        _sessionKey.value = trimmed
        scope.launch { bootstrap(forceHealth = true, refreshSessions = true) }
    }
    ```
    where `_lastAppliedMainKey` is initialized to `"main"` (matching `_sessionKey`'s initial value) and updated in `load()` as well.
    
    Without this, any user who is actively viewing an older session when a connection blip occurs will be silently redirected back to the node-default session.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: apps/android/app/src/main/java/ai/openclaw/app/chat/ChatController.kt
Line: 81-87

Comment:
**Removed guard breaks session-switch persistence across reconnects**

The old guard `if (_sessionKey.value != "main") return` prevented `applyMainSessionKey` from overriding a session the user had explicitly selected via `switchSession`. Removing it means: if a user switches to a non-default session (e.g. `"work"`) and the gateway connection drops and reconnects, `applyMainSessionKey` will be called again with `"agent:main:node-XXXXXXXX"` and forcibly reset their active session back to the device default.

The guard was removed because the default key is no longer the literal `"main"` — it's the device-specific `generateNodeSessionKey()` value. The fix is to track the canonical default key inside `ChatController` and only override when the current session is still at that default:

```kotlin
fun applyMainSessionKey(mainSessionKey: String) {
    val trimmed = mainSessionKey.trim()
    if (trimmed.isEmpty()) return
    if (_sessionKey.value == trimmed) return
    // Only override if we're still on whatever key was last set as the "main" key,
    // i.e. the user has not manually switched away to a different session.
    if (_sessionKey.value != _lastAppliedMainKey) return
    _lastAppliedMainKey = trimmed
    _sessionKey.value = trimmed
    scope.launch { bootstrap(forceHealth = true, refreshSessions = true) }
}
```
where `_lastAppliedMainKey` is initialized to `"main"` (matching `_sessionKey`'s initial value) and updated in `load()` as well.

Without this, any user who is actively viewing an older session when a connection blip occurs will be silently redirected back to the node-default session.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt
Line: 196-200

Comment:
**Short device-ID prefix may collide in larger deployments**

`deviceId` is derived from a SHA-256 hash (`sha256Hex` in `DeviceIdentityStore`). Taking only the first 8 hex characters yields a 32-bit namespace (~4 billion combinations). In a small household this is negligible, but at scale — or if many nodes register against the same gateway — two devices could share the same prefix and end up in the same session, reproducing the exact bug this PR is fixing.

Consider increasing the prefix length to at least 12–16 hex characters to give a 48–64 bit namespace, which is effectively collision-free in any realistic deployment:

```suggestion
    val shortDeviceId = deviceId.take(12)
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "[feat]Multiple nodes session context iso..." | Re-trigger Greptile

Comment thread apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 09155e198f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt Outdated
Comment thread apps/android/app/src/main/java/ai/openclaw/app/chat/ChatController.kt Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5c4ea8983a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt Outdated
Comment thread apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt Outdated
@sibbl
Copy link
Copy Markdown
Contributor

sibbl commented Mar 25, 2026

How about letting users create new sessions or defining the ID themselves? I'm specifically thinking about how to deal with this in the Wear OS PR. People will want to share the same session with the app or use their own session, or have a mixture of both.

I think the solution should be more advanced, but this is a great start to separate this!

I personally still think the android app is a mixture of a node and a channel/provider. Sensors etc are definitely a node capability, but the chat works more like a channel/provider like Telegram or WhatsApp are, and could offer similar ways to create chats/groups/threads.

Copy link
Copy Markdown
Contributor Author

@lixuankai lixuankai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@lixuankai
Copy link
Copy Markdown
Contributor Author

How about letting users create new sessions or defining the ID themselves? I'm specifically thinking about how to deal with this in the Wear OS PR. People will want to share the same session with the app or use their own session, or have a mixture of both.

I think the solution should be more advanced, but this is a great start to separate this!

I personally still think the android app is a mixture of a node and a channel/provider. Sensors etc are definitely a node capability, but the chat works more like a channel/provider like Telegram or WhatsApp are, and could offer similar ways to create chats/groups/threads.

@sibbl I fully agree with your judgment that the solution should be more advanced and that Android applications mix Node and Channel responsibilities. The current change is to lay the foundation and eliminate implicit entanglement in the conversation.
On this basis, we can gradually support:

  1. User defined session ID and creation strategy
  2. Mixed mode of multi device sharing/independent sessions

In addition, the current Android application will display all conversation lists for users to choose from. Can this design meet the requirements you mentioned People will want to share the same session with the app or use their own session.

But I'm not sure how to interact with users on the WearOS screen and display a list of all sessions?

@sibbl
Copy link
Copy Markdown
Contributor

sibbl commented Mar 25, 2026

@lixuankai yep, the foundation is what we need. Just sharing my thoughts here, as my Wear OS PR has been open for a while and I watch new PRs since I need to also migrate them over until my PR is reviewed.

But I'm not sure how to interact with users on the WearOS screen and display a list of all sessions?

As of now, the selected session can be changed with a swipe and 2 taps.
However, in my daily usage I see that I may want to have multiple sessions - like a fast one with a smaller model (like setting up reminders etc) and a larger one for more complex stuff (e.g. for doing research on the go). Maybe it may make sense to allow users to pin favorite sessions and be able to swipe between them easily.

However, this depends heavily on where we want to get to with the Android app and its session management. Will it just have one session per node? Per device? Will users be able to quickly create new sessions? Can users even switch between agents? Lots of questions from a user's perspective, which are already solved on channel/provider level where I can configure which agents match which e.g. telegram group etc.

@lixuankai
Copy link
Copy Markdown
Contributor Author

@obviyus please check , thank you

@obviyus
Copy link
Copy Markdown
Contributor

obviyus commented Mar 28, 2026

I think Android should default to a private per-device chat session, with shared sessions exposed explicitly as selectable threads instead of implicitly inheriting the gateway main session. That keeps the UX predictable, avoids cross-device context bleed by default, and gives Wear OS / multi-device follow-up work a clean base: one deterministic device-owned default session, optional shared sessions by user choice.

@obviyus obviyus self-assigned this Mar 28, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c4dd3900c1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

val sessionCfg = config?.get("session").asObjectOrNull()
val mainKey = normalizeMainKey(sessionCfg?.get("mainKey").asStringOrNull())
applyMainSessionKey(mainKey)
syncMainSessionKey(gatewayDefaultAgentId)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid deriving main session from stale cached default agent

refreshBrandingFromGateway now forces syncMainSessionKey(gatewayDefaultAgentId), but gatewayDefaultAgentId is only refreshed later in refreshAgentsFromGateway and is never cleared on disconnect. In a delayed/failed agents.list refresh (including after switching gateways), this can reapply an old agent id and overwrite _mainSessionKey, so chat/mic requests are sent under the wrong session key until a future successful agents refresh.

Useful? React with 👍 / 👎.

@obviyus obviyus merged commit f0a57fa into openclaw:main Mar 28, 2026
25 of 26 checks passed
@obviyus
Copy link
Copy Markdown
Contributor

obviyus commented Mar 28, 2026

Landed on main.

Thanks @lixuankai.

alexcode-cc pushed a commit to alexcode-cc/clawdbot that referenced this pull request Mar 30, 2026
* [feat]Multiple nodes session context isolated from each other

* feat(android): Multiple nodes session context isolated from each other

* feat(android): Multiple nodes session context isolated from each other

* feat(android): Multiple nodes session context isolated from each other

* fix(android): isolate device chat defaults

---------

Co-authored-by: lixuankai <lixuankai@oppo.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
alexjiang1 pushed a commit to alexjiang1/openclaw that referenced this pull request Mar 31, 2026
* [feat]Multiple nodes session context isolated from each other

* feat(android): Multiple nodes session context isolated from each other

* feat(android): Multiple nodes session context isolated from each other

* feat(android): Multiple nodes session context isolated from each other

* fix(android): isolate device chat defaults

---------

Co-authored-by: lixuankai <lixuankai@oppo.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
* [feat]Multiple nodes session context isolated from each other

* feat(android): Multiple nodes session context isolated from each other

* feat(android): Multiple nodes session context isolated from each other

* feat(android): Multiple nodes session context isolated from each other

* fix(android): isolate device chat defaults

---------

Co-authored-by: lixuankai <lixuankai@oppo.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
* [feat]Multiple nodes session context isolated from each other

* feat(android): Multiple nodes session context isolated from each other

* feat(android): Multiple nodes session context isolated from each other

* feat(android): Multiple nodes session context isolated from each other

* fix(android): isolate device chat defaults

---------

Co-authored-by: lixuankai <lixuankai@oppo.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
* [feat]Multiple nodes session context isolated from each other

* feat(android): Multiple nodes session context isolated from each other

* feat(android): Multiple nodes session context isolated from each other

* feat(android): Multiple nodes session context isolated from each other

* fix(android): isolate device chat defaults

---------

Co-authored-by: lixuankai <lixuankai@oppo.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants