Skip to content

Conversation

@smokeyScraper
Copy link
Contributor

@smokeyScraper smokeyScraper commented Jun 13, 2025

Interactions Attached

Screenshot 2025-06-14 031514

Summary by CodeRabbit

  • New Features

    • Introduced conversation summarization and memory management for DevRel Agent sessions, enhancing long-term context and session persistence.
    • Added new Discord commands to reset conversation memory and access help for DevRel interactions via a dedicated cog.
    • Integrated updated prompt templates with explicit conversation summary and recent context sections for clearer, more contextual responses.
  • Improvements

    • Enhanced agent state tracking with session timing, interaction counts, and key topic extraction.
    • Improved response generation using recent conversation history and enriched user profile context.
    • Enabled thread-aware state recovery and persistence in synchronous and streaming agent runs with detailed logging.
  • Bug Fixes

    • Improved memory handling to prevent loss of conversation context during session timeouts or manual resets.
  • Chores

    • Added the weaviate-client dependency for future integrations.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jun 13, 2025

Walkthrough

This update introduces conversation summarization and memory management to the DevRelAgent workflow. The agent now checkpoints state, conditionally summarizes conversations, and manages session memory with new nodes and state fields. Several node functions were refactored to return dictionaries instead of mutating state. Discord bot commands for thread reset/help were moved to a new cog, and new summarization prompts and logic were added.

Changes

File(s) Change Summary
backend/app/agents/devrel/agent.py Enhanced DevRelAgent with summarization, memory checkpointing, new nodes, and state management methods.
backend/app/agents/devrel/nodes/gather_context_node.py Refactored to return a new dictionary with context/message instead of mutating state.
backend/app/agents/devrel/nodes/generate_response_node.py Refactored to return a dictionary, improved LLM prompt with summary/context, history truncation, and logging.
backend/app/agents/devrel/nodes/handle_faq_node.py
backend/app/agents/devrel/nodes/handle_web_search_node.py
Changed to return dictionaries instead of mutating AgentState.
backend/app/agents/devrel/nodes/handle_onboarding_node.py
backend/app/agents/devrel/nodes/handle_technical_support_node.py
Changed to return new dictionaries instead of mutating AgentState.
backend/app/agents/devrel/nodes/summarization_node.py New: Added summarization node, key topic extraction, and summary persistence stubs.
backend/app/agents/devrel/prompts/base_prompt.py Expanded and reorganized prompt template to include summary, context, and clearer instructions.
backend/app/agents/devrel/prompts/summarization_prompt.py New: Added conversation summary prompt template.
backend/app/agents/shared/base_agent.py Methods now accept thread IDs for state persistence and recovery; improved logging.
backend/app/agents/shared/state.py Extended AgentState with summary, topics, timestamps, counters, and custom merge logic.
backend/app/core/orchestration/agent_coordinator.py Added memory clearing, timeout handling, and thread-aware DevRel agent invocation.
backend/bots/discord/discord_bot.py Moved reset/help commands to cog; improved command handling; added memory_thread_id to messages.
backend/bots/discord/discord_cogs.py New: Added DevRelCommands cog with reset/help commands for Discord.
backend/main.py Integrated DevRelCommands cog into the Discord bot.
pyproject.toml Added weaviate-client dependency.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant DiscordBot
    participant AgentCoordinator
    participant DevRelAgent
    participant InMemorySaver
    participant LLM

    User->>DiscordBot: Sends message
    DiscordBot->>AgentCoordinator: Forwards message (with memory_thread_id)
    AgentCoordinator->>DevRelAgent: run(initial_state, thread_id)
    DevRelAgent->>InMemorySaver: Retrieve/checkpoint state
    DevRelAgent->>LLM: Generate response (uses summary/context)
    DevRelAgent->>DevRelAgent: Decide if summarization needed
    alt Summarization needed
        DevRelAgent->>LLM: Summarize conversation
        DevRelAgent->>InMemorySaver: Update state with summary/topics
    end
    DevRelAgent->>InMemorySaver: Checkpoint updated state
    DevRelAgent->>AgentCoordinator: Return response, state
    AgentCoordinator->>DiscordBot: Send response to user
Loading

Possibly related PRs

  • AOSSIE-Org/Devr.AI#69: Introduces the initial DevRelAgent class and workflow, which this PR directly extends with summarization and memory management.

Suggested labels

enhancement

Poem

A bunny hops through memory’s lane,
Summarizing threads with gentle brain.
With checkpoints set and context clear,
Conversations last from year to year.
Discord commands now neatly in a cog,
The agent’s smarter—no need to jog!
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Summary of Changes

Hello @smokeyScraper, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces significant architectural changes to the agent system, primarily focusing on implementing state persistence and conversation memory using LangGraph's checkpointer feature. It adds a summarization mechanism to maintain context over extended interactions and refactors the Discord bot's command handling to use cogs, improving code structure and enabling stateful conversations.

Highlights

  • State Persistence: Integrated LangGraph's InMemorySaver to persist agent state across messages within a thread/session, enabling multi-turn conversations with memory.
  • Conversation Summarization: Added new nodes and logic to the DevRel agent workflow to periodically summarize conversations based on interaction count or timeout, storing the summary and key topics in the agent state for long-term context.
  • Structured State Updates: Refactored existing agent nodes (gather_context, handle_faq, handle_onboarding, handle_technical_support, handle_web_search, generate_response) to return state updates as dictionaries, aligning with LangGraph's recommended pattern for state modification and persistence.
  • Discord Cog Migration: Migrated Discord commands (!reset, !help_devrel) from the main bot class to a dedicated cog (DevRelCommands) for better modularity and organization.
  • Memory Management: Implemented functionality to clear thread memory, triggered manually via the !reset command or automatically upon session timeout, including a placeholder for storing the final summary to a database.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configureGemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

The code changes migrate the command architecture to a cog-based system, incorporating memory persistence and summarization capabilities. The DevRel agent is enhanced with conversation summarization and memory management, improving its ability to handle long-term interactions. The Discord bot is updated to use cogs for command handling and includes a reset command to clear user threads and memory.

@smokeyScraper
Copy link
Contributor Author

@chandansgowda, this seems good to go. could you please review and merge?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🔭 Outside diff range comments (4)
backend/app/agents/devrel/nodes/handle_web_search_node.py (1)

21-33: 🛠️ Refactor suggestion

Add defensive error handling around the external search call

search_tool.search() is an external I/O-bound operation and may raise network or quota related exceptions. A single unhandled failure will bubble up and break the LangGraph run.

-    search_results = await search_tool.search(search_query)
+    try:
+        search_results = await search_tool.search(search_query)
+    except Exception as exc:
+        logger.error(f"Web-search failure for query '{search_query}': {exc}")
+        return {
+            "errors": [str(exc)],
+            "current_task": "web_search_failed"
+        }
backend/app/agents/devrel/nodes/handle_onboarding_node.py (1)

6-17: ⚠️ Potential issue

Return-type annotation is now wrong

The function advertises -> AgentState but returns a plain dict.
This will:

  1. Mislead static type-checkers / IDEs.
  2. Break any downstream code expecting an AgentState instance.
-async def handle_onboarding_node(state: AgentState) -> AgentState:
+async def handle_onboarding_node(state: AgentState) -> dict:
backend/app/agents/devrel/nodes/handle_technical_support_node.py (1)

6-17: ⚠️ Potential issue

Return-type annotation mismatch (same as onboarding node)

-async def handle_technical_support_node(state: AgentState) -> AgentState:
+async def handle_technical_support_node(state: AgentState) -> dict:

Without this fix, mypy/pyright will complain and callers casting to AgentState will crash.

backend/app/agents/devrel/nodes/gather_context_node.py (1)

8-36: ⚠️ Potential issue

Update signature & docstring to reflect immutable-state pattern

The node now returns a delta dict, not a mutated AgentState.

-async def gather_context_node(state: AgentState) -> AgentState:
+async def gather_context_node(state: AgentState) -> dict:

Please also adjust the docstring and any unit tests accordingly.

🧹 Nitpick comments (15)
backend/app/agents/shared/state.py (1)

12-17: add on lists risks silent duplication

operator.add concatenates the full list, potentially duplicating messages during repeated merges.
If LangGraph supports a custom dedupe combiner, consider using it, or slice the result to a max length to avoid unbounded memory growth.

backend/main.py (1)

10-11: Import order nit

Project imports (bots.*) normally come after stdlib & 3rd-party groups. Not blocking, just mentioning for consistency.

backend/app/agents/devrel/nodes/handle_faq_node.py (1)

6-6: Keep type hints aligned with new return shape

You still import AgentState, but only for the first arg. Consider declaring the return type with TypedDict or dict[str, Any] for clarity:

from typing import Any, Dict

async def handle_faq_node(state: AgentState, faq_tool) -> Dict[str, Any]:
backend/app/agents/devrel/nodes/handle_web_search_node.py (1)

34-42: Avoid hard-coding the tool identifier

"tavily_search" is embedded twice, tying the node to one concrete implementation. Either read the name from search_tool (if exposed) or inject it via the caller to keep the node reusable.

backend/app/agents/devrel/nodes/gather_context_node.py (1)

16-22: original_message may be empty – consider graceful fallback

If original_message is missing, the added new_message will have an empty content field, polluting the message history.

original = state.context.get("original_message")
if original:
    new_message = { ... }
    messages = [new_message]
else:
    messages = []
backend/bots/discord/discord_cogs.py (2)

4-4: Prefer project-root-relative import to avoid module resolution issues

Depending on how backend/ is added to PYTHONPATH, bots.discord.discord_bot may fail to resolve when the app is executed from outside the project root. Using the full package path (backend.bots.discord.discord_bot) or an absolute import anchored at the top-level package prevents such surprises.

-from bots.discord.discord_bot import DiscordBot
+from backend.bots.discord.discord_bot import DiscordBot

25-40: Minor UX enhancement – mark help embed as ephemeral

Users in busy channels may appreciate the help message being ephemeral or DM-ed to them instead of cluttering the thread. Consider await ctx.send(embed=embed, ephemeral=True) if you are on interactions or switch to slash-command + default_permission=False for cleaner UX.

backend/app/agents/devrel/nodes/generate_response_node.py (2)

84-99: Return typing hints & key ordering for downstream clarity

Down-stream nodes/consumers expect final_response first; add a TypedDict/dataclass for stronger guarantees and deterministic ordering.


17-25: Guard against missing keys in web-search results

result.get('title') etc. may return None, leading to "None" in user-facing text. Fallback to empty string improves readability.

backend/app/core/orchestration/agent_coordinator.py (2)

4-4: Remove unused datetime import

Static analysis flags it and it adds dead weight.

-from datetime import datetime
🧰 Tools
🪛 Ruff (0.11.9)

4-4: datetime.datetime imported but unused

Remove unused import: datetime.datetime

(F401)


55-62: Missing awaitable error propagation

If self.devrel_agent.run throws, the surrounding except block swallows the traceback and only sends a generic error message. Log exc_info=True to aid debugging.

backend/app/agents/shared/base_agent.py (1)

56-76: Consider back-pressure mechanism in streaming

astream may yield large bursts; optionally throttle or buffer to prevent flooding upstream consumers.

backend/app/agents/devrel/nodes/summarization_node.py (3)

28-32: Clarify additive update pattern

Returning "interaction_count": 1 relies on the LangGraph add aggregator to increment the stored count. Add a brief comment to avoid future confusion for maintainers unfamiliar with this pattern.


33-38: Hard-coded timeout/threshold

THREAD_TIMEOUT_HOURS and SUMMARIZATION_THRESHOLD are constants here. Moving them to settings (or env vars) enables tuning without code changes.


104-109: Error path swallows original exception

The except block logs but only returns a generic error string. Re-raise or include traceback.format_exc() to aid debugging.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4dd80b8 and af5e0bc.

⛔ Files ignored due to path filters (1)
  • poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (17)
  • backend/app/agents/devrel/agent.py (6 hunks)
  • backend/app/agents/devrel/nodes/gather_context_node.py (2 hunks)
  • backend/app/agents/devrel/nodes/generate_response_node.py (1 hunks)
  • backend/app/agents/devrel/nodes/handle_faq_node.py (2 hunks)
  • backend/app/agents/devrel/nodes/handle_onboarding_node.py (1 hunks)
  • backend/app/agents/devrel/nodes/handle_technical_support_node.py (1 hunks)
  • backend/app/agents/devrel/nodes/handle_web_search_node.py (2 hunks)
  • backend/app/agents/devrel/nodes/summarization_node.py (1 hunks)
  • backend/app/agents/devrel/prompts/base_prompt.py (1 hunks)
  • backend/app/agents/devrel/prompts/summarization_prompt.py (1 hunks)
  • backend/app/agents/shared/base_agent.py (2 hunks)
  • backend/app/agents/shared/state.py (2 hunks)
  • backend/app/core/orchestration/agent_coordinator.py (5 hunks)
  • backend/bots/discord/discord_bot.py (2 hunks)
  • backend/bots/discord/discord_cogs.py (1 hunks)
  • backend/main.py (2 hunks)
  • pyproject.toml (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (7)
backend/main.py (1)
backend/bots/discord/discord_cogs.py (1)
  • DevRelCommands (6-40)
backend/app/agents/devrel/nodes/handle_faq_node.py (1)
backend/app/agents/shared/state.py (1)
  • AgentState (18-73)
backend/app/agents/devrel/nodes/generate_response_node.py (1)
backend/app/agents/shared/state.py (1)
  • AgentState (18-73)
backend/app/core/orchestration/agent_coordinator.py (5)
backend/app/agents/devrel/agent.py (2)
  • get_thread_state (128-136)
  • clear_thread_memory (138-170)
backend/app/agents/shared/state.py (1)
  • AgentState (18-73)
backend/app/core/orchestration/queue_manager.py (2)
  • AsyncQueueManager (14-135)
  • register_handler (67-70)
backend/app/agents/devrel/nodes/summarization_node.py (1)
  • store_summary_to_database (132-136)
backend/app/agents/shared/base_agent.py (1)
  • run (22-54)
backend/app/agents/devrel/agent.py (2)
backend/app/agents/devrel/nodes/summarization_node.py (3)
  • check_summarization_needed (14-46)
  • summarize_conversation_node (48-109)
  • store_summary_to_database (132-136)
backend/app/agents/shared/state.py (1)
  • AgentState (18-73)
backend/app/agents/devrel/nodes/handle_web_search_node.py (1)
backend/app/agents/shared/state.py (1)
  • AgentState (18-73)
backend/app/agents/shared/base_agent.py (1)
backend/app/agents/shared/state.py (1)
  • AgentState (18-73)
🪛 Ruff (0.11.9)
backend/app/core/orchestration/agent_coordinator.py

4-4: datetime.datetime imported but unused

Remove unused import: datetime.datetime

(F401)

🪛 Pylint (3.3.7)
backend/app/agents/devrel/agent.py

[refactor] 148-163: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it

(R1705)

🔇 Additional comments (9)
pyproject.toml (1)

25-25: Confirm weaviate-client upper-bound is intentional

Pinning <5.0.0 prevents automatic upgrades to the next major. Check the project’s upgrade policy—weaviate-client v5 may ship breaking changes you actually want to adopt later.
If the cap is just precautionary, consider adding a comment in the TOML or CI alert to revisit the bound periodically.

Would you like a quick script that checks PyPI for the latest major and warns if your cap is lagging?

backend/app/agents/shared/state.py (1)

41-42: interaction_count additive merge can overflow

The additive combiner is great, but consider a ceiling or cast to int after merge to guard against malicious or erroneous large values.

backend/main.py (1)

33-33: Cog registration happens before login—good

Registering the cog early keeps command discovery synchronous and avoids race conditions. No issues spotted.

backend/app/agents/devrel/prompts/summarization_prompt.py (1)

1-18: Prompt template looks solid

Clear instructions, token-friendly length cap, and placeholders follow the existing style. 👍

backend/bots/discord/discord_bot.py (2)

46-50: 👍 Cleaner command dispatch

Using get_context + invoke respects command cooldowns, checks and cogs automatically. Nice upgrade over the manual prefix check.


87-88: memory_thread_id key looks sensible – verify consumers

The new field ties messages to long-term memory. Ensure every downstream component that deserialises queue items tolerates this extra key; otherwise add a default **kwargs sink.

backend/app/agents/devrel/agent.py (3)

73-87: Edge ordering looks correct – good use of conditional routing


128-137: Confirm CompiledGraph.get_state behaviour

self.graph.get_state(config) is called inside an async method but the call itself is synchronous. Verify that:

  1. get_state is non-blocking (no I/O);
  2. Returned object exposes values as expected.

If either assumption is false, wrap with await or move to a thread-pool.


34-34: 🛠️ Refactor suggestion

Singleton vs. per-instance memory store

InMemorySaver() is instantiated per DevRelAgent. If multiple agent instances are created (e.g., per-request workers), conversation state will fragment across processes, defeating “persistent” memory. Consider injecting a shared store (RedisSaver, DynamoDBSaver, etc.) or passing a singleton instance via DI.

⛔ Skipped due to learnings
Learnt from: smokeyScraper
PR: AOSSIE-Org/Devr.AI#75
File: backend/app/agents/devrel/agent.py:34-35
Timestamp: 2025-06-13T21:56:19.160Z
Learning: In the Devr.AI backend, the DevRelAgent follows a singleton pattern where only one instance exists for the entire application lifetime, using InMemorySaver with thread-based conversation management to persist user conversations across sessions.

Comment on lines +39 to +41
session_start_time: datetime = Field(default_factory=datetime.now)
last_interaction_time: datetime = Field(default_factory=datetime.now)
interaction_count: Annotated[int, add] = Field(default=0)
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use timezone-aware timestamps

datetime.now() returns naive datetimes. Mixing naive and aware values later (e.g., when comparing against utcnow()) will raise errors.

-from datetime import datetime
+from datetime import datetime, timezone
...
-session_start_time: datetime = Field(default_factory=datetime.now)
-last_interaction_time: datetime = Field(default_factory=datetime.now)
+session_start_time: datetime = Field(default_factory=lambda: datetime.now(tz=timezone.utc))
+last_interaction_time: datetime = Field(default_factory=lambda: datetime.now(tz=timezone.utc))
📝 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.

Suggested change
session_start_time: datetime = Field(default_factory=datetime.now)
last_interaction_time: datetime = Field(default_factory=datetime.now)
interaction_count: Annotated[int, add] = Field(default=0)
--- a/backend/app/agents/shared/state.py
+++ b/backend/app/agents/shared/state.py
@@ -1,5 +1,5 @@
-from datetime import datetime
+from datetime import datetime, timezone
from pydantic import Field
from typing import Annotated
from .helpers import add
@@ -37,8 +37,9 @@ class AgentState(BaseModel):
"""
- session_start_time: datetime = Field(default_factory=datetime.now)
- last_interaction_time: datetime = Field(default_factory=datetime.now)
+ session_start_time: datetime = Field(
+ default_factory=lambda: datetime.now(tz=timezone.utc)
+ )
+ last_interaction_time: datetime = Field(
+ default_factory=lambda: datetime.now(tz=timezone.utc)
+ )
interaction_count: Annotated[int, add] = Field(default=0)
🤖 Prompt for AI Agents
In backend/app/agents/shared/state.py around lines 39 to 41, the use of
datetime.now() creates naive datetime objects which can cause errors when mixed
with timezone-aware datetimes. Replace datetime.now() with a timezone-aware
alternative such as datetime.now(timezone.utc) or use datetime.utcnow() with
proper timezone info to ensure all timestamps are timezone-aware and consistent.

Comment on lines +6 to +11
def replace_summary(existing: Optional[str], new: Optional[str]) -> Optional[str]:
"""Replace summary"""
if new is not None:
return new
return existing

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Allow None inputs in helpers

replace_summary already accepts Optional[str], but replace_topics below doesn’t allow None. When state merges from scratch the incoming value may be None, causing a TypeError.

-def replace_topics(existing: List[str], new: List[str]) -> List[str]:
+from typing import Optional
+
+def replace_topics(existing: Optional[List[str]], new: Optional[List[str]]) -> List[str]:
     """Replace topics"""
-    if new:
+    if new:
         return new
-    return existing
+    return existing or []
📝 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.

Suggested change
def replace_summary(existing: Optional[str], new: Optional[str]) -> Optional[str]:
"""Replace summary"""
if new is not None:
return new
return existing
from typing import Optional
def replace_topics(existing: Optional[List[str]], new: Optional[List[str]]) -> List[str]:
"""Replace topics"""
if new:
return new
return existing or []
🤖 Prompt for AI Agents
In backend/app/agents/shared/state.py around lines 6 to 11, the function
replace_topics should be updated to accept Optional[str] inputs like
replace_summary does. Modify replace_topics to handle None values gracefully by
allowing its parameters to be Optional[str] and adjusting the logic to avoid
TypeErrors when merging state with None inputs.

Comment on lines +19 to 26
return {
"task_result": {
"type": "faq",
"response": faq_response,
"source": "faq_database"
},
"current_task": "faq_handled"
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling around external FAQ call

An exception inside faq_tool.get_response will bubble up and break the graph. Wrap it to surface a controlled failure:

-    faq_response = await faq_tool.get_response(latest_message)
+    try:
+        faq_response = await faq_tool.get_response(latest_message)
+    except Exception as e:
+        logger.exception("FAQ tool failed")
+        return {
+            "errors": [str(e)],
+            "current_task": "faq_failed"
+        }
📝 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.

Suggested change
return {
"task_result": {
"type": "faq",
"response": faq_response,
"source": "faq_database"
},
"current_task": "faq_handled"
}
try:
faq_response = await faq_tool.get_response(latest_message)
except Exception as e:
logger.exception("FAQ tool failed")
return {
"errors": [str(e)],
"current_task": "faq_failed"
}
return {
"task_result": {
"type": "faq",
"response": faq_response,
"source": "faq_database"
},
"current_task": "faq_handled"
}
🤖 Prompt for AI Agents
In backend/app/agents/devrel/nodes/handle_faq_node.py around lines 19 to 26, the
call to faq_tool.get_response lacks error handling, so any exception will
propagate and break the graph. Wrap the call to faq_tool.get_response in a
try-except block, catch exceptions, and return a controlled failure response
indicating the error instead of letting the exception bubble up.

Comment on lines +11 to +24
@commands.command(name="reset")
async def reset_thread(self, ctx: commands.Context):
"""Reset your DevRel thread and memory."""
user_id = str(ctx.author.id)
cleanup = {
"type": "clear_thread_memory",
"memory_thread_id": user_id,
"user_id": user_id,
"cleanup_reason": "manual_reset"
}
await self.queue.enqueue(cleanup, QueuePriority.HIGH)
self.bot.active_threads.pop(user_id, None)
await ctx.send("Your DevRel thread & memory have been reset! Send another message to start fresh.")

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Handle enqueue/cleanup failures to prevent user confusion

If enqueue raises (e.g. queue manager down) the thread won’t reset but the user still receives the success message. Wrap the enqueue/active_threads.pop pair in a try/except and inform the user on failure.

🤖 Prompt for AI Agents
In backend/bots/discord/discord_cogs.py around lines 11 to 24, the reset_thread
command does not handle exceptions from the enqueue call or the
active_threads.pop operation, which can cause the user to receive a success
message even if the reset fails. Wrap the enqueue and active_threads.pop calls
in a try/except block, and if an exception occurs, catch it and send an error
message to the user indicating the reset failed, ensuring accurate user
feedback.

Comment on lines +39 to 43
recent_messages_count = min(10, len(state.messages))
conversation_history_str = "\n".join([
f"{msg.get('type', 'unknown')}: {msg.get('content', '')}"
for msg in state.conversation_history[-5:]
f"{msg.get('role', 'user')}: {msg.get('content', '')}"
for msg in state.messages[-recent_messages_count:]
])
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fallback to legacy "type" field to avoid empty roles after migration

Older AgentState.messages objects still use the "type" key. Restricting to "role" risks emitting "user" for every historic message, degrading context.

-        f"{msg.get('role', 'user')}: {msg.get('content', '')}"
+        f"{msg.get('role', msg.get('type', 'user'))}: {msg.get('content', '')}"
📝 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.

Suggested change
recent_messages_count = min(10, len(state.messages))
conversation_history_str = "\n".join([
f"{msg.get('type', 'unknown')}: {msg.get('content', '')}"
for msg in state.conversation_history[-5:]
f"{msg.get('role', 'user')}: {msg.get('content', '')}"
for msg in state.messages[-recent_messages_count:]
])
recent_messages_count = min(10, len(state.messages))
conversation_history_str = "\n".join([
f"{msg.get('role', msg.get('type', 'user'))}: {msg.get('content', '')}"
for msg in state.messages[-recent_messages_count:]
])
🤖 Prompt for AI Agents
In backend/app/agents/devrel/nodes/generate_response_node.py around lines 39 to
43, the code currently uses only the "role" key to determine message roles,
which causes fallback to "user" for older messages that use the "type" key.
Update the code to first check for the "role" key and if missing, fallback to
the "type" key to correctly preserve roles from legacy messages and avoid
degrading context.

Comment on lines +11 to +13
"USER'S CURRENT MESSAGE:\n"
"\"{latest_message}\"\n\n"

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Escape user input to avoid prompt-format breakage

A user message containing " or {} can corrupt the prompt formatting. Use repr(latest_message) or a safe Jinja-style escape.

🤖 Prompt for AI Agents
In backend/app/agents/devrel/prompts/base_prompt.py around lines 11 to 13, the
user input latest_message is inserted directly into the prompt string, which can
break formatting if it contains quotes or braces. To fix this, escape
latest_message by using repr(latest_message) or apply a safe Jinja-style escape
before inserting it into the prompt to prevent prompt-format corruption.

Comment on lines +22 to 44
async def run(self, initial_state: AgentState, thread_id: str) -> AgentState:
"""Execute the agent workflow with memory persistence"""
try:
logger.info("Starting %s for session %s", self.agent_name, initial_state.session_id)
result = await self.graph.ainvoke(initial_state.model_dump())
logger.info(f"Starting {self.agent_name} for session {initial_state.session_id}")
config = {"configurable": {"thread_id": thread_id}}
existing_state = self.graph.get_state(config)

if existing_state and existing_state.values:
existing_agent_state = AgentState(**existing_state.values)
logger.info(f"RECOVERED existing state for thread {thread_id}:")
logger.info(f" - Messages: {len(existing_agent_state.messages)}")
logger.info(f" - Summary: '{existing_agent_state.conversation_summary or 'None'}'")
logger.info(f" - Summary length: {len(existing_agent_state.conversation_summary or '')} chars")
logger.info(f" - Topics: {existing_agent_state.key_topics}")
logger.info(f" - Interaction count: {existing_agent_state.interaction_count}")
logger.info(f" - Session start: {existing_agent_state.session_start_time}")
else:
logger.info(f"No existing state found for thread {thread_id} - starting fresh")

logger.info(f"Running {self.agent_name} with memory for thread {thread_id}")

result = await self.graph.ainvoke(initial_state.model_dump(), config)
return AgentState(**result)
Copy link
Contributor

@coderabbitai coderabbitai bot Jun 13, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

Gracefully handle empty thread_id

When thread_id is empty the checkpoint key collides for all sessions, causing state bleeding. Assert non-empty or derive a deterministic fallback.

-        config = {"configurable": {"thread_id": thread_id}}
+        if not thread_id:
+            raise ValueError("thread_id must be provided for memory persistence")
+        config = {"configurable": {"thread_id": thread_id}}
📝 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.

Suggested change
async def run(self, initial_state: AgentState, thread_id: str) -> AgentState:
"""Execute the agent workflow with memory persistence"""
try:
logger.info("Starting %s for session %s", self.agent_name, initial_state.session_id)
result = await self.graph.ainvoke(initial_state.model_dump())
logger.info(f"Starting {self.agent_name} for session {initial_state.session_id}")
config = {"configurable": {"thread_id": thread_id}}
existing_state = self.graph.get_state(config)
if existing_state and existing_state.values:
existing_agent_state = AgentState(**existing_state.values)
logger.info(f"RECOVERED existing state for thread {thread_id}:")
logger.info(f" - Messages: {len(existing_agent_state.messages)}")
logger.info(f" - Summary: '{existing_agent_state.conversation_summary or 'None'}'")
logger.info(f" - Summary length: {len(existing_agent_state.conversation_summary or '')} chars")
logger.info(f" - Topics: {existing_agent_state.key_topics}")
logger.info(f" - Interaction count: {existing_agent_state.interaction_count}")
logger.info(f" - Session start: {existing_agent_state.session_start_time}")
else:
logger.info(f"No existing state found for thread {thread_id} - starting fresh")
logger.info(f"Running {self.agent_name} with memory for thread {thread_id}")
result = await self.graph.ainvoke(initial_state.model_dump(), config)
return AgentState(**result)
async def run(self, initial_state: AgentState, thread_id: str) -> AgentState:
"""Execute the agent workflow with memory persistence"""
try:
logger.info(f"Starting {self.agent_name} for session {initial_state.session_id}")
if not thread_id:
raise ValueError("thread_id must be provided for memory persistence")
config = {"configurable": {"thread_id": thread_id}}
existing_state = self.graph.get_state(config)
if existing_state and existing_state.values:
existing_agent_state = AgentState(**existing_state.values)
logger.info(f"RECOVERED existing state for thread {thread_id}:")
logger.info(f" - Messages: {len(existing_agent_state.messages)}")
logger.info(f" - Summary: '{existing_agent_state.conversation_summary or 'None'}'")
logger.info(f" - Summary length: {len(existing_agent_state.conversation_summary or '')} chars")
logger.info(f" - Topics: {existing_agent_state.key_topics}")
logger.info(f" - Interaction count: {existing_agent_state.interaction_count}")
logger.info(f" - Session start: {existing_agent_state.session_start_time}")
else:
logger.info(f"No existing state found for thread {thread_id} - starting fresh")
logger.info(f"Running {self.agent_name} with memory for thread {thread_id}")
result = await self.graph.ainvoke(initial_state.model_dump(), config)
return AgentState(**result)
🤖 Prompt for AI Agents
In backend/app/agents/shared/base_agent.py around lines 22 to 44, the run method
does not handle empty thread_id values, which causes checkpoint key collisions
and state bleeding across sessions. Add a check at the start of the method to
assert that thread_id is not empty or provide a deterministic fallback value if
it is empty. This ensures unique keys for each session and prevents state
overlap.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the user_id itself maps as thread_id for any user so not a problem. will always be passed.

Copy link
Contributor

Choose a reason for hiding this comment

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

@smokeyScraper, understood! Thank you for the clarification. Since user_id directly maps to thread_id in your architecture, you're guaranteed to always have a non-empty value, which eliminates the potential collision issue. Your design ensures each user gets a unique thread identifier automatically.


✏️ Learnings added
Learnt from: smokeyScraper
PR: AOSSIE-Org/Devr.AI#76
File: backend/app/agents/shared/base_agent.py:22-44
Timestamp: 2025-06-14T05:57:43.849Z
Learning: In the Devr.AI codebase, user_id directly maps as thread_id for memory persistence in the agent system, ensuring thread_id is always non-empty and unique per user.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
backend/app/core/orchestration/agent_coordinator.py (1)

99-103: ⚠️ Potential issue

Duplicate summary persistence — store once

The final summary is written explicitly (store_summary_to_database) and then
clear_thread_memory(force_clear=True) is invoked, which (per earlier discussion)
already persists the same summary. This double-writes the record and wastes I/O.

-    # Store final summary to database
-    await store_summary_to_database(state)
-
-    # Clear from InMemorySaver
-    await self.devrel_agent.clear_thread_memory(memory_thread_id, force_clear=True)
+    # Let clear_thread_memory handle summary persistence
+    await self.devrel_agent.clear_thread_memory(memory_thread_id, force_clear=True)
🧹 Nitpick comments (1)
backend/app/core/orchestration/agent_coordinator.py (1)

4-4: Remove unused datetime import

datetime isn’t referenced anywhere in this module and is flagged by Ruff (F401).
Eliminate it to keep the import list lean.

-from datetime import datetime
🧰 Tools
🪛 Ruff (0.11.9)

4-4: datetime.datetime imported but unused

Remove unused import: datetime.datetime

(F401)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between af5e0bc and ecdc688.

📒 Files selected for processing (1)
  • backend/app/core/orchestration/agent_coordinator.py (5 hunks)
🧰 Additional context used
🪛 Ruff (0.11.9)
backend/app/core/orchestration/agent_coordinator.py

4-4: datetime.datetime imported but unused

Remove unused import: datetime.datetime

(F401)

🔇 Additional comments (1)
backend/app/core/orchestration/agent_coordinator.py (1)

38-40: Guard against empty memory_thread_id

Falling back to "" when both memory_thread_id and user_id are absent can lead to
different users sharing the same blank thread key, corrupting their conversation
state. Consider:

  1. Failing fast when neither identifier is present, or
  2. Generating a deterministic key (e.g., f"anonymous:{session_id}").

Please verify that upstream producers always provide one of the two fields.

Comment on lines +56 to 62
logger.info(f"Running DevRel agent for session {session_id} with memory thread {memory_thread_id}")
result_state = await self.devrel_agent.run(initial_state, memory_thread_id)

# Check if thread timeout was reached during processing
if result_state.memory_timeout_reached:
await self._handle_memory_timeout(memory_thread_id, result_state)

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

memory_timeout_reached check risks AttributeError

result_state.memory_timeout_reached assumes the attribute exists on every
returned AgentState. If an older agent version returns a state lacking that
field, this will raise. Wrap with getattr(result_state, "memory_timeout_reached", False)
or ensure the contract is enforced across all agents.

🤖 Prompt for AI Agents
In backend/app/core/orchestration/agent_coordinator.py around lines 56 to 62,
the code accesses result_state.memory_timeout_reached directly, which may cause
an AttributeError if the attribute is missing in older AgentState versions. To
fix this, replace the direct attribute access with getattr(result_state,
"memory_timeout_reached", False) to safely check the attribute's presence and
default to False if it does not exist.

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.

2 participants