The Upcycle Scheduler: The 3x Cycle as a Queue Job
A flop can hit on attempt three. The scheduler's job is deciding which assets still deserve that third attempt, and when.
Repost isn't a decision -- eligibility is
The distribution-levers doctrine names upcycling as one of the highest-leverage moves available: repost every evergreen post three times -- at publish, at 90 days, at 180 days -- because a flop can hit on the third attempt in a way it never did on the first. That's a simple rule to state and a genuinely fiddly rule to run correctly at scale, because "repost every evergreen post" quietly hides a half-dozen conditions that all have to hold before a specific asset, on a specific day, actually deserves to go back out.
The upcycle scheduler is what turns that doctrine into a queue job instead of a person's weekly calendar reminder. It doesn't decide whether upcycling as a strategy is a good idea -- that's settled. It decides, every time it runs, exactly which assets in the vault are due right now, and how many of the due ones actually fit inside this week's cap.
The eligibility filter, in order
Not every evergreen post that's old enough is a candidate. An asset has to clear several conditions before it's even considered:
Evergreen. Time-bound content -- a reaction to a specific news cycle, a reference to a dated event -- doesn't qualify. Reposting a stale news-jacking post at 90 days doesn't recapture anything; it just looks dated. The evergreen flag is set at publish time and never changes.
Performance floor. An asset's original run generated real performance data -- saves, non-follower reach, the signals the reading-the-signals doctrine treats as decision-grade. An asset that flopped and stayed flopped isn't a candidate for a second look; the floor exists so the scheduler isn't burning weekly cap slots re-posting content that already proved it doesn't work.
Upcycles remaining. The 3x cycle is a hard cap: one original post plus a maximum of two upcycles, three total appearances. An asset that's already had its two upcycles is done, permanently, regardless of how well it's performing. This isn't a soft guideline that a strong performer can talk its way past -- it's a structural limit on how many times any single piece of content is allowed back in front of the audience.
Due date. Even a fully eligible asset isn't due until the clock says so. The first upcycle fires at 90 days past the original post; the second fires at 90 days past that -- 180 days from the original. An asset that cleared every other filter but is only 40 days old simply isn't there yet.
Notice the order these run in matters for cost, not just correctness. Evergreen and the performance floor are cheap boolean and numeric comparisons against fields the vault already stores -- no date math required. Checking them first means the scheduler never bothers computing an offset or comparing timestamps for an asset that was never going to qualify anyway. Only an asset that survives both cheap checks gets its due-date math evaluated at all, which matters when the job is scanning the entire vault on every run rather than a pre-filtered subset.
The offset that depends on history, not a fixed date
The detail that's easy to get wrong: the correct offset for a given asset isn't always 90 days from today, or a fixed lookup. It depends on how many upcycles that specific asset has already had. An asset with zero upcycles done is working toward the 90-day mark. An asset with one upcycle already done is working toward the 180-day mark from its ORIGINAL post date, not 90 days from its first upcycle. Getting that mapping wrong -- treating every check as "has 90 days passed since the last event" instead of "has this asset's specific next offset, computed from upcyclesDone, been reached" -- either fires upcycles too early or misses the 180-day window entirely.
Scheduling under a weekly cap
Being due isn't the same as being scheduled. On any given week, more assets might clear every eligibility and due-date check than the weekly cap allows -- posting every due asset in one day would flood the feed and undermine the whole point of spacing content across quadrants. The scheduler resolves this by ranking due assets by performance score and taking the strongest performers first, up to the cap. An asset that's due but loses out on this week's slots doesn't lose its eligibility -- it's simply still due next week, and gets re-evaluated then.
This is also where the near-duplicate guard from the content vault interacts with the scheduler: before an upcycle actually gets queued, the guard re-runs to confirm the asset hasn't already been effectively duplicated by something else that's shipped in the meantime. And because an automated re-queue job can drift from what a human would actually want posted, a weekly spot-check samples a slice of what the scheduler queued, so drift gets caught on a cadence instead of discovered after months of silent accumulation.
Build it: implement dueForUpcycle and scheduleUpcycles
You're implementing the eligibility rule and the scheduling logic as two separate functions: dueForUpcycle, which decides whether one specific asset is due right now, and scheduleUpcycles, which filters a whole vault down to the due assets, ranks them by performance, and caps the result at the weekly limit.