| 2554 | |
| 2555 | |
| 2556 | def _assemble_conversation( |
| 2557 | traces: "TraceStore", session_id: str |
| 2558 | ) -> dict[str, Any] | None: |
| 2559 | # 1. Pull all hot rows for this session_id. |
| 2560 | matching = [r for r in traces._records if r.session_id == session_id] |
| 2561 | if not matching: |
| 2562 | return None |
| 2563 | matching.sort(key=lambda r: r.timestamp) |
| 2564 | |
| 2565 | # 2. Pull cold fields per turn. |
| 2566 | cold_by_id: dict[str, dict[str, Any]] = {} |
| 2567 | for t in matching: |
| 2568 | cold = traces.load_content(t.request_id) |
| 2569 | if cold is not None: |
| 2570 | cold_by_id[t.request_id] = cold |
| 2571 | |
| 2572 | has_any_content = any( |
| 2573 | (c.get("request_messages") or c.get("response_text")) |
| 2574 | for c in cold_by_id.values() |
| 2575 | ) |
| 2576 | |
| 2577 | # 3. Compact-break detection from msg_hashes. |
| 2578 | breaks: list[int] = [] |
| 2579 | for k in range(1, len(matching)): |
| 2580 | prev = list(matching[k - 1].msg_hashes or []) |
| 2581 | curr = list(matching[k].msg_hashes or []) |
| 2582 | if not prev or not curr: |
| 2583 | continue |
| 2584 | if curr[: len(prev)] != prev: |
| 2585 | breaks.append(k) |
| 2586 | |
| 2587 | if not has_any_content: |
| 2588 | # Surface turn-level decisions only (no message bodies). |
| 2589 | decisions = [ |
| 2590 | { |
| 2591 | "role": "assistant", |
| 2592 | "text": "", |
| 2593 | "tool_calls": [], |
| 2594 | "ts": t.timestamp, |
| 2595 | "request_id": t.request_id, |
| 2596 | "decision": _trace_decision_card(t), |
| 2597 | } |
| 2598 | for t in matching |
| 2599 | ] |
| 2600 | return { |
| 2601 | "session_id": session_id, |
| 2602 | "turn_count": len(matching), |
| 2603 | "content_available": False, |
| 2604 | "compact_breaks": breaks, |
| 2605 | "messages": decisions, |
| 2606 | } |
| 2607 | |
| 2608 | # 4. Build backbone from the LAST turn's request_messages. |
| 2609 | last_turn = matching[-1] |
| 2610 | last_cold = cold_by_id.get(last_turn.request_id, {}) |
| 2611 | backbone = list(last_cold.get("request_messages") or []) |
| 2612 | |
| 2613 | # 5. Walk backbone, expand into chat messages with decisions. |