-
Notifications
You must be signed in to change notification settings - Fork 287
feat: Add assistant chat persistence and conversation management #45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
feat: Add assistant chat persistence and conversation management #45
Conversation
- Auto-resume most recent conversation when opening chat panel - Add "New Chat" button to start fresh conversations - Add "resume" WebSocket message type for reconnecting without greeting - Add skip_greeting parameter to session.start() for resumed conversations - Update greeting message to reflect feature management capabilities - Store conversations in assistant.db with full message history - Update README with Project Assistant documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe changes introduce a new Project Assistant feature enabling conversation history management. The backend adds support for resuming conversations via a new WebSocket "resume" message type and optional skip_greeting parameter, while the frontend adds UI components for viewing conversation history, switching between conversations, and explicitly starting new chats. README documentation is updated accordingly. Changes
Sequence DiagramsequenceDiagram
participant UI as UI Component
participant Hook as useAssistantChat Hook
participant WS as WebSocket Router
participant Session as AssistantChatSession
participant DB as Backend (History)
UI->>Hook: Mount component
Hook->>DB: Fetch conversation history
DB-->>Hook: Return conversations (sorted by updated_at)
Hook->>Hook: Load messages from most recent conversation
alt Has Previous Messages
Hook->>WS: Send resume message (conversation_id, skip_greeting=true)
WS->>Session: Create session with skip_greeting=true
Session->>Session: Start without greeting
Session-->>WS: Stream message chunks
WS-->>Hook: Receive streamed content
else No Previous Messages
Hook->>WS: Send start message
WS->>Session: Create new session
Session->>Session: Start with greeting (skip_greeting=false)
Session-->>WS: Stream greeting + chunks
WS-->>Hook: Receive greeting and content
end
Hook-->>UI: Update state (messages, conversations)
UI-->>UI: Render chat interface with New Chat button
alt User Clicks "New Chat"
UI->>Hook: Call startNewConversation()
Hook->>Hook: Reset chat state
Hook->>WS: Send start message (new conversation)
else User Switches Conversation
UI->>Hook: Call switchConversation(conversationId)
Hook->>Hook: Load selected conversation messages
Hook->>WS: Send resume/start based on message existence
end
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🤖 Fix all issues with AI agents
In @README.md:
- Around line 111-123: Update the Project Assistant capabilities text in
README.md to accurately list only the supported operations (create and skip)
instead of "Create, edit, delete, and reorder features", and make the same
change to the assistant greeting text defined in the AssistantChatSession
implementation so both UX text and backend greeting match; locate the assistant
greeting string in the AssistantChatSession class (the method that composes the
initial/system prompt) and remove references to edit/delete/reorder, replacing
them with the correct "create and skip" wording.
In @server/routers/assistant_chat.py:
- Around line 291-315: The resume handler fails to validate conversation_id and
may call create_session with None; before calling create_session in the "resume"
branch, check that message.get("conversation_id") returns a non-empty
string/UUID and if missing/invalid, log a warning and send a websocket error
JSON (type:"error", content:"Missing or invalid conversation_id for resume") and
skip create_session; otherwise proceed to call
create_session(conversation_id=conversation_id) and keep the existing try/except
logic around session.start and websocket.send_json.
In @server/services/assistant_chat_session.py:
- Around line 273-285: The greeting text in the initial message (the local
variable greeting in the assistant_chat_session.py initial send block) is
inaccurate about feature management; update the string so it reflects only the
implemented operations (feature_create, feature_create_bulk, and feature_skip) —
e.g. change "manage features (create, edit, delete, reorder)" to "manage
features (create and deprioritize (skip))" or similar, and ensure the same
corrected string is what is passed to add_message(self.project_dir,
self.conversation_id, "assistant", greeting) and yielded to the client.
In @ui/src/hooks/useAssistantChat.ts:
- Around line 409-439: The retry loop in resumeConversation can leak timeouts
and touch stale wsRef.current after unmount; create a ref (e.g.,
resumeTimeoutRef via useRef<number | null>) to store the timeout id,
clearTimeout(resumeTimeoutRef.current) before scheduling a new setTimeout in
checkAndResume and when sending the resume, and ensure you clear it in a
useEffect cleanup (component unmount) so checkAndResume never runs against an
unmounted component; reference the existing symbols resumeConversation, connect,
wsRef, checkAndResume, maxRetries and retryCount when adding/clearing the
timeout ref.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
README.mdserver/routers/assistant_chat.pyserver/services/assistant_chat_session.pystart_ui.shui/src/components/AssistantChat.tsxui/src/hooks/useAssistantChat.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use SQLite with SQLAlchemy ORM for database access and feature management
Files:
server/services/assistant_chat_session.pyserver/routers/assistant_chat.py
ui/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
ui/src/**/*.{ts,tsx}: Use TypeScript for React components in the UI (React 18 with TypeScript)
Use React Query (TanStack Query) for API calls and data fetching in the UI
Use Radix UI for component primitives in the React UI
Run ESLint to lint React UI code
Files:
ui/src/hooks/useAssistantChat.tsui/src/components/AssistantChat.tsx
server/routers/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use FastAPI for REST API endpoints in the server/routers/ directory
Files:
server/routers/assistant_chat.py
🧠 Learnings (5)
📚 Learning: 2026-01-10T08:23:04.012Z
Learnt from: CR
Repo: leonvanzyl/autocoder PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-10T08:23:04.012Z
Learning: Applies to mcp_server/**/*.py : Use MCP (Model Context Protocol) servers for agent tools: feature_mcp.py for feature management, with tools: feature_get_stats, feature_get_next, feature_get_for_regression, feature_mark_passing, feature_skip, feature_create_bulk
Applied to files:
README.md
📚 Learning: 2026-01-10T08:23:04.012Z
Learnt from: CR
Repo: leonvanzyl/autocoder PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-10T08:23:04.012Z
Learning: Two-agent pattern: Initializer Agent reads app spec and creates features in first session; Coding Agent implements features one by one in subsequent sessions
Applied to files:
README.md
📚 Learning: 2026-01-10T08:23:04.012Z
Learnt from: CR
Repo: leonvanzyl/autocoder PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-10T08:23:04.012Z
Learning: Applies to server/routers/**/*.py : Use FastAPI for REST API endpoints in the server/routers/ directory
Applied to files:
README.md
📚 Learning: 2026-01-10T08:23:04.012Z
Learnt from: CR
Repo: leonvanzyl/autocoder PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-10T08:23:04.012Z
Learning: Applies to ui/src/styles/**/*.css : React UI custom animations: animate-slide-in, animate-pulse-neo, animate-shimmer
Applied to files:
ui/src/components/AssistantChat.tsx
📚 Learning: 2026-01-10T08:23:04.012Z
Learnt from: CR
Repo: leonvanzyl/autocoder PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-10T08:23:04.012Z
Learning: Applies to ui/src/styles/**/*.css : React UI uses neobrutalism design system with CSS variables for color tokens: --color-neo-pending (yellow), --color-neo-progress (cyan), --color-neo-done (green)
Applied to files:
ui/src/components/AssistantChat.tsx
🧬 Code graph analysis (4)
server/services/assistant_chat_session.py (1)
server/services/assistant_database.py (1)
add_message(162-196)
ui/src/hooks/useAssistantChat.ts (3)
ui/src/lib/types.ts (2)
AssistantConversation(238-245)ChatMessage(224-232)ui/src/lib/api.ts (2)
listAssistantConversations(253-257)getAssistantConversation(259-266)server/services/assistant_chat_session.py (1)
start(194-285)
server/routers/assistant_chat.py (1)
server/services/assistant_chat_session.py (2)
create_session(377-403)start(194-285)
ui/src/components/AssistantChat.tsx (1)
ui/src/hooks/useAssistantChat.ts (1)
useAssistantChat(46-523)
🔇 Additional comments (9)
server/services/assistant_chat_session.py (1)
194-194: LGTM!The
skip_greetingparameter addition is correctly documented and defaults toFalseto preserve backward compatibility.Also applies to: 201-202
ui/src/components/AssistantChat.tsx (3)
28-38: LGTM!The hook integration correctly uses the new
isLoadingHistoryandstartNewConversationexports from the updateduseAssistantChathook.
98-106: LGTM!The "New Chat" button is well-implemented with proper disabled states, accessibility attributes, and clear visual feedback.
113-125: LGTM!The loading states are well-ordered:
isLoadingHistorytakes precedence overisLoading, providing clear feedback when fetching conversation history versus waiting for assistant responses.ui/src/hooks/useAssistantChat.ts (5)
89-102: LGTM!The
refreshConversationsfunction correctly fetches and sorts conversations. Error handling logs failures without disrupting the UI, which is appropriate for a background refresh operation.
105-122: LGTM!The
loadConversationMessagesfunction properly transforms backend messages to the UI format and handles errors gracefully with an empty array fallback.
442-460: LGTM!The
switchConversationfunction correctly manages the loading state and conditionally resumes or starts based on whether the conversation has existing messages.
463-506: LGTM!The initialization effect is well-structured with proper error handling and fallback behavior. The
hasInitializedRefguard prevents duplicate initialization, and the loading state is correctly managed in thefinallyblock.
26-40: LGTM!The
UseAssistantChatReturninterface correctly reflects all new exports while maintaining thestartmethod for backward compatibility.
README.md
Outdated
| - `feature_create` - Create a single feature | ||
| - `feature_update` - Edit feature details | ||
| - `feature_delete` - Remove a feature |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Documentation inconsistent with implementation.
The README lists feature_update and feature_delete as available MCP tools, but these tools are not included in the ASSISTANT_FEATURE_TOOLS list in server/services/assistant_chat_session.py (lines 48-62). Only feature_create, feature_create_bulk, and feature_skip are actually available.
Based on the implementation in server/services/assistant_chat_session.py, the actual tools are:
feature_create- Create a single featurefeature_create_bulk- Initialize all featuresfeature_skip- Move feature to end of queue
| ### Project Assistant | ||
|
|
||
| The Web UI includes a **Project Assistant** - an AI-powered chat interface for each project. Click the chat button in the bottom-right corner to open it. | ||
|
|
||
| **Capabilities:** | ||
| - **Explore the codebase** - Ask questions about files, architecture, and implementation details | ||
| - **Manage features** - Create, edit, delete, and reorder features via natural language | ||
| - **Get feature details** - Ask about specific features, their status, and test steps | ||
|
|
||
| **Conversation Persistence:** | ||
| - Conversations are automatically saved to `assistant.db` in each project directory | ||
| - When you navigate away and return, your conversation resumes where you left off | ||
| - Click "New Chat" to start a fresh conversation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Capabilities documentation overstates actual features.
Line 118 claims the assistant can "Create, edit, delete, and reorder features" but the implementation only supports create and skip operations. This aligns with the same issue found in the greeting text (server/services/assistant_chat_session.py line 276).
🤖 Prompt for AI Agents
In @README.md around lines 111 - 123, Update the Project Assistant capabilities
text in README.md to accurately list only the supported operations (create and
skip) instead of "Create, edit, delete, and reorder features", and make the same
change to the assistant greeting text defined in the AssistantChatSession
implementation so both UX text and backend greeting match; locate the assistant
greeting string in the AssistantChatSession class (the method that composes the
initial/system prompt) and remove references to edit/delete/reorder, replacing
them with the correct "create and skip" wording.
| elif msg_type == "resume": | ||
| # Resume an existing conversation without sending greeting | ||
| conversation_id = message.get("conversation_id") | ||
|
|
||
| try: | ||
| # Create session | ||
| session = await create_session( | ||
| project_name, | ||
| project_dir, | ||
| conversation_id=conversation_id, | ||
| ) | ||
| # Initialize but skip the greeting | ||
| async for chunk in session.start(skip_greeting=True): | ||
| await websocket.send_json(chunk) | ||
| # Confirm we're ready | ||
| await websocket.send_json({ | ||
| "type": "conversation_created", | ||
| "conversation_id": conversation_id, | ||
| }) | ||
| except Exception as e: | ||
| logger.exception(f"Error resuming assistant session for {project_name}") | ||
| await websocket.send_json({ | ||
| "type": "error", | ||
| "content": f"Failed to resume session: {str(e)}" | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validate conversation_id before resuming.
The resume message handler does not validate that conversation_id is provided. If a client sends {"type": "resume"} without a conversation_id, this will pass None to create_session, which could create unintended behavior (a new conversation instead of resuming).
🛡️ Proposed validation
elif msg_type == "resume":
# Resume an existing conversation without sending greeting
conversation_id = message.get("conversation_id")
+
+ if conversation_id is None:
+ await websocket.send_json({
+ "type": "error",
+ "content": "conversation_id is required for resume"
+ })
+ continue
try:
# Create session📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| elif msg_type == "resume": | |
| # Resume an existing conversation without sending greeting | |
| conversation_id = message.get("conversation_id") | |
| try: | |
| # Create session | |
| session = await create_session( | |
| project_name, | |
| project_dir, | |
| conversation_id=conversation_id, | |
| ) | |
| # Initialize but skip the greeting | |
| async for chunk in session.start(skip_greeting=True): | |
| await websocket.send_json(chunk) | |
| # Confirm we're ready | |
| await websocket.send_json({ | |
| "type": "conversation_created", | |
| "conversation_id": conversation_id, | |
| }) | |
| except Exception as e: | |
| logger.exception(f"Error resuming assistant session for {project_name}") | |
| await websocket.send_json({ | |
| "type": "error", | |
| "content": f"Failed to resume session: {str(e)}" | |
| }) | |
| elif msg_type == "resume": | |
| # Resume an existing conversation without sending greeting | |
| conversation_id = message.get("conversation_id") | |
| if conversation_id is None: | |
| await websocket.send_json({ | |
| "type": "error", | |
| "content": "conversation_id is required for resume" | |
| }) | |
| continue | |
| try: | |
| # Create session | |
| session = await create_session( | |
| project_name, | |
| project_dir, | |
| conversation_id=conversation_id, | |
| ) | |
| # Initialize but skip the greeting | |
| async for chunk in session.start(skip_greeting=True): | |
| await websocket.send_json(chunk) | |
| # Confirm we're ready | |
| await websocket.send_json({ | |
| "type": "conversation_created", | |
| "conversation_id": conversation_id, | |
| }) | |
| except Exception as e: | |
| logger.exception(f"Error resuming assistant session for {project_name}") | |
| await websocket.send_json({ | |
| "type": "error", | |
| "content": f"Failed to resume session: {str(e)}" | |
| }) |
🤖 Prompt for AI Agents
In @server/routers/assistant_chat.py around lines 291 - 315, The resume handler
fails to validate conversation_id and may call create_session with None; before
calling create_session in the "resume" branch, check that
message.get("conversation_id") returns a non-empty string/UUID and if
missing/invalid, log a warning and send a websocket error JSON (type:"error",
content:"Missing or invalid conversation_id for resume") and skip
create_session; otherwise proceed to call
create_session(conversation_id=conversation_id) and keep the existing try/except
logic around session.start and websocket.send_json.
| # Send initial greeting (unless resuming) | ||
| if not skip_greeting: | ||
| try: | ||
| greeting = f"Hello! I'm your project assistant for **{self.project_name}**. I can help you understand the codebase, manage features (create, edit, delete, reorder), and answer questions about the project. What would you like to do?" | ||
|
|
||
| # Store the greeting in the database | ||
| add_message(self.project_dir, self.conversation_id, "assistant", greeting) | ||
| # Store the greeting in the database | ||
| add_message(self.project_dir, self.conversation_id, "assistant", greeting) | ||
|
|
||
| yield {"type": "text", "content": greeting} | ||
| yield {"type": "response_done"} | ||
| except Exception as e: | ||
| logger.exception("Failed to send greeting") | ||
| yield {"type": "error", "content": f"Failed to start conversation: {str(e)}"} | ||
| yield {"type": "text", "content": greeting} | ||
| yield {"type": "response_done"} | ||
| except Exception as e: | ||
| logger.exception("Failed to send greeting") | ||
| yield {"type": "error", "content": f"Failed to start conversation: {str(e)}"} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, let's look at the ASSISTANT_FEATURE_TOOLS definition in the file
cat -n server/services/assistant_chat_session.py | sed -n '48,62p'Repository: leonvanzyl/autocoder
Length of output: 648
🏁 Script executed:
# Search for all feature-related tools in the codebase
rg "feature_(create|delete|edit|update|reorder|skip)" --type py -A 2 | head -100Repository: leonvanzyl/autocoder
Length of output: 2589
🏁 Script executed:
# Look for any tool definitions that might handle feature management
rg "def feature_" --type pyRepository: leonvanzyl/autocoder
Length of output: 616
🏁 Script executed:
# Check the system prompt to understand capabilities mentioned there
cat -n server/services/assistant_chat_session.py | sed -n '88,156p'Repository: leonvanzyl/autocoder
Length of output: 3330
Update greeting text to accurately reflect available feature management capabilities.
The greeting claims "manage features (create, edit, delete, reorder)" but the assistant's actual feature management capabilities are limited to:
feature_createandfeature_create_bulkfor creating featuresfeature_skipfor moving features to the end of the queue (deprioritization)
The system prompt (lines 100-103) correctly describes these constraints. The greeting should be updated to match, such as: "manage features (create and deprioritize)" or "manage features (create and skip)" to avoid misleading users about the assistant's capabilities.
🤖 Prompt for AI Agents
In @server/services/assistant_chat_session.py around lines 273 - 285, The
greeting text in the initial message (the local variable greeting in the
assistant_chat_session.py initial send block) is inaccurate about feature
management; update the string so it reflects only the implemented operations
(feature_create, feature_create_bulk, and feature_skip) — e.g. change "manage
features (create, edit, delete, reorder)" to "manage features (create and
deprioritize (skip))" or similar, and ensure the same corrected string is what
is passed to add_message(self.project_dir, self.conversation_id, "assistant",
greeting) and yielded to the client.
| const resumeConversation = useCallback( | ||
| (convId: number) => { | ||
| connect(); | ||
| setConversationId(convId); | ||
|
|
||
| // Wait for connection then send resume message (no greeting) | ||
| const maxRetries = 50; | ||
| let retryCount = 0; | ||
|
|
||
| const checkAndResume = () => { | ||
| if (wsRef.current?.readyState === WebSocket.OPEN) { | ||
| // Send start with conversation_id but backend won't send greeting | ||
| // for resumed conversations with messages | ||
| wsRef.current.send( | ||
| JSON.stringify({ | ||
| type: "resume", | ||
| conversation_id: convId, | ||
| }), | ||
| ); | ||
| } else if (wsRef.current?.readyState === WebSocket.CONNECTING) { | ||
| retryCount++; | ||
| if (retryCount < maxRetries) { | ||
| setTimeout(checkAndResume, 100); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| setTimeout(checkAndResume, 100); | ||
| }, | ||
| [connect], | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential memory leak in retry loop.
The resumeConversation function uses setTimeout for retry logic (line 431), but these timeouts are not tracked or cleaned up if the component unmounts. This could lead to:
- Callbacks firing after unmount
- Attempts to use stale
wsRef.current - State updates on unmounted component
🔧 Proposed fix to track and clean up timeout
Add a ref to track the resume timeout and clean it up:
const reconnectTimeoutRef = useRef<number | null>(null);
const checkAndSendTimeoutRef = useRef<number | null>(null);
+const resumeTimeoutRef = useRef<number | null>(null);
const hasInitializedRef = useRef(false);
// Clean up on unmount
useEffect(() => {
return () => {
if (pingIntervalRef.current) {
clearInterval(pingIntervalRef.current);
}
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
}
if (checkAndSendTimeoutRef.current) {
clearTimeout(checkAndSendTimeoutRef.current);
}
+ if (resumeTimeoutRef.current) {
+ clearTimeout(resumeTimeoutRef.current);
+ }
if (wsRef.current) {
wsRef.current.close();
}
currentAssistantMessageRef.current = null;
};
}, []);Then in resumeConversation:
} else if (wsRef.current?.readyState === WebSocket.CONNECTING) {
retryCount++;
if (retryCount < maxRetries) {
- setTimeout(checkAndResume, 100);
+ resumeTimeoutRef.current = window.setTimeout(checkAndResume, 100);
}
}
};
- setTimeout(checkAndResume, 100);
+ resumeTimeoutRef.current = window.setTimeout(checkAndResume, 100);🤖 Prompt for AI Agents
In @ui/src/hooks/useAssistantChat.ts around lines 409 - 439, The retry loop in
resumeConversation can leak timeouts and touch stale wsRef.current after
unmount; create a ref (e.g., resumeTimeoutRef via useRef<number | null>) to
store the timeout id, clearTimeout(resumeTimeoutRef.current) before scheduling a
new setTimeout in checkAndResume and when sending the resume, and ensure you
clear it in a useEffect cleanup (component unmount) so checkAndResume never runs
against an unmounted component; reference the existing symbols
resumeConversation, connect, wsRef, checkAndResume, maxRetries and retryCount
when adding/clearing the timeout ref.
- Update README and greeting to reflect actual capabilities (create and skip) - Remove inaccurate references to edit/delete/reorder features - Add conversation_id validation in resume handler with error message - Fix timeout leak in resumeConversation with resumeTimeoutRef Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Summary
resumeWebSocket message type andskip_greetingparameter to avoid repeating the greeting on reconnectChanges
ui/src/hooks/useAssistantChat.ts: Added conversation fetching, persistence, and resume logicui/src/components/AssistantChat.tsx: Added "New Chat" button and loading statesserver/routers/assistant_chat.py: Addedresumemessage type handlerserver/services/assistant_chat_session.py: Addedskip_greetingparameter tostart()README.md: Documented Project Assistant and added feature MCP toolsTest plan
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.