Project 01 was about depth in a domain. Project 02 is about orchestration — making three Skills cooperate well enough that the user doesn't notice there are three. The hard part isn't the code. The hard part is choosing the seams.
This is also the project where empathy enters Skills Workshop. The user is one named person — not "users" — and you'll know you've succeeded when you can say "I built this because I wanted to make Wednesday afternoons easier for Aunt Mei."
Because real software has shape. One huge Skill that does everything is unmaintainable, untestable, and impossible to reason about. Three Skills with clean boundaries are how a builder actually thinks. You're not learning Skills — you're learning systems.
Step by step
-
Pick the person. Name them. Watch them.
Pick one specific person — a grandparent, a sibling, an aunt, a teacher. Watch what they actually struggle with for a few days. Take notes. You're looking for the gap between what they need and what current tools give them. Example: Mei is 78. She loves cooking. The recipes she learned from her mother were never written down. She gives up on apps after two attempts. That's the brief.
-
Decompose into three Skills with clean seams.
Not three random Skills. Three Skills with one clear boundary each. The trick is finding seams a beginner wouldn't see. The decomposition rule: if you can't say in one sentence what each Skill does and doesn't do, you haven't decomposed yet — you've drawn lines around a single big Skill.
-
Design the shared memory schema first.
Before any Skill code: write the JSON schema all three Skills will read and write. Make it explicit. Version it. Treat it like a database schema — because that's exactly what it is. Without this step, your three Skills will silently disagree on the shape of the data, and the bugs will be impossible to track down.
-
Build the Skills in order: write → check → coach.
Build them in the order data flows through them.
recipe-recallwrites new recipes.ingredient-checkerreads recipes and returns shopping lists.kitchen-coachreads recipes and walks Mei through them. Build write before read; you can't test reads against an empty store. -
Wire up the orchestration handoffs.
The pack feels like one tool only if the handoffs between Skills feel natural.
recipe-recall, when finished, writes to memory and tells the user "I've saved it; would you like a shopping list?" — which triggersingredient-checker. Design the handoff language as carefully as the Skills themselves. "I've saved it. Would you like a shopping list?" is a designed sentence. -
Run a real session with the named person.
Sit with the actual person. Not your imagination of them. Watch what they actually do. Note three things you guessed wrong. (You will guess wrong about three things.) Edit the Skills. You're done when she finishes a recipe end-to-end without asking you for help.
A complete worked example, every file
Mei's recipe pack — every file, ready to copy. Three Skills, one shared schema, two example sessions.
## the user
name: Aunt Mei
age: 78
languages: native Cantonese, basic English reading, no typing
context: lives alone, cooks daily, has an iPad she rarely opens
the pain (her words): "我记得我妈妈做的菜,但是没有写下来,
现在做的时候总是忘记顺序。"
(I remember my mother's dishes, but I never
wrote them down, and now I forget the order.)
## what other apps fail at, that I observed
- All recipe apps assume the user knows the dish name first.
Mei doesn't. She remembers the smell, the season, a feeling.
- All apps default to 16pt text. She can't read it without glasses
she keeps losing.
- All apps assume the user can scroll. She can't — she taps too hard
and triggers links.
- All apps want her to "create an account". She won't.
## the design constraints (non-negotiable)
- 28pt minimum text
- read-aloud on every screen, calm female voice, slow speed
- no scrolling — one screen at a time, with a "next" button big
enough for one finger
- no accounts, no login, no sync
- never panic her. no pop-ups. no loading spinners she might mistake
for an error.
## the three Skills
recipe-recall → asks her one question at a time, builds up
a recipe over 5–7 turns, saves in her own words
ingredient-checker → reads a saved recipe, lists ingredients with
Cantonese first, suggests substitutions
kitchen-coach → reads any saved recipe back, one step at a
time, with read-aloud + confirm-to-advance
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "mei.recipes shared memory v1.0",
"type": "object",
"required": ["version", "preferences", "recipes"],
"properties": {
"version": { "const": "1.0" },
"preferences": {
"type": "object",
"required": ["font_size_pt", "language_ratio", "voice", "speed"],
"properties": {
"font_size_pt": { "type": "integer", "minimum": 24, "maximum": 48 },
"language_ratio": {
"type": "object",
"properties": {
"zh": { "type": "number", "minimum": 0, "maximum": 1 },
"en": { "type": "number", "minimum": 0, "maximum": 1 }
}
},
"voice": { "enum": ["calm female", "calm male"] },
"speed": { "enum": ["slow", "normal"] }
}
},
"recipes": {
"type": "array",
"items": {
"type": "object",
"required": ["id", "saved_at", "in_her_words", "ingredients", "steps"],
"properties": {
"id": { "type": "string", "pattern": "^[a-z0-9-]+$" },
"saved_at": { "type": "string", "format": "date" },
"in_her_words": { "type": "string", "minLength": 10 },
"ingredients": {
"type": "array",
"items": {
"type": "object",
"required": ["item_zh", "qty"],
"properties": {
"item_zh": { "type": "string" },
"item_en": { "type": "string" },
"qty": { "type": "string" },
"substitute_zh": { "type": "string" }
}
}
},
"steps": {
"type": "array",
"minItems": 2,
"items": { "type": "string" }
}
}
}
}
}
}
---
name: recipe-recall
version: 0.1
description: |
Asks Aunt Mei one question at a time about a dish she remembers
from childhood. Builds the recipe over 5–7 turns. Writes it into
shared memory in her own Cantonese words.
when_to_use: |
Triggered when Mei says she "想做" (wants to make) a dish she
hasn't yet recorded, or "想起来" (just remembered) one. Refuses
to recall a recipe she's already saved (suggests kitchen-coach).
response_style: |
ONE question per turn. Always Cantonese-first. No more than 18 words
per question. Never assume the next ingredient — always ask.
Always confirm before writing to memory: "我这样写下来对吗?"
does:
- ask single questions in Mei's pace
- write the saved recipe to mei.recipes
- hand off to ingredient-checker when done
does_not:
- skip ahead
- guess ingredients she didn't name
- rewrite recipes that already exist (refuses, redirects to coach)
- use English unless she does first
writes_to: mei.recipes
hands_off_to:
- condition: "recipe is saved AND user hasn't said no"
target: ingredient-checker
prompt_template: "我帮你把方子存好了。要我帮你列张买菜清单吗?"
---
name: ingredient-checker
version: 0.1
description: |
Given a saved recipe, lists the ingredients Mei will need, with
Cantonese first, English second. Suggests substitutions for
hard-to-find items. Writes nothing to recipes — read-only.
when_to_use: |
Triggered after recipe-recall completes, OR when Mei opens an
existing recipe and asks "I need to buy what?". Refuses if
asked to MODIFY a recipe (that's recall's job, not checker's).
response_style: |
Bilingual list, Cantonese first, English in parentheses. Quantity
in the unit Mei understood it (catty, half a bowl, "a handful").
When suggesting a substitute, explain why in one short sentence.
does:
- read mei.recipes
- format ingredient lists bilingually
- suggest substitutes for items hard to find in Mei's neighborhood
- hand off to kitchen-coach when she says she's ready to cook
does_not:
- add or change steps in any recipe (refuses → recall)
- write to mei.recipes (read-only by design)
- assume which substitute she'd want (always asks)
reads_from: mei.recipes
writes_to: (none — read-only)
hands_off_to:
- condition: "user says ready to cook OR uses 'now' / '现在'"
target: kitchen-coach
--- name: kitchen-coach version: 0.1 description: | Reads any saved recipe back to Mei, one step at a time, with large text and read-aloud. Pauses for confirmation between steps. Refuses to skip ahead — pacing is the whole feature. when_to_use: | Triggered when Mei says she's "now" cooking, OR opens a recipe with the kitchen-coach button. Refuses if no recipe exists in memory (redirects to recall). response_style: | ONE step per screen. 28pt minimum. Read-aloud automatically on show. After speaking the step: "準備好了告訴我,我等你。" Wait. Never play more than one step until the user confirms. does: - read steps from mei.recipes - speak them with calm female voice, slow speed - wait for confirmation between steps - allow the user to say "再说一次" / "say it again" does_not: - skip ahead even if asked (politely refuses) - reorder steps - speed up - quit if interrupted (resumes from the step it last spoke) reads_from: mei.recipes writes_to: (none — read-only) respects: mei.preferences (font_size_pt, voice, speed)
## handoff graph
flow:
- from: (entry)
to: recipe-recall
when: "user mentions a dish not yet in mei.recipes"
- from: (entry)
to: ingredient-checker
when: "user asks 'I need what to buy?' AND a recipe exists"
- from: (entry)
to: kitchen-coach
when: "user says she is cooking now AND a recipe exists"
- from: recipe-recall
to: ingredient-checker
when: "recipe-recall completed AND user did not decline"
bridge: "我帮你把方子存好了。要我帮你列張買菜清單嗎?"
- from: ingredient-checker
to: kitchen-coach
when: "user says 'ready' / '現在' / 'I'm cooking'"
bridge: "好。我帮你一步一步念出来。準備好了告訴我。"
- from: kitchen-coach
to: (end)
when: "all steps confirmed OR user says 'done'"
## refusals
refuse_routes:
- if: "kitchen-coach asked but no recipe exists"
redirect_to: recipe-recall
message: "我们還沒有寫下這個菜的方子。要我先問你,再幫你寫好嗎?"
- if: "ingredient-checker asked to MODIFY a recipe"
redirect_to: recipe-recall
message: "我只能讀方子,不能改。要找方子的人是 recipe-recall。"
## shared memory contract
writers: [recipe-recall]
readers: [recipe-recall, ingredient-checker, kitchen-coach]
schema: memory.schema.json (v1.0)
Live demo 1: watch the three Skills hand off
A simulated session. Click "next turn" to advance the conversation and watch shared memory update on the right.
Mei's recipe pack · simulated session
Live demo 2: does your shared schema actually hold up?
Paste a JSON record into the validator. The check runs against Mei's memory.schema.json v1.0 and tells you exactly which field is wrong. This is the most boring widget on the site, and the most useful — schema bugs are 70% of multi-Skill failures.
JSON schema validator
Live demo 3: does your handoff feel like one experience?
The seam between Skills shows up in language. A bad handoff: "Skill A has finished. Switching to Skill B." A good handoff: "我帮你把方子存好了。要我列張買菜清單嗎?" The user shouldn't notice there's a switch. Type your handoff sentence — the checker flags machine-speak.
Handoff-language checker
What makes this hard
The hardest part is not three Skills. It's three Skills that feel like one experience. The seams will show if you don't think about handoffs explicitly. The user will feel a "switch" between Skills — and the switch will break the trust the pack has earned. The cure is to design the handoff language as carefully as the Skills themselves. "I've saved it. Would you like a shopping list?" is a designed sentence. So is the next one.
The second hardest part is restraint. You will be tempted to add a fourth Skill, a fifth feature. Don't. Three Skills, used by Mei, beats six Skills, used by no-one.
Self-check before you ship v0.1
- I can name the user out loud and describe one specific thing they struggle with.
- The brief.md exists and includes 4+ design constraints I observed (not invented).
- The three Skills have clean seams — what each does AND does not do, in one sentence each.
- The shared memory schema is explicit, versioned, and validates clean against the validator above.
- Each handoff sentence passes the handoff-language checker.
- I sat with the real person and ran a session. I noticed three things I had guessed wrong.
- The pack feels like one tool. The handoffs use language I designed.
Push further · for the harder end of 15+
Three Skills that cooperate is the minimum. Here's where it stops feeling like a school project.
- Add an undo Skill as the fourth. Mei makes mistakes. She says "no, that wasn't right" after recipe-recall has already saved a turn. Right now, there's no way back. Add a fourth Skill,
recipe-undo, that snapshots before each write and restores on command. Watch how this changes the orchestration. Hint: it has to come before any writer, not after. - Build the conflict-resolution layer. Two Skills now want to read or write the same field at the same time. Design the locking strategy — optimistic? Last-writer-wins? Append-only log + replay? Pick one, write down why, document the failure modes. Real production multi-agent systems live or die on this question.
- Internationalize the schema, not just the surface. Mei's preferences include a
language_ratiofield. Right now the Skill responses respect it on output. But what about input? Mei sometimes types in pinyin, sometimes Cantonese characters, sometimes a mix with English brand names. Add aninput_normalizerstage so all three Skills see the same canonical form regardless of how Mei wrote it. Bonus: write the normalizer so it could be swapped for any other language pair without changing the three Skills.