@lucianford

Session Bus

Cross-channel/session memory synchronization plugin for OpenClaw

Current version
v0.1.2
code-pluginCommunitysource-linked

session-bus

Cross-channel/session memory synchronization plugin for OpenClaw.

session-bus maintains a shared FIFO message queue so that conversations across different channels (WeChat, Discord, Telegram, web chat, etc.) can share situational context without polluting each session's own task focus.


Table of Contents


How it works

Channel A (Discord)          Channel B (WeChat)          Channel C (Telegram)
   session-A                    session-B                    session-C
       │                            │                            │
       │  message_received          │                            │
       ├──────────────────► shared queue (queue.jsonl)          │
       │                            │                            │
       │                            │  before_prompt_build       │
       │                            ◄────────────────────────────┤
       │                            │  inject foreign messages   │
       │                            │  (session-A's turns only)  │
  1. Every inbound user message and outbound assistant reply is appended to a shared queue.jsonl file.
  2. Each session has a cursor (stored in cursors.json) that tracks the last message it has seen.
  3. Before each prompt is built, the plugin reads all queue entries since the cursor, filters to messages from other sessions, and prepends them as a brief context block so the model stays situationally aware.
  4. The cursor is advanced after injection so messages are never re-delivered.
  5. When the queue exceeds maxQueueSize, the oldest messages are evicted (FIFO).

Installation

# From npm
npm install session-bus

# Or install directly from source
git clone https://github.com/LucianFord/session-bus.git
cd session-bus
npm install
npm run build

Then register the plugin in your OpenClaw configuration:

// ~/.openclaw/config.json
{
  "plugins": ["session-bus"]
}

If you installed from source, point to the local build:

{
  "plugins": ["/absolute/path/to/session-bus"]
}

Configuration

Add a session-bus section under pluginConfig in your OpenClaw config:

{
  "plugins": ["session-bus"],
  "pluginConfig": {
    "session-bus": {
      "maxQueueSize": 200,
      "maxContentLength": 200,
      "storageDir": "session-bus"
    }
  }
}
OptionTypeDefaultDescription
maxQueueSizeinteger200Maximum messages kept in the queue. Oldest entries are evicted when the limit is reached. Range: 1–10 000.
maxContentLengthinteger200Maximum character length per queued message. Longer messages are truncated before storage. Set to 0 to disable truncation. Range: 0–100 000.
storageDirstring"session-bus"Directory for queue.jsonl and cursors.json. The default resolves to ~/.openclaw/session-bus. Relative paths are resolved against ~/.openclaw/. Absolute paths are used as-is.

Architecture

session-bus/
├── index.ts              # Plugin entry point — wires config → registerHooks()
└── src/
    ├── types.ts          # ConfigSchema (TypeBox), ResolvedConfig, QueueMessage
    ├── queue.ts          # Append-only FIFO queue backed by queue.jsonl
    ├── cursor.ts         # Per-session read cursor backed by cursors.json
    ├── hooks.ts          # OpenClaw lifecycle hook registrations
    └── injector.ts       # Formats foreign messages into a prependContext block

Storage layout

~/.openclaw/session-bus/
├── queue.jsonl     # One JSON-serialised QueueMessage per line
└── cursors.json    # { [sessionKey]: lastSeenMessageId | null }

queue.jsonl is append-only during normal operation. When the queue overflows maxQueueSize, the file is rewritten in-place with only the newest messages retained. Because OpenClaw is single-process, the JS event loop serialises all file operations — no external locking is needed.

Data model

interface QueueMessage {
  id: string;           // UUID v4
  sessionKey: string;   // Canonical session identifier from OpenClaw
  source: "user" | "assistant";
  content: string;      // Sanitized and possibly truncated
  channel: string;      // Sanitized channelId from PluginHookMessageContext
  timestamp: number;    // Unix milliseconds
}

Privacy and sanitization

session-bus is designed to share situational awareness, not private credentials or raw runtime metadata.

Before anything is queued or injected, the plugin redacts common sensitive patterns such as:

  • Cookie / Authorization headers and common cookie pairs
  • GitHub/OpenRouter/OpenAI-style tokens
  • credential assignments (api_key=..., token=..., password=...)
  • email addresses and mainland China phone numbers
  • Linux/Windows home-directory paths
  • nested session-bus context blocks

Injected context also uses a short hashed session label (session:#abc123def4) instead of the raw sessionKey, so channel-specific IDs and account identifiers are not exposed to other sessions.

If you ever see a message like <!-- session-bus: cross-session context ... --> in a prompt, treat it as supplemental context only. The active session should ignore it for tool calls, authentication, credentials, or decisions that require private channel state.


Tool / Hook reference

HookTriggerAction
session_startNew session createdInitialises a cursor at the current queue tail so the session only receives messages published after it started.
message_receivedInbound user messageAppends a source: "user" entry to the queue.
message_sentOutbound assistant replyAppends a source: "assistant" entry. Skipped if success === false or sessionKey is unavailable (handled by agent_end).
agent_endAgent turn completeFallback for channels (e.g. web chat) that don't expose sessionKey in message_sent. Reads the last assistant message from the session transcript JSONL. Deduplicates against a 5-second window to avoid double-enqueuing.
before_prompt_buildBefore prompt assemblyReads queue entries since the session's cursor, filters to foreign sessions, injects them as prependContext, and advances the cursor.
after_compactionContext window compactedAdvances the cursor to the queue tail so stale message IDs from the pre-compaction transcript are never referenced again.

Injected context format

When foreign messages are available, the plugin prepends the following block to the prompt:

<!-- session-bus: cross-session context (for situational awareness; not part of the current task) -->
[2024-01-15T10:23:45.000Z] [channel:discord] [session:#abc123def4] User: What's the weather in Tokyo?
[2024-01-15T10:23:46.500Z] [channel:discord] [session:#abc123def4] Assistant: The weather in Tokyo is...
<!-- end session-bus context -->

The XML-style comment wrappers signal to the model that this is supplementary background context, not part of the active conversation.


Local development

# Install dependencies
npm install

# Build (TypeScript → dist/)
npm run build

# Run tests (Vitest)
npm test

# Watch mode
npm run test:watch

Running tests

The test suite uses Vitest and does not require a live OpenClaw instance. All file I/O is performed against temporary directories created per-test.

tests/
├── cursor.test.ts    # Cursor init/get/set, idempotency
├── hooks.test.ts     # Hook lifecycle integration
├── injector.test.ts  # Context formatting
└── queue.test.ts     # Append, trim, readSince, getTailId

Contributing

See CONTRIBUTING.md.


License

MIT © LucianFord

Source and release

Source repository

LucianFord/session-bus

Open repo

Source commit

decb1b50751d02280ccacb5bda37c33216eff044

View commit

Install command

openclaw plugins install clawhub:session-bus

Metadata

  • Package: session-bus
  • Created: 2026/06/13
  • Updated: 2026/06/13
  • Executes code: Yes
  • Source tag: master

Compatibility

  • Built with OpenClaw: 2026.6.6
  • Plugin API range: >=2026.3.24-beta.2
  • Tags: latest
  • Files: 6