---
name: project-attribution
title: "Project Attribution"
description: "Suggest a project assignment for a transaction (or per-line-item for split receipts) using project keywords, recent activity, and the memory graph."
category: finance
status: active
model_tier: capable
priority: 6
triggers:
  - "attribute project"
  - "assign project to transaction"
  - "which project does this belong to"
tools_granted:
  - update_transaction
  - query_finance
  - list_projects
  - query_knowledge
language:
  output_policy: source
---

## What this skill is

Step 3 of the `receipt-processing` workflow. After categorization, this skill maps each transaction (or each line item, for split receipts) to a project slug. The signals it uses:

1. **Direct keyword match** — line item description mentions a project name or related entity from `query_knowledge`.
2. **Active project hint** — the user's currently active project (from recent journal entries / open tasks) gets a soft preference IF the merchant + category fit.
3. **Merchant default project** — some merchants are project-specific (a vendor invoice for a one-off project always goes to that project).
4. **Empty (unassigned)** — the honest answer when no signal is strong. Better to leave empty than fabricate.

## When to use

Called by the `bookkeeper` agent during the workflow's `attribute` step. This step has a `CritiqueSpec(agent=auditor, threshold=0.7)` — the auditor reviews attributions and loops back if the bookkeeper's reasoning is weak.

## Flow

1. Call `list_projects()` to get active projects with their descriptions.
2. Call `query_finance(draft_id)` to read the draft's items.
3. For each line item, score against project keywords + recent activity:
   - High signal (≥0.8): assign `project_slug`.
   - Medium signal (0.4–0.8): assign with `notes: "auto-attributed; please confirm"`.
   - Low signal (<0.4): leave empty.
4. If ALL lines map to one project, also set the transaction-level `project_slug`. If lines split across projects, only set per-line slugs (split receipt) — leave transaction-level empty.
5. Call `update_transaction(draft_id, project_slug=..., items=[...])`.

## Hard rules — anti-fabrication

- NEVER assign to a project that doesn't exist in `list_projects()` output. If the receipt clearly references work for a project not yet in the system, surface a proposal in `notes` (`"appears related to <name>; create project?"`) and leave `project_slug` empty.
- NEVER assign a personal expense (groceries, restaurants without business context) to a work project just because that's the active project this week.
- NEVER override the line item's category — that's `transaction-categorize`'s decision. Project and category are orthogonal.

## Auditor critique signals

The auditor downvotes attributions that:
- Lack any keyword match AND aren't in the user's recent activity.
- Assign restaurants/groceries to a project without explicit business context.
- Spread one transaction across more than 3 projects (unusual; usually mis-attribution).

When auditor downvotes, the bookkeeper loops back, re-checks the signals, and either reassigns or explicitly clears the slug with a note.

## Examples

- input: |
    Attribute draft txn_aws_2026_04 to a project.
  output: |
    [Calls tool: list_projects]
    [Reviews: agntspace, side-research-aging, personal]
    [Calls tool: query_finance with {"draft_id": "txn_aws_2026_04"}]
    [Reviews 12 AWS line items: EC2, RDS, S3, Lambda — all standard infra]
    [Calls tool: update_transaction with project_slug="agntspace" + each item project_slug="agntspace"]
    Attributed all 12 items to agntspace project — AWS infra spend matches the project's known cloud usage.

- input: |
    Attribute draft txn_costco_2026_04 to a project.
  output: |
    [Calls tool: list_projects]
    [Calls tool: query_finance with {"draft_id": "txn_costco_2026_04"}]
    [Reviews 5 items: groceries (3) + automotive engine oil (1) + health toothpaste (1)]
    [Calls tool: update_transaction with project_slug="" — no per-line attributions either; this is a personal expense.]
    Left unassigned. Receipt is personal-expense pattern (groceries + household goods) with no project signal.
