Skip to content

feat(setbuilder): persist agent sidebar history + compact LLM context#440

Merged
thewrz merged 17 commits into
mainfrom
feat/agent-sidebar-history
Jun 17, 2026
Merged

feat(setbuilder): persist agent sidebar history + compact LLM context#440
thewrz merged 17 commits into
mainfrom
feat/agent-sidebar-history

Conversation

@thewrz

@thewrz thewrz commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

Why

The WrzDJSet agent sidebar conversation was ephemeral — held only in React state, so a page reload wiped the whole transcript. That also meant the client resent the full history every turn and the entire transcript was fed to the LLM (a cost/scale risk called out in the WrzDJSet exec summary), while applied tool calls rendered as raw JSON noise.

What

Moves agent conversation ownership to the backend and bounds what the model sees:

  • Persistence — new SetAgentSession / SetAgentMessage models + migration 060, scoped per DJ + set. GET .../agent/history rehydrates the sidebar with no LLM call; chat turns are stored atomically (request + response together).
  • Bounded context — the server assembles model context from current set state + a deterministic summary of older turns + recent turns, instead of replaying the full transcript. Older history is compacted deterministically once thresholds are crossed; the full transcript is kept for UI display.
  • Readable tool output — applied tools carry a display_summary (e.g. "Locked slot 1."); the sidebar renders that instead of raw JSON.

Frontend ChatSidebar loads persisted history, posts only the new message, and renders the readable summaries.

Integration notes

This branch was developed before #433/#434/#435 landed and has now been merged up to current main. The only conflict was in ChatSidebar.tsx, resolved to keep both this branch's readable summaries and #434's locked-slot skip reasons / formatAgentError handling. #434's null-agent-result regression test was reconciled to the new chat contract.

Testing

  • Backend tests pass — 2869 passed, coverage 87.99% (gate 85%)
  • Frontend tests pass — vitest 1229 passed (89 files)
  • Lint/format/type — ruff, ruff format, bandit, eslint, tsc --noEmit all clean
  • Migration drift — alembic upgrade head (…059 → 060) + alembic check → "No new upgrade operations detected"
  • CI green (pending on this PR)

🤖 Co-authored by Claude (Opus 4.8). Closes #439.

Summary by CodeRabbit

Release Notes

  • New Features

    • Agent chat history is now persisted per set and automatically loads when you open the sidebar.
    • Tool actions in the chat now show readable, human-friendly summaries (with compact history context where applicable).
  • Improvements

    • Chat UI rendering was updated to use the returned summaries and improve overflow/scroll behavior.
    • Added stronger handling for concurrent/late history loads so locally-sent turns stay intact.
  • Bug Fixes

    • Reduced stale updates and improved resilience when history loads after a new message.

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4de7165c-1b4d-4949-844b-d7207506b3c9

📥 Commits

Reviewing files that changed from the base of the PR and between a37ba77 and 6fa5d42.

📒 Files selected for processing (1)
  • dashboard/app/(dj)/setbuilder/components/ChatSidebar.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • dashboard/app/(dj)/setbuilder/components/ChatSidebar.tsx

📝 Walkthrough

Walkthrough

Adds server-side persistence of per-DJ/set agent chat sessions and messages via new SQLAlchemy models and an Alembic migration. Introduces an agent_history service for bounded context assembly and history compaction. Refactors pass2_agent to use a single outer transaction with per-tool display summaries. Adds a GET /agent/history endpoint and updates the chat endpoint to persist turns. Updates the dashboard ChatSidebar to load persisted history and send only the current message.

Changes

Agent Sidebar History + Compaction

Layer / File(s) Summary
ORM models and Alembic migration
server/app/models/set_agent.py, server/app/models/set.py, server/app/models/__init__.py, server/alembic/versions/060_add_set_agent_history.py
Defines SetAgentSession and SetAgentMessage ORM models with full cascade relationships and ordering; wires agent_sessions relationship onto Set; exports both models from models/__init__; creates migration 060 with two tables, indexes, and (set_id, user_id) unique constraint.
agent_history service
server/app/services/setbuilder/agent_history.py
New service with constants for budgets/thresholds, get_or_create_session (query or create per-user session), list_messages (fetch ordered transcript), append_message (persist with optional tool JSON), context_messages (assemble bounded prompt from set context + compact summary + recent turns), compact_if_needed (threshold-based compaction with summary advancement), and decode_json_list helper.
pass2_agent transaction refactor and display summaries
server/app/services/setbuilder/pass2_agent.py, server/app/services/setbuilder/pass1_deterministic.py, server/app/services/setbuilder/curve.py
AppliedToolCall gains display_summary field; chat_with_agent accepts optional prebuilt messages and commit flag; tool application runs in single outer transaction with slot snapshots, per-tool _tool_display_summary generation, and conditional final commit with rollback on error; all individual tool handlers replaced db.commit() with db.flush(); recompute_transition_scores and replace_vibe_windows gain commit parameter for transaction control.
Pydantic schemas, GET history endpoint, and updated chat endpoint
server/app/schemas/setbuilder.py, server/app/api/setbuilder.py
AppliedToolCallOut adds optional display_summary; new AgentChatMessageOut and AgentChatHistoryOut schemas represent persisted message and transcript; AgentChatOut updated to include assistant_message field. New GET /sets/{set_id}/agent/history returns persisted history without LLM dispatch. Chat endpoint uses agent_history service for context assembly, persists user/assistant turns with commit=False, rolls back on error, and constructs response with assistant_message.
OpenAPI spec and TypeScript type wiring
server/openapi.json, dashboard/lib/api-types.generated.ts, dashboard/lib/api-types.ts, dashboard/lib/api.ts
openapi.json adds AgentChatHistoryOut, AgentChatMessageOut schemas, updates AgentChatOut and AppliedToolCallOut, and wires new GET history operation. Generated TS types updated from OpenAPI. api-types.ts exports AgentChatHistory and AgentChatMessage aliases. api.ts adds ApiClient.getSetAgentHistory(setId) method.
ChatSidebar refactor and CSS
dashboard/app/(dj)/setbuilder/components/ChatSidebar.tsx, dashboard/app/(dj)/setbuilder/setbuilder.module.css
ChatEntry type derives from AgentChatMessage fields plus optional pending flag; sidebar load-on-open fetches persisted history via getSetAgentHistory with request ordering and stale-response guards; send appends pending user entry, submits only { message }, replaces pending with result.assistant_message; ToolCard renders display_summaryrationale → tool name; entries keyed by id; historyMeta banner renders context mode. CSS constrains .panelChat and .chatSection overflow; adds .chatContextMeta and .toolSummary styling.
Tests
server/tests/test_setbuilder_pass2.py, server/tests/test_setbuilder_pass_api.py, dashboard/app/(dj)/setbuilder/components/__tests__/ChatSidebar.test.tsx, dashboard/lib/__tests__/api.test.ts
Server tests verify tool display_summary generation, gateway text preservation, context assembly with compact summary and recent turns, compaction boundary enforcement, decode_json_list robustness, turn persistence, tool-error rollback with database consistency, and per-user history isolation. Frontend tests verify persisted history rendering without raw JSON, async race condition handling, mutation callback triggering for nested tool calls, and tool-name fallback. API client test restructured to verify two-step flow (history load, then chat without client history).
Design spec and implementation plan
docs/superpowers/specs/2026-06-15-agent-sidebar-history-compaction-design.md, docs/superpowers/plans/2026-06-15-agent-sidebar-history-compaction.md
Design spec contrasts current client-only history with server-durable context, specifies backend session/message models, bounded context assembly, deterministic summaries, frontend ChatSidebar load/send/render flows, and testing strategy. Implementation plan outlines six tasks from models/migration through verification, with inline acceptance criteria and specific file/function changes.

Sequence Diagram(s)

sequenceDiagram
  participant Browser as Browser (ChatSidebar)
  participant DashboardAPI as ApiClient
  participant SetbuilderAPI as FastAPI /setbuilder
  participant AgentHistory as agent_history service
  participant Pass2Agent as pass2_agent
  participant DB as Database

  rect rgba(100, 149, 237, 0.5)
    note over Browser,DB: Sidebar opens — load persisted history
    Browser->>DashboardAPI: getSetAgentHistory(setId)
    DashboardAPI->>SetbuilderAPI: GET /sets/{set_id}/agent/history
    SetbuilderAPI->>AgentHistory: get_or_create_session(set_id, user_id)
    AgentHistory->>DB: query/insert SetAgentSession
    SetbuilderAPI->>AgentHistory: list_messages(session)
    AgentHistory->>DB: SELECT SetAgentMessage ORDER BY id
    SetbuilderAPI-->>DashboardAPI: AgentChatHistoryOut (messages + metadata)
    DashboardAPI-->>Browser: render persisted messages + historyMeta banner
  end

  rect rgba(144, 238, 144, 0.5)
    note over Browser,DB: User sends a new message
    Browser->>DashboardAPI: chatWithSetAgent(setId, {message})
    DashboardAPI->>SetbuilderAPI: POST /sets/{set_id}/agent/chat {message}
    SetbuilderAPI->>AgentHistory: context_messages(set_obj, session, message)
    AgentHistory-->>SetbuilderAPI: bounded message list (context + current)
    SetbuilderAPI->>AgentHistory: append_message(user, commit=False)
    SetbuilderAPI->>Pass2Agent: chat_with_agent(messages=context, commit=False)
    Pass2Agent->>DB: flush tool mutations (reorder/swap/remove/…)
    Pass2Agent-->>SetbuilderAPI: AgentChatResult + display_summary per tool
    SetbuilderAPI->>AgentHistory: append_message(assistant, commit=False)
    SetbuilderAPI->>AgentHistory: compact_if_needed(session)
    SetbuilderAPI->>DB: commit + refresh assistant_message
    SetbuilderAPI-->>DashboardAPI: AgentChatOut + assistant_message
    DashboardAPI-->>Browser: replace pending entry with assistant_message
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related issues

Possibly related PRs

  • wrzonance/WrzDJ#424: Both PRs modify ChatSidebar.tsx and its tests to drive api.chatWithSetAgent tool-card and mutation UI; this PR extends that work by replacing client-built history with a server-loaded persisted history flow and refactoring the message payload contract.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.55% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding persistence and context bounding for the WrzDJSet agent sidebar, matching the changeset's core objective.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/agent-sidebar-history

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
dashboard/app/(dj)/setbuilder/components/ChatSidebar.tsx (1)

74-93: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard against null/undefined display_summary before calling .trim().

If tool.display_summary is null or undefined, the .trim() call on line 77 will throw a TypeError. The fallback chain suggests missing values are expected.

🐛 Proposed fix
 function ToolCard({ tool }: { tool: AppliedToolCall }) {
   const toolName = tool.name.replaceAll('_', ' ');
   const rationale = tool.rationale?.trim() ?? '';
-  const summary = tool.display_summary.trim() || rationale || toolName;
+  const summary = tool.display_summary?.trim() || rationale || toolName;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dashboard/app/`(dj)/setbuilder/components/ChatSidebar.tsx around lines 74 -
93, In the ToolCard function, the `summary` constant assignment calls `.trim()`
directly on `tool.display_summary` without checking if it's null or undefined
first, which will throw a TypeError if the value is missing. Since the fallback
chain (rationale || toolName) indicates that missing values are expected, add
optional chaining before calling `.trim()` on `tool.display_summary`, similar to
how it's already done for `tool.rationale?.trim()` on the line above.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@dashboard/app/`(dj)/setbuilder/components/ChatSidebar.tsx:
- Around line 74-93: In the ToolCard function, the `summary` constant assignment
calls `.trim()` directly on `tool.display_summary` without checking if it's null
or undefined first, which will throw a TypeError if the value is missing. Since
the fallback chain (rationale || toolName) indicates that missing values are
expected, add optional chaining before calling `.trim()` on
`tool.display_summary`, similar to how it's already done for
`tool.rationale?.trim()` on the line above.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1ddac495-1b17-478f-9990-f7f75c703281

📥 Commits

Reviewing files that changed from the base of the PR and between 0dd34a4 and a37ba77.

📒 Files selected for processing (22)
  • dashboard/app/(dj)/setbuilder/components/ChatSidebar.tsx
  • dashboard/app/(dj)/setbuilder/components/__tests__/ChatSidebar.test.tsx
  • dashboard/app/(dj)/setbuilder/setbuilder.module.css
  • dashboard/lib/__tests__/api.test.ts
  • dashboard/lib/api-types.generated.ts
  • dashboard/lib/api-types.ts
  • dashboard/lib/api.ts
  • docs/superpowers/plans/2026-06-15-agent-sidebar-history-compaction.md
  • docs/superpowers/specs/2026-06-15-agent-sidebar-history-compaction-design.md
  • server/alembic/versions/060_add_set_agent_history.py
  • server/app/api/setbuilder.py
  • server/app/models/__init__.py
  • server/app/models/set.py
  • server/app/models/set_agent.py
  • server/app/schemas/setbuilder.py
  • server/app/services/setbuilder/agent_history.py
  • server/app/services/setbuilder/curve.py
  • server/app/services/setbuilder/pass1_deterministic.py
  • server/app/services/setbuilder/pass2_agent.py
  • server/openapi.json
  • server/tests/test_setbuilder_pass2.py
  • server/tests/test_setbuilder_pass_api.py

Use optional chaining before .trim() on the applied tool's
display_summary so a missing value falls back to rationale/tool name
instead of throwing, matching the adjacent tool.rationale?.trim()
handling. Addresses CodeRabbit outside-diff-range review on PR #440.

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

thewrz commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the CodeRabbit outside-diff-range comment on ChatSidebar.tsx (ToolCard, lines 74–93):

Guard against null/undefined display_summary before calling .trim().

Fixed in commit 6fa5d42tool.display_summary?.trim() now mirrors the adjacent tool.rationale?.trim() handling, so a missing summary falls back to the rationale/tool name instead of throwing. The OpenAPI type declares display_summary non-null, so this is defensive (guards against backend drift) and consistent with the neighboring field. tsc, eslint, and the ChatSidebar tests pass.

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.

feat(setbuilder): persist agent sidebar history + compact LLM context

1 participant