April 22, 2026 • Version: v2026.3.1

[Direktnachrichten ohne Absenderzuordnung im LLM-Kontext] - DM Messages Lack Sender Attribution in LLM Context (BodyForAgent)

Direktnachrichten-Unterhaltungen erscheinen als undifferenzierte Textströme für das LLM, da die Absenderidentifikation nicht durch die eingehende Pipeline propagiert wird und BodyForAgent Absenderpräfixe fehlen.

🔍 Symptome

Primäre Manifestation

Wenn der Agent DM-Konversationen verarbeitet, erhält das LLM Nachrichten ohne Absenderkontext. Betrachten wir diesen DM-Austausch:

Was das LLM tatsächlich empfängt (aktuelles Verhalten):

Hey, are you free tonight?
Yes, I'll be there at 8
Great, see you then!
Looking forward to it!

Was das LLM empfangen sollte (erwartetes Verhalten):

[Alice]: Hey, are you free tonight?
[Agent]: Yes, I'll be there at 8
[Alice]: Great, see you then!
[Agent]: Looking forward to it!

Technische Beobachtungen

Das fromMe-Flag existiert auf Protocol-Adapter-Ebene, ist aber dem LLM nicht verfügbar:

// Auf Adapter-Ebene (WhatsApp-Beispiel):
msg.key.fromMe  // Boolean - identifiziert Absender korrekt

// Auf LLM-Eingabe-Ebene (BodyForAgent):
params.msg.body  // "Hey, are you free tonight?" — kein Absenderkontext

Diagnosebefehl

So inspizieren Sie den aktuellen Zustand von inboundMessage:

# Debug-Logging aktivieren, um eingehende Nachrichtenstruktur zu beobachten
DEBUG=openclaw:inbound node agent.js

# Erwartete Debug-Ausgabe mit fehlenden Feldern:
# inboundMessage {
#   body: "Hey, are you free tonight?",
#   from: "+1234567890",
#   pushName: "Alice",
#   chatType: "direct",
#   // fromMe: undefined  ← FEHLT
#   // senderName: undefined  ← NICHT PROPAGIERT
# }

Versionsspezifisches Verhalten

Dieses Problem tritt spezifisch seit v2026.3.1 auf, da das Framework von der Übergabe von Body zur Übergabe von BodyForAgent an das LLM gewechselt hat. Änderungen an formatInboundEnvelope betreffen nur Body und sind für das LLM unsichtbar.

🧠 Ursache

Architektonische Lücke: Der fehlende Propagierungspfad

Die Ursache ist ein unterbrochener Datenfluss zwischen dem Protocol-Adapter und dem LLM-Kontext. Das fromMe-Flag und senderName sind früh in der Pipeline verfügbar, werden aber nicht durch die gesamte Kette bis zu BodyForAgent propagiert.

Datenflussanalyse

┌─────────────────────────────────────────────────────────────────────────┐ │ AKTUELLER DATENFLUSS │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ Protocol-Adapter │ │ ├── msg.key.fromMe = true/false ✓ VERFÜGBAR │ │ ├── msg.pushName = “Alice” ✓ VERFÜGBAR │ │ └── msg.chatType = “direct” ✓ VERFÜGBAR │ │ │ │ │ ▼ │ │ inboundMessage-KONSTRUKTOR │ │ └── fromMe-Feld NICHT HINZUGEFÜGT ✗ HIER VERWORFEN │ │ │ │ │ ▼ │ │ processMessage → buildInboundLine → formatInboundEnvelope │ │ └── fromMe noch undefined ✗ NICHT WEITERGELEITET │ │ │ │ │ ▼ │ │ finalizeInboundContext-Aufrufer │ │ └── BodyForAgent enthält kein [senderName]: Präfix ✗ LLM ERHÄLT MEHRDEUTIGE DATEN │ │ └─────────────────────────────────────────────────────────────────────────┘

Code-Level-Analyse

1. inboundMessage-Konstruktion (Verwerfungspunkt #1)

javascript // Aktuelle Implementierung - fehlendes fromMe-Feld function inboundMessage(msg, chatId) { return { body: msg.body || msg.text || “”, from: msg.from || msg.chat?.id, pushName: msg.pushName || msg.sender?.first_name, chatType: msg.chatType || (msg.chat?.isGroup ? “group” : “direct”), timestamp: msg.timestamp || Date.now(), // FEHLT: fromMe: Boolean(msg.key?.fromMe) // FEHLT: senderName: extractSenderName(msg) }; }

2. formatInboundEnvelope (Beeinflusst LLM nicht)

javascript // Diese Funktion modifiziert Body, das NICHT vom LLM empfangen wird function formatInboundEnvelope(params) { const selfMarker = params.fromMe ? “[You]: " : “”; return { Body: ${selfMarker}${params.body}, // BodyForAgent wird HIER NICHT gesetzt — LLM empfängt den rohen Wert }; }

3. finalizeInboundContext-Aufrufer (Verwerfungspunkt #2)

Die finale Transformation, die BodyForAgent erstellt, enthält keine Sender-Präfix-Logik:

javascript // Aktuelle Implementierung const BodyForAgent = params.msg.body; // Roher Text, keine Zuordnung

Warum Gruppennachrichten korrekt funktionieren

Gruppennachrichten enthalten von Natur aus Absenderzuordnung, da das Nachrichtenformat bereits den Absendernamen enthält:

javascript // Gruppennachrichten haben bereits diese Struktur vom Protokoll: “[GroupName] @Alice: message content” // oder “[Alice]: message content”

DM-Nachrichten fehlt dieses strukturelle Präfix, was die Absenderidentifikation ohne explizite Behandlung unmöglich macht.

Versions-Regressionsanalyse

VersionLLM-EingabeVerhalten
< v2026.3.1BodyKonnte durch formatInboundEnvelope modifiziert werden
≥ v2026.3.1BodyForAgentKann nicht durch formatInboundEnvelope modifiziert werden

Der Refactor in v2026.3.1 führte ein direktes BodyForAgent-Feld ein, das die formatInboundEnvelope-Transformation umgeht und diese Lücke verursacht.

🛠️ Schritt-für-Schritt-Lösung

Diese Lösung erfordert Änderungen an fünf Funktionen in der eingehenden Pipeline. Wenden Sie die Änderungen in der angegebenen Reihenfolge an, um die Datenintegrität zu wahren.

Phase 1: fromMe durch die Pipeline propagieren

Schritt 1.1: fromMe zur inboundMessage-Konstruktion hinzufügen

Datei: src/core/message/inbound-message.js

Vorher: javascript function inboundMessage(msg, chatId) { return { body: msg.body || msg.text || “”, from: msg.from || msg.chat?.id, pushName: msg.pushName || msg.sender?.first_name, chatType: msg.chatType || (msg.chat?.isGroup ? “group” : “direct”), timestamp: msg.timestamp || Date.now(), // FEHLT }; }

Nachher: javascript function inboundMessage(msg, chatId) { return { body: msg.body || msg.text || “”, from: msg.from || msg.chat?.id, pushName: msg.pushName || msg.sender?.first_name, chatType: msg.chatType || (msg.chat?.isGroup ? “group” : “direct”), timestamp: msg.timestamp || Date.now(), fromMe: Boolean(msg.key?.fromMe), // HINZUFÜGEN: fromMe-Flag propagieren senderName: msg.pushName || msg.sender?.first_name || msg.sender?.username || “Unknown”, // HINZUFÜGEN: Absendername extrahieren }; }

Schritt 1.2: fromMe durch processMessage übergeben

Datei: src/core/message/process-message.js

Vorher: javascript async function processMessage(msg, chatId, context) { const inbound = inboundMessage(msg, chatId);

// … andere Verarbeitung …

await buildInboundLine(inbound, context); }

Nachher: javascript async function processMessage(msg, chatId, context) { const inbound = inboundMessage(msg, chatId);

// … andere Verarbeitung …

await buildInboundLine(inbound, context, { fromMe: inbound.fromMe }); }

Schritt 1.3: fromMe in buildInboundLine destrukturieren und weiterleiten

Datei: src/core/message/build-inbound-line.js

Vorher: javascript async function buildInboundLine(inbound, context) { const { body, from, pushName, chatType, timestamp } = inbound;

// … Verarbeitung …

await formatInboundEnvelope({ msg: { body, from, pushName, chatType, timestamp }, conversation: context.conversation, }); }

Nachher: javascript async function buildInboundLine(inbound, context, options = {}) { const { body, from, pushName, chatType, timestamp, fromMe, senderName } = inbound;

// … Verarbeitung …

await formatInboundEnvelope({ msg: { body, from, pushName, chatType, timestamp, fromMe, senderName }, conversation: context.conversation, fromMe: options.fromMe ?? fromMe, }); }

Schritt 1.4: Selbstmarkierung zu formatInboundEnvelope hinzufügen (Für Body)

Datei: src/core/message/format-inbound-envelope.js

Vorher: javascript function formatInboundEnvelope(params) { return { Body: params.msg.body, // … andere Felder }; }

Nachher: javascript function formatInboundEnvelope(params) { const selfMarker = params.fromMe ? “[You]: " : “”;

return { Body: ${selfMarker}${params.msg.body}, // … andere Felder }; }

Phase 2: Sender-Präfix zu BodyForAgent hinzufügen

Schritt 2.1: finalizeInboundContext-Aufrufer modifizieren

Datei: src/core/context/finalize-inbound-context.js

Vorher: javascript function finalizeInboundContext(params) { // … andere Verarbeitung …

const BodyForAgent = params.msg.body;

return { // … andere Felder BodyForAgent, }; }

Nachher: javascript function finalizeInboundContext(params) { // … andere Verarbeitung …

// Sender-Präfix nur für DMs hinzufügen; Gruppennachrichten haben bereits Zuordnung const dmPrefix = params.msg.chatType !== “group” ? [${params.msg.senderName || params.msg.from || "Unknown"}]: : “”; const BodyForAgent = ${dmPrefix}${params.msg.body};

return { // … andere Felder BodyForAgent, }; }

Phase 3: Verifizierungs-Checkliste

Nach dem Anwenden aller Änderungen verifizieren Sie die folgenden Modifikationen:

DateiÄnderungVerifizierung
inbound-message.jsfromMe und senderName-Felder hinzugefügtPrüfen Sie, dass inbound.fromMe ein Boolean ist
process-message.jsÜbergibt fromMe an buildInboundLinePrüfen Sie, dass drittes Argument vorhanden ist
build-inbound-line.jsDestrukturiert und leitet fromMe, senderName weiterPrüfen Sie, dass Parameter korrekt übergeben werden
format-inbound-envelope.jsFügt [You]:-Selbstmarkierung zu Body hinzuPrüfen Sie Body-Format in Logs
finalize-inbound-context.jsFügt [Name]:-Präfix für DMs hinzuPrüfen Sie BodyForAgent-Format

🧪 Verifizierung

Verifizierungsmethode 1: Unit-Test-Validierung

Erstellen und führen Sie den folgenden Test aus, um die Propagierungskette zu validieren:

// test/inbound-propagation.test.js
const { inboundMessage } = require('../src/core/message/inbound-message');
const { processMessage } = require('../src/core/message/process-message');
const { buildInboundLine } = require('../src/core/message/build-inbound-line');
const { formatInboundEnvelope } = require('../src/core/message/format-inbound-envelope');
const { finalizeInboundContext } = require('../src/core/context/finalize-inbound-context');

describe('fromMe propagation and sender identification', () => {
  const mockMsg = {
    body: "Test message",
    from: "+1234567890",
    pushName: "Alice",
    chatType: "direct",
    timestamp: Date.now(),
    key: { fromMe: false },
  };

  const mockMsgFromMe = {
    ...mockMsg,
    key: { fromMe: true },
  };

  test('inboundMessage includes fromMe and senderName', () => {
    const result = inboundMessage(mockMsg, 'chat123');
    expect(result.fromMe).toBe(false);
    expect(result.senderName).toBe('Alice');
  });

  test('inboundMessage.fromMe is true when key.fromMe is true', () => {
    const result = inboundMessage(mockMsgFromMe, 'chat123');
    expect(result.fromMe).toBe(true);
  });

  test('BodyForAgent includes [senderName]: prefix for DMs', () => {
    const context = { conversation: [] };
    const result = finalizeInboundContext({
      msg: { body: "Test", chatType: "direct", senderName: "Alice", from: "+1234567890" },
      conversation: context.conversation,
    });
    expect(result.BodyForAgent).toBe('[Alice]: Test');
  });

  test('BodyForAgent has no prefix for group messages', () => {
    const context = { conversation: [] };
    const result = finalizeInboundContext({
      msg: { body: "Test", chatType: "group", senderName: "Alice", from: "+1234567890" },
      conversation: context.conversation,
    });
    expect(result.BodyForAgent).toBe('Test');
  });

  test('Body includes [You]: prefix when fromMe is true', () => {
    const result = formatInboundEnvelope({
      msg: { body: "My message", fromMe: true },
    });
    expect(result.Body).toBe('[You]: My message');
  });
});

Führen Sie die Tests aus:

npm test -- test/inbound-propagation.test.js

Erwartete Ausgabe:

✓ inboundMessage includes fromMe and senderName
✓ inboundMessage.fromMe is true when key.fromMe is true
✓ BodyForAgent includes [senderName]: prefix for DMs
✓ BodyForAgent has no prefix for group messages
✓ Body includes [You]: prefix when fromMe is true

Verifizierungsmethode 2: Integrationstest mit Mock-Protocol-Adapter

javascript // test/dm-sender-attribution.test.js const { runFullPipeline } = require(’../src/core/test-helpers’);

async function testDMSenderAttribution() { const mockAdapter = { name: ‘mock’, sendMessage: jest.fn(), onMessage: (handler) => { // Simuliere eingehende DM von Alice handler({ key: { fromMe: false }, body: “Hey, are you free tonight?”, from: “+1111111111”, pushName: “Alice”, chatType: “direct”, timestamp: Date.now(), });

  // Simuliere ausgehende DM (fromMe = true)
  handler({
    key: { fromMe: true },
    body: "Yes, I'll be there at 8",
    from: "+2222222222",
    pushName: "Agent",
    chatType: "direct",
    timestamp: Date.now(),
  });
},

};

const agent = createAgent({ adapter: mockAdapter }); await agent.start();

// Erfasse die LLM-Eingabe const llmInput = captureLLMInput();

console.log(‘LLM empfing BodyForAgent:’); console.log(llmInput.BodyForAgent);

// Erwartete Ausgabe: // [Alice]: Hey, are you free tonight? // [Agent]: Yes, I’ll be there at 8

await agent.stop(); }

testDMSenderAttribution();

Verifizierungsmethode 3: Manuelles Debug-Logging

Aktivieren Sie ausführliches Logging, um die gesamte Pipeline zu inspizieren:

# Umgebungsvariablen setzen
export DEBUG=openclaw:inbound,openclaw:context
export LOG_LEVEL=debug

# Agent ausführen
node agent.js 2>&1 | grep -E "(fromMe|senderName|BodyForAgent|\[.*\]:)"

# Erwartete Log-Ausgabe für DM-Nachrichten:
# [debug] inboundMessage.fromMe: false
# [debug] inboundMessage.senderName: "Alice"
# [debug] BodyForAgent: "[Alice]: Hey, are you free tonight?"

Verifizierungsmethode 4: Datenbankzustands-Inspektion

Wenn Sie persistente Kontexte verwenden, verifizieren Sie die gespeicherten Nachrichten:

# Tabelle messages abfragen
SELECT id, sender_name, from_me, body, body_for_agent 
FROM messages 
WHERE chat_type = 'direct' 
ORDER BY timestamp DESC LIMIT 5;

-- Erwartetes Ergebnis:
-- | id | sender_name | from_me | body            | body_for_agent                    |
-- | 1  | Alice       | false   | Hey, free?      | [Alice]: Hey, free?               |
-- | 2  | Agent       | true    | Yes, at 8       | [Agent]: Yes, at 8                |

⚠️ Häufige Fehler

Fehler 1: Protokoll-Adapter-Feldnamensabweichung

Problem: Verschiedene Messaging-Plattformen exponieren fromMe unter unterschiedlichen Feldnamen.

  • WhatsApp: msg.key.fromMe
  • Telegram: msg.from.is_bot (invertierte Logik) oder msg.outgoing
  • Signal: msg.direction === "outgoing"
  • Discord: msg.author.id === msg.client.user.id

Abhilfe: Erstellen Sie adapter-spezifische Feld-Mapper im inboundMessage-Konstruktor:

javascript function extractFromMe(msg, platform) { switch (platform) { case ‘whatsapp’: return Boolean(msg.key?.fromMe); case ’telegram’: return Boolean(msg.outgoing); case ‘signal’: return msg.direction === ‘outgoing’; case ‘discord’: return msg.author?.id === msg.client?.user?.id; default: return false; } }

Fehler 2: Fehlende senderName-Fallback-Kette

Problem: pushName ist möglicherweise nicht verfügbar (Benutzer hat Datenschutzeinstellungen aktiviert oder erste Nachricht bevor Push-Name gecacht ist).

Abhilfe: Implementieren Sie eine robuste Fallback-Kette:

javascript function extractSenderName(msg) { return ( msg.pushName || msg.sender?.first_name || msg.sender?.username || msg.from?.split(’@’)[0] || // JID/Telefonnummer als letzten Ausweg verwenden “Unknown” ); }

Fehler 3: Doppeltes Präfix in Gruppennachrichten

Problem: Wenn Gruppennachrichten bereits Absenderzuordnung im Protokoll-Payload enthalten, erzeugt das Hinzufügen eines weiteren Präfix eine Duplizierung.

Beispiel einer schlechten Ausgabe:

[#general] @Alice: [Alice]: Message content // Doppelte Zuordnung

Abhilfe: Überprüfen Sie das vorhandene Nachrichtenformat, bevor Sie ein Präfix hinzufügen:

javascript function shouldAddPrefix(msg, chatType) { if (chatType !== ‘direct’) { // Überprüfen, ob Gruppennachricht bereits Zuordnungsmuster hat const existingPattern = /^[[^]]+]\s*@\w+:/; return !existingPattern.test(msg.body); } return true; }

Fehler 4: Plattformspezifische Absendernamensformate

Problem: Verschiedene Plattformen formatieren Absendernamen unterschiedlich.

  • WhatsApp: Anzeigename (z.B. "John Smith")
  • Telegram: Vorname + optionaler Nachname
  • Discord: Benutzername + Diskriminator (z.B. "User#1234")

Abhilfe: Normalisieren Sie Absendernamen vor der Verwendung in Präfixen:

javascript function normalizeSenderName(name, platform) { if (!name) return “Unknown”;

let normalized = name.trim();

if (platform === ‘discord’) { // Diskriminator entfernen, falls vorhanden normalized = normalized.split(’#’)[0]; }

// Sonderzeichen entfernen, die das Parsen unterbrechen könnten return normalized.replace(/[[]]/g, ‘’).substring(0, 50); }

Fehler 5: Versionskompatibilität nach v2026.3.1

Problem: Wenn der Code bedingte Logik basierend darauf enthält, ob BodyForAgent oder Body verwendet wird, wird die Lösung möglicherweise nicht einheitlich angewendet.

Abhilfe: Verifizieren Sie, welches Feld der LLM-Adapter tatsächlich konsumiert:

javascript // Überprüfen Sie Ihre LLM-Adapter-Konfiguration const llmAdapter = config.llm?.adapter;

// Wenn Body direkt verwendet wird (altes Verhalten), gilt die formatInboundEnvelope-Lösung // Wenn BodyForAgent verwendet wird (neues Verhalten), gilt die finalizeInboundContext-Lösung // Einige Adapter verwenden möglicherweise beide — stellen Sie Konsistenz sicher

Fehler 6: Race Condition bei paralleler Nachrichtenverarbeitung

Problem: Bei der gleichzeitigen Verarbeitung mehrerer DMs kann der fromMe-Zustand veraltet oder falsch mit einem anderen Nachrichtenkontext assoziiert sein.

Abhilfe: Stellen Sie sicher, dass fromMe zum Konstruktionszeitpunkt an den spezifischen Nachrichtenkontext gebunden ist, nicht global abgerufen wird:

javascript // FALSCH: Globaler Zustandsverweis inboundMessage.fromMe = globalLastMessageFromMe; // Race condition

// KORREKT: Nachrichtenspezifische Extraktion inboundMessage.fromMe = Boolean(msg.key?.fromMe); // Isoliert pro Nachricht

🔗 Zugehörige Fehler

Zugehöriges Problem #32060: Ausgehende DMs in Kontext einbeziehen

Symptom: Der Agent empfängt nur eingehende DMs, aber keine ausgehenden Nachrichten, die vom Agenten selbst gesendet wurden, was Konversationen einseitig erscheinen lässt.

Zusammenhang: Dieses Problem ist das Komplement zur aktuellen Lösung. Während #32060 sicherstellt, dass ausgehende Nachrichten im Kontext gespeichert werden, stellt diese Lösung sicher, dass sowohl eingehende als auch ausgehende Nachrichten eine ordnungsgemäße Absenderzuordnung haben.

Lösungsabhängigkeit: Die fromMe-Propagierung, die in dieser Lösung implementiert wurde, ist für die ordnungsgemäße Implementierung von #32060 erforderlich.


Zugehöriges Problem #32059: DM-Kontextfenster-Überlauf

Symptom: Lange DM-Konversationen verbrauchen übermäßig viele Kontextfenster-Token, da jede Nachricht eine effiziente Absenderidentifikation vermissen lässt.

Zusammenhang: Das [Name]:-Präfixformat, das durch diese Lösung eingeführt wurde, ist absichtlich prägnant, um den Token-Overhead zu minimieren und gleichzeitig die notwendige Zuordnung bereitzustellen.


Zugehöriger Fehler: undefined senderName in BodyForAgent

Fehlermuster:

TypeError: Cannot read property 'senderName' of undefined
    at finalizeInboundContext (finalize-inbound-context.js:42)
    at processMessage (process-message.js:87)

Ursache: Auf senderName wird zugegriffen, bevor die inboundMessage-Propagierung abgeschlossen ist.

Lösung: Stellen Sie sicher, dass die inboundMessage-Konstruktion das senderName-Feld enthält, bevor finalizeInboundContext aufgerufen wird.


Zugehörige Warnung: fromMe is not a boolean

Warnungsmuster:

Warning: BodyForAgent received non-boolean fromMe value: "true"
Warning: Self-marker logic may behave unexpectedly

Ursache: fromMe wurde als String (“true”/“false”) statt als Boolean gespeichert.

Lösung: Stellen Sie die Boolean(msg.key?.fromMe)-Koersion im inboundMessage-Konstruktor sicher.


Zugehörige Konfiguration: dm.senderPrefix.enabled

Konfigurationsschlüssel: openclaws.dm.senderPrefix.enabled

Zweck: Sender-Präfix in DMs zum Testen oder für Benutzerpräferenzen umschalten.

Standard: true

Interaktion: Wenn deaktiviert, kehren DMs zum mehrdeutigen Format zurück; die fromMe-Propagierung funktioniert weiterhin für andere Zwecke (Analytik, Filterung).


Zugehörige Log-Kategorie: openclaw:inbound:fromme

Debug-Flag: DEBUG=openclaw:inbound:fromme

Ausgabe: Loggt fromMe-Wert in jedem Pipeline-Stadium, nützlich zum Nachverfolgen von Propagierungsfehlern.


Historischer Hinweis: Verhalten vor v2026.3.1

Kontext: Vor v2026.3.1 verwendete das Framework Body (modifiziert durch formatInboundEnvelope) als LLM-Eingabe. Eine dort angewendete Selbstmarkierungs-Lösung wäre für das LLM sichtbar gewesen.

Änderung: v2026.3.1 führte BodyForAgent als separates Feld ein, das die Envelope-Transformation umgeht.

Auswirkung: Diese architektonische Änderung schuf die Propagierungslücke, die diese Lösung adressiert.

Belege & Quellen

Diese Troubleshooting-Anleitung wurde automatisch von der FixClaw Intelligence Pipeline aus Community-Diskussionen synthetisiert.