← Late Pile
API

Drive Late Pile from your own code

The one metered task, triage, is available over HTTP. It takes a pile of late tasks as the page reads it - what is going on, today's date, the minutes of task work each day can hold, every working day ahead as one numbered line (d1, d2 ...), every late task as one numbered line (t1, t2 ...) with its due date, how late it is, its minutes, an urgent mark and its notes, the totals, the page's plain plan and what it left out - and returns one action per task (do it on a given plan day with a first step, hand it to someone with a message, ask whether it is still needed with a message, or leave it for later), each with a reason, a verdict (clears or overloaded) and a note.

The division of labour first: the page reads the list and works out every due date, lateness and minute exactly, lays out the days ahead and builds the plain plan; the model decides what happens to each task and writes the first steps and messages, referring to tasks and days only by id so the page can check every one. The page reads up to 40 lines, plans up to 30 late tasks, counts a task with no estimate as 30 minutes, and leaves out tasks that are not due yet or have no date. After the reply it keeps one action per task it sent, refuses any day the reply would overfill, lists a task the reply left out under later, re-checks the verdict against what is left for later and every number the reply quotes, and builds the checklist and the CSV. Your code can do the same with the engine files the page loads (see "Build the input yourself").

Base URL and the envelope

Every endpoint lives under https://api.skillsafe.ai/v1/app-api, and every response uses one envelope:

{ "ok": true,  "data":  { ... } }
{ "ok": false, "error": { "code": "...", "message": "..." } }

The token is minted for this app (the guest call takes {"slug":"late-pile"}), so no slug header is needed afterwards. Send it as Authorization: Bearer …. The run body is the input object, sent bare.

StatusCodeMeaning
400validation_errorThe body is not a JSON object, or a required field (task, context, today, capacity, plan_days, tasks, totals, page_plan, not_sent) is missing.
401unauthorizedNo token, or a stale one. Mint a guest token or sign in again.
402insufficient_creditsThe balance is under min_credits. Price with /estimate first.
403forbiddenA guest token tried to run: the triage run is metered and needs a personal sign-in.
404not_foundUnknown job id.
429rate_limitedBack off and retry.
5xxserver_errorTransient. Retry with the same Idempotency-Key so a retry never double-bills.

The task field, the input and the rules

Every run sends task: triage (the one task is triage); a missing or different one is still answered as triage. All fields are strings.

FieldRequiredWhat it holds
taskyesAlways triage.
contextyesWhat is going on, in the person's words (at most 160 characters): Back from a week of leave, freelance design work, or none.
todayyesToday's date as the page counts lateness from it: Wed 28 Oct 2026.
capacityyesHow many minutes of task work each plan day can hold, on the quarter hour: 180 minutes a day.
plan_daysyesOne line per working day ahead (3, 5, 7 or 10 of them), (today) marking today: d1. Wed 28 Oct (today) - 180 min.
tasksyesOne line per late task, at most 30: t2. Export final logo files for Fennel Bakery | due Thu 22 Oct, 6 days late | 60 min | notes: client asked twice - id, title, due date and how late, minutes ((assumed) when no estimate was given), urgent when marked, and notes when there are any.
totalsyesThe number of tasks, the minutes of work and the minutes of room: 9 tasks, 510 minutes of work; 900 minutes of room over 5 days.
page_planyesThe page's plain plan, one line per day and then what it left for later: d1: t1, t6, t5 (180 of 180 min) ... later: none.
not_sentyesThe tasks the page left out because they are not due yet or have no date: 1 not due yet, or none.
retry_notenoOnly on a reformat retry: why the previous reply was unusable.

How the page reads the list (PileKit): one task per line, up to 40 lines. In the pipe form (title | due | time | ! | notes) the first part is the title and each other part is read as a due date, a time estimate, an urgent mark or notes; in free text the date and the estimate are found anywhere and the rest is the title. Due dates: 2026-10-20, 10/20 (or 20/10 when the first number is over 12), Oct 20, 20 Oct, today, yesterday, 3 days ago, 2 weeks ago, last week, Fri or last Fri; a date with no year is this year unless that lands more than 60 days after today, then last year. Estimates: 20m, 45 min, 1h, 1.5 hours, 1h30, from 5 minutes to 8 hours; a task with none counts as 30 minutes, marked (assumed). Urgent marks: !, urgent, asap, high, p1 or important. Lateness is counted in whole days against today (any date you set); a task due today is sent as due today, and tasks not due yet or with no date are counted in not_sent and left out. The plan days are the next working days from today in the chosen week (Mon-Fri, Sun-Thu, Mon-Sat or every day), each holding the same capacity. The page needs at least one task that is late or due today before it will send.

The plain plan (PileKit.freePlan) needs no run: it takes the urgent tasks first, then the latest, then the list order, and puts each on the first day with room; a task longer than a whole day gets an empty day to itself, and whatever finds no day is left for later. It is sent as page_plan and shown, as a checklist, before any run.

The model's rules: exactly one action for every task id, in id order. hand_off only when the task's own line names another person or team who could do it instead, with a message of at most 35 words to them naming the task and its due date; ask only when the task's notes say it may no longer be needed - being late is never a reason - with a message of at most 30 words asking whether it is still needed; do for every other task, on a plan day id, urgent tasks on the earliest days, then the latest, with a first step of at most 12 words taken from the task's own words; later for a task that cannot fit. The minutes on any one day must not exceed the capacity, except that a single task longer than the capacity may sit alone on a day of its own. Every action has a reason of at most 18 words using the task's due date, lateness, minutes, urgency or notes. The verdict is clears when no task is left for later, otherwise overloaded; the note says how many tasks land on which days and how many minutes today, and for an overloaded pile how many wait. Sentences name tasks by title and days by day and date, never by id; they quote only numbers from the input or sums of planned minutes, and never tell the person to drop, abandon, ignore or delete a task.

The reply

One JSON object:

{"task": "triage", "verdict": "overloaded", "actions": [{"id": "t1", "action": "do", "day": "d1", "first_step": "...", "message": "", "why": "..."}], "note": "..."}
FieldWhat it holds
verdictclears (no task is left for later) or overloaded (the plan days cannot hold them all).
actions[].idThe id of a task the page sent; one action per task, in id order.
actions[].actiondo, hand_off, ask or later.
actions[].dayFor do: the plan day id (d1 ...). Otherwise "".
actions[].first_stepFor do: the first physical action, at most 12 words, from the task's own words. Otherwise "".
actions[].messageFor hand_off and ask: one sentence to send - asking the named person to take the task on, or asking whether it is still needed. Otherwise "".
actions[].whyOne sentence: why this action, using the task's due date, lateness, minutes, urgency or notes.
noteOne sentence: how many tasks land on which days and the minutes today, and for overloaded, how many wait.

The page reads the reply with ReplyKit.normalize (ids and actions matched in any case) and keeps what it trusts with ReplyKit.trusted(reply, built): an action is set aside, with the reason, when it names a task the page did not send, when its task already has an action, when its action is not one of the four, when a do has no plan day, or when a do would overfill its day (more minutes than the capacity, unless the task is longer than a whole day and sits alone). A task with no kept action is listed under later. A first step is kept only on do and a message only on hand_off and ask; titles, dates and minutes always come from the page's own reading of the list, never from the reply. The number of tasks left for later is what the page checks the verdict against.

ReplyKit.check(reply, built) lists, under "The page's check": a missing verdict, or one that what is left for later contradicts; every action set aside; every task the reply left out; a task left for later while a plan day still has room for it; a hand-off or a question with no message; a task to do with no first step; a hand-off whose line names no one; an urgent task placed after one that is not; any number in the note, a first step, a reason or a message that is not in the input or the plan's own sums; a sentence that names an id, an internal field or uses backticks; any word telling you to drop, abandon, ignore, delete or skip a task when your list never does; and words such as "again", "already", "always", "never" or "every" that your list and context never used. Any warning makes the status flagged; otherwise it is kept. The verdict on the page reads "The pile clears" or "More than the days can hold".

The downloads: ReplyKit.plain(form, t) copies the plan as a checklist, day by day, then the hand-offs, the questions and what waits; ReplyKit.csv(built, t) writes one row per task (id, task, due, days late, minutes, urgent, action, day, first step, message) with CRLF line ends; ReplyKit.md and ReplyKit.json write the plan with the messages, and the slip line picture saves as PNG or SVG.

Worked examples - every sample

These are the free samples on the page: the list as typed, the tasks and plan days the page reads, its plain plan, the exact input it sends, the reply that replays for free, and what the page does with it - the plan it trusts, the verdict against what is left for later and its own check. Together they cover the verdicts clears and overloaded and the actions do, hand_off, ask, later.

Back from a week off

The form: Back from a week of leave, freelance design work; today Wed 28 Oct 2026, 3 h of task work a day over the next 5 working days (Mon-Fri). The list as typed:

Send October invoice to Harbor Studio | Oct 23 | 20 min | !
Export final logo files for Fennel Bakery | Oct 22 | 1h | client asked twice
Reply to Ola about the brochure proofs | Oct 24 | 15 min
Update portfolio with the museum project | Oct 9 | 2h
Book accountant call | Oct 21 | 10 min
Revise homepage mockups for Kite Labs | Oct 27 | 2h 30m | !
Back up the work drive | Oct 20 | 30 min
Renew font licence | Oct 26 | 15 min | Sam at Kite Labs said they might buy it for the project
Write case study draft | Oct 16 | 1h30
Prepare workshop slides | Nov 5 | 2h

The late tasks as the page reads them, with how late each is (9 tasks, 510 minutes of work; 900 minutes of room over 5 days; left out: 1 not due yet), and the plan days (PileKit.buildInput):

t1. Send October invoice to Harbor Studio | due Fri 23 Oct, 5 days late | 20 min | urgent
t2. Export final logo files for Fennel Bakery | due Thu 22 Oct, 6 days late | 60 min | notes: client asked twice
t3. Reply to Ola about the brochure proofs | due Sat 24 Oct, 4 days late | 15 min
t4. Update portfolio with the museum project | due Fri 9 Oct, 19 days late | 120 min
t5. Book accountant call | due Wed 21 Oct, 7 days late | 10 min
t6. Revise homepage mockups for Kite Labs | due Tue 27 Oct, 1 day late | 150 min | urgent
t7. Back up the work drive | due Tue 20 Oct, 8 days late | 30 min
t8. Renew font licence | due Mon 26 Oct, 2 days late | 15 min | notes: Sam at Kite Labs said they might buy it for the project
t9. Write case study draft | due Fri 16 Oct, 12 days late | 90 min
d1. Wed 28 Oct (today) - 180 min
d2. Thu 29 Oct - 180 min
d3. Fri 30 Oct - 180 min
d4. Mon 2 Nov - 180 min
d5. Tue 3 Nov - 180 min

The page warns: "1 not due yet - left out of the plan."

The page's plain plan, made with no run (ReplyKit.plain(form, ReplyKit.fromPlan(built))):

Wed 28 Oct (today)
  [ ] Send October invoice to Harbor Studio (20 min)
  [ ] Book accountant call (10 min)
  [ ] Revise homepage mockups for Kite Labs (150 min)
Thu 29 Oct
  [ ] Reply to Ola about the brochure proofs (15 min)
  [ ] Update portfolio with the museum project (120 min)
  [ ] Back up the work drive (30 min)
  [ ] Renew font licence (15 min)
Fri 30 Oct
  [ ] Export final logo files for Fennel Bakery (60 min)
  [ ] Write case study draft (90 min)

Input:

{
 "task": "triage",
 "context": "Back from a week of leave, freelance design work",
 "today": "Wed 28 Oct 2026",
 "capacity": "180 minutes a day",
 "plan_days": "d1. Wed 28 Oct (today) - 180 min\nd2. Thu 29 Oct - 180 min\nd3. Fri 30 Oct - 180 min\nd4. Mon 2 Nov - 180 min\nd5. Tue 3 Nov - 180 min",
 "tasks": "t1. Send October invoice to Harbor Studio | due Fri 23 Oct, 5 days late | 20 min | urgent\nt2. Export final logo files for Fennel Bakery | due Thu 22 Oct, 6 days late | 60 min | notes: client asked twice\nt3. Reply to Ola about the brochure proofs | due Sat 24 Oct, 4 days late | 15 min\nt4. Update portfolio with the museum project | due Fri 9 Oct, 19 days late | 120 min\nt5. Book accountant call | due Wed 21 Oct, 7 days late | 10 min\nt6. Revise homepage mockups for Kite Labs | due Tue 27 Oct, 1 day late | 150 min | urgent\nt7. Back up the work drive | due Tue 20 Oct, 8 days late | 30 min\nt8. Renew font licence | due Mon 26 Oct, 2 days late | 15 min | notes: Sam at Kite Labs said they might buy it for the project\nt9. Write case study draft | due Fri 16 Oct, 12 days late | 90 min",
 "totals": "9 tasks, 510 minutes of work; 900 minutes of room over 5 days",
 "page_plan": "d1: t1, t6, t5 (180 of 180 min)\nd2: t4, t7, t3, t8 (180 of 180 min)\nd3: t9, t2 (150 of 180 min)\nd4: nothing (0 of 180 min)\nd5: nothing (0 of 180 min)\nlater: none",
 "not_sent": "1 not due yet"
}

Reply:

{
 "task": "triage",
 "verdict": "clears",
 "actions": [
  {
   "id": "t1",
   "action": "do",
   "day": "d1",
   "first_step": "Send the October invoice to Harbor Studio.",
   "message": "",
   "why": "Sending the Harbor Studio invoice is urgent and 5 days late; it takes 20 minutes."
  },
  {
   "id": "t2",
   "action": "do",
   "day": "d3",
   "first_step": "Export the final logo files for Fennel Bakery.",
   "message": "",
   "why": "The Fennel Bakery logo files are 6 days late at 60 minutes, and the client has asked twice."
  },
  {
   "id": "t3",
   "action": "do",
   "day": "d2",
   "first_step": "Reply to Ola about the brochure proofs.",
   "message": "",
   "why": "The reply to Ola about the brochure proofs is 4 days late and takes 15 minutes."
  },
  {
   "id": "t4",
   "action": "do",
   "day": "d2",
   "first_step": "Update the portfolio with the museum project.",
   "message": "",
   "why": "Updating the portfolio with the museum project is 19 days late and takes 120 minutes."
  },
  {
   "id": "t5",
   "action": "do",
   "day": "d1",
   "first_step": "Book the accountant call.",
   "message": "",
   "why": "Booking the accountant call is 7 days late and takes 10 minutes."
  },
  {
   "id": "t6",
   "action": "do",
   "day": "d1",
   "first_step": "Revise the homepage mockups for Kite Labs.",
   "message": "",
   "why": "The Kite Labs homepage mockups are urgent, 1 day late, and take 150 minutes."
  },
  {
   "id": "t7",
   "action": "do",
   "day": "d2",
   "first_step": "Back up the work drive.",
   "message": "",
   "why": "Backing up the work drive is 8 days late and takes 30 minutes."
  },
  {
   "id": "t8",
   "action": "ask",
   "day": "",
   "first_step": "",
   "message": "Sam, is the font licence renewal still needed now that Kite Labs might buy it for the project?",
   "why": "Renewing the font licence is 2 days late at 15 minutes, but Kite Labs might buy it."
  },
  {
   "id": "t9",
   "action": "do",
   "day": "d3",
   "first_step": "Write the case study draft.",
   "message": "",
   "why": "Writing the case study draft is 12 days late and takes 90 minutes."
  }
 ],
 "note": "Three tasks land today, Wed 28 Oct, filling all 180 minutes; three sit on Thu 29 Oct and two on Fri 30 Oct, with one question for Sam."
}

The plan the page trusts (ReplyKit.trusted), each task to its day or its tray:

Minutes per day: Wed 28 Oct (today) 180 of 180 min; Thu 29 Oct 165 of 180 min; Fri 30 Oct 150 of 180 min.

The verdict: clears - "The pile clears"; it agrees with the trusted plan, where no task is left for later.

The note: "Three tasks land today, Wed 28 Oct, filling all 180 minutes; three sit on Thu 29 Oct and two on Fri 30 Oct, with one question for Sam."

The page's check (ReplyKit.check): kept.

No notes.

A team lead's backlog

The form: Team lead, two hours a day left between meetings until Friday; today Wed 28 Oct 2026, 2 h of task work a day over the next 3 working days (Mon-Fri). The list as typed:

Approve Q4 hiring plan | Oct 24 | 45 min | urgent
Write performance reviews for Dana and Luis | Oct 17 | 4h | HR wants them this week
Review Marco's architecture doc | Oct 22 | 1h
Update onboarding wiki | Oct 3 | 2h | Priya offered to take this
Prepare budget variance summary | Oct 23 | 1h30 | !
Reply to vendor about licence renewal | Oct 27 | 20 min
Plan team offsite agenda | Oct 10 | 2h | offsite may move to January
Finish security training | Oct 20 | 40 min
Draft Q1 roadmap one-pager | Oct 21 | 3h
Clean up the Jira board | Oct 14 | 1h | Sam usually does this
Send interview feedback for the backend candidate | Oct 26 | 15 min | !

The late tasks as the page reads them, with how late each is (11 tasks, 990 minutes of work; 360 minutes of room over 3 days; left out: none), and the plan days (PileKit.buildInput):

t1. Approve Q4 hiring plan | due Sat 24 Oct, 4 days late | 45 min | urgent
t2. Write performance reviews for Dana and Luis | due Sat 17 Oct, 11 days late | 240 min | notes: HR wants them this week
t3. Review Marco's architecture doc | due Thu 22 Oct, 6 days late | 60 min
t4. Update onboarding wiki | due Sat 3 Oct, 25 days late | 120 min | notes: Priya offered to take this
t5. Prepare budget variance summary | due Fri 23 Oct, 5 days late | 90 min | urgent
t6. Reply to vendor about licence renewal | due Tue 27 Oct, 1 day late | 20 min
t7. Plan team offsite agenda | due Sat 10 Oct, 18 days late | 120 min | notes: offsite may move to January
t8. Finish security training | due Tue 20 Oct, 8 days late | 40 min
t9. Draft Q1 roadmap one-pager | due Wed 21 Oct, 7 days late | 180 min
t10. Clean up the Jira board | due Wed 14 Oct, 14 days late | 60 min | notes: Sam usually does this
t11. Send interview feedback for the backend candidate | due Mon 26 Oct, 2 days late | 15 min | urgent
d1. Wed 28 Oct (today) - 120 min
d2. Thu 29 Oct - 120 min
d3. Fri 30 Oct - 120 min

The page's plain plan, made with no run (ReplyKit.plain(form, ReplyKit.fromPlan(built))):

Wed 28 Oct (today)
  [ ] Prepare budget variance summary (90 min)
  [ ] Send interview feedback for the backend candidate (15 min)
Thu 29 Oct
  [ ] Approve Q4 hiring plan (45 min)
  [ ] Clean up the Jira board (60 min)
Fri 30 Oct
  [ ] Update onboarding wiki (120 min)
Later
  - Write performance reviews for Dana and Luis
  - Review Marco's architecture doc
  - Reply to vendor about licence renewal
  - Plan team offsite agenda
  - Finish security training
  - Draft Q1 roadmap one-pager

Input:

{
 "task": "triage",
 "context": "Team lead, two hours a day left between meetings until Friday",
 "today": "Wed 28 Oct 2026",
 "capacity": "120 minutes a day",
 "plan_days": "d1. Wed 28 Oct (today) - 120 min\nd2. Thu 29 Oct - 120 min\nd3. Fri 30 Oct - 120 min",
 "tasks": "t1. Approve Q4 hiring plan | due Sat 24 Oct, 4 days late | 45 min | urgent\nt2. Write performance reviews for Dana and Luis | due Sat 17 Oct, 11 days late | 240 min | notes: HR wants them this week\nt3. Review Marco's architecture doc | due Thu 22 Oct, 6 days late | 60 min\nt4. Update onboarding wiki | due Sat 3 Oct, 25 days late | 120 min | notes: Priya offered to take this\nt5. Prepare budget variance summary | due Fri 23 Oct, 5 days late | 90 min | urgent\nt6. Reply to vendor about licence renewal | due Tue 27 Oct, 1 day late | 20 min\nt7. Plan team offsite agenda | due Sat 10 Oct, 18 days late | 120 min | notes: offsite may move to January\nt8. Finish security training | due Tue 20 Oct, 8 days late | 40 min\nt9. Draft Q1 roadmap one-pager | due Wed 21 Oct, 7 days late | 180 min\nt10. Clean up the Jira board | due Wed 14 Oct, 14 days late | 60 min | notes: Sam usually does this\nt11. Send interview feedback for the backend candidate | due Mon 26 Oct, 2 days late | 15 min | urgent",
 "totals": "11 tasks, 990 minutes of work; 360 minutes of room over 3 days",
 "page_plan": "d1: t5, t11 (105 of 120 min)\nd2: t1, t10 (105 of 120 min)\nd3: t4 (120 of 120 min)\nlater: t7, t2, t8, t9, t3, t6",
 "not_sent": "none"
}

Reply:

{
 "task": "triage",
 "verdict": "overloaded",
 "actions": [
  {
   "id": "t1",
   "action": "do",
   "day": "d2",
   "first_step": "Approve Q4 hiring plan",
   "message": "",
   "why": "It is urgent and 4 days late, needing 45 minutes."
  },
  {
   "id": "t2",
   "action": "do",
   "day": "d3",
   "first_step": "Write performance reviews for Dana and Luis",
   "message": "",
   "why": "HR wants these done this week, and it is 11 days late."
  },
  {
   "id": "t3",
   "action": "later",
   "day": "",
   "first_step": "",
   "message": "",
   "why": "It is 6 days late, needs 60 minutes, and no day has enough room left."
  },
  {
   "id": "t4",
   "action": "hand_off",
   "day": "",
   "first_step": "",
   "message": "Priya, could you take on updating the onboarding wiki, due Sat 3 Oct?",
   "why": "Priya offered to take this on, and it is 25 days late."
  },
  {
   "id": "t5",
   "action": "do",
   "day": "d1",
   "first_step": "Prepare budget variance summary",
   "message": "",
   "why": "It is urgent and 5 days late, needing 90 minutes."
  },
  {
   "id": "t6",
   "action": "do",
   "day": "d2",
   "first_step": "Reply to vendor about licence renewal",
   "message": "",
   "why": "It is 1 day late and takes 20 minutes."
  },
  {
   "id": "t7",
   "action": "ask",
   "day": "",
   "first_step": "",
   "message": "Is the team offsite agenda still needed, since the offsite may move to January?",
   "why": "Its notes say the offsite may move to January, so it may no longer be needed."
  },
  {
   "id": "t8",
   "action": "do",
   "day": "d2",
   "first_step": "Finish security training",
   "message": "",
   "why": "It is 8 days late and needs 40 minutes."
  },
  {
   "id": "t9",
   "action": "later",
   "day": "",
   "first_step": "",
   "message": "",
   "why": "It is 7 days late and needs 180 minutes, more than the remaining room."
  },
  {
   "id": "t10",
   "action": "hand_off",
   "day": "",
   "first_step": "",
   "message": "Sam, could you take on cleaning up the Jira board, due Wed 14 Oct?",
   "why": "Sam usually does this, and it is 14 days late."
  },
  {
   "id": "t11",
   "action": "do",
   "day": "d1",
   "first_step": "Send interview feedback for the backend candidate",
   "message": "",
   "why": "It is urgent and 2 days late, taking 15 minutes."
  }
 ],
 "note": "Two tasks land today for 105 minutes, three more on Thursday 29 Oct, one long task alone on Friday 30 Oct, and two tasks wait because the three days don't have enough room."
}

The plan the page trusts (ReplyKit.trusted), each task to its day or its tray:

Minutes per day: Wed 28 Oct (today) 105 of 120 min; Thu 29 Oct 105 of 120 min; Fri 30 Oct 240 of 120 min (one task longer than a day, alone).

The verdict: overloaded - "More than the days can hold"; it agrees with the trusted plan, where 2 tasks are left for later.

The note: "Two tasks land today for 105 minutes, three more on Thursday 29 Oct, one long task alone on Friday 30 Oct, and two tasks wait because the three days don't have enough room."

The page's check (ReplyKit.check): kept.

No notes.

Household admin

The form: Household admin that piled up; today Wed 28 Oct 2026, 1 h of task work a day over the next 7 working days (every day). The list as typed:

Pay council tax | Oct 25 | 10 min | urgent
Return the jacket parcel | Oct 18 | 20 min
Book dentist check-up | Sep 30 | 10 min
Reply to landlord about the boiler service | Oct 26 | 15 min
Renew car insurance | Oct 27 | 45 min | !
Sort the receipts drawer | Oct 5 | 1h | Alex offered to do it this weekend
Fix the bike puncture | Oct 22 | 40 min
Order birthday present for Mum | Oct 24 | 20 min | party is Nov 2
Cancel the old gym membership | Oct 12 | 15 min | it may have lapsed already
Water the balcony plants

The late tasks as the page reads them, with how late each is (9 tasks, 235 minutes of work; 420 minutes of room over 7 days; left out: 1 with no due date), and the plan days (PileKit.buildInput):

t1. Pay council tax | due Sun 25 Oct, 3 days late | 10 min | urgent
t2. Return the jacket parcel | due Sun 18 Oct, 10 days late | 20 min
t3. Book dentist check-up | due Wed 30 Sep, 28 days late | 10 min
t4. Reply to landlord about the boiler service | due Mon 26 Oct, 2 days late | 15 min
t5. Renew car insurance | due Tue 27 Oct, 1 day late | 45 min | urgent
t6. Sort the receipts drawer | due Mon 5 Oct, 23 days late | 60 min | notes: Alex offered to do it this weekend
t7. Fix the bike puncture | due Thu 22 Oct, 6 days late | 40 min
t8. Order birthday present for Mum | due Sat 24 Oct, 4 days late | 20 min | notes: party is Nov 2
t9. Cancel the old gym membership | due Mon 12 Oct, 16 days late | 15 min | notes: it may have lapsed already
d1. Wed 28 Oct (today) - 60 min
d2. Thu 29 Oct - 60 min
d3. Fri 30 Oct - 60 min
d4. Sat 31 Oct - 60 min
d5. Sun 1 Nov - 60 min
d6. Mon 2 Nov - 60 min
d7. Tue 3 Nov - 60 min

The page warns: "1 with no date - left out of the plan."

The page's plain plan, made with no run (ReplyKit.plain(form, ReplyKit.fromPlan(built))):

Wed 28 Oct (today)
  [ ] Pay council tax (10 min)
  [ ] Renew car insurance (45 min)
Thu 29 Oct
  [ ] Return the jacket parcel (20 min)
  [ ] Book dentist check-up (10 min)
  [ ] Reply to landlord about the boiler service (15 min)
  [ ] Cancel the old gym membership (15 min)
Fri 30 Oct
  [ ] Sort the receipts drawer (60 min)
Sat 31 Oct
  [ ] Fix the bike puncture (40 min)
  [ ] Order birthday present for Mum (20 min)

Input:

{
 "task": "triage",
 "context": "Household admin that piled up",
 "today": "Wed 28 Oct 2026",
 "capacity": "60 minutes a day",
 "plan_days": "d1. Wed 28 Oct (today) - 60 min\nd2. Thu 29 Oct - 60 min\nd3. Fri 30 Oct - 60 min\nd4. Sat 31 Oct - 60 min\nd5. Sun 1 Nov - 60 min\nd6. Mon 2 Nov - 60 min\nd7. Tue 3 Nov - 60 min",
 "tasks": "t1. Pay council tax | due Sun 25 Oct, 3 days late | 10 min | urgent\nt2. Return the jacket parcel | due Sun 18 Oct, 10 days late | 20 min\nt3. Book dentist check-up | due Wed 30 Sep, 28 days late | 10 min\nt4. Reply to landlord about the boiler service | due Mon 26 Oct, 2 days late | 15 min\nt5. Renew car insurance | due Tue 27 Oct, 1 day late | 45 min | urgent\nt6. Sort the receipts drawer | due Mon 5 Oct, 23 days late | 60 min | notes: Alex offered to do it this weekend\nt7. Fix the bike puncture | due Thu 22 Oct, 6 days late | 40 min\nt8. Order birthday present for Mum | due Sat 24 Oct, 4 days late | 20 min | notes: party is Nov 2\nt9. Cancel the old gym membership | due Mon 12 Oct, 16 days late | 15 min | notes: it may have lapsed already",
 "totals": "9 tasks, 235 minutes of work; 420 minutes of room over 7 days",
 "page_plan": "d1: t1, t5 (55 of 60 min)\nd2: t3, t9, t2, t4 (60 of 60 min)\nd3: t6 (60 of 60 min)\nd4: t7, t8 (60 of 60 min)\nd5: nothing (0 of 60 min)\nd6: nothing (0 of 60 min)\nd7: nothing (0 of 60 min)\nlater: none",
 "not_sent": "1 with no due date"
}

Reply:

{
 "task": "triage",
 "verdict": "clears",
 "actions": [
  {
   "id": "t1",
   "action": "do",
   "day": "d1",
   "first_step": "Pay the council tax.",
   "message": "",
   "why": "Paying the council tax is urgent and 3 days late, taking 10 minutes."
  },
  {
   "id": "t2",
   "action": "do",
   "day": "d2",
   "first_step": "Return the jacket parcel.",
   "message": "",
   "why": "Returning the jacket parcel is 10 days late and takes 20 minutes."
  },
  {
   "id": "t3",
   "action": "do",
   "day": "d2",
   "first_step": "Book the dentist check-up.",
   "message": "",
   "why": "Booking the dentist check-up is 28 days late and only takes 10 minutes."
  },
  {
   "id": "t4",
   "action": "do",
   "day": "d3",
   "first_step": "Reply to the landlord about the boiler service.",
   "message": "",
   "why": "Replying to the landlord about the boiler service is 2 days late and takes 15 minutes."
  },
  {
   "id": "t5",
   "action": "do",
   "day": "d1",
   "first_step": "Renew the car insurance.",
   "message": "",
   "why": "Renewing the car insurance is urgent and 1 day late, needing 45 minutes."
  },
  {
   "id": "t6",
   "action": "hand_off",
   "day": "",
   "first_step": "",
   "message": "Alex, could you take on sorting the receipts drawer, due Mon 5 Oct, since you offered to help this weekend?",
   "why": "Alex offered to sort the receipts drawer, which is 23 days late."
  },
  {
   "id": "t7",
   "action": "do",
   "day": "d3",
   "first_step": "Fix the bike puncture.",
   "message": "",
   "why": "Fixing the bike puncture is 6 days late and takes 40 minutes."
  },
  {
   "id": "t8",
   "action": "do",
   "day": "d2",
   "first_step": "Order the birthday present for Mum.",
   "message": "",
   "why": "Ordering Mum's birthday present is 4 days late and the party is Nov 2."
  },
  {
   "id": "t9",
   "action": "ask",
   "day": "",
   "first_step": "",
   "message": "Is cancelling the old gym membership still needed, given it may have lapsed already?",
   "why": "The old gym membership is 16 days late and may have lapsed already."
  }
 ],
 "note": "Tasks are done on Wed 28 Oct, Thu 29 Oct and Fri 30 Oct, with 55 of today's minutes used and 160 minutes planned in total."
}

The plan the page trusts (ReplyKit.trusted), each task to its day or its tray:

Minutes per day: Wed 28 Oct (today) 55 of 60 min; Thu 29 Oct 50 of 60 min; Fri 30 Oct 55 of 60 min.

The verdict: clears - "The pile clears"; it agrees with the trusted plan, where no task is left for later.

The note: "Tasks are done on Wed 28 Oct, Thu 29 Oct and Fri 30 Oct, with 55 of today's minutes used and 160 minutes planned in total."

The page's check (ReplyKit.check): kept.

No notes.

The CSV for the first sample

What ReplyKit.csv(built, ReplyKit.trusted(ReplyKit.normalize(reply), built)) writes for "Back from a week off" above: a header and one row per task in id order, 9 rows (lines end in CRLF; a cell with a comma or a quote is quoted).

id,task,due,days_late,minutes,urgent,action,day,first_step,message
t1,Send October invoice to Harbor Studio,2026-10-23,5,20,yes,do,2026-10-28,Send the October invoice to Harbor Studio.,
t2,Export final logo files for Fennel Bakery,2026-10-22,6,60,,do,2026-10-30,Export the final logo files for Fennel Bakery.,
t3,Reply to Ola about the brochure proofs,2026-10-24,4,15,,do,2026-10-29,Reply to Ola about the brochure proofs.,
t4,Update portfolio with the museum project,2026-10-09,19,120,,do,2026-10-29,Update the portfolio with the museum project.,
t5,Book accountant call,2026-10-21,7,10,,do,2026-10-28,Book the accountant call.,
t6,Revise homepage mockups for Kite Labs,2026-10-27,1,150,yes,do,2026-10-28,Revise the homepage mockups for Kite Labs.,
t7,Back up the work drive,2026-10-20,8,30,,do,2026-10-29,Back up the work drive.,
t8,Renew font licence,2026-10-26,2,15,,ask first,,,"Sam, is the font licence renewal still needed now that Kite Labs might buy it for the project?"
t9,Write case study draft,2026-10-16,12,90,,do,2026-10-30,Write the case study draft.,

Build the input yourself

Load the engine in node (global.window = global; require("./taskkit.js"); require("./replykit.js")), then:

const K = PileKit, R = ReplyKit;
const form = K.form({ tasks: "Send invoice to Harbor Studio | Oct 23 | 20 min | !\nBook accountant call | Oct 21 | 10 min",
  today: "2026-10-28", capacity: "1", week: "mon-fri", horizon: "3", context: "" });
// form.tasks is the late list as read: [{id, title, due, late, mins, assumed, high, notes}], dates as whole-day numbers
const built = K.buildInput(form);     // built.ok is false, with built.note, until a task is late or due today
// built.plan: the plain plan {place, used, later}; built.warn: what the page warns about
R.plain(form, R.fromPlan(built))      // the plain plan as a checklist, no run needed
const wire = K.toWire(built.input);   // POST this to /estimate and /run
// then: const reply = R.normalize(R.parseReply(job.output.output));
// const t = R.trusted(reply, built)      // {acts, dropped, missing, load, later}: one action per task the page sent
// R.plain(form, t)                        // "Wed 28 Oct (today)" then "  [ ] Send invoice to Harbor Studio (20 min)" ...
// R.check(reply, built)                   // {status: kept or flagged, notes}
// R.csv(built, t)                         // the CSV, one row per task

Form keys: tasks (one task per line), today (YYYY-MM-DD, or empty for the device's own date), capacity (hours of task work a day, 0.5 to 10, rounded to the quarter hour), week (mon-fri, sun-thu, mon-sat or every), horizon (3, 5, 7 or 10 working days) and context (what is going on, or empty).

Step 1 — a tiny client helper

One function that sends JSON and unwraps the envelope. Paste a personal token from /tokens.html, or leave it empty and mint a guest token in step 2.

BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="paste-your-token-here"   # from https://late-pile.skillsafe.ai/tokens.html
api() { # api METHOD PATH [JSON-BODY]
  curl -s -X "$1" "$BASE$2" -H "Content-Type: application/json" \
    ${TOKEN:+-H "Authorization: Bearer $TOKEN"} ${3:+--data "$3"}
}

Step 2 — a token

A guest token is enough for /me and /estimate. A run is metered, so it needs a personal token: sign in on /tokens.html and copy it from there.

# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN=$(curl -s -X POST "$BASE/guest" -H "Content-Type: application/json" -d '{"slug":"late-pile"}' | jq -r '.data.token')

Step 3 — GET /me

Who the token is (subject_type: user or guest) and the credit balance.

api GET /me | jq '.data | {subject_type, credits}' 

Step 4 — POST /estimate

Free. The body is the run input itself, the bare object (never wrapped in {"input": ...}). hold_credits is a reservation, not the price; min_credits is what the balance must cover; model_alias reads gpt-terra.

# input.json: the run input object - {"task": "triage", "context": "none", "today": "Wed 28 Oct 2026", "capacity": "60 minutes a day", "plan_days": "d1. Wed 28 Oct (today) - 60 min\nd2. Thu 29 Oct - 60 min\nd3. Fri 30 Oct - 60 min", "tasks": "t1. Send invoice to Harbor Studio | due Fri 23 Oct, 5 days late | 20 min | urgent\nt2. Book accountant call | due Wed 21 Oct, 7 days late | 10 min", "totals": "2 tasks, 30 minutes of work; 180 minutes of room over 3 days", "page_plan": "d1: t1, t2 (30 of 60 min)\nd2: nothing (0 of 60 min)\nd3: nothing (0 of 60 min)\nlater: none", "not_sent": "none"}
# - see the field table
api POST /estimate "$(cat input.json)" | jq '.data | {hold_credits, min_credits, model, model_alias, markup_bps, warnings}' 

Step 5 — POST /run, then poll the job

Send an Idempotency-Key built from the lane and the input: a retry with the same key never bills twice. The page uses late-pile:<task>:<inputHash>:a<attempt> (the one task is triage), and its one reformat retry (with retry_note added) is attempt 2, a new key and a second run. The job's output.output is the reply text; truncated: true means the balance cut it short.

KEY="late-pile:triage:$(shasum -a 256 input.json | cut -c1-16):a1"
JOB=$(curl -s -X POST "$BASE/run" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $KEY" --data @input.json | jq -r '.data.job_id')
while :; do
  J=$(api GET "/jobs/$JOB"); S=$(echo "$J" | jq -r '.data.status')
  case "$S" in succeeded|failed|cancelled) break;; esac; sleep 2
done
echo "$J" | jq -r '.data.output.output' > reply.json   # one JSON object, the reply contract
echo "$J" | jq '.data | {charged_credits, truncated}'
jq -r '.verdict, (.actions[] | "\(.id) \(.action) \(.day)")' reply.json

Step 6 — POST /run-stream

The same run as Server-Sent Events, with the same input.json. Browsers receive progress ticks rather than text deltas; the final done event carries the reply.

# Server-Sent Events: tick/delta events while the job runs, then a final event with the job
curl -N -s -X POST "$BASE/run-stream" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: late-pile:triage:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json