Technology
Skylight MCP, Part 4: Chores, Stars, and Rewards
Pointing the same Skylight MCP server at the kids' chore chart: what the reward data really looks like, the upstream fix it needed, a star economy priced off one exchange rate, and a ledger that outlives the API.
Parts 1 through 3 were about dinner. This one is about the other thing our Skylight is supposed to run: the kids' chore chart and star rewards.
In theory the chart was already on the frame. In practice it had drifted. Some chores lived in Skylight, the ones that actually paid money lived in FamZoo, star values had been set on a whim, and nobody could say with confidence which rewards had been redeemed or whether we'd ever followed through on them. The kids, for the record, could say with great confidence. Their numbers just didn't agree with ours.
This post walks through how I used the same MCP server to clean that up, what I learned about the data along the way (including the bug that became my first upstream PR), and how I'm running it week to week.
The setup, briefly
The plumbing is the same as Part 1: skylight-mcp running locally in the Claude desktop app, credentials in the extension config, and the family calendar's frame ID passed explicitly on every call. Two things matter more for chores than they did for meals:
- It runs locally. Anything scheduled against it needs the Mac awake and the Claude desktop app open. That shapes the weekly report below.
- It's unofficial. Any of this can break when Skylight changes their API. I treat it as a convenience layer, not a system of record. That decision drives the whole second half of this post.
The tools that mattered for this project:
| Area | Tools |
|---|---|
| Chores | list, search, create, create recurring, update, delete, complete, uncomplete |
| Rewards | list, get, create, update, delete, redeem, unredeem |
| Points | get reward points (per family member: lifetime earned and current balance) |
| People | list categories (each family member is a "category" with an ID) |
What the data actually looks like
Before changing anything, I had Claude pull everything and tell me what was there. A few things surprised me.
Rewards respawn, and redemptions leave a dated record
Each reward in Skylight belongs to one person. If a reward is set to respawn on redemption, redeeming it stamps that copy with a redeemed_at timestamp and creates a fresh, unredeemed copy with a new ID. So the redemption history is really just the list of reward records with a non-null redeemed_at.
That's good news, because it means you can see past redemptions:
| Redeemed | Reward | Stars |
|---|---|---|
| Jun 23 | Ice cream trip | 150 |
| Jul 27 | Ice cream trip | 150 |
| Sep 12 | Bookstore gift card | 75 |
| Oct 9 | Ice cream trip | 150 |
The connector was dropping who redeemed what
The Skylight app clearly knows whose reward is whose; it shows each one in that kid's column. But the MCP server's skylight_list_rewards only returned the name, points, and redemption date. No person. With two kids, a redemption history with no names on it is worthless.
The cause turned out to be simple. Skylight's API speaks JSON:API, and a reward's owner only exists as a relationship:
{
"id": "11150707",
"type": "reward",
"attributes": { "name": "Ice cream trip", "point_value": 150, "redeemed_at": "2026-10-09T11:50:42.772Z" },
"relationships": { "category": { "data": { "id": "1234567", "type": "category" } } }
}
The server's generic flattener keeps id, type, and attributes and throws away relationships. As it turns out, the maintainer had already hit the exact same bug with chores and fixed it with a chore-specific flattener. Rewards just never got the same treatment.
Before asking anyone to change anything, I wanted proof the data was really there. I had Claude write a small Python probe that reused the token the MCP server had already cached and called the rewards endpoint directly. Two gotchas on the way:
- Skylight sits behind Cloudflare. Python's default
Python-urllibuser agent gets a 403. Sending a browser-style user agent (which is what the MCP server does) fixed it. - Wrong frame, 404. The probe grabbed the first frame on the account, which was a photo frame. Pointing it at the calendar frame returned every reward with its
relationships.categoryintact.
With that evidence in hand, I had Claude Code make the patch in my fork and opened PR #274, "fix(rewards): show which family member each reward belongs to." The change:
- Adds
flattenReward()/flattenRewards()helpers that inlinerelationships.category.data.idascategory_id(orcategory_idsfor arrays, with an explicit attribute winning if the API ever starts sending one). - Pulls the relationship-inlining logic out of the chore flattener into a shared
inlineRelationshipId(), so chores and rewards behave the same way and the next resource type gets it for free. - Updates the
skylight_list_rewardstool description to say it returns all rewards (redeemed_at: nullfor unredeemed ones) and that owner names come fromskylight_list_categories. - Regenerates the manifest, updates the README, and extends the chore and reward tests to cover the new field.
I verified it live against our frame before submitting, and it's open as of this writing. That one category_id field is what makes per-kid redemption tracking possible. Everything below depends on it.
Completion history is short
Chores come back per occurrence, with a status, a completed_on date, and a completed_category_id (who checked it off). That's great for "what got done this week." It's not great for "what got done this year." When I swept back through the calendar, completions only showed up for roughly the last month. Older dates came back with every chore still pending, even though the kids' lifetime point totals said otherwise.
Two other things showed up in the completion data:
- Catch-up check-offs. A lot of chores were checked off in bursts, several days after they were due, within a few minutes of each other. That's a parent back-filling from the app, not the kids tapping as they went. (Guilty.)
- Timestamps aren't always real. Some completions carry a real time of day, others are stamped exactly noon. The date is reliable; the time isn't.
Both of those matter for the weekly report below.
Designing the star economy
Once I could see the data, the real work was deciding what a star is worth. The kids want rewards that pay out cash to their FamZoo accounts (which split between spending, savings, and charity). I'm fine with that, with two concerns:
- I don't want to be overly generous. They're not consistent yet, and we often have them redo things.
- If cash is the best deal on the board, they'll pick cash every time and spend it on squishies.
So I worked through it with Claude like a pricing exercise.
Work out the exchange rate from the chores, not the other way around
I'm not going to share our actual star-to-dollar rate, mostly because we're still tuning it. The thought process is the useful part anyway, and it goes in this order:
- Start with the list of chores. Assign each one a star value based on how much effort it really takes. Making a bed is not laundry.
- Figure out how often each one happens. One-offs, a few days a week, daily, weekly.
- Sum all those out and you have the maximum stars a kid can earn in a week. Knowing how your kids actually do, you can also pencil in a "realistic" weekly number. That one matters more.
- Decide how many weeks of saving should sit between the kid and a cash reward. Too few and it's an allowance with extra steps. Too many and they stop caring. That number, against the realistic weekly total, is your exchange rate.
- Price experiences off the kid's cut, then discount them. For an outing, figure out their share of the real cost (ticket, dinner, treat), convert it to stars at your rate, and then knock a chunk off. 20%? More? The discount is the lever that makes the outing a better deal than cash.
- Vary the price range. Some rewards should be cheap enough for a quick win. Others should be things they REALLY want, priced high enough that they have to save. The spread is what teaches the saving.
Pricing all of it off one rate gives you a deliberate ordering. In our case:
| Reward type | Pricing rule |
|---|---|
| Money for Books | Deliberately cheap. We want to encourage reading. |
| Experiences | The kid's share of the real cost, converted to stars, then heavily discounted |
| Cash to FamZoo | Exactly the rate, no discount |
If an outing is priced right at the cash rate, a kid comparing the two sees no reason to pick the outing. Discounting experiences makes them a better deal than cash, and books are the best deal by far. That ordering (books, then experiences, then cash) is the whole defense against the squishy problem.
I also put the split in the reward name, something like "$20 to FamZoo ($8/$8/$4)," so there's no argument later about why only part of it was spendable.
Even out the chore chart
The other rule: both kids get the same number of stars available per week, even if the chores differ. The chart breaks into two groups.
Household chores are mostly shared. A simplified version:
| Chore | When | Stars | Max per week |
|---|---|---|---|
| Make bed | Daily | 1 | 7 |
| Downstairs on time for school | School days | 1 | 5 |
| Feed the dog | Split between kids | 2 | 6 |
| Laundry: wash, dry, put away | Weekly | 10 | 10 |
| Clean bedroom | Weekly | 5 | 5 |
| Clean bathroom | Weekly | 5 | 5 |
Learning tasks differ per kid (workbooks, a chapter book, typing practice, a sports skills app), but each kid's daily list adds up to the same number of stars.
One thing I changed my mind on: I originally wanted learning tasks only on school days, then only on non-sports days. The kids have practices most weeknights, and scheduling around that got complicated fast. We settled on putting learning tasks on the chart every day, so they can do them whenever they have time. Each day stands alone, so a missed Tuesday can't be doubled up on Saturday. Weekends just give them more chances.
Penalties and bonuses
Skylight has no concept of a negative chore, so penalties and bonuses happen outside the chart. A time-out costs a fixed number of stars, and "exceeds expectations" earns a small bonus, both applied by a parent. It maps nicely onto the "needs improvement, meets, exceeds" framing we've been using with the kids: needs improvement means the chore gets un-checked until it's redone, meets earns the base stars, exceeds earns a bonus.
Making changes safely
The MCP server can create, update, and delete chores and rewards, including recurring series. This is the kids' live chart, so a few habits I'm keeping:
- Preview, then confirm. Any change that touches more than one occurrence of a recurring chore (or deletes a series) returns a preview and a short-lived confirmation token instead of running. Nothing happens until I approve it in chat. That's exactly the friction I want here.
- End series, don't delete them. To retire a recurring chore, set an end date. Deleting a series likely takes its completion history with it, and that history is already thin.
- Change from tomorrow forward. Updates use "this and future" starting the next day, so past check-offs stay untouched.
- Leave balances alone. Repricing rewards doesn't touch anyone's current star balance. Nobody loses stars because we changed the menu.
- Payouts stay manual. FamZoo doesn't have a public API either (the community option scrapes the website). Moving money through a scraper is a bad failure mode, so Claude tells me what's owed and I do the transfer myself.
The Star Ledger
Here's the core problem the rest of this builds toward: rewards don't stay visible in Skylight. Once a reward is redeemed, the app shows "Redeemed on Oct 9" for a short while, then it's gone from view. There's no "did Dad actually take us?" field. Completion history ages out after a month or so. And the kids have excellent memories for things they think they're owed.
So the system of record for rewards is now a separate page: a Claude artifact I'm calling the Star Ledger. It's a small web page backed by the artifact's own database, which means Claude can read and write rows from chat (or from a scheduled task), and I can mark things fulfilled from the page itself.
It has four tabs:
- Redemptions. Date redeemed, child, reward chosen, stars, and date fulfilled. Each unfulfilled row has a date picker and a "Today" button. When I pay out or we go on the trip, I set the date. The kids can see it, which settles any "you never paid me" debate. A "most popular" panel underneath tallies which rewards get picked, which will tell me over time whether the pricing is steering them where I want.
- Check-offs. Every chore completion pulled from Skylight, with the date it was checked off, the date it was due, and the stars. Anything checked off two or more days late is flagged.
- Weekly balances. One row per kid per week with stars earned that week, current balance, and lifetime earned. This is the permanent history Skylight doesn't keep. It will also catch manual star changes, since the balance should only move by completions, redemptions, and adjustments.
- Chore chart. The current chore list with who it's for, when it repeats, its star value, and the maximum it can earn per week, with a total per kid at the bottom. It's the reference the kids check when they want to know what something is worth.
A summary strip at the top shows how many redemptions are waiting to be fulfilled and each kid's latest balance.
When I set it up, Claude pre-filled it with the redemptions still visible in Skylight (at their old prices) and the check-offs it could still find from October, so the ledger starts with what history we had rather than a blank page.
The design choice I like most: the ledger doesn't depend on the MCP server to be readable. If the unofficial API breaks tomorrow, the history and the fulfilled dates are still there.
The weekly report
The last piece is a scheduled task that runs every Sunday night. It's a fresh Claude session each week with a standalone prompt, so the prompt carries everything it needs: the frame ID, which category ID is which kid, the ledger's address, and the rules. It does three things.
1. Posts the week's check-offs by kid and date. This is my spot check that the kids aren't checking off things they didn't do:
Kid A:
- Feed the dog: Mon 11/9, Wed 11/11, Fri 11/13
- Laundry: Mon 11/9
- Chapter book: Mon 11/9, Tue 11/10, Thu 11/12
Kid B:
- Feed the dog: Tue 11/10, Thu 11/12
- Make bed: Tue 11/10 (late 3d)
It filters by the date something was checked off, not the date it was due, so catch-up check-offs from the previous week still show up, and anything late gets flagged.
2. Appends new redemptions to the ledger. Any reward with a new redeemed_at since the last run gets a row, with the kid filled in from category_id (there's that PR again), and the fulfilled date left blank for me.
3. Logs check-offs and balances. Every completion goes into the Check-offs tab, and each kid's current balance and lifetime total goes into Weekly balances.
A few rules in the prompt matter more than the rest:
- Read-only in Skylight. The weekly run never creates, completes, redeems, or deletes anything on the kids' chart. It only reads.
- Never overwrite a redemption. I fill in the fulfilled date by hand, so the run only creates redemption rows that don't exist yet.
- The window comes from the ledger. Each run starts the day after the last balances row it wrote, and looks back an extra two weeks of due dates to catch late check-offs. A skipped week gets picked up by the next run.
- Fail loudly, write nothing. Since the Skylight MCP is local, the Mac needs to be awake with the Claude desktop app open. If Skylight can't be reached, the run writes nothing to the ledger and sends me a one-line "couldn't run" message instead of a half-empty report.
How I'm planning to use it
The weekly rhythm I'm aiming for:
- During the week: the kids check things off on the Skylight. If something wasn't really done, I un-check it until it's redone.
- Sunday night: the report runs. I skim the check-off list for anything suspicious (especially late check-offs in a burst) and look at the new redemptions.
- When a reward is cashed in: I make the FamZoo transfer or plan the outing, then set the fulfilled date in the ledger.
- After a month: look at the balances and the "most popular" panel. If cash is winning every time, books and experiences get cheaper or cash gets more expensive. If it's taking the kids five weeks to reach a payout, the rate is too steep.
We'll see how it holds up once the novelty wears off. For the kids and for me.
Lessons learned
Lesson Learned: Look at the data before you design anything. Pulling every reward, chore, and balance first is what surfaced the respawning rewards, the short completion history, and the missing person field. Every one of those changed the design.
Lesson Learned: Prove it before you patch it. A ten-line probe that hit the raw API with the cached token turned "I think the connector drops this" into evidence I could put in a pull request.
Lesson Learned: Pick one exchange rate and price everything off it. Once stars had a dollar value, every reward decision became arithmetic plus one deliberate choice about which way to round.
Lesson Learned: Don't make an unofficial API your system of record. Skylight is the kids' interface. The ledger is the record. If the API changes, I lose the automation, not the history.
Comments welcome!