| name | wa-characterize |
| description | Characterize a WhatsApp AI agent before writing any code. Use when a student has finished wa-setup and is ready to define what the bot does, or says 'wa-characterize', 'ืืคืืื ืกืืื', 'ืืื ื ืชืื ื ืืช ืืืื', 'ืื ืืืื ืขืืฉื', 'ืืืืจ ืืช ืืืื'. This skill asks the hard questions (who does it answer? what's its knowledge? which tools?) and produces a spec.json that wa-build reads to generate the bot. |
Characterize the WhatsApp Agent
Define precisely what the agent does, who it answers, and what tools it needs - before a single line of code is written.
This skill does not write agent code. It extracts a specification. The output is a spec.json file that wa-build reads.
Prerequisites: wa-setup completed (.env with Green API credentials exists).
Interaction Style
Simple Hebrew. Ask one question at a time. Wait for the answer. Summarize back what you understood before moving on. The student is making product decisions, not technical ones.
Flow
digraph wa_characterize {
rankdir=TB;
"Pick bot archetype\n(personal assistant vs customer service)" [shape=diamond];
"Identity & voice" [shape=box];
"Audience:\nwho does it answer?" [shape=box];
"Scope:\nwhat's in / what's out?" [shape=box];
"Knowledge base" [shape=box];
"Tools needed" [shape=box];
"Human handoff rules" [shape=box];
"Summarize spec\n+ show to student" [shape=box];
"Student approves?" [shape=diamond];
"Write spec.json" [shape=box];
"Done - suggest wa-build" [shape=doublecircle];
"Pick bot archetype\n(personal assistant vs customer service)" -> "Identity & voice";
"Identity & voice" -> "Audience:\nwho does it answer?";
"Audience:\nwho does it answer?" -> "Scope:\nwhat's in / what's out?";
"Scope:\nwhat's in / what's out?" -> "Knowledge base";
"Knowledge base" -> "Tools needed";
"Tools needed" -> "Human handoff rules";
"Human handoff rules" -> "Summarize spec\n+ show to student";
"Summarize spec\n+ show to student" -> "Student approves?";
"Student approves?" -> "Identity & voice" [label="no - revise"];
"Student approves?" -> "Write spec.json" [label="yes"];
"Write spec.json" -> "Done - suggest wa-build";
}
The Two Archetypes
Everything downstream splits on this. Ask first:
"ืืืื ืืื ืื ืื ื ืืืคืืื ืื - ืขืืืจ ืืืฉื ืืขืฆืื, ืื ืืื ืฉืืจืืช ืืงืืืืช?"
The defaults for every subsequent question differ:
| Personal Assistant | Customer Service |
|---|
| Audience | Whitelist (you + spouse + assistant) | Public (anyone who messages) |
| Knowledge | Thin (delegates to tools) | Thick (business info embedded in prompt) |
| Tools | Many (calendar, email, groups, reminders) | Usually one (human handoff) |
| Group messages | Often reads groups for context | Ignores groups |
| Human handoff | N/A (you are the human) | Required feature |
Use these as defaults, then let the student override.
The Questions (Ask One at a Time)
Q1. Identity & voice
"ืืื ื ืชืืื ืืืืืช. ืื ืืฉื ืฉื ืืืื? ืืื ืืื ืืืืจ - ืจืฉืื, ืืืจื, ืืฆืืืง? ืชื ืื ืืืืื ืงืฆืจื ืืื ืืชื ืจืืฆื ืฉืืขื ื ืืืืืขื 'ืืื'."
Record: name, tone_description, greeting_example.
Q2. Audience - who does it answer?
Critical for personal assistants. Re-read Speaker 1 at 13:27 in the source transcript: "ืืืช ืืฉืืืืช ืืืืื, ืืืื ืืฉืืืืช ืฉืืื ืืฉืื ืื ืืืืื ืืกืคืจืื ืืื ืขืื ื."
For personal assistant:
"ืืืื ืืื ืืืื ืืืืืจ ืืืืื ืฉืื, ืืืืื ืฉืื. ืื ืื ื ืื ืจืืฆืื ืฉืืขื ื ืืื ืื ืฉืืืชื. ืืืืื ืืกืคืจืื ืืื ืื ืขืื ื? ืชื ืื ืฉืืืช ืืืกืคืจื ืืืคืื - ืื ื, ืืฉืชื, ืขืืืจืช, ืืื'."
Record as a whitelist: authorized_contacts: [{name, phone_e164}, ...]
For customer service:
"ืืืื ืืขื ื ืืืืื ืืืฅ ืืงืืืฆืืช. ื ืืื?"
Record: answer_groups: false, answer_public: true, blocklist: [] (optional future).
Q3. Scope - what's in, what's out?
"ืชื ืื ืฉืืืฉื ื ืืฉืืื ืฉืืืื ืืขื ื ืขืืืื, ืืฉืืืฉื ืฉืืื ืืกืจื. ืืจืขืืื ืืื ืฉืื ืืืฉืื ืืฉืื ืขื ืืฉืื ืืืืฅ ืืชืืื, ืืืื ืืืื ืื ืืืืก ืฉืื ืื ืืชืคืงืื ืฉืื."
Record: in_scope: [...], out_of_scope: [...], out_of_scope_response (how to decline politely).
Q4. Knowledge base
For personal assistant:
Skip this question if the student's tools (calendar, mail) cover it. Otherwise: "ืืฉ ืืืจืื ืฉืืืื ืฉืืืื ืืืข ืืจืืฉ ืขืืื - ืืืฉื ืืชืืืช ืืขืกืง, ืฉืขืืช ืฉืืื ืืชื ืื ืืืื?"
Record as static_knowledge (short paragraphs).
For customer service โ offer a skeleton, don't start from a blank page:
"ื ืืชืื ืืช ืืืืจ ืืืืข. ืืืงืื ืืืชืืื ืืืคืก, ืื ื ืืฉืื ืืืชื ืกืขืืฃ-ืกืขืืฃ. ืขื ื ืขื ืื ืฉืจืืืื ืื, ืืื ืขื ืื ืฉืื:"
Ask each, wait for answer, summarize back:
- ืฉืขืืช ืคืขืืืืช: "ืืชื ืืขืกืง ืคืชืื? ืืืฉื 'ืืณ-ืืณ 9-18, ืืณ 9-13, ืฉืืช ืกืืืจ'"
- ืืืงืื: "ืืชืืืช ืคืืืืช ืื ืจืืืื ืื, ืื 'ืืื ืืืื ืืืื'"
- ืฉืืจืืชืื/ืืืฆืจืื: "3-5 ืืืจืื ืขืืงืจืืื ืฉืืขืกืง ืืฆืืข"
- ืืืืจืื: "ืืืืื ืืืืจ ืื ืืชื ืืืื ืืคืจืกื, ืื 'ืืืืืจ ืืชืืื ืืืฉืืช' ืื ืื"
- ืืืื ืืืช: "ืืืืจืื, ืืืืืืื, ืฉืืืื, ืืืจืืืช - ืื ืฉืจืืืื ืื ืืขืกืง ืฉืื"
- ืฉืืืืช ื ืคืืฆืืช: "3-5 ืฉืืืืช ืฉืืงืืืืช ืฉืืืืื ืืืชื ืื ืืืื, ืขื ืืชืฉืืืืช ืฉืื"
Record as kb_sections: {hours, location, offerings, pricing, policies, faq}.
Important: the student may ramble or give too much. Summarize back every 3-4 sentences: "ืื ืื ื ืืืื ืฉ: [ืกืืืื]. ื ืืื?" Keep kb_sections tight and factual - the prompt grows fast.
Q5. Tools
Explain the concept first: "ืืืื ืืืื 'ืืขืฉืืช ืืืจืื', ืื ืจืง ืืืืจ. ืื 'ืขืืฉื ืืืจืื' ืืื ืืื. ืืื ื ืืืื ืืืื ืืืื ืืื ืฆืจืื."
Show the menu with concrete WhatsApp examples โ this makes the choice much easier than abstract capability descriptions:
- ๐
Google Calendar - "ืื ืืฉ ืื ืืืจ?" / "ืชืงืืข ืื ืคืืืฉื ืขื ืืื ื ืืืื ืืืืฉื ื-10" / "ืชืืื ืืช ืืคืืืฉื ืฉื 14:00 ื-15:00"
- โ๏ธ Gmail - "ืืฉ ืืืืืื ืืืฉืื?" / "ืืคืฉ ืืืืืื ืืืื ื ืขื ืคืจืืืงื X" / "ืงืจื ืื ืืช ืืืืื ืืืืจืื"
- ๐ฌ WhatsApp groups - "ืื ืืื ืืงืืืฆืช ืืืฉืคืื ืืืื?" / "ืกืื ืื ืืช ืืงืืืฆื ืฉื ืืฆืืืช"
- โฐ Reminders - "ืชืืืืจ ืื ืืขืื ืฉืขื ืืืืฆืื ืืืืกื" / "ืชืืืืจ ืื ืืืจ ื-9 ืืืชืงืฉืจ ืืืื"
- ๐ค Human handoff - "ืื ื ืจืืฆื ืืืืจ ืขื ืื ืืื" - ืืืื ืืขืืืจ ืคืจืืื ืืืขื ืืขืกืง (ืืืจื ืืื ืืืื ืฉืืจืืช ืืงืืืืช)
"ืืืื ืืืืืื ืืืื ืืืื ืฆืจืื? ืื ื ืืขืืืจ ืืืืจ ืืืชื ืืืจ ืื - ืขืืฉืื ืจืง ืืกืื ืื."
Record: tools: ["google_calendar", "gmail", "whatsapp_groups", "reminders", "human_handoff"] (subset).
For each selected tool, ask a follow-up:
- google_calendar โ "ืืืืื ืืืื - ืืื ืืืฉื, ืืืื ืขืืืื, ืฉื ืืื?"
- gmail โ "ืืื ืืืื ืจืง ืืงืจืื, ืื ืื ืืฉืืื? (ืืจืืจืช ืืืื: ืจืง ืืงืจืื - ืืืื ืืืชืจ)"
- whatsapp_groups โ "ืืืื ืงืืืฆืืช? ืชื ืื ืฉืืืช - ื ืืื ืืืชื ืืืจ ืื ื-wa-connect"
- reminders โ no follow-up
- human_handoff โ ask Q6 now
Record per-tool config under tools_config.
Q6. Human handoff (if selected)
Re-read Speaker 1 at 15:59 in the transcript - it's "ืืขืืจ ืื ืฆืื ืื ืืฉื" not "ืืกืืื".
"ืืฉืืงืื ืจืืฆื ืืืืจ ืขื ื ืฆืื ืื ืืฉื, ืื ืงืืจื? ืืฉ ืืื ืืคืฉืจืืืืช:"
- ืืืื ืฉืืื ืืชืจืื ืืืฉืืช ืืืื ืืืืืืกืืค (ืขื ืงืืฉืืจ ืืฉืืื ืขื ืืืงืื)
- ืืืื ืฉืืื ืืช ืืืงืื ืืืคืื ืืืืกืจ ืื ืืืชื (ืขืืงืฃ ืืช ืืขืืืช ืืืืืช "ืืขื ืืขืกืง ืขืื ื ืืืืกืคืจ ืฉื ืืืื")
- ืฉืืืื - ืืชืจืื + ืืกืคืจ ืฉื ืืืงืื
Strong default: option 2 or 3. Explain the loop problem explicitly: "ืื ืชืืืจ ืืืงืื ืืืืกืคืจ ืฉื ืืืื, ืืืื ืืขื ื ืืืงืืื. ืืื ืืื ืืื ืฉืืืื ืืขืืืจ ืื ืืช ืืืืคืื ืฉื ืืืงืื, ืืืชื ืชืชืงืฉืจ/ืชืืชืื ืืืืกืคืจ ืืืืฉื ืฉืื."
Record: handoff: {trigger_phrases: [...], manager_phone: "...", manager_name: "...", mode: "phone_number_relay"}.
Q7. Extras (optional)
Ask these only if time permits:
- Response length - short (1-2 sentences), medium, long?
- Language - Hebrew only? Reply in the language sent?
- Off-hours behavior - does the bot reply at 3am, or defer?
- Holiday mode - how do they want to handle pesach/rosh hashana?
Summarize & Approve
After collecting all answers, say:
"ืื ืื ืฉืงืืืชื. ืชืงืจื, ืชืืื ืื ืื ืืฉื ืืช:"
Then print a Hebrew human-readable summary of the spec - not JSON. Example:
ืฉื ืืืื: ืจืื ื
ืกืื: ืขืืืจ ืืืฉื
ืืืืจ: ืืืจื ืืขื ืืืืืจ ืงื
ืขืื ื ื: ืืฉืจ (0501234567), ืืืื (0527654321)
ืืืืข ืืืืจ ืขื: ืืืื, ืืืื, ืชืืืืจืืช
ืืกืจื: ืื ืืฉืืจ ืื ืืืืก
ืืืื: ืืืื ืืืื, Gmail (ืงืจืืื ืืืื), ืชืืืืจืืช
Iterate until the student says yes.
Write spec.json
Only after approval, write spec.json to the project directory (same dir as .env):
{
"version": 1,
"archetype": "personal_assistant | customer_service",
"identity": {
"name": "...",
"tone_description": "...",
"greeting_example": "..."
},
"audience": {
"answer_groups": false,
"mode": "whitelist | public",
"authorized_contacts": [{"name": "...", "phone_e164": "972..."}],
"blocklist": []
},
"scope": {
"in_scope": ["..."],
"out_of_scope": ["..."],
"out_of_scope_response": "..."
},
"knowledge": {
"static_knowledge": "...",
"kb_sections": { "hours": "...", "location": "...", "offerings": "...", "pricing": "...", "policies": "...", "faq": "..." }
},
"tools": ["google_calendar", "gmail"],
"tools_config": {
"google_calendar": {"calendars": ["primary"]},
"gmail": {"mode": "read_only"}
},
"handoff": null | {
"trigger_phrases": ["ื ืฆืื ืื ืืฉื", "ืืืืจ ืขื ืื ืืื"],
"manager_phone": "972...",
"manager_name": "...",
"mode": "phone_number_relay | notification | both"
},
"extras": {
"response_length": "short | medium | long",
"language_mode": "hebrew_only | match_sender",
"off_hours_mode": "always_reply | business_hours_only"
}
}
Fields not answered are set to sensible defaults - document the default in a comment nearby.
Update state & hand off
Update .wa-state.json:
- Append
"characterize" to completed_stages
- Set
current_stage: "build"
- Set
bot_name from spec.identity.name
- Confirm
archetype matches spec.archetype (update if mismatch)
- Update
last_touched_iso
Then say:
"ืืืคืืื ืืืื. ืืฉ ืื ื spec.json ืฉืืชืืจ ืืืืืง ืื ืืืื ืืืืข ืืขืฉืืช. ืืฉืื ืืื: ืืื ืืช ืืช ืืงืื. ืจืืฆื ืืืืฉืื ืขืืฉืื?"
- If yes โ invoke
wa-build via Skill tool
- If "ืชื ืื ืจืืข" โ "ืืขืืื. ืชืืื
/wa ืืฉืชืืืืจ."
Architectural Notes (for Claude Code's reference)
- Why a spec file and not just a long conversation: the student will iterate (fine-tune, add tools, change audience). A spec makes those iterations cheap - re-run
wa-build with an updated spec instead of regenerating from scratch.
- Why "whitelist mode" for personal assistants is enforced at the code level, not the prompt level: prompts can be jailbroken; a hard check on sender phone in
main.py cannot. The spec drives both.
- Why the tools list is decided here and not in
wa-connect: the prompt that wa-build generates must describe the tools to the LLM. If we decide tools later, we'd have to regenerate the prompt. Easier to decide once.
out_of_scope vs blocklist: out_of_scope is about topics (bot refuses); blocklist is about senders (bot doesn't see them). Students confuse these - ask the right question.
trigger_phrases for handoff: also detected semantically by the LLM in the prompt, but explicit phrases give deterministic behavior for the common cases.
Error Handling
| Problem | Solution |
|---|
| Student can't articulate the bot's purpose | Ask for a concrete example of a conversation they want to have |
| Student wants 20 tools | Negotiate down to 2-3 for MVP. Promise: "ื ืืกืืฃ ืขืื ืืืจื ืฉืื ืขืืื" |
| Knowledge base becoming enormous | Cap at ~2000 words. Offer: "ื ืฉืื ืืช ืืงืฆืจ ืืคืจืืืคื; ืื ืชืจืฆื ืืืืจ ืืืข ืืืืชื, ืื ืขืชืืื" |
| Student skipping audience decision ("ืืืื") | Push back for personal assistant - explicitly list contacts. Loop bugs live here. |
No .env file exists | Skill precondition failed - send student back to wa-setup |