Skip to content

feat(templates): add invoicing collections agent#6335

Open
Ttian18 wants to merge 8 commits intoaden-hive:mainfrom
Ttian18:feat/tina/invoicing-collections-agent
Open

feat(templates): add invoicing collections agent#6335
Ttian18 wants to merge 8 commits intoaden-hive:mainfrom
Ttian18:feat/tina/invoicing-collections-agent

Conversation

@Ttian18
Copy link
Copy Markdown
Contributor

@Ttian18 Ttian18 commented Mar 14, 2026

Closes #5181
Related: #6755

Description

New template agent that automates invoice follow-up and collections with
human-in-the-loop escalation. Based on use case #76 (Polite Accounts
Receivable Clerk) from the sample prompts.

6-node graph: scan invoices → classify overdue → send reminders → judge
classification → escalation review → generate report.

Type of Change

  • New feature (non-breaking change that adds functionality)

Related Issues

Demo

https://drive.google.com/file/d/1JDQp-sa0Niw3GmkN5YbRU5GD1s4avPBa/view?usp=sharing

Full end-to-end run via hive open showing all 6 nodes:

  1. Scan Invoices — reads sample CSV, identifies unpaid invoices
  2. Classify Overdue — buckets by aging threshold, flags high-value accounts
  3. Send Reminders — drafts tiered emails (gentle → firm) based on bucket
  4. Judge Reminders — validates classification accuracy against config rules
  5. Escalation Review (HITL) — human approves/rejects escalation accounts
  6. Generate Report — produces AR aging summary CSV with per-bucket totals

Changes Made

  • examples/templates/invoicing_collections_agent/__init__.py — agent package init, exports default_agent
  • examples/templates/invoicing_collections_agent/__main__.py — CLI with run, info, validate commands
  • examples/templates/invoicing_collections_agent/agent.py — goal, edges, graph spec, InvoicingCollectionsAgent class
  • examples/templates/invoicing_collections_agent/config.pyCOLLECTION_CONFIG (aging buckets, thresholds, email templates)
  • examples/templates/invoicing_collections_agent/nodes/__init__.py — 6 node definitions with system prompts
  • examples/templates/invoicing_collections_agent/data/sample_invoices.csv — sample invoice data for offline testing
  • examples/templates/invoicing_collections_agent/mcp_servers.json — MCP server config

Testing

  • ruff check and ruff format --check pass
  • default_agent.validate() passes
  • GraphSpec.validate() passes with no errors
  • AgentRunner.load() succeeds (same path as hive open)
  • All 6 nodes reachable from entry, no dead-end nodes
  • Judge edges use CONDITIONAL (not ON_FAILURE) with unique priorities
  • Data flow verified: all node input_keys satisfied by predecessors
  • collection_config injected into graph state via run()
  • Code style matches existing template agents (email_reply_agent)
  • hive open --agent examples/templates/invoicing_collections_agent loads correctly

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • My changes generate no new warnings
  • New and existing unit tests pass locally with my changes

@github-actions
Copy link
Copy Markdown

PR Requirements Warning

This PR does not meet the contribution requirements.
If the issue is not fixed within ~24 hours, it may be automatically closed.

Missing: No linked issue found.

To fix:

  1. Create or find an existing issue for this work
  2. Assign yourself to the issue
  3. Re-open this PR and add Fixes #123 in the description

Exception: To bypass this requirement, you can:

  • Add the micro-fix label or include micro-fix in your PR title for trivial fixes
  • Add the documentation label or include doc/docs in your PR title for documentation changes

Micro-fix requirements (must meet ALL):

Qualifies Disqualifies
< 20 lines changed Any functional bug fix
Typos & Documentation & Linting Refactoring for "clean code"
No logic/API/DB changes New features (even tiny ones)

Why is this required? See #472 for details.

@github-actions github-actions bot added pr-requirements-warning PR doesn't follow contribution guidelines. Please fix or it will be auto-closed. and removed pr-requirements-warning PR doesn't follow contribution guidelines. Please fix or it will be auto-closed. labels Mar 14, 2026
Ttian18 added 8 commits March 29, 2026 12:28
Config, sample data, and node definitions for the invoicing &
collections agent template (issue aden-hive#5181). Includes COLLECTION_CONFIG
with aging buckets/thresholds, 15-row sample CSV, and 6 node specs
(scan, classify, remind, judge, HITL escalate, report).
- Add agent.py stub (imports only, goal/edges/class TBD)
- Set classify-overdue max_node_visits to 3 for bounded feedback loop
- Add goal, edges, and agent class to agent.py
- Add __init__.py with all module-level exports
- Add __main__.py CLI entry point
- Add mcp_servers.json for tool server config
- Judge node uses CONDITIONAL edges for feedback loop
- Escalation review is client-facing for human-in-the-loop
- Pass COLLECTION_CONFIG as initial input so classify, send-reminders,
  and judge nodes can access it at runtime
- Switch __main__.py to @cli.command() pattern matching other templates
- Update sample_invoices.csv with dates covering all aging buckets
  (current, 30-day, 60-day, 90+) and high-value escalation cases
- Default CLI to bundled sample data when no --file is provided
@Ttian18 Ttian18 force-pushed the feat/tina/invoicing-collections-agent branch from 6eb28b2 to 53dcb22 Compare March 29, 2026 19:43
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 29, 2026

📝 Walkthrough

Walkthrough

Introduces a new runnable invoicing & collections agent template at examples/templates/invoicing_collections_agent/. The package includes a CLI entry point, a graph-based workflow with six nodes (scan, classify, send reminders, judge, escalate for review, generate report), runtime configuration, and HITL escalation support for human approval of high-value overdue accounts.

Changes

Cohort / File(s) Summary
Package initialization
examples/templates/invoicing_collections_agent/__init__.py
Exports public API including agent class, graph primitives, prompts, config, and metadata.
CLI entry point
examples/templates/invoicing_collections_agent/__main__.py
Implements click-based CLI with logging, run command (accepts optional invoice file or loads bundled sample CSV), info command (displays agent metadata), and validate command (checks graph wiring).
Core agent
examples/templates/invoicing_collections_agent/agent.py
Defines InvoicingCollectionsAgent class with graph construction, agent runtime lifecycle management, goal/constraints (classification accuracy, reminder/escalation compliance, report completeness), and async execution via run() method.
Configuration
examples/templates/invoicing_collections_agent/config.py, mcp_servers.json
Defines AgentMetadata with agent name/version/description, COLLECTION_CONFIG dictionary with AR aging buckets, reminder cadence, HITL escalation thresholds, and email templates; MCP server configuration for Hive tools via stdio transport.
Workflow nodes
examples/templates/invoicing_collections_agent/nodes/__init__.py
Defines six NodeSpec nodes: scan invoices from CSV, classify overdue accounts into aging buckets, send reminder emails, judge reminder correctness, escalate high-value accounts for human review (client-facing), and generate AR aging report.

Sequence Diagram

sequenceDiagram
    participant CLI as CLI/User
    participant Runtime as Agent Runtime
    participant ScanN as scan-invoices<br/>Node
    participant ClassN as classify-overdue<br/>Node
    participant SendN as send-reminders<br/>Node
    participant JudgeN as judge-reminders<br/>Node
    participant EscN as escalate-review<br/>Node (HITL)
    participant ReportN as generate-report<br/>Node
    participant Tools as Tools<br/>(CSV, Email)
    
    CLI->>Runtime: run(invoice_file, context)
    activate Runtime
    Runtime->>ScanN: execute (invoice_file_path)
    activate ScanN
    ScanN->>Tools: csv_read, csv_sql
    Tools-->>ScanN: all_invoices, invoice_count
    ScanN-->>Runtime: scan_summary
    deactivate ScanN
    
    Runtime->>ClassN: execute (all_invoices, collection_config)
    activate ClassN
    ClassN-->>Runtime: classified_invoices, reminder_accounts, escalation_accounts
    deactivate ClassN
    
    Runtime->>SendN: execute (reminder_accounts, collection_config)
    activate SendN
    SendN->>Tools: send_email (reminder templates)
    Tools-->>SendN: reminders_sent
    SendN-->>Runtime: reminder_summary
    deactivate SendN
    
    Runtime->>JudgeN: execute (classified, reminders_sent, config)
    activate JudgeN
    JudgeN-->>Runtime: judgment_passed or needs_reclassification
    deactivate JudgeN
    
    alt judgment_passed
        Runtime->>EscN: execute (escalation_accounts, classified_invoices)
        activate EscN
        Note over EscN: Human reviews & approves<br/>escalation accounts (HITL)
        EscN-->>Runtime: approval_result, approved_accounts
        deactivate EscN
    else needs_reclassification
        Runtime->>ClassN: loop back to classify-overdue
    end
    
    Runtime->>ReportN: execute (classified, reminders_sent, escalation_accounts)
    activate ReportN
    ReportN->>Tools: csv_write (AR aging report)
    Tools-->>ReportN: report_file_path, ar_aging_totals
    ReportN-->>Runtime: report_summary
    deactivate ReportN
    
    Runtime-->>CLI: ExecutionResult {success, output}
    deactivate Runtime
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 Invoices overdue? A-ha! ✉️
My whiskers twitch with glee—
This agent hops through CSV,
Sends reminders (so polite),
Then asks a human's say when stakes bite tight.
Collections now hop faster still! 🚀

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(templates): add invoicing collections agent' accurately summarizes the main change—a new invoicing collections agent template is being added to the examples/templates directory.
Linked Issues check ✅ Passed The PR implements all core objectives from #5181: 5-node workflow (scan→classify→send reminders→escalate review→generate report), HITL escalation review, configurable COLLECTION_CONFIG, sample CSV data, and integration with existing tools.
Out of Scope Changes check ✅ Passed All changes are directly aligned with #5181 requirements: agent implementation, configuration, sample data, CLI entry point, and MCP server setup for the invoicing collections template.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

Copy link
Copy Markdown

@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: 2

🧹 Nitpick comments (13)
examples/templates/invoicing_collections_agent/__main__.py (4)

13-20: Add type hints to function signature.

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)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/templates/invoicing_collections_agent/__main__.py` around lines 13 -
20, Add type hints to the setup_logging function signature: annotate the
parameters verbose and debug as bool and the return type as None (i.e., def
setup_logging(verbose: bool = False, debug: bool = False) -> None). No other
changes to behavior; keep existing default values and body (logging.basicConfig
call). Update any necessary imports if your linter requires typing imports (none
required for bool/None).

38-56: Add type hints to command function.

Per coding guidelines, function signatures should include type hints.

Suggested fix
-def run(invoice_file, verbose):
+def run(invoice_file: str | None, verbose: bool) -> None:
     """Execute the agent."""
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/templates/invoicing_collections_agent/__main__.py` around lines 38 -
56, The run function lacks type hints; update the signature of run to include
types (e.g., invoice_file: Optional[str], verbose: bool) and a return type (->
None), import Optional from typing, and ensure any local usages that depend on
the type (like passing invoice_file or "") remain valid; specifically modify the
def run(invoice_file, verbose): declaration to include the type annotations and
add the necessary typing import at the top of the module.

59-67: Add return type hint.

Suggested fix
 `@cli.command`()
-def info():
+def info() -> None:
     """Show agent info."""
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/templates/invoicing_collections_agent/__main__.py` around lines 59 -
67, The info() CLI command is missing a return type hint; update its signature
to include -> None to explicitly indicate it returns nothing. Locate the
function def info(): in the CLI/register area (the function that calls
default_agent.info() and uses click.echo) and change its declaration to def
info() -> None: so the return type is explicit and matches typical click command
handlers.

70-80: Add return type hint.

Suggested fix
 `@cli.command`()
-def validate():
+def validate() -> None:
     """Validate agent structure."""
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/templates/invoicing_collections_agent/__main__.py` around lines 70 -
80, The validate CLI function is missing a return type hint; update the function
signature for validate (the `@cli.command`() function named validate) to include
an explicit return type (e.g., def validate() -> None:) so the function's return
type is clear and matches its use of sys.exit rather than returning a value;
leave the body unchanged and keep references to default_agent.validate() as-is.
examples/templates/invoicing_collections_agent/agent.py (9)

267-276: Add return type hints to async methods.

Suggested fix
-    async def start(self):
+    async def start(self) -> None:
         if self._agent_runtime is None:
             self._setup()
         if not self._agent_runtime.is_running:
             await self._agent_runtime.start()

-    async def stop(self):
+    async def stop(self) -> None:
         if self._agent_runtime and self._agent_runtime.is_running:
             await self._agent_runtime.stop()
         self._agent_runtime = None
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/templates/invoicing_collections_agent/agent.py` around lines 267 -
276, Annotate the asynchronous lifecycle methods with explicit return type
hints: add "-> None" to the async def start(self) and async def stop(self)
methods in the class (methods named start and stop) so their signatures become
async def start(self) -> None and async def stop(self) -> None; this makes the
intent explicit and satisfies type checkers without changing runtime behavior.

3-22: Add from __future__ import annotations for modern type syntax.

The file uses union type syntax (|) in some type positions. Adding the future import ensures consistent behavior across Python versions.

Suggested fix
 """Agent graph construction for Invoicing & Collections Agent."""
 
+from __future__ import annotations
+
 from pathlib import Path
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/templates/invoicing_collections_agent/agent.py` around lines 3 - 22,
Add "from __future__ import annotations" at the top of the module so the file
uses postponed evaluation of annotations and supports modern union type syntax
(|) consistently; insert this import as the very first line of
examples/templates/invoicing_collections_agent/agent.py above the existing
imports (before the pathlib and framework imports) to ensure symbols like
EdgeSpec, GraphSpec, ExecutionResult and the node functions (scan_invoices_node,
classify_overdue_node, send_reminders_node, judge_reminders_node,
escalate_review_node, generate_report_node) benefit from the forward-reference
behavior.

208-224: Add return type hint.

Suggested fix
-    def _build_graph(self):
+    def _build_graph(self) -> GraphSpec:
         return GraphSpec(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/templates/invoicing_collections_agent/agent.py` around lines 208 -
224, The _build_graph method lacks a return type annotation; add an explicit
return type of GraphSpec to the def _build_graph signature so it reads def
_build_graph(self) -> GraphSpec: and ensure GraphSpec is imported or referenced
(if needed) to satisfy typing; update any related type imports at top of the
file if GraphSpec isn't already in scope.

293-303: 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:
         # Inject collection_config so downstream nodes can access it
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/templates/invoicing_collections_agent/agent.py` around lines 293 -
303, Add Python type hints to the async run method signature: annotate the
method name run with types for context (e.g., Dict[str, Any] or a specific
Context type), session_state (Optional[SessionState] or Optional[Dict[str,
Any]]), and the return type as Awaitable[ExecutionResult] or Async return of
ExecutionResult; update any imports (from typing import Optional, Dict, Any,
Awaitable) and reference the ExecutionResult type used in the body so the
signature becomes fully typed while keeping the implementation intact (symbols
to edit: run, context, session_state, ExecutionResult).

226-265: Add return type hint.

Suggested fix
-    def _setup(self):
+    def _setup(self) -> None:
         self._storage_path = (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/templates/invoicing_collections_agent/agent.py` around lines 226 -
265, The _setup method in the Invoicing Collections agent is missing a return
type hint; update the method signature for def _setup(self) to include an
explicit return type (None) so it becomes def _setup(self) -> None: and ensure
any type imports are present if needed; locate the method by name "_setup" in
agent.py and update the signature accordingly.

278-291: Add type hints to trigger_and_wait method.

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.")

Note: The timeout parameter is defined but not used in the method body. Consider either using it or removing it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/templates/invoicing_collections_agent/agent.py` around lines 278 -
291, The trigger_and_wait method lacks type annotations and declares an unused
timeout parameter; update the signature of trigger_and_wait to include proper
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 an appropriate return type (e.g., -> Any or a concrete
response type), import typing symbols as needed, and then either (A) pass
timeout through to self._agent_runtime.trigger_and_wait if that backend accepts
a timeout parameter, or (B) remove the timeout parameter from the signature if
it is not supported by _agent_runtime.trigger_and_wait; ensure references to
_agent_runtime and trigger_and_wait remain intact.

193-206: Add type hints to class methods.

Per coding guidelines, function signatures should include type hints. The __init__ method and instance variables would benefit from type annotations.

Suggested fix
+from framework.config import RuntimeConfig
+from framework.graph import GraphSpec as GraphSpecType
+from framework.runtime.agent_runtime import AgentRuntime
+
 class InvoicingCollectionsAgent:
-    def __init__(self, config=None):
+    def __init__(self, config: RuntimeConfig | None = None) -> 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
+        self._graph: GraphSpecType | None = None
+        self._agent_runtime: AgentRuntime | None = None
+        self._tool_registry: ToolRegistry | None = None
+        self._storage_path: Path | None = None
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/templates/invoicing_collections_agent/agent.py` around lines 193 -
206, Add explicit type hints to the InvoicingCollectionsAgent __init__ signature
and its instance attributes: annotate the parameter config (e.g.,
Optional[Dict[str, Any]] or the specific config type), and add types for goal
(str), nodes (List[Node] or List[Dict[str, Any]]), edges (List[Edge]),
entry_node (str), entry_points (List[str]), pause_nodes (Set[str] or List[str]),
terminal_nodes (Set[str] or List[str]), and internal attributes _graph,
_agent_runtime, _tool_registry, _storage_path (use Optional[...] with the
appropriate classes or Any). Import necessary typing symbols (Optional, List,
Dict, Any, Set) and any domain types (Node, Edge) and update the __init__
signature and assignments accordingly so instance variables are type-annotated.

322-338: Add return type hint and note unused variable.

The warnings list is initialized but never populated.

Suggested fix
-    def validate(self):
-        errors, warnings = [], []
+    def validate(self) -> dict:
+        errors: list[str] = []
+        warnings: list[str] = []  # Reserved for future use
         node_ids = {n.id for n in self.nodes}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/templates/invoicing_collections_agent/agent.py` around lines 322 -
338, The validate method lacks a return type hint and initializes a warnings
list that is never used; update the signature of validate to include an explicit
return type (e.g. def validate(self) -> dict[str, object]:) and remove the
unused local variable warnings (or replace its initialization with directly
returning an empty list in the return dict), ensuring you still return the
"warnings" key (e.g. "warnings": []) alongside "errors" and "valid"; reference
symbols: validate, warnings, errors.

305-320: Add return type hint.

Suggested fix
-    def info(self):
+    def info(self) -> dict:
         return {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/templates/invoicing_collections_agent/agent.py` around lines 305 -
320, The info method lacks a return type hint; update the signature of def
info(self) to include an explicit type (e.g. def info(self) -> Dict[str, Any]:
or -> Mapping[str, Any]:) and import the corresponding typing symbols (Dict and
Any or Mapping and Any) at the top of the file, ensuring the method still
returns the same dict built from metadata, self.goal, self.nodes, self.edges,
entry_node, entry_points, terminal_nodes, and client_facing_nodes.
🤖 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/invoicing_collections_agent/agent.py`:
- Around line 165-171: The EdgeSpec with id "report-to-scan" creates a loop from
node "generate-report" back to "scan-invoices", causing the graph to never
terminate when terminal_nodes is empty; to fix this for single-pass execution,
add "generate-report" to the agent's terminal_nodes list (or alternatively
update README/docs to state the loop is intentional for continuous/daemon
processing with dynamic invoice sources) so the workflow ends after report
generation.

In `@examples/templates/invoicing_collections_agent/nodes/__init__.py`:
- Around line 277-299: The system_prompt string in escalate_review_node contains
unreplaced template placeholders (e.g., {escalation_accounts},
{classified_invoices}, {judgment_details}, {collection_config.cfo_email}) but
the code never calls .format() and input_keys only lists
["escalation_accounts","classified_invoices","judgment_details"], so those
values (and collection_config) will be sent literally to the LLM; fix by either
removing placeholders from system_prompt and moving the data into
_build_initial_message() / the initial user message, or implement interpolation
before constructing the NodeConversation (e.g., call
system_prompt.format(**ctx.input_data) or otherwise resolve
collection_config.cfo_email into a flat key and add it to input_keys) so that
system_prompt contains real values when passed to NodeConversation.

---

Nitpick comments:
In `@examples/templates/invoicing_collections_agent/__main__.py`:
- Around line 13-20: Add type hints to the setup_logging function signature:
annotate the parameters verbose and debug as bool and the return type as None
(i.e., def setup_logging(verbose: bool = False, debug: bool = False) -> None).
No other changes to behavior; keep existing default values and body
(logging.basicConfig call). Update any necessary imports if your linter requires
typing imports (none required for bool/None).
- Around line 38-56: The run function lacks type hints; update the signature of
run to include types (e.g., invoice_file: Optional[str], verbose: bool) and a
return type (-> None), import Optional from typing, and ensure any local usages
that depend on the type (like passing invoice_file or "") remain valid;
specifically modify the def run(invoice_file, verbose): declaration to include
the type annotations and add the necessary typing import at the top of the
module.
- Around line 59-67: The info() CLI command is missing a return type hint;
update its signature to include -> None to explicitly indicate it returns
nothing. Locate the function def info(): in the CLI/register area (the function
that calls default_agent.info() and uses click.echo) and change its declaration
to def info() -> None: so the return type is explicit and matches typical click
command handlers.
- Around line 70-80: The validate CLI function is missing a return type hint;
update the function signature for validate (the `@cli.command`() function named
validate) to include an explicit return type (e.g., def validate() -> None:) so
the function's return type is clear and matches its use of sys.exit rather than
returning a value; leave the body unchanged and keep references to
default_agent.validate() as-is.

In `@examples/templates/invoicing_collections_agent/agent.py`:
- Around line 267-276: Annotate the asynchronous lifecycle methods with explicit
return type hints: add "-> None" to the async def start(self) and async def
stop(self) methods in the class (methods named start and stop) so their
signatures become async def start(self) -> None and async def stop(self) ->
None; this makes the intent explicit and satisfies type checkers without
changing runtime behavior.
- Around line 3-22: Add "from __future__ import annotations" at the top of the
module so the file uses postponed evaluation of annotations and supports modern
union type syntax (|) consistently; insert this import as the very first line of
examples/templates/invoicing_collections_agent/agent.py above the existing
imports (before the pathlib and framework imports) to ensure symbols like
EdgeSpec, GraphSpec, ExecutionResult and the node functions (scan_invoices_node,
classify_overdue_node, send_reminders_node, judge_reminders_node,
escalate_review_node, generate_report_node) benefit from the forward-reference
behavior.
- Around line 208-224: The _build_graph method lacks a return type annotation;
add an explicit return type of GraphSpec to the def _build_graph signature so it
reads def _build_graph(self) -> GraphSpec: and ensure GraphSpec is imported or
referenced (if needed) to satisfy typing; update any related type imports at top
of the file if GraphSpec isn't already in scope.
- Around line 293-303: Add Python type hints to the async run method signature:
annotate the method name run with types for context (e.g., Dict[str, Any] or a
specific Context type), session_state (Optional[SessionState] or
Optional[Dict[str, Any]]), and the return type as Awaitable[ExecutionResult] or
Async return of ExecutionResult; update any imports (from typing import
Optional, Dict, Any, Awaitable) and reference the ExecutionResult type used in
the body so the signature becomes fully typed while keeping the implementation
intact (symbols to edit: run, context, session_state, ExecutionResult).
- Around line 226-265: The _setup method in the Invoicing Collections agent is
missing a return type hint; update the method signature for def _setup(self) to
include an explicit return type (None) so it becomes def _setup(self) -> None:
and ensure any type imports are present if needed; locate the method by name
"_setup" in agent.py and update the signature accordingly.
- Around line 278-291: The trigger_and_wait method lacks type annotations and
declares an unused timeout parameter; update the signature of trigger_and_wait
to include proper 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 an appropriate return type (e.g., -> Any or
a concrete response type), import typing symbols as needed, and then either (A)
pass timeout through to self._agent_runtime.trigger_and_wait if that backend
accepts a timeout parameter, or (B) remove the timeout parameter from the
signature if it is not supported by _agent_runtime.trigger_and_wait; ensure
references to _agent_runtime and trigger_and_wait remain intact.
- Around line 193-206: Add explicit type hints to the InvoicingCollectionsAgent
__init__ signature and its instance attributes: annotate the parameter config
(e.g., Optional[Dict[str, Any]] or the specific config type), and add types for
goal (str), nodes (List[Node] or List[Dict[str, Any]]), edges (List[Edge]),
entry_node (str), entry_points (List[str]), pause_nodes (Set[str] or List[str]),
terminal_nodes (Set[str] or List[str]), and internal attributes _graph,
_agent_runtime, _tool_registry, _storage_path (use Optional[...] with the
appropriate classes or Any). Import necessary typing symbols (Optional, List,
Dict, Any, Set) and any domain types (Node, Edge) and update the __init__
signature and assignments accordingly so instance variables are type-annotated.
- Around line 322-338: The validate method lacks a return type hint and
initializes a warnings list that is never used; update the signature of validate
to include an explicit return type (e.g. def validate(self) -> dict[str,
object]:) and remove the unused local variable warnings (or replace its
initialization with directly returning an empty list in the return dict),
ensuring you still return the "warnings" key (e.g. "warnings": []) alongside
"errors" and "valid"; reference symbols: validate, warnings, errors.
- Around line 305-320: The info method lacks a return type hint; update the
signature of def info(self) to include an explicit type (e.g. def info(self) ->
Dict[str, Any]: or -> Mapping[str, Any]:) and import the corresponding typing
symbols (Dict and Any or Mapping and Any) at the top of the file, ensuring the
method still returns the same dict built from metadata, self.goal, self.nodes,
self.edges, entry_node, entry_points, terminal_nodes, and client_facing_nodes.
🪄 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: e3494263-d2b7-4d9d-ae9a-863a94b3621e

📥 Commits

Reviewing files that changed from the base of the PR and between eba7524 and 53dcb22.

⛔ Files ignored due to path filters (1)
  • examples/templates/invoicing_collections_agent/data/sample_invoices.csv is excluded by !**/*.csv
📒 Files selected for processing (6)
  • examples/templates/invoicing_collections_agent/__init__.py
  • examples/templates/invoicing_collections_agent/__main__.py
  • examples/templates/invoicing_collections_agent/agent.py
  • examples/templates/invoicing_collections_agent/config.py
  • examples/templates/invoicing_collections_agent/mcp_servers.json
  • examples/templates/invoicing_collections_agent/nodes/__init__.py

Comment on lines +165 to +171
EdgeSpec(
id="report-to-scan",
source="generate-report",
target="scan-invoices",
condition=EdgeCondition.ON_SUCCESS,
priority=1,
),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential infinite loop: report loops back to scan.

The report-to-scan edge creates a continuous loop that re-scans the same invoice file after report generation. With terminal_nodes=[], the graph never terminates naturally.

If the input CSV is static (e.g., the bundled sample data), this will process the same invoices repeatedly. Consider either:

  1. Adding generate-report to terminal_nodes for single-pass execution
  2. Documenting that this design is intentional for batch/daemon processing with dynamic invoice sources
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/templates/invoicing_collections_agent/agent.py` around lines 165 -
171, The EdgeSpec with id "report-to-scan" creates a loop from node
"generate-report" back to "scan-invoices", causing the graph to never terminate
when terminal_nodes is empty; to fix this for single-pass execution, add
"generate-report" to the agent's terminal_nodes list (or alternatively update
README/docs to state the loop is intentional for continuous/daemon processing
with dynamic invoice sources) so the workflow ends after report generation.

Comment on lines +277 to +299
system_prompt="""\
The following invoices require human review before further collection action.
They have been flagged for escalation due to high value (>$10,000) or being
30+ days past due.

Escalation accounts: {escalation_accounts}
Classification details: {classified_invoices}
Judgment details: {judgment_details}

**Pending actions for approved accounts:**
- Client software access will be restricted via API
- The CFO ({collection_config.cfo_email}) will be notified
- A final notice email will be sent to the client

Please review each account and decide:
- "approved" — proceed with access restriction, CFO notification, and final notice
- "rejected" — do not escalate; no further action
- "deferred" — revisit later

Provide any notes for the collections team.
""",
tools=[],
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how system_prompt templating works in the framework
rg -n "system_prompt" --type=py -A 10 -B 2 | head -100

# Look for format or template methods that process system_prompt
ast-grep --pattern 'system_prompt.format($$$)'
ast-grep --pattern 'format($$$system_prompt$$$)'

Repository: aden-hive/hive

Length of output: 9263


🏁 Script executed:

# First, find and read the specific file
find . -path "*/invoicing_collections_agent/nodes/__init__.py" -type f

Repository: aden-hive/hive

Length of output: 124


🏁 Script executed:

# Look at the NodeSpec class definition to understand how system_prompt is used
rg -n "class NodeSpec" --type=py -A 20

Repository: aden-hive/hive

Length of output: 1384


🏁 Script executed:

# Search for how system_prompt is formatted or processed in the framework
rg -n "\.format\(" --type=py | grep -i "prompt\|system" | head -20

Repository: aden-hive/hive

Length of output: 40


🏁 Script executed:

# Look for the framework's graph module to understand NodeSpec
find . -path "*/framework/graph*" -type f

Repository: aden-hive/hive

Length of output: 1160


🏁 Script executed:

# Read the specific file around lines 277-299
sed -n '270,305p' ./examples/templates/invoicing_collections_agent/nodes/__init__.py

Repository: aden-hive/hive

Length of output: 1290


🏁 Script executed:

# Read the prompt_composer.py to see how prompts are templated
cat ./core/framework/graph/prompt_composer.py

Repository: aden-hive/hive

Length of output: 14430


🏁 Script executed:

# Also check if there's any formatting logic in the event loop
rg -n "system_prompt" ./core/framework/graph/event_loop/ -A 5 -B 2 --type=py

Repository: aden-hive/hive

Length of output: 5056


🏁 Script executed:

# Search for where system_prompt is actually formatted with context
rg -n "\.format\(" ./core/framework/graph/event_loop/ --type=py -B 3 -A 3 | head -80

Repository: aden-hive/hive

Length of output: 40


🏁 Script executed:

# Look for conversation.system_prompt or where it's built
rg -n "system_prompt" ./core/framework/graph/conversation.py --type=py -A 5 -B 2

Repository: aden-hive/hive

Length of output: 2672


🏁 Script executed:

# Search for how context/input variables are prepared before passing to LLM
rg -n "input_keys\|context\|prepare" ./core/framework/graph/event_loop_node.py --type=py -A 3 -B 1 | head -100

Repository: aden-hive/hive

Length of output: 40


🏁 Script executed:

# Check if there's a template formatter used
rg -n "format\|template\|interpolate" ./core/framework/graph/ --type=py | grep -i "prompt\|system" | head -30

Repository: aden-hive/hive

Length of output: 40


🏁 Script executed:

# Find where the conversation.system_prompt is actually sent to an LLM
rg -n "system_prompt\|\.complete\|\.chat\|llm" ./core/framework/graph/event_loop/event_publishing.py -A 5 -B 2 | head -100

Repository: aden-hive/hive

Length of output: 40


🏁 Script executed:

# Check the executor to see how prompts are built and passed
cat ./core/framework/graph/executor.py | head -200

Repository: aden-hive/hive

Length of output: 7541


🏁 Script executed:

# Look for judge_pipeline which might process prompts
rg -n "system_prompt" ./core/framework/graph/event_loop/judge_pipeline.py -A 5 -B 2

Repository: aden-hive/hive

Length of output: 40


🏁 Script executed:

# Search more broadly for any .format calls on prompts
rg -n "\.format\(" ./core/framework/graph/ --type=py -A 2 -B 2 | grep -i "prompt" -A 3 -B 3

Repository: aden-hive/hive

Length of output: 40


🏁 Script executed:

# Search for where node context is prepared - look for input_keys processing
rg -n "input_keys" ./core/framework/graph/ --type=py -A 3 -B 1 | head -150

Repository: aden-hive/hive

Length of output: 6006


🏁 Script executed:

# Look for where context dict is built from input_keys
rg -n "context\|Context" ./core/framework/graph/event_loop_node.py --type=py -A 2 -B 2 | head -100

Repository: aden-hive/hive

Length of output: 40


🏁 Script executed:

# Search for "collection_config" in examples to understand data structure
rg -n "collection_config" ./examples/templates/invoicing_collections_agent/ --type=py -A 3 -B 3

Repository: aden-hive/hive

Length of output: 8033


🏁 Script executed:

# Look for how prompt context is built from input - any format or template calls
rg -n "\.format\(|f\"|str\.format" ./core/framework/graph/event_loop_node.py --type=py -A 5 -B 2

Repository: aden-hive/hive

Length of output: 16050


🏁 Script executed:

# Look more carefully at where the system_prompt from NodeSpec is used in the event loop
rg -n "ctx.node_spec.system_prompt\|node_spec.system_prompt" ./core/framework/graph/event_loop_node.py -A 5 -B 2

Repository: aden-hive/hive

Length of output: 40


🏁 Script executed:

# Check how the LLM call is made - what parameters are passed
rg -n "await.*llm\|self.llm\|\.complete\|\.chat" ./core/framework/graph/event_loop_node.py -A 3 -B 1 | head -100

Repository: aden-hive/hive

Length of output: 40


🏁 Script executed:

# Look at conversation initialization and how it uses system_prompt
rg -n "Conversation\(" ./core/framework/graph/event_loop_node.py -A 10 -B 2 | head -80

Repository: aden-hive/hive

Length of output: 711


🏁 Script executed:

# Check if there's any string formatting happening before conversation initialization
rg -n "compose_system_prompt\|\.format\(" ./core/framework/graph/event_loop_node.py -A 5 -B 2 | head -100

Repository: aden-hive/hive

Length of output: 40


🏁 Script executed:

# Find where system_prompt is assigned before being passed to NodeConversation
sed -n '420,510p' ./core/framework/graph/event_loop_node.py

Repository: aden-hive/hive

Length of output: 4947


🏁 Script executed:

# Check if LLM provider or conversation handles template interpolation
rg -n "\.complete\|\.chat\|system_prompt" ./core/framework/graph/event_loop_node.py -A 3 -B 1 | sed -n '1,200p'

Repository: aden-hive/hive

Length of output: 40


🏁 Script executed:

# Look at the LLM provider to see if it does template formatting
find ./core/framework/llm -name "*.py" -type f

Repository: aden-hive/hive

Length of output: 329


🏁 Script executed:

# Verify the exact input_keys for the escalation review node
sed -n '260,310p' ./examples/templates/invoicing_collections_agent/nodes/__init__.py

Repository: aden-hive/hive

Length of output: 1726


🏁 Script executed:

# Check the LLM provider to see if it does string formatting on prompts
rg -n "system_prompt\|\.format\|\.complete" ./core/framework/llm/provider.py -A 3 -B 1 | head -80

Repository: aden-hive/hive

Length of output: 40


🏁 Script executed:

# Check if there's any post-processing of system_prompt before LLM call
rg -n "def.*complete\|def.*chat" ./core/framework/llm/provider.py -A 20 | head -100

Repository: aden-hive/hive

Length of output: 40


🏁 Script executed:

# See if Conversation or any middleware formats the prompt
rg -n "def complete\|async def complete" ./core/framework/ --type=py -A 10 | head -100

Repository: aden-hive/hive

Length of output: 40


🏁 Script executed:

# Confirm collection_config is passed in input_data
sed -n '290,310p' ./examples/templates/invoicing_collections_agent/agent.py

Repository: aden-hive/hive

Length of output: 786


🏁 Script executed:

# Check the Anthropic LLM provider to see if it formats prompts
cat ./core/framework/llm/anthropic.py | head -300

Repository: aden-hive/hive

Length of output: 3312


🏁 Script executed:

# Look for the complete method signature in provider base class
rg -n "class LLMProvider\|def complete\|async def complete" ./core/framework/llm/provider.py -A 30 | head -150

Repository: aden-hive/hive

Length of output: 40


🏁 Script executed:

# Get the LLMProvider base class definition with complete method
sed -n '1,500p' ./core/framework/llm/provider.py | grep -A 40 "def complete\|class LLMProvider"

Repository: aden-hive/hive

Length of output: 1835


🏁 Script executed:

# Check if LiteLLMProvider does any formatting
rg -n "def complete\|def acomplete" ./core/framework/llm/litellm.py -A 30 | head -150

Repository: aden-hive/hive

Length of output: 40


🏁 Script executed:

# Search for any .format() calls on system prompts in the entire codebase
rg -n "system.*\.format\|\.format.*system" ./core/framework/ --type=py

Repository: aden-hive/hive

Length of output: 40


🏁 Script executed:

# Check NodeContext to see what data is passed to nodes
rg -n "class NodeContext\|input_data\|memory" ./core/framework/graph/node.py -A 5 | head -100

Repository: aden-hive/hive

Length of output: 40


🏁 Script executed:

# Look at the full LiteLLMProvider complete method
sed -n '1,300p' ./core/framework/llm/litellm.py

Repository: aden-hive/hive

Length of output: 11580


🏁 Script executed:

# Check if system prompt is formatted anywhere before being sent to LLM
rg -n "messages\|system" ./core/framework/llm/litellm.py -A 5 -B 2 | head -150

Repository: aden-hive/hive

Length of output: 40


🏁 Script executed:

# Search if there's any user message injection that formats the prompt
rg -n "add_user_message\|format\|template" ./core/framework/graph/event_loop_node.py -A 3 -B 1 | grep -i "prompt\|format\|template" | head -40

Repository: aden-hive/hive

Length of output: 40


🏁 Script executed:

# Check if there's middleware that processes prompts
rg -n "system_prompt.*format\|format.*prompt" ./core/framework/graph/conversation.py -A 5 -B 2

Repository: aden-hive/hive

Length of output: 40


🏁 Script executed:

# Verify the exact input_keys for the escalate_review_node and check if collection_config is there
rg -n "escalate_review_node\|input_keys" ./examples/templates/invoicing_collections_agent/nodes/__init__.py -A 3 | grep -A 20 "escalate_review_node"

Repository: aden-hive/hive

Length of output: 40


🏁 Script executed:

# Check how input data is passed to the node - look at how ctx.input_data is constructed
rg -n "ctx.input_data\|NodeContext" ./core/framework/graph/executor.py -B 2 -A 5 | head -100

Repository: aden-hive/hive

Length of output: 40


🏁 Script executed:

# Verify if the system_prompt is ever formatted with context before being used
rg -n "system_prompt.*format\|format.*system_prompt\|\.format\(" ./core/framework/graph/event_loop_node.py | head -20

Repository: aden-hive/hive

Length of output: 40


🏁 Script executed:

# Let me verify the exact node definition one more time to be absolutely sure
sed -n '260,305p' ./examples/templates/invoicing_collections_agent/nodes/__init__.py

Repository: aden-hive/hive

Length of output: 1554


🏁 Script executed:

# Check if there's any formatting happening in the initial prompt composition
sed -n '425,440p' ./core/framework/graph/event_loop_node.py

Repository: aden-hive/hive

Length of output: 874


🏁 Script executed:

# Verify collection_config is in the input_data by checking execution flow
rg -n "def execute_node\|input_data" ./core/framework/graph/executor.py -B 2 -A 10 | head -150

Repository: aden-hive/hive

Length of output: 40


🏁 Script executed:

# Check if there's any final formatting or injection of input data into the system prompt
rg -n "system_prompt\|inject\|add_user_message" ./core/framework/graph/event_loop_node.py -A 3 -B 1 | sed -n '1,200p'

Repository: aden-hive/hive

Length of output: 40


🏁 Script executed:

# Verify once more if any string substitution happens on system_prompt
rg -n "\.format\(|f\"|str\(.*{" ./core/framework/graph/event_loop_node.py | grep -i prompt

Repository: aden-hive/hive

Length of output: 165


🏁 Script executed:

# Check if the LLM call directly gets the unformatted system_prompt
rg -n "await.*complete\|acomplete" ./core/framework/graph/event_loop_node.py -A 10 | head -80

Repository: aden-hive/hive

Length of output: 40


🏁 Script executed:

# Check what input_data is actually passed to the node - look at the executor's node call
rg -n "execute_node\|NodeContext\(" ./core/framework/graph/executor.py -A 20 | head -200

Repository: aden-hive/hive

Length of output: 40


🏁 Script executed:

# Verify if collection_config is even accessible to the escalate_review_node
rg -n "input_keys\|input_data\|memory" ./core/framework/graph/event_loop_node.py -A 3 | sed -n '1,120p'

Repository: aden-hive/hive

Length of output: 40


🏁 Script executed:

# Final check: confirm that system_prompt is sent to LLM without any formatting
sed -n '495,550p' ./core/framework/graph/event_loop_node.py | grep -A 30 "NodeConversation"

Repository: aden-hive/hive

Length of output: 1644


🏁 Script executed:

# Check if Conversation.system_prompt is ever formatted
rg -n "def __init__\|system_prompt" ./core/framework/graph/conversation.py -A 5 | sed -n '1,100p'

Repository: aden-hive/hive

Length of output: 40


🏁 Script executed:

# Confirm the ultimate fate of system_prompt when sent to LLM
sed -n '1700,1850p' ./core/framework/graph/event_loop_node.py | grep -B 10 -A 5 "complete\|acomplete"

Repository: aden-hive/hive

Length of output: 1067


Remove template placeholders from system_prompt or implement format string substitution.

The system_prompt contains uninterpolated template placeholders: {escalation_accounts}, {classified_invoices}, {judgment_details}, and {collection_config.cfo_email}. These will be sent to the LLM literally with curly braces, not replaced with actual values.

The escalate_review_node declares only ["escalation_accounts", "classified_invoices", "judgment_details"] as input_keys, excluding collection_config. More critically, the framework never calls .format() on the system_prompt before passing it to the LLM—the template is concatenated with preambles and sent unchanged.

Two solutions:

  1. Remove placeholders and rely on the initial user message — move the data context into _build_initial_message() instead.
  2. Implement format string interpolation — call system_prompt.format(**ctx.input_data) or similar before initializing NodeConversation, ensuring all required keys (including collection_config) are available in input_keys.

Option 1 is simpler and avoids the need for multi-level attribute access (collection_config.cfo_email).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/templates/invoicing_collections_agent/nodes/__init__.py` around
lines 277 - 299, The system_prompt string in escalate_review_node contains
unreplaced template placeholders (e.g., {escalation_accounts},
{classified_invoices}, {judgment_details}, {collection_config.cfo_email}) but
the code never calls .format() and input_keys only lists
["escalation_accounts","classified_invoices","judgment_details"], so those
values (and collection_config) will be sent literally to the LLM; fix by either
removing placeholders from system_prompt and moving the data into
_build_initial_message() / the initial user message, or implement interpolation
before constructing the NodeConversation (e.g., call
system_prompt.format(**ctx.input_data) or otherwise resolve
collection_config.cfo_email into a flat key and add it to input_keys) so that
system_prompt contains real values when passed to NodeConversation.

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.

[Feature]: Invoicing & Collections Sample Agent template

1 participant