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
- Configure OpenClaw to use EverOS as
contextEngine.
- Run any conversation through the gateway.
- 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.json → components.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.
Bug: OpenClaw plugin's
saveMemoriesrequest doesn't matchPersonalAddRequestschema —afterTurnreturns HTTP 422Environment
/health@evermind-ai/openclaw-pluginv1.5.2evermind-ai-everosuserId=everos-userSteps to reproduce
contextEngine.journalctl --user -u openclaw-gateway.Observed
Every
afterTurnsave attempt logs:After manually adding
user_id, the next error isField required: messages.Expected
afterTurnshould successfully persist conversation memory (200 OK).Root cause
The OpenClaw plugin's
saveMemories()insrc/api.jsissues:So each HTTP request body is a single flat message object (with
userIdmapped tosender).The backend's
PersonalAddRequestschema (per/openapi.json→components.schemas.PersonalAddRequest) requires:{ "user_id": "string", // required, missing "session_id": "string|null", // optional "messages": [ /* array */ ] // required, plugin sends a bare message instead }Mismatch summary:
user_iduserIdassender)messagessenderuserId)user_idSuggested fix
Two options, plugin-side (preferred, less invasive):
Option A — fix the plugin to wrap messages in an array and add
user_id: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
Impact
afterTurn(memory persistence) fails silently for every conversation turn.assemble(memory recall) still works (uses a different endpoint).