Carryforward watch

The running jobjob_4Tx2 · delivered · workpapers-2026.txt · return.txt · $1,800.00 charged

The steward a taxpayer keeps for the dates a return leaves on it. $9 a year, the brand answers for each carryforward's expiry, gives notice 180 days out, and sends a letter each year of what is still open.

tax's wordsreturn is a jobtaxpayer is a subjectreview is a taskpreparer is a member

A return leaves dates behind it: the day a credit carried forward expires, and the days refund claims and the IRS's time to assess close. The annual return names Carryforward Watch as the taxpayer's steward, so those dates have an owner once no plan runs. For $9 a year, the brand answers for them.

Run
cat services/carryforward-watch.ts
magic values: none
import { Service } from 'services.dev'
import { earliestExpiry } from '../lib/tax.ts'

// The steward a taxpayer keeps once no plan runs: it answers for the dates a return leaves on the subject. The brand
// owns the carryforwards' expiry, gives notice 180 days out, and sends a letter each year of what is still open.
export default Service({
  name: 'Carryforward Watch',
  description: 'Carryforwards, watched until they expire.',
  input: { owner: 'party: who hears the notice', ein: 'text: such as 12-3456789' },
  output: { letter: 'file: what is still open, and when it expires' },
  subject: { kind: 'taxpayer', key: 'ein' },
  deliver: { letter: { by: 'ai', cite: 'the carryforwards the taxpayer keeps' } },
  clocks: { expiring: { due: earliestExpiry, owner: 'brand', lives_on: 'subject', rungs: ['notice 180d'] } },
  price: { usd: 9, every: 'year' },
})
Run
npx services.dev check
magic values: none
          expiring · a date your code decides · the brand · on the taxpayer
services/carryforward-watch.ts · 8 lines · $9 per year, charged at the start of each year.
  clocks  expiring · a date your code decides · the brand · on the taxpayer
  plan    Launch (services.dev) · subject at :11 · clocks.lives_on: 'subject' at :13 · price.every at :14

earliestExpiry reads the carryforwards the taxpayer keeps and returns the first to expire. Acme's 2026 return used up the 2025 federal credit, so its earliest is now the 2026 credit, $13,800, on 31 December 2046:

Call
→ GET /taxpayers/sbj_7Hq2
magic values: owner.email = signs@example.test, metadata.sandbox.asks = {"organizer.books":"after 160d","organizer.assets":"waive","organizer #2":"after 2d","w9":"after 19d"}, metadata.sandbox.outside = {"reject":{"file.US-NY":["ein_mismatch"]}}, metadata.sandbox.events = { notice.received: { at: 2027-11-14T15:00:00Z, data: { notice, reply_by, tax_years } } }
  keeps   carryforwards 2025    US credit 2025 $8,370, US-CA credit 2025 $6,537 · job_4Tx2 at compute · 2026-02-01
  keeps   carryforwards 2026    US credit 2026 $13,800, US-CA credit 2025 $331, US-CA credit 2026 $12,000 · job_4Tx3 at compute · 2027-07-20
  clocks  assessment and refund, 2025: 2029-04-17 · 2026: 2030-07-11 · expiring 2046-12-31, US credit 2026 $13,800 · each the brand's, the refunds mbr_8Ea1's

A client who leaves gets each date back in a letter, with the watch offered to keep them. The brand answers for each date it keeps, and the signer of record stays on each return until its assessment date:

captured/tax-cases.txt · 200 · the disengagement letter · Acme Robotics · 2029-01-20 · magic values: owner.email = signs@example.test, metadata.sandbox.outside = {"reject":{"file.US-NY":["ein_mismatch"]}}
→ GET /portal/subjects/sbj_7Hq2/letter
  Your Tax Plan ended on 20 January 2029. These dates are still running, and from today they are yours to keep:
    2029-04-17  refund claims on your 2025 return close, and so does the IRS's time to assess it
    2030-07-11  the same, for your 2026 return

Subjects covers who owns a subject's clocks, and Two clients, over the years the cancel.