feat(templates): add Discord Community Digest agent template#6343
feat(templates): add Discord Community Digest agent template#6343Ttian18 wants to merge 1 commit intoaden-hive:mainfrom
Conversation
- 3-node pipeline: configure → scan-channels → generate-digest - Uses repo's built-in discord tools (list_guilds, list_channels, get_messages, send_message) - Per-channel dedup cursor via load_data/save_data to avoid duplicate messages on reruns - Digest categories: Action Needed, Interesting Threads, Announcements, FYI - Delivery to user-specified Discord channel - One-shot pipeline (terminal_nodes = ["generate-digest"])
e1001c4 to
82b41fe
Compare
📝 WalkthroughWalkthroughA new Discord Community Digest agent template is added, implementing a 3-node linear pipeline that scans Discord servers, categorizes messages by priority, and delivers summaries. The template includes the agent class, graph definition, three node specifications, CLI tooling, configuration, and MCP server setup. Changes
Sequence DiagramsequenceDiagram
actor User
participant CLI as CLI Interface
participant Agent as DiscordDigestAgent
participant Runtime as Agent Runtime
participant ConfigNode as configure_node
participant ScanNode as scan_channels_node
participant DiscordAPI as Discord API
participant GenNode as generate_digest_node
User->>CLI: Run agent with --run flag
CLI->>Agent: start()
Agent->>Runtime: create_agent_runtime()
Agent->>Runtime: initialize with checkpoint storage
Runtime->>ConfigNode: trigger entry point "default"
ConfigNode->>ConfigNode: collect user preferences (servers, lookback window, keywords)
ConfigNode-->>Runtime: output digest_config
Runtime->>ScanNode: execute with digest_config
ScanNode->>DiscordAPI: discord_list_guilds()
ScanNode->>DiscordAPI: discord_list_channels()
ScanNode->>DiscordAPI: discord_get_messages(within lookback window)
ScanNode->>ScanNode: flag mentions, replies, admin posts, high-engagement
ScanNode-->>Runtime: output channel_data
Runtime->>GenNode: execute with digest_config + channel_data
GenNode->>GenNode: categorize messages (Action Needed, Interesting Threads, Announcements, FYI)
GenNode->>DiscordAPI: discord_send_message(digest summary as DM)
GenNode-->>Runtime: output digest_report
Runtime-->>Agent: execution complete
Agent->>Agent: stop()
CLI-->>User: return ExecutionResult (success + output)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (4)
examples/templates/discord_digest/nodes/__init__.py (1)
1-4: Consider addingfrom __future__ import annotations.Per coding guidelines for Python files.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/templates/discord_digest/nodes/__init__.py` around lines 1 - 4, Add the future annotations import to the module by inserting "from __future__ import annotations" at the top of the file before any other imports or module-level code (the file containing the module docstring and the import of NodeSpec from framework.graph). This ensures forward-compatible type annotations in the module that defines the Discord Community Digest nodes (referenced symbol: NodeSpec) and follows the project coding guidelines.examples/templates/discord_digest/config.py (1)
1-7: Consider addingfrom __future__ import annotationsfor modern type syntax.Per coding guidelines, this import is recommended for Python files to enable modern type annotation syntax.
Suggested fix
"""Runtime configuration for Discord Community Digest.""" +from __future__ import annotations + from dataclasses import dataclass from framework.config import RuntimeConfig🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/templates/discord_digest/config.py` around lines 1 - 7, Add "from __future__ import annotations" as the first import in this module to enable postponed evaluation of annotations and modern type syntax; update the top of the file before any other imports (which currently include dataclass and RuntimeConfig) so that dataclass usage and the RuntimeConfig instantiation (default_config = RuntimeConfig()) benefit from future annotations.examples/templates/discord_digest/__main__.py (1)
23-27: Version is duplicated — consider importing frommetadata.The version
"1.0.0"is hardcoded here and also defined inconfig.pyasAgentMetadata.version. This violates DRY and can lead to version drift.Suggested fix
+from .config import metadata + `@click.group`() -@click.version_option(version="1.0.0") +@click.version_option(version=metadata.version) def cli():🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/templates/discord_digest/__main__.py` around lines 23 - 27, Replace the hardcoded version string in the click.version_option decorator with the centralized version constant; import AgentMetadata (or the module exporting version) and use AgentMetadata.version in the click.version_option call inside the cli function so the CLI version matches the project's single source of truth (refer to the cli function and the click.version_option usage and the AgentMetadata.version symbol).examples/templates/discord_digest/agent.py (1)
1-19: Addfrom __future__ import annotationsand consolidate framework.graph imports.The
GraphSpecimport fromframework.graph.edge(line 6) should be consolidated with otherframework.graphimports (line 5) for consistency. Add the future annotations import at the top to enable modern type syntax.Suggested fix
"""Agent graph construction for Discord Community Digest.""" +from __future__ import annotations + from pathlib import Path -from framework.graph import EdgeSpec, EdgeCondition, Goal, SuccessCriterion, Constraint -from framework.graph.edge import GraphSpec +from framework.graph import ( + EdgeSpec, + EdgeCondition, + Goal, + GraphSpec, + SuccessCriterion, + Constraint, +) from framework.graph.executor import ExecutionResult🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/templates/discord_digest/agent.py` around lines 1 - 19, Add "from __future__ import annotations" as the very first import and consolidate the GraphSpec import into the existing framework.graph import line by importing GraphSpec alongside EdgeSpec, EdgeCondition, Goal, SuccessCriterion, and Constraint; update the imports so GraphSpec comes from framework.graph (not framework.graph.edge) to keep imports consistent (refer to GraphSpec, EdgeSpec, EdgeCondition, Goal, SuccessCriterion, Constraint).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@examples/templates/discord_digest/__main__.py`:
- Around line 13-20: Add type hints to the setup_logging function signature by
changing it to def setup_logging(verbose: bool = False, debug: bool = False) ->
None: so both parameters are annotated as bool and the function return type is
None; leave the internal implementation unchanged and ensure any callers still
pass booleans.
In `@examples/templates/discord_digest/agent.py`:
- Around line 217-225: Add explicit type hints to the async run method: annotate
parameters (e.g., context: Dict[str, Any], session_state:
Optional[YourSessionStateType] = None) and the return type (-> ExecutionResult).
Import needed typing symbols (Optional, Dict, Any) and replace
YourSessionStateType with the actual session state type used in this module;
keep the body using existing calls (start, trigger_and_wait, stop, and
ExecutionResult) unchanged.
- Around line 119-132: Add type hints to the DiscordDigestAgent.__init__
signature and its parameters/return: annotate the config parameter (e.g.,
RuntimeConfig or "RuntimeConfig") and annotate self return as None; update any
other parameters referenced/assigned in __init__ (goal, nodes, edges,
entry_node, entry_points, pause_nodes, terminal_nodes) with appropriate types or
forward-reference strings. If RuntimeConfig is available, import it at top;
otherwise enable from __future__ import annotations and use string annotations
to avoid circular imports. Ensure the method signature and any new imports
follow existing project style and lint rules.
- Around line 202-215: The trigger_and_wait method currently accepts a timeout
param but doesn't forward it to the runtime; update the method signature of
trigger_and_wait to include type hints (e.g., entry_point: str = "default",
input_data: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None,
session_state: Optional[Dict[str, Any]] = None) and pass timeout through to
self._agent_runtime.trigger_and_wait by adding timeout=timeout to that call (or
if you prefer to remove the parameter, delete timeout from the signature and all
references); refer to the trigger_and_wait method and the
self._agent_runtime.trigger_and_wait call when making the change.
In `@examples/templates/discord_digest/nodes/__init__.py`:
- Around line 57-58: The prompt instructions call load_data and save_data
without the required data_dir positional argument; update the prompt text to
call load_data("discord_digest_cursors", data_dir) and
save_data("discord_digest_cursors", updated_cursor_dict, data_dir) (or
equivalent variable name for the data directory) so the LLM-generated tool calls
match the actual function signatures load_data(filename, data_dir, ...) and
save_data(filename, data, data_dir); update both occurrences around the load at
"discord_digest_cursors" and the later save call so the tool won't fail due to
missing data_dir.
---
Nitpick comments:
In `@examples/templates/discord_digest/__main__.py`:
- Around line 23-27: Replace the hardcoded version string in the
click.version_option decorator with the centralized version constant; import
AgentMetadata (or the module exporting version) and use AgentMetadata.version in
the click.version_option call inside the cli function so the CLI version matches
the project's single source of truth (refer to the cli function and the
click.version_option usage and the AgentMetadata.version symbol).
In `@examples/templates/discord_digest/agent.py`:
- Around line 1-19: Add "from __future__ import annotations" as the very first
import and consolidate the GraphSpec import into the existing framework.graph
import line by importing GraphSpec alongside EdgeSpec, EdgeCondition, Goal,
SuccessCriterion, and Constraint; update the imports so GraphSpec comes from
framework.graph (not framework.graph.edge) to keep imports consistent (refer to
GraphSpec, EdgeSpec, EdgeCondition, Goal, SuccessCriterion, Constraint).
In `@examples/templates/discord_digest/config.py`:
- Around line 1-7: Add "from __future__ import annotations" as the first import
in this module to enable postponed evaluation of annotations and modern type
syntax; update the top of the file before any other imports (which currently
include dataclass and RuntimeConfig) so that dataclass usage and the
RuntimeConfig instantiation (default_config = RuntimeConfig()) benefit from
future annotations.
In `@examples/templates/discord_digest/nodes/__init__.py`:
- Around line 1-4: Add the future annotations import to the module by inserting
"from __future__ import annotations" at the top of the file before any other
imports or module-level code (the file containing the module docstring and the
import of NodeSpec from framework.graph). This ensures forward-compatible type
annotations in the module that defines the Discord Community Digest nodes
(referenced symbol: NodeSpec) and follows the project coding guidelines.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 3ac19822-5bea-4f5d-91b9-66756967d60a
📒 Files selected for processing (6)
examples/templates/discord_digest/__init__.pyexamples/templates/discord_digest/__main__.pyexamples/templates/discord_digest/agent.pyexamples/templates/discord_digest/config.pyexamples/templates/discord_digest/mcp_servers.jsonexamples/templates/discord_digest/nodes/__init__.py
| def setup_logging(verbose=False, debug=False): | ||
| if debug: | ||
| level, fmt = logging.DEBUG, "%(asctime)s %(name)s: %(message)s" | ||
| elif verbose: | ||
| level, fmt = logging.INFO, "%(message)s" | ||
| else: | ||
| level, fmt = logging.WARNING, "%(levelname)s: %(message)s" | ||
| logging.basicConfig(level=level, format=fmt, stream=sys.stderr) |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Add type hints to setup_logging function.
Per coding guidelines, all function signatures should have type hints.
Suggested fix
-def setup_logging(verbose=False, debug=False):
+def setup_logging(verbose: bool = False, debug: bool = False) -> None:
if debug:
level, fmt = logging.DEBUG, "%(asctime)s %(name)s: %(message)s"
elif verbose:
level, fmt = logging.INFO, "%(message)s"
else:
level, fmt = logging.WARNING, "%(levelname)s: %(message)s"
logging.basicConfig(level=level, format=fmt, stream=sys.stderr)As per coding guidelines: "Use type hints on all function signatures".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@examples/templates/discord_digest/__main__.py` around lines 13 - 20, Add type
hints to the setup_logging function signature by changing it to def
setup_logging(verbose: bool = False, debug: bool = False) -> None: so both
parameters are annotated as bool and the function return type is None; leave the
internal implementation unchanged and ensure any callers still pass booleans.
| class DiscordDigestAgent: | ||
| def __init__(self, config=None): | ||
| self.config = config or default_config | ||
| self.goal = goal | ||
| self.nodes = nodes | ||
| self.edges = edges | ||
| self.entry_node = entry_node | ||
| self.entry_points = entry_points | ||
| self.pause_nodes = pause_nodes | ||
| self.terminal_nodes = terminal_nodes | ||
| self._graph = None | ||
| self._agent_runtime = None | ||
| self._tool_registry = None | ||
| self._storage_path = None |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Add type hints to __init__ method.
Per coding guidelines, function signatures should have type hints.
Suggested fix
- def __init__(self, config=None):
+ def __init__(self, config: RuntimeConfig | None = None) -> None:
self.config = config or default_configYou'll need to import RuntimeConfig or use a string annotation with from __future__ import annotations.
As per coding guidelines: "Use type hints on all function signatures".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@examples/templates/discord_digest/agent.py` around lines 119 - 132, Add type
hints to the DiscordDigestAgent.__init__ signature and its parameters/return:
annotate the config parameter (e.g., RuntimeConfig or "RuntimeConfig") and
annotate self return as None; update any other parameters referenced/assigned in
__init__ (goal, nodes, edges, entry_node, entry_points, pause_nodes,
terminal_nodes) with appropriate types or forward-reference strings. If
RuntimeConfig is available, import it at top; otherwise enable from __future__
import annotations and use string annotations to avoid circular imports. Ensure
the method signature and any new imports follow existing project style and lint
rules.
| async def trigger_and_wait( | ||
| self, | ||
| entry_point="default", | ||
| input_data=None, | ||
| timeout=None, | ||
| session_state=None, | ||
| ): | ||
| if self._agent_runtime is None: | ||
| raise RuntimeError("Agent not started. Call start() first.") | ||
| return await self._agent_runtime.trigger_and_wait( | ||
| entry_point_id=entry_point, | ||
| input_data=input_data or {}, | ||
| session_state=session_state, | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if agent_runtime.trigger_and_wait accepts a timeout parameter
ast-grep --pattern $'async def trigger_and_wait($$$) -> $_:
$$$'Repository: aden-hive/hive
Length of output: 13939
🏁 Script executed:
cat -n examples/templates/discord_digest/agent.py | sed -n '200,220p'Repository: aden-hive/hive
Length of output: 848
🏁 Script executed:
grep -n "trigger_and_wait" examples/templates/discord_digest/agent.pyRepository: aden-hive/hive
Length of output: 210
🏁 Script executed:
cat -n examples/templates/discord_digest/agent.py | sed -n '217,230p'Repository: aden-hive/hive
Length of output: 625
Forward timeout parameter to underlying trigger_and_wait() call or remove it.
The timeout parameter is defined in the method signature but not passed to self._agent_runtime.trigger_and_wait(), which accepts and uses it. Either remove the parameter or forward it to maintain consistency with other agent templates.
Additionally, add type hints to the method signature to match the codebase conventions:
Suggested fix
async def trigger_and_wait(
self,
- entry_point="default",
- input_data=None,
- timeout=None,
- session_state=None,
- ):
+ entry_point: str = "default",
+ input_data: dict | None = None,
+ timeout: float | None = None,
+ session_state: dict | None = None,
+ ) -> ExecutionResult:
if self._agent_runtime is None:
raise RuntimeError("Agent not started. Call start() first.")
return await self._agent_runtime.trigger_and_wait(
entry_point_id=entry_point,
input_data=input_data or {},
session_state=session_state,
+ timeout=timeout,
)📝 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.
| async def trigger_and_wait( | |
| self, | |
| entry_point="default", | |
| input_data=None, | |
| timeout=None, | |
| session_state=None, | |
| ): | |
| if self._agent_runtime is None: | |
| raise RuntimeError("Agent not started. Call start() first.") | |
| return await self._agent_runtime.trigger_and_wait( | |
| entry_point_id=entry_point, | |
| input_data=input_data or {}, | |
| session_state=session_state, | |
| ) | |
| async def trigger_and_wait( | |
| self, | |
| entry_point: str = "default", | |
| input_data: dict | None = None, | |
| timeout: float | None = None, | |
| session_state: dict | None = None, | |
| ) -> ExecutionResult: | |
| if self._agent_runtime is None: | |
| raise RuntimeError("Agent not started. Call start() first.") | |
| return await self._agent_runtime.trigger_and_wait( | |
| entry_point_id=entry_point, | |
| input_data=input_data or {}, | |
| session_state=session_state, | |
| timeout=timeout, | |
| ) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@examples/templates/discord_digest/agent.py` around lines 202 - 215, The
trigger_and_wait method currently accepts a timeout param but doesn't forward it
to the runtime; update the method signature of trigger_and_wait to include type
hints (e.g., entry_point: str = "default", input_data: Optional[Dict[str, Any]]
= None, timeout: Optional[float] = None, session_state: Optional[Dict[str, Any]]
= None) and pass timeout through to self._agent_runtime.trigger_and_wait by
adding timeout=timeout to that call (or if you prefer to remove the parameter,
delete timeout from the signature and all references); refer to the
trigger_and_wait method and the self._agent_runtime.trigger_and_wait call when
making the change.
| async def run(self, context, session_state=None): | ||
| await self.start() | ||
| try: | ||
| result = await self.trigger_and_wait( | ||
| "default", context, session_state=session_state | ||
| ) | ||
| return result or ExecutionResult(success=False, error="Execution timeout") | ||
| finally: | ||
| await self.stop() |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Add type hints to run method.
Suggested fix
- async def run(self, context, session_state=None):
+ async def run(
+ self, context: dict, session_state: dict | None = None
+ ) -> ExecutionResult:
await self.start()As per coding guidelines: "Use type hints on all function signatures".
📝 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.
| async def run(self, context, session_state=None): | |
| await self.start() | |
| try: | |
| result = await self.trigger_and_wait( | |
| "default", context, session_state=session_state | |
| ) | |
| return result or ExecutionResult(success=False, error="Execution timeout") | |
| finally: | |
| await self.stop() | |
| async def run( | |
| self, context: dict, session_state: dict | None = None | |
| ) -> ExecutionResult: | |
| await self.start() | |
| try: | |
| result = await self.trigger_and_wait( | |
| "default", context, session_state=session_state | |
| ) | |
| return result or ExecutionResult(success=False, error="Execution timeout") | |
| finally: | |
| await self.stop() |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@examples/templates/discord_digest/agent.py` around lines 217 - 225, Add
explicit type hints to the async run method: annotate parameters (e.g., context:
Dict[str, Any], session_state: Optional[YourSessionStateType] = None) and the
return type (-> ExecutionResult). Import needed typing symbols (Optional, Dict,
Any) and replace YourSessionStateType with the actual session state type used in
this module; keep the body using existing calls (start, trigger_and_wait, stop,
and ExecutionResult) unchanged.
| 1. Load the dedup cursor by calling load_data("discord_digest_cursors"). | ||
| If the file doesn't exist, start with an empty dict {{}}. |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify load_data and save_data signatures
ast-grep --pattern $'def load_data($$$) -> $_:
$$$'
ast-grep --pattern $'def save_data($$$) -> $_:
$$$'Repository: aden-hive/hive
Length of output: 17094
🏁 Script executed:
# Check the file size first
wc -l examples/templates/discord_digest/nodes/__init__.pyRepository: aden-hive/hive
Length of output: 113
🏁 Script executed:
# Read the file around lines 57-58 and 79-80
sed -n '50,85p' examples/templates/discord_digest/nodes/__init__.pyRepository: aden-hive/hive
Length of output: 1475
Update the prompt instructions to include data_dir parameter in function calls.
The prompt at lines 57-58 and 79-80 instructs the LLM to call:
load_data("discord_digest_cursors")— missingdata_dirargumentsave_data("discord_digest_cursors", updated_cursor_dict)— missingdata_dirargument
The actual function signatures require data_dir as a mandatory positional parameter:
load_data(filename: str, data_dir: str, offset_bytes: int = 0, limit_bytes: int = 10000)save_data(filename: str, data: str, data_dir: str)
LLM tool calls will fail due to missing arguments.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@examples/templates/discord_digest/nodes/__init__.py` around lines 57 - 58,
The prompt instructions call load_data and save_data without the required
data_dir positional argument; update the prompt text to call
load_data("discord_digest_cursors", data_dir) and
save_data("discord_digest_cursors", updated_cursor_dict, data_dir) (or
equivalent variable name for the data directory) so the LLM-generated tool calls
match the actual function signatures load_data(filename, data_dir, ...) and
save_data(filename, data, data_dir); update both occurrences around the load at
"discord_digest_cursors" and the later save call so the tool won't fail due to
missing data_dir.
Closes #5342
Summary
Test plan
ruff checkandruff format --checkpassdefault_agent.validate()passesGraphSpec.validate()passes with no errorsAgentRunner.load()succeedshive open --agent examples/templates/discord_digestloads correctly (health check OK, session created)Summary by CodeRabbit
run,info, andvalidatecommands for agent management and configuration verification