Skip to content

Action history + weekly HTML report#5

Merged
gmr merged 5 commits into
mainfrom
feature/action-history
Jul 2, 2026
Merged

Action history + weekly HTML report#5
gmr merged 5 commits into
mainfrom
feature/action-history

Conversation

@gmr

@gmr gmr commented Jul 2, 2026

Copy link
Copy Markdown
Owner

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.Store records 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.

  • Columns: 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.
  • Hybrid capture: a new record_action MCP tool writes each row at action time (validated tool args); agent.run then reconciles the agent's final JSON summary and inserts any acted-upon row the model forgot to record, tagged source='summary'. Only acted-upon messages are stored; dry runs write nothing.
  • Default location ~/.use-agent/actions.db, overridable via [storage] path in config.toml or the USE_AGENT_DB env var.

Weekly report. use_agent.report rolls the history up (default last 7 days) into the approved "Weekly Report" design, rendered from an inline-styled Jinja2 template (autoescape on — sender names/subjects are untrusted).

use-agent report                       # render HTML to stdout
use-agent report --days 30 --output r.html
use-agent report --email               # send from your account to [report] recipients
use-agent report --email --to you@example.com

GmailClient.send_html sends the report; [report] recipients / subject in config, overridable per run with --to / --subject.

Notes

  • Verified end-to-end: rendered a populated sample DB through the CLI and confirmed the output matches the design.
  • 110 tests pass; ruff check/format clean. The template ships in the wheel.
  • config.example.toml documents the new [storage] and [report] sections.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a new report CLI subcommand to generate a weekly HTML report that can be viewed, saved, or emailed.
    • Added configurable HTML report recipients and an overridable email subject.
    • Introduced SQLite-backed action-history storage with configurable database path via config and USE_AGENT_DB.
  • Bug Fixes
    • Improved action-history reliability with reconciliation to ensure acted-on items are recorded even if partially missed.
    • Prevents duplicate entries and safely tolerates empty or skipped results.
    • Report rendering now escapes sender content and shows an empty report when the database is missing.

gmr and others added 4 commits July 2, 2026 12:04
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>
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9bc8c8b8-34a2-41ea-a455-3a1ef51144ff

📥 Commits

Reviewing files that changed from the base of the PR and between e18802a and 3ff057b.

📒 Files selected for processing (3)
  • use_agent/agent.py
  • use_agent/config.py
  • use_agent/report.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • use_agent/config.py
  • use_agent/agent.py
  • use_agent/report.py

📝 Walkthrough

Walkthrough

Adds SQLite-backed action-history storage, a record_action MCP tool, agent reconciliation of missed history entries, a weekly HTML report module and Jinja2 template, a new report CLI subcommand, Gmail HTML sending, and related config/settings updates and tests.

Changes

Action History Storage and Reporting

Layer / File(s) Summary
Storage and report config
use_agent/config.py, use_agent/settings.py, config.example.toml, tests/test_config.py, tests/test_settings.py
Adds db_path(), template path constants, new settings fields for storage/report config, documented TOML sections, and matching path-resolution tests.
SQLite history store
use_agent/storage.py, tests/test_storage.py
Implements query target parsing, action detection, date normalization, SQLite persistence, de-duplication, and store behavior tests.
record_action tool and reconciliation
use_agent/tools.py, use_agent/agent.py, tests/test_reconcile.py, use_agent/reporter.py
Adds the record_action MCP tool, passes an optional store through agent execution, expands the prompt/schema, captures reporter summary, and backfills missing history rows.
Weekly report rendering
use_agent/report.py, use_agent/templates/report.html.j2, use_agent/gmail.py, tests/test_report.py
Builds the weekly HTML report context and template, adds Gmail HTML sending and message dates, and tests aggregation and rendering behavior.
CLI report command
use_agent/cli.py
Adds the report subcommand with render/email/output paths and dispatch wiring.

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()
Loading
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
Loading

Poem

A rabbit hops through SQLite trails,
And leaves neat notes of inbox tales.
With charts and mail and HTML glow,
The weekly report begins to flow. 🐇
Thump! The summary is done.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the two main additions: persistent action history and a weekly HTML report.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (2)
use_agent/report.py (1)

23-23: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Avoid from ... import for the config submodule.

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.config

And update all config.X references in this file to use_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 value

Avoid from ... import for report/storage submodules.

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.storage

And update report.X/storage.X references 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

📥 Commits

Reviewing files that changed from the base of the PR and between eb79db3 and e18802a.

📒 Files selected for processing (16)
  • config.example.toml
  • tests/test_config.py
  • tests/test_reconcile.py
  • tests/test_report.py
  • tests/test_settings.py
  • tests/test_storage.py
  • use_agent/agent.py
  • use_agent/cli.py
  • use_agent/config.py
  • use_agent/gmail.py
  • use_agent/report.py
  • use_agent/reporter.py
  • use_agent/settings.py
  • use_agent/storage.py
  • use_agent/templates/report.html.j2
  • use_agent/tools.py

Comment thread tests/test_storage.py
Comment thread use_agent/agent.py
Comment on lines +23 to +25
from use_agent import (
storage as storage_mod,
)

@coderabbitai coderabbitai Bot Jul 2, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 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.

Suggested change
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

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

This is not correct

Comment thread use_agent/agent.py
Comment thread use_agent/agent.py
Comment thread use_agent/config.py
Comment thread use_agent/report.py
Comment thread use_agent/tools.py
- 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>
@gmr gmr merged commit 0d5f698 into main Jul 2, 2026
2 checks passed
@gmr gmr deleted the feature/action-history branch July 2, 2026 18:55
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.

1 participant