Skip to content

fix: recover from a Letta conversation the backend no longer has - #14

Merged
EthanThatOneKid merged 2 commits into
mainfrom
fix/stale-conversation-recovery
Sep 26, 2026
Merged

EthanThatOneKid merged 2 commits into
mainfrom
fix/stale-conversation-recovery

Conversation

@EthanThatOneKid

Copy link
Copy Markdown
Contributor

Problem

A session's stored conversation id is replayed on every later turn. When the local
Letta backend no longer has that conversation, every request for that session fails
permanently — no retry ever succeeds, because the same dead id is re-sent.

Data's Discord session held a pre-cutover con_ id that the local backend never had
(it names conversations local-conv-N). Every Discord mention returned 502:

letta -> exit 1: Conversation con_QPvFAwS14xUBFIIb not found

Observed live: a real mention (1553268586628255786) failed to answer at
2026-09-26T04:54:50Z for this reason.

Fix

  • Detect the backend's missing-conversation failure (lib/conversation-recovery.ts),
    which prints Conversation <id> not found on stderr and exits 1 with no stdout.
  • In askLetta, drop the stale mapping and retry once against a fresh conversation,
    so a stale id costs one extra turn instead of bricking the session.
  • The detector is only consulted when no result object was parsed from the turn, so
    prose in a real answer cannot trigger a spurious retry.

Verification

  • npm run typecheck clean; npm test 26/26 pass (2 new tests).

  • Isolated end-to-end run of channels/http/index.ts against the real local Letta
    agent, seeded with the stale id as a session mapping:

    request POST /ask {session:"stale-test"}
    log conversation con_QPvFAwS14xUBFIIb is gone; starting a fresh one for session stale-test
    response 200 with a fresh local-conv-63
    state mapping rewritten to the new id
  • The live host's stale entry was cleared and data-http restarted; a /ask on the
    real Discord session (discord:1531147245318176848) now returns 200 with a fresh
    conversation, where it previously 502'd.

A session's stored conversation id is replayed on every later turn, so when
the local backend loses that conversation (its store is replaced, the agent is
re-created, or state from another backend is reused) every request for that
session fails. Data's Discord session stored a pre-cutover 'con_' id that the
local backend never had, so every mention failed with a 502:

  letta -> exit 1: Conversation con_QPvFAwS14xUBFIIb not found

Detect that failure and drop the mapping, then retry once against a fresh
conversation, so a stale id costs one extra turn instead of bricking the
session. The detector only consults stderr when no result object was parsed,
so prose in a real answer cannot trigger a retry.
@wazoocomputer

wazoocomputer Bot commented Sep 26, 2026

Copy link
Copy Markdown

This pull request addresses an issue where a session's stored conversation ID, when no longer recognized by the Letta backend, would cause all subsequent requests for that session to fail. The fix introduces a mechanism to detect this "missing conversation" failure, drop the stale conversation ID, and retry the request with a fresh conversation.

File Change Summary
channels/http/index.ts modified Implements conversation recovery logic and retries requests with fresh conversations when a stale ID is detected.
lib/conversation-recovery.test.ts added Adds tests for the isMissingConversation utility function.
lib/conversation-recovery.ts added Defines isMissingConversation to detect missing conversation errors from the Letta backend.

Reviewers, please start by examining lib/conversation-recovery.ts and its associated tests, then move to channels/http/index.ts to see how the recovery logic is integrated.

1 similar comment
@wazoocomputer

wazoocomputer Bot commented Sep 26, 2026

Copy link
Copy Markdown

This pull request addresses an issue where a session's stored conversation ID, when no longer recognized by the Letta backend, would cause all subsequent requests for that session to fail. The fix introduces a mechanism to detect this "missing conversation" failure, drop the stale conversation ID, and retry the request with a fresh conversation.

File Change Summary
channels/http/index.ts modified Implements conversation recovery logic and retries requests with fresh conversations when a stale ID is detected.
lib/conversation-recovery.test.ts added Adds tests for the isMissingConversation utility function.
lib/conversation-recovery.ts added Defines isMissingConversation to detect missing conversation errors from the Letta backend.

Reviewers, please start by examining lib/conversation-recovery.ts and its associated tests, then move to channels/http/index.ts to see how the recovery logic is integrated.

On the recovery path `existing` names the conversation the backend no
longer has, so a fresh turn whose JSON omits `conversation_id` wrote the
dead id straight back into conversations.json - recreating the exact state
this recovery exists to clear. Track that recovery ran and fall back to
undefined, which leaves the session unmapped so the next question starts a
new conversation.

Also document the behavior and its boundary: conversation loss recovers
automatically, agent loss (`Agent <id> not found`) does not and needs
`letta agents create` plus a `DATA_LETTA_AGENT_ID` update.
@EthanThatOneKid

Copy link
Copy Markdown
Contributor Author

Added the re-brick guard as recovered (40bf2d5) and documented the boundary.

Boundary — what this does not cover. Recovery here handles conversation loss. A wiped backend also loses the agent, which surfaces differently — Agent agent-local-... not found instead of Conversation ... not found — and the detector deliberately ignores it. That case still needs letta --backend local agents create plus a DATA_LETTA_AGENT_ID update. Now stated in channels/http/README.md.

Verification. A stub letta reproduces the backend's shape (exit 1, empty stdout, Conversation <id> not found on stderr) and a fresh turn that answers but omits conversation_id from its JSON — the precondition for the re-brick. Seeding a dead id for discord:test and running one /ask:

  • against committed ea6ba1d: dead id written back with a fresh timestamp → RE-BRICKED
  • against this branch: conversations.json left as {}, conversationId: null → fixed

Typecheck clean, npm test 26/26.

@EthanThatOneKid
EthanThatOneKid merged commit 39cc623 into main Sep 26, 2026
1 check passed
@EthanThatOneKid
EthanThatOneKid deleted the fix/stale-conversation-recovery branch September 26, 2026 05:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant