Skip to content

SCAL-352370: store the analytical session with each chat-history answer - #78

Open
mouryabalabhadra wants to merge 5 commits into
mainfrom
SCAL-352370-chat-history-per-answer-session
Open

mouryabalabhadra wants to merge 5 commits into
mainfrom
SCAL-352370-chat-history-per-answer-session

Conversation

@mouryabalabhadra

Copy link
Copy Markdown
Collaborator

Problem

A ThoughtSpot analytical session belongs to the Org it was created in. After an Org switch mid-chat, the old session rejects new questions (HTTP 400) and reading it returns 422 "Invalid org id", so the agent opens a new session. The chat-history example stored one session id per chat, numbered charts across the whole chat, and assumed one ThoughtSpot message per reply. On reopen, charts from the second session were looked up in the first one: they came back as the wrong chart or were silently dropped. The one-message-per-reply assumption also dropped charts whenever the agent retried inside a reply.

Change (mcp/python-react-agent-simple-ui)

  • Per-chart session: every answer / Show work query event carries the session it was polled from; reply rows and answer placeholders store it. Charts are numbered within their own session on reopen.
  • Per-session question counts: a new session_message SSE event (ignored by the UI) fires each time ThoughtSpot accepts a send_session_message. Replies store ts_message_counts ({session: n}), so failed/cancelled turns, greetings (0) and retries (2+) pair correctly with ThoughtSpot's messages.
  • Newest session wins: a later create_analysis_session replaces the chat's current session, so follow-ups go straight to it.
  • Failure handling: a session ThoughtSpot will not return (4xx, e.g. another Org) keeps its placeholders, so the Visual Embed SDK shows "This answer could not be loaded."; a 5xx/timeout retries once and then returns 503 instead of guessing.
  • Security: only sessions the conversation created are trusted as chart sessions (the poll's id comes from the model); ids are format-checked and URL-escaped; the work-answers endpoint rejects sessions the chat never used; deleting a chat clears its trust set.
  • Migration: additive only (turns.analytical_session_id, turns.ts_message_counts), in one transaction; older replies are pinned once to the chat's session and count as one question.
  • Client: stored charts and Show work loads use their own session.
  • README: plain-language "How chat history works" with a worked example of one chat across two Orgs; updated storage, endpoints, SSE events and limits.

SDK side (doc-only): thoughtspot/visual-embed-sdk#748.

Testing

  • Live on a multi-Org cluster (Orgs switched by minting org-scoped tokens): reply 1 in Org A, replies 2-3 in Org B. Reopened in Org B: reply 1 shows the SDK notice, replies 2-3 render their own charts; reopened in Org A: the reverse, nothing dropped.
  • Live: greeting + question + follow-up; a turn cancelled after ThoughtSpot accepted the question, then a follow-up (each chart lands on the reply that asked for it); a reply where the agent retried; Show work expansion; malformed/foreign session ids rejected (404).
  • Live: the server's message list (/api/rest/2.0/ai/agent/conversations/{id}/messages) and the SDK's (/conversation/v2/{id}/public) return the same final answers in the same order.
  • 22 offline pytest cases (reconcile per session, counts, retries, greetings, two-session replies, 4xx vs 5xx, trust, raw-update tagging, migration on a copy of a real DB, run twice). They are not part of this PR; the example has no test suite.
  • Client tsc --noEmit passes.

Known limit: chats saved before this change that already spanned two sessions, or had retries, cannot be repaired (nothing recorded which reply used which session).

🤖 Generated with Claude Code

mouryabalabhadra and others added 5 commits November 13, 2026 23:32
A ThoughtSpot analytical session belongs to the Org it was created in.
After an Org switch the old session rejects new messages, so the agent
opens a new one and later charts live there. The chat history kept one
session id per chat, so on reopen those charts were looked up in the
first session and came back wrong or not at all.

- Tag every answer and Show work query with the session it was polled
  from, and let a newer create_analysis_session replace the stored one.
- Store the session on each reply and each answer placeholder, plus how
  many questions the reply sent (ts_message_count). Number answers
  within their own session on reopen, so a session another Org cannot
  read keeps its placeholders and the SDK shows its notice instead of
  the app dropping them.
- Pair replies with ThoughtSpot messages by that count instead of
  assuming one message per reply, which dropped charts when the agent
  retried within a reply.
- Load Show work answers from the reply's session; reject sessions the
  chat never used.
- Additive migration: new turns columns, older replies pinned once to
  the chat's session.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Security review follow-up. The session id in a get_session_updates call
comes from the model's tool arguments, so a prompt could point it at any
session and have that id stored with the chat's answers.

- Track the sessions create_analysis_session returned per conversation
  (seeded from storage on reload) and tag charts only with those.
- Accept only URL-safe session ids, and quote them in ThoughtSpot REST
  paths.
- Reject a malformed session_id on the work-answers endpoint as well as
  one the chat never used.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deleting a conversation cleared its message cache and current session
but left its trusted-session set behind. Clear all three together.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rewrite "How chat history works" in plainer terms: a short glossary,
what to save and why, and a step-by-step example of one chat that
spans two sessions after an Org switch - what the agent does, what is
saved, how the charts are numbered on reopen, the replay links, and
what a viewer in each Org sees.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review follow-ups.

- Count the questions ThoughtSpot accepted as they happen (a new
  session_message SSE event from call_tool) instead of reading the
  finished Claude history. A turn that fails or is cancelled after
  asking now still records its questions, and a greeting before any
  session records none, so later replies are no longer shifted.
- Store the counts per session (ts_message_counts JSON) and number each
  chart within its own stored session, so a reply that spans two
  sessions keeps both charts.
- Store a reply even when all it did was ask ThoughtSpot a question.
- Distinguish "ThoughtSpot will not return this session" (4xx: keep the
  placeholders, the SDK shows its notice) from "ThoughtSpot is
  unavailable" (5xx/timeout: retry once, then 503) so a transient
  failure never numbers charts onto the wrong answer.
- Run the migration's ALTERs and backfill in one transaction; carry
  over counts written by the pre-release ts_message_count column.
- README: correct the worked example (accepted vs rejected questions,
  the old-behaviour counterfactual), document session_message and
  ts_message_counts, and list the limits for chats saved before this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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