> ## Documentation Index
> Fetch the complete documentation index at: https://80x.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Stale-deal sweep

> A weekly sweep finds open deals that have gone quiet, flags any past a stage-specific age threshold, and tasks the owner to act.

A weekly sweep finds open deals that have gone quiet, flags any past a stage-specific age threshold, and tasks the owner to act. This is pipeline-hygiene work. It is for sales managers and revenue operations (RevOps) engineers who watch a business-to-business (B2B) pipeline drift between Monday reviews. A [Recurring schedule](/reference/triggers/recurring-schedule) wakes the workflow. [Find records](/reference/records/find-records) pulls the open deals, and a [Loop](/reference/utilities/loop) walks them one at a time. For each deal, an [If](/reference/conditions/if) condition compares the deal's idle time against an [Adjust time](/reference/calculations/adjust-time) cutoff tuned per stage. A stale deal then gets a "Needs review" flag from [Update record](/reference/records/update-record) and a [Create task](/reference/tasks/create-task) for its owner. The sweep flags and nudges. It never auto-closes anything.

| | |
| - | - |
| **Difficulty** | Intermediate |
| **Time to build** | About 15 minutes |
| **Trigger** | [Recurring schedule](/reference/triggers/recurring-schedule) |
| **Blocks used** | [Find records](/reference/records/find-records), [Loop](/reference/utilities/loop), [Adjust time](/reference/calculations/adjust-time), [If](/reference/conditions/if), [Update record](/reference/records/update-record), [Create task](/reference/tasks/create-task) |
| **Credit cost per run** | 2 credits per stale deal (the flag write plus the task); free if nothing is stale |
| **Plan required** | Plus or higher |

## Prerequisites

* An Attio plan on the **Plus** tier or higher, so the new Workflows engine is available.
* The **Deals** object with a **Stage** status attribute and an **Owner** attribute populated on live deals.
* A **Last interaction** attribute on **Deals** that reflects real activity. Attio maintains a system **Last interaction** value. Confirm it updates the way your team expects before you trust it.
* A **Needs review** checkbox or single-select attribute on **Deals** to hold the flag. Create it before you build the workflow.
* No external integrations or API keys are required for the core build. The optional digest needs a connected Slack workspace, covered under [Variations](#variations).

## How it works

The schedule fires once a week. It hands a list of open, owned deals to a loop. For each deal, the workflow computes a cutoff date that depends on the deal's stage. A `Qualified` deal is stale after 7 days. A `Proposal` deal is stale after 30. The workflow then checks whether the deal's last interaction falls before that cutoff. Deals that fail this freshness check get flagged and generate an owner task. Fresh deals fall through untouched. Nothing in this flow closes or moves a deal. So a false positive costs an owner one glance, not a lost record.

The blocks run in this order:

1. [Recurring schedule](/reference/triggers/recurring-schedule) fires Monday morning in the team's time zone.
2. [Find records](/reference/records/find-records) returns open, owned deals only.
3. [Loop](/reference/utilities/loop) runs one iteration per matching deal.
4. [Adjust time](/reference/calculations/adjust-time) subtracts the stage-specific threshold from now to produce a cutoff date.
5. [If](/reference/conditions/if) tests whether the deal's last interaction is before the cutoff.
6. [Update record](/reference/records/update-record) sets **Needs review** on stale deals.
7. [Create task](/reference/tasks/create-task) assigns a follow-up to the deal owner.

## Build steps

<Steps>
  <Step title="Add the recurring-schedule trigger">
    Create a new workflow and add the [Recurring schedule](/reference/triggers/recurring-schedule) trigger. Set it to run weekly so the sweep lands before the Monday pipeline review. Pick the time zone your reps actually work in, not the workspace default. That way "Monday morning" means the same thing for everyone.

    | Field | Value |
    | - | - |
    | Trigger | Recurring schedule |
    | Frequency | Weekly |
    | Day | Monday |
    | Time | 07:00 |
    | Time zone | Europe/London (the team's working region) |
  </Step>

  <Step title="Find the open, owned deals">
    Add a [Find records](/reference/records/find-records) block on the **Deals** object. Filter hard. A tight filter is your main defense against a loop credit blow-up, because every record it returns becomes a loop iteration. Restrict to open stages, require an owner, and add a wide outer activity bound so you never loop the whole pipeline.

    | Field | Value |
    | - | - |
    | Object | Deals |
    | Filter | Stage is any of `Qualified`, `Proposal`, `Negotiation` |
    | Filter | Owner is not empty |
    | Filter | Last interaction is before `now − 7 days` |
    | Limit (while testing) | 5 |

    <Warning>
      While you are building, keep the **Limit** at 5. A single **Find records** block returns at most 100 records. So a maxed-out sweep with a loose filter can still fire 100 task writes in one run. Raise the limit only after a clean test, and keep the stage filter narrow.
    </Warning>
  </Step>

  <Step title="Loop over the matches">
    Add a [Loop](/reference/utilities/loop) block and point it at the records from the previous step. The loop is free. What runs inside it is not. Every write lives inside the loop and is gated by the condition two steps on.

    | Field | Value |
    | - | - |
    | Iterate over | `{{find_records.records}}` |
    | Current item variable | `deal` |
  </Step>

  <Step title="Compute a stage-specific cutoff">
    Inside the loop, add an [Adjust time](/reference/calculations/adjust-time) block to turn now into a cutoff date. Per-stage ages are how you avoid false positives. A `Qualified` deal idle for 8 days is genuinely stale. Flagging a `Proposal` deal at the same age just trains reps to ignore the flag. Subtract the right number of days for the current deal's stage.

    | Field | Value |
    | - | - |
    | Base time | `now` |
    | Operation | Subtract |
    | Amount | 7 days for `Qualified`, 30 days for `Proposal`, 14 days for `Negotiation` |
    | Output | `cutoff_date` |

    <Note>
      The cleanest way to pick the per-stage number is a [Switch](/reference/conditions/switch) on `{{deal.stage}}` ahead of this block. It writes the day count into a variable that you feed into **Amount**. Both **Switch** and **Adjust time** are free.
    </Note>
  </Step>

  <Step title="Gate on staleness">
    Add an [If](/reference/conditions/if) condition that compares the deal's last interaction against the cutoff. A condition that stops a run is free. So this is where you spend nothing to protect the writes that follow it. Only deals that fail the freshness check continue down the true branch.

    | Field | Value |
    | - | - |
    | Left | `{{deal.last_interaction}}` |
    | Operator | is before |
    | Right | `{{cutoff_date}}` |
  </Step>

  <Step title="Flag the deal">
    On the true branch, add an [Update record](/reference/records/update-record) block to raise the review flag. This write is idempotent, which means re-running the sweep on an already-flagged deal just re-sets the same value.

    | Field | Value |
    | - | - |
    | Record | `{{deal}}` |
    | Attribute | Needs review |
    | Value | `true` |
  </Step>

  <Step title="Task the owner">
    Add a [Create task](/reference/tasks/create-task) block on the same branch. Assign it to the deal owner and link it to the deal so the owner opens straight into context. Name the deal and its stage in the task text so the task is actionable without a click.

    | Field | Value |
    | - | - |
    | Task text | `Stale deal, review: {{deal.name}} ({{deal.stage}}), no activity since {{deal.last_interaction}}` |
    | Assignee | `{{deal.owner}}` |
    | Linked record | `{{deal}}` |
    | Due date | Today |
  </Step>
</Steps>

## Variable mapping

| Variable | Source block | Field | Example value |
| - | - | - | - |
| `{{find_records.records}}` | Find records | Matching records | 23 deals |
| `{{deal}}` | Loop | Current item | The Acme renewal deal |
| `{{deal.stage}}` | Loop item | Stage | `Proposal` |
| `{{deal.last_interaction}}` | Loop item | Last interaction | `2026-05-02` |
| `{{deal.owner}}` | Loop item | Owner | Priya Sharma |
| `{{cutoff_date}}` | Adjust time | Computed cutoff | `2026-05-12` |

## Credit cost worked example

Most of the blocks here are free. The schedule, [Find records](/reference/records/find-records), [Loop](/reference/utilities/loop), [Adjust time](/reference/calculations/adjust-time), the optional [Switch](/reference/conditions/switch), and the [If](/reference/conditions/if) condition are all logic and lookup blocks. They cost nothing no matter how often they run. You pay only for the two writes on the stale branch. Those are the [Update record](/reference/records/update-record) flag and the [Create task](/reference/tasks/create-task), each 1 credit.

So each stale deal costs 2 credits per run, and fresh deals cost nothing. Say a typical Monday surfaces 15 stale deals. That is 15 × 2 = 30 credits per run, or roughly 120 credits a month across four sweeps. The loop only writes on the true branch. So a quiet week with two stale deals costs 4 credits, not the full pipeline.

Loops multiply. The per-run cost is (writes on the stale branch) × (number of stale deals). The only lever that matters is keeping the stale count honest. You do that with a tight filter in the find step and accurate per-stage thresholds. Check your plan's monthly allowance on [Credits & pricing explained](/reference/credits-and-pricing). Never assume the number from another page.

## Test plan

1. Set the **Find records** limit to 5 so a bad run cannot fan out.
2. Pick or create one `Qualified` deal with a last interaction 10 days ago, owned by a real user such as Priya Sharma. This is your expected pass.
3. Add one deliberately failing input: a `Proposal` deal touched 8 days ago. With the 30-day threshold, it must *not* be flagged. If it gets a task, your stage thresholds are wired backward.
4. Open the workflow and click the **Run test** button to fire the trigger manually instead of waiting for Monday.
5. In run history, expect this:
   * **Find records** returns up to 5 deals.
   * **Loop** shows one iteration per deal.
   * **Adjust time** outputs a `cutoff_date` that differs by stage.
   * **If** branches true only for the 10-day `Qualified` deal.
6. Confirm exactly one **Update record** and one **Create task** ran, both for the `Qualified` deal, and that the `Proposal` deal produced neither.
7. Open the flagged deal and confirm **Needs review** is set and the owner has a linked task. Then raise the limit deliberately toward its maximum of 100 for the live schedule.

## Failure modes

| Symptom | Cause | Fix |
| - | - | - |
| Hundreds of tasks in one run | The find filter is too broad, so the loop iterates the whole pipeline | Narrow the find filter to open stages with an owner, and retest with the limit at 5 |
| Fresh deals get flagged | A single global age threshold instead of per-stage cutoffs | Use the **Switch** plus **Adjust time** pattern so each stage gets its own day count |
| Nothing ever flags | **Last interaction** is not updating, so every deal looks recent | Verify the activity attribute reflects real touches before trusting it, and swap to a reliable timestamp |
| Owners ignore the tasks | An operating-agreement gap, not a workflow bug | Do not add more workflow logic. Define what stale means, who owns follow-up, and when managers review ignored tasks |
| Duplicate tasks each week | The deal stayed stale and the sweep re-ran | This is expected. Gate **Create task** behind a check on **Needs review** if you want one task per stale spell |

## Variations

### One digest instead of per-deal tasks

If per-deal tasks feel noisy, collect the stale deals into a single message instead. Drop [Create task](/reference/tasks/create-task). Keep the loop and the [If](/reference/conditions/if) condition, and append each stale deal to a variable. After the loop, send one [Broadcast message](/reference/workspace/broadcast-message) or a Slack post through a [third-party block](/reference/integrations) to the pipeline channel such as `#revenue`. This trades one credit per deal for one credit per digest. It gives managers a single Monday list instead of scattered tasks.

### Auto-snooze low-priority stages

Some early-stage deals are not worth chasing weekly. For those, add a [Switch](/reference/conditions/switch) that routes `Qualified` deals through a longer threshold. Instead of flagging, write a "Snoozed until" date with [Adjust time](/reference/calculations/adjust-time). The sweep then skips snoozed deals until their date passes. There is still no auto-close, just a quieter cadence for the top of the funnel.

## See also

* [Slack deal alerts on stage change](/guides/route-and-sequence/slack-deal-alerts)
* [Daily renewal watch at 90, 60, and 30 days](/guides/pipeline-hygiene/renewal-watch)
* [Loops & processing many records](/explanation/loops)
* [Infinite loops and safety](/explanation/infinite-loops-and-safety)
* [Credits & pricing explained](/reference/credits-and-pricing)

*Source: [Attio Workflows block library](https://attio.com/help/reference/automations/workflows/workflows-block-library) and [Attio Workflows help](https://attio.com/help/workflows). Last reviewed against the Attio Help Center, June 2026.*


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.