docs/plans/reliable-http-messages-api.md
Replace Socket.IO-based message read/write with simple HTTP endpoints optimized for CLI usage. The new v3 API provides:
seq field — fetch from start, then poll for new messages after last known seqThis is server-side only. CLI client migration will happen separately. Existing Socket.IO message flow remains fully functional (backward compatibility). The plan is to replace Socket.IO with SSE later.
SessionMessage table with per-session seq (allocated via allocateSessionSeq in storage/seq.ts)localId field with @@unique([sessionId, localId]) constraintGET /v1/sessions/:id/messages — limited to 150, no cursor, ordered by createdAt descmessage event in socket/sessionUpdateHandler.ts — single message at a time with AsyncLockeventRouter.emitUpdate() sends new-message updates to Socket.IO clientsGET /v2/sessions uses ID-based cursor — we'll follow a similar pattern but use seqPOST /v1/kv does atomic batch mutations — good referenceGET /v3/sessions/:sessionId/messagesQuery Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
after_seq | number | 0 | Return messages with seq > after_seq |
limit | number | 100 | Max messages to return (1-500) |
Response:
{
"messages": [
{
"id": "cuid",
"seq": 1,
"content": { "t": "encrypted", "c": "base64..." },
"localId": "optional-dedup-id",
"createdAt": 1234567890,
"updatedAt": 1234567890
}
],
"hasMore": true
}
Behavior:
seq ASC (oldest first, natural reading order)seq received, polls with after_seq=<lastSeq> to get new messageshasMore=true means there are more messages beyond the returned batch — fetch again with after_seq set to last message's seqafter_seq=0 fetches from the very beginningafter_seq=<lastKnownSeq> fetches only new messages@@index([sessionId, seq]) for efficient queriesPOST /v3/sessions/:sessionId/messagesRequest Body:
{
"messages": [
{
"content": "base64-encrypted-content",
"localId": "client-generated-dedup-id"
}
]
}
Constraints:
localId for deduplicationcontent is the base64-encoded encrypted message (same format as Socket.IO message event)Response:
{
"messages": [
{
"id": "cuid",
"seq": 5,
"localId": "client-dedup-id",
"createdAt": 1234567890,
"updatedAt": 1234567890
}
]
}
Behavior:
seq numbers are allocated sequentially within the sessionlocalId messages are skipped (idempotent) — their existing record is returnednew-message updates via Socket.IO eventRouter for backward compatibility[x] immediately when doneGET /v3/sessions/:sessionId/messages route in a new v3SessionRoutes.ts fileafter_seq (number, default 0), limit (number, 1-500, default 100)SessionMessage where sessionId and seq > after_seq, order by seq ASC, take limit + 1hasMore boolean (based on whether limit+1 rows returned)api.tsPOST /v3/sessions/:sessionId/messages route in v3SessionRoutes.tsmessages array (max 100), each with content (string) and localId (string)localId already exists (dedup), skip if soallocateSessionSeqnew-message updates via eventRouter.emitUpdate() for each new message (backward compat with Socket.IO clients)new-message updates⚠️ Full package test suite currently fails on an existing unrelated fixture issue: sources/storage/processImage.spec.ts expects sources/storage/__testdata__/image.jpg which is missing in this workspace.
⚠️ No lint script is defined in packages/happy-server/package.json, so linter execution is currently not available.
The existing allocateSessionSeq increments by 1. For batch sends, we need N sequential seq numbers. Two options:
allocateSessionSeq N times — simple, uses existing code, but N DB roundtripsallocateSessionSeqBatch(sessionId, count) function — single UPDATE sessions SET seq = seq + N returning the new seq, then assign (newSeq - N + 1) through newSeqOption 2 is preferred — single DB roundtrip regardless of batch size.
For batch sends with mixed new/existing messages:
localId values in a single query@@index([sessionId, seq]) — efficient range scan@@unique([sessionId, localId]) — efficient point lookupsCLI Migration (separate effort):
happy-agent to use POST /v3/sessions/:id/messages instead of Socket.IO message eventhappy-agent to poll GET /v3/sessions/:id/messages for receiving messagesSSE Migration (future):
GET /v3/sessions/:id/messages/stream SSE endpointLast-Event-ID = last seq for catch-up