- A US TikTok Shop livestream agency schedules about 30 part-time hosts and operators across 8 brand rooms, roughly 10am–9pm, seven days a week.
- The first-draft scheduling work used to take about six hours each week. That first draft is now generated automatically; a person still reviews, edits and publishes the schedule.
- The scheduling engine is deterministic code, not a language model. The rules live in the client’s own spreadsheet.
- Changing the unit of work from hours to shows took mid-show operator changes from 41% to 0%. The last measured iteration staffed 456 role-hours and left 10 required shows unfilled, because of evening and weekend availability.
- Net hours saved are not claimed. Review time has not been measured yet.
Key facts
- Business
- TikTok Shop livestream agency, United States (name withheld)
- Business type
- Runs brand livestream rooms with part-time hosts and operators
- Team being scheduled
- About 30 part-time hosts and operators across 8 brand rooms, roughly 10am–9pm, seven days a week
- What the client asked for
- Only the zero-to-one step: a usable first draft of next week’s schedule
- Outside the system
- Attendance, payroll, daily briefings and final publishing stayed as they were
- Status
- Live. A draft builds weekly; the operations lead reviews, edits and publishes
Before and now
One operations lead collected next week’s availability by messaging each person, then filled the schedule cell by cell in a spreadsheet. Conflicts such as “available until 4, scheduled until 5” were found by eye after the fact.
- About six hours each week to build the first draft
- Availability collected person by person
- Schedule filled cell by cell
- Conflicts caught by eye
- Rules lived partly in the operations lead’s head
- Staff submit availability through signed links
- Client-owned rules live in Lark
- A deterministic solver generates the first draft
- The output explains unfilled constraints
- The operations lead reviews, edits and publishes
A roughly six-hour weekly first-draft scheduling task is now automated; a person still reviews, edits and publishes the schedule.
What was built
One Cloudflare Worker with scheduled triggers. Each person gets a signed link that is valid for one week and one name; they pick a start and end time per day. Submissions land in the client’s Lark Base.
Every Thursday the Worker reads availability, the client’s scoring sheets and room requirements, and writes a draft workbook back into Lark: one tab per day, a summary, and a plain-language page describing the logic.
The rules belong to the client. Host-to-room scores, operator scores, who cannot work together, weekly hour ranges, and which rooms open when all live in the client’s spreadsheet. Changing a rule means editing a cell, not asking for a code change.
What the system does not do: publish the schedule, message staff, track attendance, or decide on last-minute swaps.
Why deterministic code, not an LLM
The scheduling solver is deterministic code, not a language model.
Before this project the client had handed two months of past schedules to a chatbot and asked it to carry on the pattern. It did not learn the rules reliably. In the first meeting the operations lead said the model “learned it badly”; the owner’s brief was “if A then B, leave it no room.”
The reason became clear later in the build. Historical schedules contain exceptions and residue that are not rules. Five percent of past shows were one hour long. That looked like a pattern; it was what was left when hosts called in sick. Anything that learns from that history copies the residue along with the rules.
This workflow needed explicit constraints and predictable fallbacks. A negative pair score means never. A weekly cap means a cap. An empty slot has to say which constraint blocked it. A language model can still be useful around a workflow like this, for example turning a manager’s free-text note into a proposed rule that a person approves. The engine that makes the scheduling decision is deterministic.
Use an LLM where ambiguity is useful; use deterministic logic where business rules must be enforced exactly.
Implementation notes
- The first version assigned people hour by hour and produced schedules nobody could work: one person in five rooms in a day, operators swapped mid-show. Comparing against a real week showed why. People think in shows, the code was thinking in hours. The unit was changed to 2–3 hour shows with one host and one operator each.
- Operators are assigned before hosts. The client’s manager pointed out that the number of operators free at 2pm decides how many rooms can open at 2pm.
- A negative pair score means those two people are never put together, in any fallback. The client was explicit: anyone who could tolerably work together was not given a negative.
- When a show cannot be staffed the system first shortens it to two hours, then allows a lower-tier person, then lets someone cover a single remaining hour. If it still fails, the slot is left empty and the summary says which constraint blocked it and how many people it blocked.
- One host is an exception the client asked for: every hour that host is available is filled in one room, with no break and no caps. It is driven by a flag in the client’s sheet, not by a name in the code.
- Every day tab starts with a line stating its own weekday and date. The reason is in the failures section.
- After Wednesday the form closes to staff. The operations lead can still open anyone’s link, enter the admin key, and submit for them.
- 32 automated tests run before every deploy. Ten of them reproduce bugs the client actually hit.
What was measured
Structure of the generated week against a real hand-built week, before and after the unit changed from hours to shows:
| Hand-built week | Hour-by-hour version | Show-based version | |
|---|---|---|---|
| One-hour shows | 5% | 17% | 3% |
| Operator changed mid-show | 26% | 41% | 0% |
| Most rooms one person worked in a day | 2 | 5 | 2 |
| Longest day for one person | 7h | 8h | 7h |
Coverage for one week as rules were added, with the number of required shows left unfilled. The unit is role-hours: one staffed room-hour counts as two role-hours, one host plus one operator.
| Change | Role-hours | Unfilled |
|---|---|---|
| Show-based solver | 392 | 25 |
| Match score as strict priority | 420 | 20 |
| Operator first | 440 | 18 |
| Lower-tier fill-in allowed | 446 | 16 |
| One-hour cover allowed | 456 | 10 |
The ten that remained were evenings after 7pm and weekend shows. The summary showed why: more than twenty people’s submitted hours did not reach those slots. That is a staffing fact, not something ordering can fix.
What broke before the workflow became reliable
- Friday, Saturday and Sunday were written into the wrong tabs. Adding a tab shifted every day one position. Nothing errored, and the shifted output looked plausible because Saturday and Sunday have the same shape. The client caught it: “lots of people can work Friday and none are scheduled.” Fixed with a hard check before writing, plus the self-labelling first row.
- Nobody could submit for most of a day. A query meant to update a person’s existing row used a date filter Lark rejected. The client found it while submitting on someone’s behalf.
- A rule was inferred from history that was not a rule. Five percent of past shows were one hour long, so one-hour shows were proposed as acceptable. They were leftovers from hosts calling in sick.
- A keyword match was too loose. A note saying one person should get “as many operator shifts as possible” was read as the fill-every-hour flag, and that person was given 40 hours against a 30-hour limit.
- Two work sessions deployed over each other. One pushed through CI while the other deployed directly. The repo is now push-only.
- Automatic reminders were never turned on. Lark blocks messages across organisations from a personal account, and no SMS or email provider has been connected. The operations lead copies links by hand.
What still needs validation
- How much of each draft survives review. Nobody has yet compared a generated draft with the schedule the client finally published.
- Net weekly time saved. The old first draft took about six hours. The current review and edit time has not been measured, so net hours saved are not claimed yet.
- Evening and weekend coverage, which depends on who signs up for those hours.
- Two operators in one room at the same hour, which the client’s hand-built schedule sometimes has.
- Scheduling two weeks ahead. The system only ever works on next week.
Why there is no net “hours saved” figure: the first full cycle ran in September 2026 and the client is still correcting rules every week. The number worth publishing is how many cells the operations lead changes before publishing, and that has not been counted yet.
What this case teaches about AI workflows
- Historical data is not the same as business rules. The one-hour shows in two months of schedules were sick-day leftovers. Ask the person who owns the process before encoding a pattern.
- Choose the correct unit of work. People schedule in shows; the first version scheduled in hours. Changing the unit took mid-show operator changes from 41% to 0%.
- Keep client-owned rules outside the code. Scores, pairings, hour ranges and room openings live in the client’s spreadsheet. A rule change is a cell edit.
- Explain why a request cannot be met. An empty slot names the constraint that blocked it and how many people it blocked. That is how the last ten gaps were identified as a staffing problem.
- Automate the repeatable step; keep judgment where it belongs. The first draft is generated. Review, edits, publishing and last-minute swaps stay with the operations lead.
- AI workflow design does not mean putting an LLM everywhere. The client had already tried a chatbot. The part that had to be exact was written as code.
This is the same method taught in corporate AI training: pick one real task, write the rules down, and decide what a person must check. For more operations examples see AI for Operations and the workflow library.