Skip to content

feat(#6220): hydrate ID-linked historical tool transcripts into Anchor#6297

Open
webtecnica wants to merge 1 commit into
nesquena:masterfrom
webtecnica:feat/6220-tool-transcripts-anchor
Open

feat(#6220): hydrate ID-linked historical tool transcripts into Anchor#6297
webtecnica wants to merge 1 commit into
nesquena:masterfrom
webtecnica:feat/6220-tool-transcripts-anchor

Conversation

@webtecnica

Copy link
Copy Markdown
Contributor

Summary

Fixes #6220 by adding _hydrateHistoricalToolTranscriptAnchorScenes() which scans settled messages for eligible assistant turns with ID-linked tool_calls[] and matching role: tool result messages, then builds deterministic activity_scene_v1 scenes and attaches them as _anchor_activity_scene.

The existing _renderSettledAnchorSceneForMessage path renders the scene; the legacy fallback in renderMessages skips anchor-owned turns.

Fail-closed guards

  • Missing/empty tool_call id
  • Unmatched tool result
  • Cross-turn-boundary results
  • Duplicate tool_call ids within a message
  • Pre-existing _anchor_activity_scene

Changes

  • static/ui.js: Add _hydrateHistoricalToolTranscriptAnchorScenes() + call site in renderMessages
  • tests/test_6220_tool_transcripts_anchor.py: Regression test suite (8 tests covering source-level assertions and behavioral scenarios)

Verification

  • Source-level: function exists in ui.js, called from renderMessages
  • Behavioral (node): eligible transcript hydrates, missing id fails, unmatched result fails, cross-turn fails, duplicate ids fail, existing scene preserved, multi-tool ordering

…to Anchor

Add _hydrateHistoricalToolTranscriptAnchorScenes() which scans settled
messages for eligible assistant turns with ID-linked tool_calls[] and
matching role:tool result messages. Builds deterministic activity_scene_v1
scenes and attaches them as _anchor_activity_scene so the existing
_renderSettledAnchorSceneForMessage path renders them — the legacy
fallback in renderMessages skips anchor-owned turns.

Fail closed on:
- Missing/empty tool_call id
- Unmatched tool result
- Cross-turn-boundary results
- Duplicate tool_call ids within a message
- Pre-existing _anchor_activity_scene

Add regression test suite (tests/test_6220_tool_transcripts_anchor.py).
@greptile-apps

greptile-apps Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR implements _hydrateHistoricalToolTranscriptAnchorScenes() to back-fill activity_scene_v1 scenes onto settled assistant turns that carry ID-linked tool_calls[], making historical tool transcripts visible through the existing Anchor rendering path without touching the live-stream code.

  • The hydration function is called at the top of renderMessages and includes fail-closed guards for missing IDs, unmatched results, cross-turn results, and duplicate IDs — all of which are covered by the regression suite.
  • The idempotency guard (m._anchor_activity_scene check at the loop's top) only protects the tool-call-bearing assistant; when the scene is correctly written to a different, turn-final assistant (ownerIdx ≠ i), the guard is bypassed and the scene object is silently rebuilt and overwritten on every renderMessages invocation. The accompanying idempotency test compares serialised JSON content rather than object identity, so it passes even when the defect is present.

Confidence Score: 3/5

The hydration logic is structurally sound and correctly fail-closed for the invalid-input paths, but the idempotency mechanism misfires in the common multi-message turn layout, causing repeated scene allocations on every render cycle.

The function works correctly on first call, but on every subsequent renderMessages invocation it re-creates and overwrites the frozen scene object for any turn where the tool-call-bearing assistant and the scene-owning turn-final assistant are different messages. This is the normal, tested layout. The test suite does not detect this because test_6220_hydration_idempotent only compares JSON strings, which are identical even when the object is replaced.

Both static/ui.js (the guard condition in _hydrateHistoricalToolTranscriptAnchorScenes) and tests/test_6220_tool_transcripts_anchor.py (the idempotency test) need attention.

Important Files Changed

Filename Overview
static/ui.js Adds _hydrateHistoricalToolTranscriptAnchorScenes() which scans settled messages and attaches activity_scene_v1 scenes to eligible turns; idempotency guard checks the wrong message object, causing scenes to be re-created on every renderMessages call when the scene owner differs from the tool-call-bearing assistant.
tests/test_6220_tool_transcripts_anchor.py New 8-test regression suite using source-level string assertions and Node-executed JavaScript snippets; covers the main guard paths well but the idempotency test only compares JSON content rather than object identity, so it passes even when the scene is silently recreated on every call.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant RM as renderMessages()
    participant HY as _hydrateHistoricalToolTranscriptAnchorScenes()
    participant MSG as S.messages[]
    participant RS as _renderSettledAnchorSceneForMessage()

    RM->>HY: call (every render)
    HY->>MSG: index all role:tool results by tool_call_id
    loop each assistant message with tool_calls
        HY->>MSG: check messages[i]._anchor_activity_scene
        note over HY,MSG: Guard checks tool-call bearer (i),<br/>not the scene owner (ownerIdx)
        HY->>MSG: validate all tool_call ids are linked within turn boundary
        HY->>MSG: find turn-final assistant → ownerIdx
        HY->>MSG: write messages[ownerIdx]._anchor_activity_scene (frozen)
        note over HY,MSG: Re-written every call when ownerIdx ≠ i
    end
    RM->>RS: for each assistant segment with _anchor_activity_scene → render worklog
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant RM as renderMessages()
    participant HY as _hydrateHistoricalToolTranscriptAnchorScenes()
    participant MSG as S.messages[]
    participant RS as _renderSettledAnchorSceneForMessage()

    RM->>HY: call (every render)
    HY->>MSG: index all role:tool results by tool_call_id
    loop each assistant message with tool_calls
        HY->>MSG: check messages[i]._anchor_activity_scene
        note over HY,MSG: Guard checks tool-call bearer (i),<br/>not the scene owner (ownerIdx)
        HY->>MSG: validate all tool_call ids are linked within turn boundary
        HY->>MSG: find turn-final assistant → ownerIdx
        HY->>MSG: write messages[ownerIdx]._anchor_activity_scene (frozen)
        note over HY,MSG: Re-written every call when ownerIdx ≠ i
    end
    RM->>RS: for each assistant segment with _anchor_activity_scene → render worklog
Loading

Comments Outside Diff (1)

  1. tests/test_6220_tool_transcripts_anchor.py, line 655-698 (link)

    P1 Idempotency test doesn't exercise the broken case

    test_6220_hydration_idempotent detects idempotency failure by comparing JSON.stringify of the scene before and after the second call. Because the scene content is deterministic, the strings will be equal even when the underlying object is silently replaced by a new allocation on every call. The test therefore passes even with the broken guard. To surface the actual defect, the test needs a transcript where the scene lands on a different message than the one carrying tool_calls — the layout already present in test_6220_eligible_transcript_hydrates_anchor_scene — and must assert that messages[ownerIdx]._anchor_activity_scene is the same object reference before and after the second invocation (using a sentinel identity token or a wrapper that counts writes).

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Reviews (1): Last reviewed commit: "feat(#6220): hydrate ID-linked historica..." | Re-trigger Greptile

Comment thread static/ui.js
Comment on lines +15363 to +15365
for(let i=0;i<messages.length;i++){
const m=messages[i];
if(!m||m.role!=='assistant'||m._anchor_activity_scene) continue;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Idempotency guard misses the common case

The check m._anchor_activity_scene only prevents re-hydration when the scene was already attached to the tool-call-bearing assistant at index i. In the normal transcript layout ([user, assistant(tool_calls), tool, assistant(answer)]), the scene is written to messages[ownerIdx] (the turn-final assistant, index 3), while messages[i] (index 1) stays scene-free. On every subsequent call to renderMessages, the guard on messages[i] passes, the function re-validates the turn, builds a new frozen scene object, and overwrites messages[ownerIdx]._anchor_activity_scene with a fresh allocation — defeating the stated "Pre-existing _anchor_activity_scene" guard. A guard added just before the assignment — if(messages[ownerIdx]._anchor_activity_scene) continue; — would close this gap.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L Large PR (>10 files or >250 LOC)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Live Stream: hydrate ID-linked historical tool transcripts into Anchors

2 participants