Skip to content

[bug] OpenClaw plugin saveMemories: POST /api/v1/memories 422 (Field required: user_id) #237

@215264803-web

Description

@215264803-web

Bug: OpenClaw plugin's saveMemories request doesn't match PersonalAddRequest schema — afterTurn returns HTTP 422

Environment

  • EverOS backend: 1995, healthy /health
  • OpenClaw plugin: @evermind-ai/openclaw-plugin v1.5.2
  • OpenClaw: 2026.5.28 (gateway), contextEngine: evermind-ai-everos
  • User config: userId=everos-user

Steps to reproduce

  1. Configure OpenClaw to use EverOS as contextEngine.
  2. Run any conversation through the gateway.
  3. Tail journalctl --user -u openclaw-gateway.

Observed

Every afterTurn save attempt logs:

[evermind-ai-everos] request failed, retrying: HTTP 422 – {"code":"HTTP_ERROR","message":"Field required: user_id","request_id":"…","path":"/api/v1/memories"}
[plugins] [evermind-ai-everos] afterTurn: save failed: HTTP 422 – … "Field required: user_id"

After manually adding user_id, the next error is Field required: messages.

Expected

afterTurn should successfully persist conversation memory (200 OK).

Root cause

The OpenClaw plugin's saveMemories() in src/api.js issues:

const payloads = messages.map((msg, i) => ({ message_id, create_time, role, sender, sender_name, content, group_id, group_name, scene, raw_data_type, ... }));
for (const payload of payloads) {
  await request(cfg, "POST", "/api/v1/memories", payload);
}

So each HTTP request body is a single flat message object (with userId mapped to sender).

The backend's PersonalAddRequest schema (per /openapi.jsoncomponents.schemas.PersonalAddRequest) requires:

{
  "user_id": "string",         // required, missing
  "session_id": "string|null", // optional
  "messages": [ /* array */ ]  // required, plugin sends a bare message instead
}

Mismatch summary:

Field Plugin sends API requires
user_id (missing — uses userId as sender) required
messages (sends single object per call) required array
sender yes (built from userId) n/a — server-side handled via user_id

Suggested fix

Two options, plugin-side (preferred, less invasive):

Option A — fix the plugin to wrap messages in an array and add user_id:

// src/api.js, saveMemories()
const body = {
  user_id: userId,
  session_id: idSeed || undefined,
  messages: messages.map((msg, i) => {
    const { role = "user", content = "" } = msg;
    return {
      message_id: messageId(idSeed, role, content),
      create_time: new Date(stamp + i).toISOString(),
      role,
      sender: userId,
      sender_name: role === "assistant" ? "assistant" : userId,
      ...(flush && i === messages.length - 1 && { flush: true }),
    };
  }),
};
await request(cfg, "POST", "/api/v1/memories", body);

Option B — fix the OpenAPI schema doc to reflect required: [] (so 422 doesn't fire, since the runtime validator and OpenAPI doc disagree today).

Option A is the right fix; Option B is a workaround that papers over the mismatch.

Quick verification commands

# Reproduce 422
curl -sS -X POST http://localhost:1995/api/v1/memories \
  -H "Content-Type: application/json" \
  -d '{"userId":"everos-user","content":"test","memoryType":"episodic","tags":["test"]}'
# → {"code":"HTTP_ERROR","message":"Field required: user_id",…}

# After adding user_id
curl -sS -X POST http://localhost:1995/api/v1/memories \
  -H "Content-Type: application/json" \
  -d '{"user_id":"everos-user","content":"test","memoryType":"episodic"}'
# → {"code":"HTTP_ERROR","message":"Field required: messages",…}

# Inspect required fields
curl -sS http://localhost:1995/openapi.json | jq '.components.schemas.PersonalAddRequest.required'
# → ["user_id","messages"]

Impact

  • afterTurn (memory persistence) fails silently for every conversation turn.
  • assemble (memory recall) still works (uses a different endpoint).
  • User-visible: no obvious error in the chat, but the long-term memory is not being written, defeating the purpose of the contextEngine integration.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions