Action history + weekly HTML report#5
Conversation
Persist one row per acted-upon message so runs can later be reported on. New use_agent.storage.Store writes to a SQLite database (default ~/.config/use-agent/actions.db, override via USE_AGENT_DB) opened in WAL mode with a busy timeout, so a daemon and an ad-hoc run can write concurrently on a local filesystem. Capture is hybrid: a new record_action MCP tool writes each row at action time (validated tool args), and agent.run reconciles the agent's final JSON summary afterward, inserting any acted-upon row the model forgot to record. Rows carry a source column (tool vs summary) so the reconciliation is observable. - Stored columns: processed_at, query_target (derived from the search query), sender, subject, sent_at (original Date header, normalized to ISO 8601 UTC), classification, category (a 1-5 word content label the agent assigns, e.g. "Recruiter", "AI Solution"), response_mode, action_taken, score, message_id, source - Add Message.date (Date header) and surface it in get_message output and the agent's results schema so sent_at can be captured - Only acted-upon messages are stored; skips and dry-run rows are excluded (dry runs write nothing at all) - Reporter exposes the parsed summary for reconciliation Report generation and emailing are intentionally out of scope here. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Roll the action history up into the "Weekly Report" design and render it as standalone inline-styled HTML suitable for emailing. New `use-agent report` subcommand renders to stdout/--output, or sends the report via Gmail with --email. - report.py aggregates rows from the SQLite store over a window (default last 7 days, --days / --since) into the view model: headline stats, cold/bulk split, activity by day, response breakdown, top repeat offenders (by sender), and top themes (by category). Filtered on processed_at; days bucketed in local time. A missing DB/table yields an empty report, not an error. - templates/report.html.j2 is the design's markup with sc-for replaced by Jinja loops; rendered with autoescape on since sender names and subjects are untrusted. - GmailClient.send_html sends a multipart/alternative message from the authenticated account to the configured recipients. - [report] recipients / subject in config.toml; --to and --subject override per run. Settings are only loaded when emailing, so a plain HTML render needs no config.toml. Verified end-to-end: rendered a populated sample DB through the CLI and confirmed the output matches the approved design. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Give the SQLite action history a stable default home and let it be relocated without an env var. - Default path is now ~/.use-agent/actions.db (was under ~/.config/use-agent). - [storage] path in config.toml overrides it; a leading ~ is expanded. USE_AGENT_DB still takes precedence over both. - Settings exposes the resolved db_path; agent.run and the report command read it from there. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cleanups from a self-review of the report code (no behavior change). - Extract report._bars((name, count) -> rows): the day, response, and theme sections were identical bar charts each duplicating the max/width and dict construction. They now delegate to _bars, which standardizes the row key on `name` (template + test updated). - Replace the `'rash' in action_taken` substring hack (matched any word containing "rash") with `'trashed' in action_taken.lower()`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (3)
📝 WalkthroughWalkthroughAdds SQLite-backed action-history storage, a ChangesAction History Storage and Reporting
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Agent as agent.run
participant Store as storage.Store
participant Tools as build_mcp_server
participant Reporter as reporter.Reporter
Agent->>Store: create Store (if not dry_run)
Agent->>Tools: build_mcp_server(store=Store)
loop per acted message
Tools->>Store: record(sender, subject, action_taken, ...)
end
Agent->>Reporter: finish()
Reporter-->>Agent: summary
Agent->>Agent: _reconcile_history(store, summary)
Agent->>Store: has(message_id, sender, subject)
alt missing row
Agent->>Store: record(source='summary')
end
Agent->>Store: close()
sequenceDiagram
participant CLI as _cmd_report
participant Settings as Settings.load
participant Report as report.render
participant DB as SQLite actions.db
participant Gmail as GmailClient
CLI->>Settings: load()
CLI->>Report: render(db_path, days/since)
Report->>DB: fetch rows
DB-->>Report: rows
Report-->>CLI: HTML
alt --email
CLI->>Gmail: send_html(to, subject, html)
else --output
CLI->>CLI: write file
else
CLI->>CLI: print stdout
end
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (2)
use_agent/report.py (1)
23-23: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAvoid
from ... importfor theconfigsubmodule.Per repo guidelines, only bare
import module+ namespace access is allowed.As per coding guidelines, "Use module-level imports only; do not use
from ... import ...style imports. Import the module (import pathlib,import typing) and reference members through the module namespace."♻️ Proposed fix
-from use_agent import config +import use_agent.configAnd update all
config.Xreferences in this file touse_agent.config.X.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@use_agent/report.py` at line 23, The import in report.py violates the module-level import rule by using a from-import for config. Replace the existing import with a bare module import for use_agent.config, then update every config reference in the file to use the module namespace explicitly so symbols are accessed as use_agent.config.X.Source: Coding guidelines
tests/test_report.py (1)
5-5: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAvoid
from ... importforreport/storagesubmodules.As per coding guidelines, "Use module-level imports only; do not use
from ... import ...style imports."♻️ Proposed fix
-from use_agent import report, storage +import use_agent.report +import use_agent.storageAnd update
report.X/storage.Xreferences accordingly.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_report.py` at line 5, The test file uses a forbidden from-import for the report and storage submodules. Replace the `from use_agent import report, storage` import with module-level imports from the same package, then update any `report.X` and `storage.X` references in this test to use the imported module names consistently.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/test_storage.py`:
- Line 5: The import in tests/test_storage.py violates the module-level import
rule by using a direct symbol import instead of importing the module namespace.
Update the test to use a module import for use_agent.storage and reference
storage members through that namespace, matching the pattern used in
use_agent/agent.py with storage_mod and keeping imports consistent with the repo
guideline.
In `@use_agent/agent.py`:
- Around line 379-387: The Store lifecycle in run() can leak because
store.close() is only reached after the agent loop, reporter.finish(), and
_reconcile_history complete successfully. Wrap the body that uses store in a
try/finally so store.close() always runs when store is not None, even if
claude_agent_sdk.query, reporter.finish(), or history reconciliation raises;
keep the existing store creation and references to run(), reporter.finish(), and
_reconcile_history while moving the close into the guaranteed cleanup path.
- Around line 442-462: The history-recording block in the summary loop is
coercing explicit null values into the literal string "None" for sender,
subject, classification, and response_mode. Update the row field extraction in
this summary-processing path to use the same fallback pattern already used for
message_id, category, and sent_at so nulls become empty strings instead of
"None". Keep the fix localized to the summary loop that calls store.has and
store.record so persisted history and weekly reports stay clean.
- Around line 23-25: The import in agent.py uses a from-import alias for
storage_mod, which violates the module-level import guideline. Replace it with a
module-level import of the use_agent.storage module and keep all existing
references through storage_mod.X so the rest of the file remains unchanged.
In `@use_agent/config.py`:
- Around line 58-70: The db_path function returns the USE_AGENT_DB override
without tilde expansion, unlike the configured path branch. Update db_path so
the environment override is normalized with expanduser() before returning,
keeping behavior consistent for values like "~/mydb.db" and matching the
existing path resolution logic.
In `@use_agent/report.py`:
- Around line 87-110: Normalize the `processed_at` filter in `_fetch_rows` by
converting `start` to UTC before building the query argument so the comparison
matches how action timestamps are stored, and keep the row ordering unchanged.
Also tighten the `sqlite3.OperationalError` handling in `_fetch_rows` so it only
returns an empty list for the missing-table case, while letting other database
errors (for example locked or corrupted DBs) surface instead of being treated as
an empty report.
In `@use_agent/tools.py`:
- Line 18: The module import in tools.py uses a from-import style that violates
the project’s import guideline; replace it with a module-level import for
storage and keep the existing storage_mod.X call sites unchanged. This is a
straightforward drop-in change in the top-level import section of tools.py, and
no downstream logic should need to change because all usages already reference
storage_mod.
---
Nitpick comments:
In `@tests/test_report.py`:
- Line 5: The test file uses a forbidden from-import for the report and storage
submodules. Replace the `from use_agent import report, storage` import with
module-level imports from the same package, then update any `report.X` and
`storage.X` references in this test to use the imported module names
consistently.
In `@use_agent/report.py`:
- Line 23: The import in report.py violates the module-level import rule by
using a from-import for config. Replace the existing import with a bare module
import for use_agent.config, then update every config reference in the file to
use the module namespace explicitly so symbols are accessed as
use_agent.config.X.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: bccd02a8-adf1-422b-901c-60d52d07c7db
📒 Files selected for processing (16)
config.example.tomltests/test_config.pytests/test_reconcile.pytests/test_report.pytests/test_settings.pytests/test_storage.pyuse_agent/agent.pyuse_agent/cli.pyuse_agent/config.pyuse_agent/gmail.pyuse_agent/report.pyuse_agent/reporter.pyuse_agent/settings.pyuse_agent/storage.pyuse_agent/templates/report.html.j2use_agent/tools.py
| from use_agent import ( | ||
| storage as storage_mod, | ||
| ) |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Use a module-level import, not from ... import ....
As per coding guidelines: **/*.py: "Use module-level imports only; do not use from ... import ... style imports." All usages already go through storage_mod.X.
♻️ Proposed fix
-from use_agent import (
- storage as storage_mod,
-)
+import use_agent.storage as storage_mod📝 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.
| from use_agent import ( | |
| storage as storage_mod, | |
| ) | |
| import use_agent.storage as storage_mod |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@use_agent/agent.py` around lines 23 - 25, The import in agent.py uses a
from-import alias for storage_mod, which violates the module-level import
guideline. Replace it with a module-level import of the use_agent.storage module
and keep all existing references through storage_mod.X so the rest of the file
remains unchanged.
Source: Coding guidelines
- Ensure store.close() runs even if the agent loop, reporter.finish(), or history reconciliation raises (try/finally in agent.run) - Stop coercing explicit None summary fields to the literal string "None" when reconciling history (sender, subject, classification, response_mode) - Expand ~ in the USE_AGENT_DB env override, matching the configured [storage] path branch - Normalize the report window start to UTC before querying processed_at (stored in UTC), and only swallow the missing-table case of sqlite3.OperationalError instead of all operational errors Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Summary
Adds a persistent record of the triage actions use-agent takes, and a weekly HTML report (viewable or emailed) built from that history.
Problem
The agent replied to, unsubscribed from, and trashed mail with no durable record of what it did. There was no way to review activity over time or share a digest.
Solution
Action history (SQLite). A new
use_agent.storage.Storerecords one row per acted-upon message to a SQLite database, opened in WAL mode with a busy timeout so a daemon and an ad-hoc run can write concurrently on a local filesystem.processed_at,query_target,sender,subject,sent_at(original Date, ISO 8601 UTC),classification,category(a 1-5 word content label, e.g. "Recruiter", "AI Solution"),response_mode,action_taken,score,message_id,source.record_actionMCP tool writes each row at action time (validated tool args);agent.runthen reconciles the agent's final JSON summary and inserts any acted-upon row the model forgot to record, taggedsource='summary'. Only acted-upon messages are stored; dry runs write nothing.~/.use-agent/actions.db, overridable via[storage] pathinconfig.tomlor theUSE_AGENT_DBenv var.Weekly report.
use_agent.reportrolls the history up (default last 7 days) into the approved "Weekly Report" design, rendered from an inline-styled Jinja2 template (autoescapeon — sender names/subjects are untrusted).GmailClient.send_htmlsends the report;[report] recipients/subjectin config, overridable per run with--to/--subject.Notes
config.example.tomldocuments the new[storage]and[report]sections.🤖 Generated with Claude Code
Summary by CodeRabbit
reportCLI subcommand to generate a weekly HTML report that can be viewed, saved, or emailed.USE_AGENT_DB.