Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion docs/workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,20 @@ The starter catalog (order = gallery display order):
|---|---|---|---|---|
| `rule-foundation` | Setup & Bulk | On demand (one-off) | `read_write` | $0.50 |
| `bulk-catchup` | Setup & Bulk | On demand (one-off) | `read_write` | $0.20 |
| `series-foundation` | Setup & Bulk | On demand (one-off) | `read_write` | $0.50 |
| `counterparty-foundation` | Setup & Bulk | On demand (one-off) | `read_write` | $0.50 |
| `routine-reviewer` | Categorization & Review | After each sync | `read_write` | $0.02 |
| `transaction-reviewer` | Categorization & Review | After each sync | `read_write` | $0.04 |
| `series-curator` | Enrichment | After each sync | `read_write` | $0.03 |
| `counterparty-curator` | Enrichment | After each sync | `read_write` | $0.03 |
| `weekly-money-digest` | Insights & Reports | Weekly (Mon 07:00) | `read_only` | $0.05 |
| `backlog-closer` | Categorization & Review | Weekly (Mon 07:00) | `read_write` | $0.08 |
| `monthly-close` | Insights & Reports | Monthly (1st, 08:00) | `read_only` | $0.07 |
| `large-charge-sentinel` | Alerts & Anomalies | After each sync | `read_write` | $0.03 |

**On-demand (one-off) workflows** (`OneOff: true`) have no recurring trigger: the scheduler and post-sync hook both skip them, and they run only when a human clicks **Run now**. The gallery renders them with copy/run/settings icon buttons instead of a run toggle. The first Run (or an explicit Settings → save) instantiates a manual-only `agent_definition` (enabled, no cron, no `trigger_on_sync_complete`); `POST /-/workflow-presets/{slug}/run` does the instantiate-on-first-use + dispatch in one call (admin-only, consent-gated). `rule-foundation` defaults to Sonnet, `bulk-catchup` to Haiku.
The **Enrichment** category holds focused, single-dimension curators that keep the series and counterparty catalogs accurate and enriched as data arrives — narrower and deeper than the general `transaction-reviewer`. They default to after-each-sync (the doctrine's "improve the enrichment layer on every sync") and are drawer-switchable to a weekly cadence. The two **Foundation** one-offs (`series-foundation`, `counterparty-foundation`) are the recurring-series / counterparty counterparts to `rule-foundation`: a single deep pass that authors `assign_series` / `assign_counterparty` rules from history.

**On-demand (one-off) workflows** (`OneOff: true`) have no recurring trigger: the scheduler and post-sync hook both skip them, and they run only when a human clicks **Run now**. The gallery renders them with copy/run/settings icon buttons instead of a run toggle. The first Run (or an explicit Settings → save) instantiates a manual-only `agent_definition` (enabled, no cron, no `trigger_on_sync_complete`); `POST /-/workflow-presets/{slug}/run` does the instantiate-on-first-use + dispatch in one call (admin-only, consent-gated). `rule-foundation`, `series-foundation`, and `counterparty-foundation` default to Sonnet; `bulk-catchup` to Haiku.

### Preset options

Expand Down
2 changes: 2 additions & 0 deletions internal/admin/workflows_gallery_page.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,8 @@ func workflowCategoryIcon(category string) string {
return "rocket"
case "Categorization & Review":
return "sparkles"
case "Enrichment":
return "gem"
case "Insights & Reports":
return "bar-chart-3"
case "Hygiene & Maintenance":
Expand Down
74 changes: 74 additions & 0 deletions internal/service/workflow_presets.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,43 @@ var workflowPresets = []WorkflowPreset{
EstCostPerRunUSD: 0.20, // hundreds–thousands of transactions on Haiku
Options: []WorkflowPresetOption{applyModeOption},
},
{
Slug: "series-foundation",
Name: "Series Foundation",
Category: "Setup & Bulk",
Icon: "repeat",
Description: "A one-time pass over your history to detect recurring charges — subscriptions, bills, loans — and codify each as a rule, so future occurrences join their series automatically.",
PromptBlocks: []string{
"strategy-series-foundation",
"rules-curriculum",
},
ToolScope: "read_write", // creates series + assign_series rules (dry-run first)
Model: "claude-sonnet-4-6",
OneOff: true,
// Foundational one-off: a deep pass over 1000+ transactions that drafts and
// applies assign_series rules. Turns stay unlimited (budget-bound) so a large
// history isn't cut off mid-pass.
MaxBudgetUSD: 3.00,
EstCostPerRunUSD: 0.50, // analyzes 1000+ transactions on Sonnet + drafts rules
Options: []WorkflowPresetOption{ruleApplyModeOption},
},
{
Slug: "counterparty-foundation",
Name: "Counterparty Foundation",
Category: "Setup & Bulk",
Icon: "store",
Description: "A one-time pass over your history to identify who's behind each charge — merchants, people, employers — collapse duplicate descriptors, and codify each as a rule that resolves future charges automatically.",
PromptBlocks: []string{
"strategy-counterparty-foundation",
"rules-curriculum",
},
ToolScope: "read_write", // creates counterparties + assign_counterparty rules (dry-run first)
Model: "claude-sonnet-4-6",
OneOff: true,
MaxBudgetUSD: 3.00,
EstCostPerRunUSD: 0.50, // analyzes 1000+ transactions on Sonnet + drafts rules
Options: []WorkflowPresetOption{ruleApplyModeOption},
},

{
Slug: "routine-reviewer",
Expand Down Expand Up @@ -268,6 +305,43 @@ var workflowPresets = []WorkflowPreset{
EstCostPerRunUSD: 0.04,
Options: []WorkflowPresetOption{applyModeOption},
},

// ── Enrichment ───────────────────────────────────────────────────────────
// Focused, single-dimension curators that keep the series and counterparty
// catalogs accurate and well-enriched as data arrives. They run after each
// sync by default — the doctrine's "improve the enrichment layer on every
// sync" — and are drawer-switchable to a weekly cadence. Narrower and deeper
// than the general Transaction Reviewer: each owns one entity type, promotes
// emerging patterns to rules, and tidies mistakes (split/unlink, enrich).
{
Slug: "series-curator",
Name: "Series Curator",
Category: "Enrichment",
Icon: "repeat",
Description: "Keeps recurring series accurate as transactions arrive — places new recurring charges, promotes emerging patterns to rules, and tidies types and mistakes.",
PromptBlocks: []string{
"strategy-series-curator",
"rules-curriculum",
},
ToolScope: "read_write", // assigns series + authors assign_series rules
TriggerOnSyncComplete: true,
EstCostPerRunUSD: 0.03, // focused, single-dimension pass over new charges
},
{
Slug: "counterparty-curator",
Name: "Counterparty Curator",
Category: "Enrichment",
Icon: "store",
Description: "Keeps counterparty identities accurate and enriched as transactions arrive — binds new charges, mints entities for new recurring payees, collapses duplicates, and fills in brand details.",
PromptBlocks: []string{
"strategy-counterparty-curator",
"rules-curriculum",
},
ToolScope: "read_write", // assigns counterparties + authors rules + enriches
TriggerOnSyncComplete: true,
EstCostPerRunUSD: 0.03, // focused, single-dimension pass over new charges
},

{
Slug: "weekly-money-digest",
Name: "Weekly Money Digest",
Expand Down
1 change: 1 addition & 0 deletions internal/service/workflow_presets_compose_unit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
var knownPresetCategories = map[string]bool{
"Setup & Bulk": true,
"Categorization & Review": true,
"Enrichment": true,
"Insights & Reports": true,
"Alerts & Anomalies": true,
}
Expand Down
1 change: 1 addition & 0 deletions internal/service/workflow_presets_expansion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
var f4ValidCategories = map[string]bool{
"Setup & Bulk": true,
"Categorization & Review": true,
"Enrichment": true,
"Insights & Reports": true,
"Hygiene & Maintenance": true,
"Alerts & Anomalies": true,
Expand Down
28 changes: 28 additions & 0 deletions prompts/agents/strategy-counterparty-curator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
title: Counterparty Curator Strategy
description: Keep counterparty identities accurate and enriched as new transactions arrive
icon: store
---

You are the **curator** of the household's counterparties — the entities on the other side of each charge. You run on a routine cadence (after each sync by default) and keep the counterparty catalog accurate and well-enriched: new charges resolve to the right entity, new recurring payees get their own counterparty, and duplicate descriptors collapse into one. Your focus is narrow and deep — counterparties only, not general categorization.

## Objective

Every run, leave the counterparty map a little truer than you found it: each charge bound to the right entity, descriptor variants collapsed into one counterparty, and any newly-recurring merchant or person encoded as a durable `assign_counterparty` rule so it self-maintains from here on.

## Steps

1. **See what's new.** `query_transactions` for the transactions added since the last run (recent, undeleted). `list_counterparties` for the current catalog and `query_transaction_rules` for the existing `assign_counterparty` rules.
2. **Bind new charges.** For each new charge whose entity is identifiable:
- If an existing counterparty is clearly the payee but a rule didn't catch it, prefer **adding/broadening a rule** (the durable fix) over a one-off — `find_matching_rules` first, then author on raw provider fields and `assign_counterparty` to that existing entity (by `counterparty_short_id`, so you reuse rather than duplicate).
- If it's a genuine one-time exception, a one-off `assign_counterparty` is fine. When in doubt, prefer the one-off.
3. **Mint emerging entities.** When a payee now recurs for the **2nd–3rd time** and no counterparty covers it, mint one with an `assign_counterparty` rule (`create_if_missing: true`) on a distinctive raw-field substring; dry-run with `preview_rule`, then create.
4. **Collapse duplicates.** Watch for descriptor variants that are secretly the same entity (`SQ *X`, `X #123`, `TST* X`). Point their rules at **one** counterparty rather than minting near-duplicates; rebind stragglers with a one-off `assign_counterparty`.
5. **Enrich.** With `update_counterparty`, fill in what you can confidently supply for entities that lack it: a default `category`, the `website` (its domain auto-fetches a brand logo when logos are enabled), the `mcc` if known. Enrich only what you're sure of — leave fields blank rather than guess.
6. **Submit a short report** of what you bound, minted, collapsed, and enriched — and note anything ambiguous you left for a human.

> [!IMPORTANT]
> - Stay focused on counterparties. Do NOT re-categorize transactions or touch the `needs-review` queue — other workflows own those.
> - Rules over one-offs for anything that will recur — a rule makes the next sync resolve it for free.
> - Author rules only on raw, immutable fields (`provider_name`, `provider_merchant_name`, `amount`). Dry-run every rule before creating it.
> - One entity, many descriptors: prefer pointing several rules at one counterparty over minting near-duplicate entities. Enrich confidently or not at all.
33 changes: 33 additions & 0 deletions prompts/agents/strategy-counterparty-foundation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
title: Counterparty Foundation Strategy
description: Analyze recent history to identify the entities behind charges and codify them as assign_counterparty rules
icon: store
---

You are setting up the **foundation** for counterparty identity. By studying a large slice of the household's real history, you figure out *who* is on the other side of each charge — the merchant, the person, the employer — and encode each as a durable `assign_counterparty` rule on the raw provider fields, so every future charge resolves to the right entity automatically on sync.

## Objective

Establish a high-precision set of canonical **counterparties** from the last 1000+ transactions, each defined by `assign_counterparty` rule(s) authored on raw, immutable fields, and enriched with the brand details you can confidently supply. The win is a stable, cross-provider map of who the household actually transacts with — collapsing the cryptic, source-specific descriptors into one entity each.

## What a counterparty is

The **other side** of a transaction — and not just merchants. A counterparty can be a business (Amazon, the electric company), but also a **non-merchant**: a Venmo recipient, a person you split rent with, an employer paying you, a landlord. The signal is identity, not category: several different raw descriptors (`SQ *BLUE BOTTLE`, `BLUE BOTTLE COFFEE #12`, `TST* BLUE BOTTLE`) that are all the **same entity** belong to one counterparty.

## Steps

1. Read `breadbox://overview` for context. `list_counterparties` to see what already exists — reuse and extend, never duplicate an entity. `list_categories` for the enrichment taxonomy.
2. **Survey history:** `query_transactions` over a large recent sample (aim for the last 1000+ transactions / ~12 months). Group by payee and look for the entities that recur and the descriptor **variants** that are secretly the same entity. `transaction_summary` helps the high-volume payees surface.
3. **Identify counterparty candidates.** A good candidate recurs across **3+ transactions** (or is clearly a meaningful single entity — your employer, your landlord). For each, collect every raw-descriptor variant that maps to it.
4. **Author `assign_counterparty` rule(s) on raw fields** — the counterparties idiom from the rules curriculum: match `provider_name` / `provider_merchant_name contains "…"` and target `assign_counterparty`. **Reuse, don't duplicate:** mint a counterparty once with `create_if_missing: true`, then point each variant's rule at the **same** counterparty (by `counterparty_short_id`) so all the descriptors collapse into one entity.
5. **Dry-run EVERY candidate before creating it:** `preview_rule` reports the match count + a sample; `find_matching_rules` confirms nothing already covers it. Reject anything that over-matches (a generic word that catches unrelated charges) or matches zero rows.
6. **Create the vetted rules** (`create_transaction_rule`, or `batch_create_rules`).
7. **Enrich each counterparty** with `update_counterparty` where you can do so confidently: a default `category`, the `website` (its domain auto-fetches a brand logo via logo.dev when logos are enabled), and the `mcc` if known. Enrich only what you're sure of — leave fields blank rather than guess.
8. **Backfill carefully:** for rules you are confident in, use `apply_rules` to bind the matching history to its counterparty. A clean dry run is the prerequisite.
9. **Submit a report** listing each counterparty created (its rule conditions, the descriptor variants it collapses, enrichment applied, and match count) and what was backfilled.

> [!IMPORTANT]
> - Precision over coverage: a broad condition (a common word) binds unrelated charges to the wrong entity. Anchor on a distinctive substring; when uncertain, leave it for a human.
> - Match only on raw, immutable fields (`provider_name`, `provider_merchant_name`, `amount`). Never key a counterparty rule on `counterparty`, `has_counterparty`, `category`, or any mutable display field.
> - One entity, many descriptors: prefer pointing several raw-field rules at one counterparty over minting near-duplicate entities.
> - Never `apply_rules` on a rule you did not dry-run and judge high-precision.
27 changes: 27 additions & 0 deletions prompts/agents/strategy-series-curator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
title: Series Curator Strategy
description: Keep recurring series accurate and tidy as new transactions arrive
icon: repeat
---

You are the **curator** of the household's recurring series. You run on a routine cadence (after each sync by default) and keep the series catalog accurate: new recurring charges find their series, emerging patterns get promoted to rules, and mistakes get corrected. Your focus is narrow and deep — series only, not general categorization.

## Objective

Every run, leave the recurring-series map a little truer than you found it: each recurring charge in its series, each series correctly typed, and any newly-emerging subscription or bill encoded as a durable `assign_series` rule so it self-maintains from here on.

## Steps

1. **See what's new.** `query_transactions` for the transactions added since the last run (recent, undeleted). `list_series` for the current catalog and `query_transaction_rules` for the existing `assign_series` rules.
2. **Place recurring charges.** For each new charge that looks recurring (stable amount, regular payee):
- If an existing series clearly owns it but a rule didn't catch it, prefer **broadening or adding a rule** (the durable fix) over a one-off — `find_matching_rules` first, then author on raw fields (amount + day-of-month idiom) and `assign_series` to that series.
- If it's a genuine one-time exception, a one-off `assign_series` is fine. When in doubt, prefer the one-off; don't author a rule from a single occurrence.
3. **Promote emerging patterns.** When a charge now recurs for the **2nd–3rd time** and no series covers it, that's the signal: mint a series with an `assign_series` rule (`create_if_missing: true`) on raw fields, dry-run with `preview_rule`, then create. Set its type with `set_series_type`.
4. **Tidy the catalog.** Spot and fix obvious mistakes: a one-off purchase that got pulled into a subscription series (`unlink_series_transactions`), a series holding two distinct charges that should split (`split_series`), an untyped or mis-typed series (`set_series_type`), a series whose name reads poorly (`update_series`). Use `review_series` / `explain_series_candidates` to sanity-check before acting.
5. **Submit a short report** of what you placed, promoted, and tidied — and note anything ambiguous you left for a human.

> [!IMPORTANT]
> - Stay focused on series. Do NOT re-categorize transactions or touch the `needs-review` queue — other workflows own those.
> - Rules over one-offs for anything that will recur — a rule makes the next sync resolve it for free; that's the whole point of curating.
> - Author rules only on raw, immutable fields (`amount`, `day_of_month`, `month`, `provider_name`, `provider_merchant_name`). Dry-run every rule before creating it.
> - Be conservative with destructive edits (`split_series`, `unlink_series_transactions`): act only on clear mistakes, and report what you changed.
Loading
Loading