This is a submission for the OpenClaw Challenge.
What I Built
KPLC Sentinel is an OpenClaw skill that tracks prepaid electricity for Kenyan households.
Some context for those outside Kenya: electricity here works differently from most of the world. Instead of getting a bill at the end of the month, most Kenyan homes use a prepaid system. You send money via M-Pesa (mobile money), receive an SMS from Kenya Power (KPLC) with a 20-digit token number and the units you purchased, then physically punch that token into a meter box mounted on your wall. The meter counts down as you use power. When it hits zero, the lights go off. No grace period, no warning.
There's also no usage dashboard. No app that tells you how fast you're burning through units or when you'll run out. The only way to check your balance is to walk to the meter and press 20# on the keypad. Most people don't bother until the power cuts.
On top of that, Kenya Power publishes a weekly PDF listing planned maintenance outages by area. If your neighborhood is on the list, you'll lose power for 8-10 hours on the scheduled day. These notices get posted on their website and sometimes shared on social media, but most people miss them entirely.
KPLC Sentinel fixes all of this. It tracks token purchases and meter readings, predicts when you'll run out, monitors your spending against a budget, spots usage patterns, and scrapes the KPLC maintenance schedule to warn you about planned outages. The whole thing runs through WhatsApp (or Telegram), which is how most Kenyans communicate anyway.
Architecture
βββββββββββββββ ββββββββββββββββββββ βββββββββββββββ
β WhatsApp / ββββββΆβ OpenClaw Agent ββββββΆβ SKILL.md β
β Telegram βββββββ (LLM routing) β β (routing) β
βββββββββββββββ ββββββββββββββββββββ ββββββββ¬βββββββ
β β
βββββββββΌβββββββββ βββββββββΌββββββββ
β SOUL.md β β entrypoint.py β
β (personality) β β (returns JSON)β
βββββββββ¬βββββββββ βββββββββ¬ββββββββ
β β
Agent composes βββββββββΌβββββββββ
response from ββββββΌββββ ββββΌβββ ββββΌββββ
JSON + persona βlogic.pyβ βparseβ βinit_dbβ
β(core) β β r.pyβ β .py β
ββββββββββββββββ ββββββ¬ββββ βββββββ ββββββββ
β HEARTBEAT.md β β
β(6hr/weekly) β ββββββΌββββββ ββββββββββββ
ββββββββ¬ββββββββ β SQLite β β KPLC PDF β
β β (local) β β (remote) β
ββββββββΌβββββββ βββββββββββββ ββββββββββββ
β sentinel.py β
β (alerts) β
βββββββββββββββ
All data stays local. The only outbound network call is to download KPLC's maintenance PDF from their public website.
How I Used OpenClaw
KPLC Sentinel is published on ClawHub. Install it with clawhub install kplc-sentinel.
The agent is the brain, the scripts are the data layer. This is the core design decision. The Python entrypoint returns structured JSON, not formatted text. The agent reads the data, applies the Stima persona from SOUL.md, and composes a natural response. The skill never prints a user-facing message. It returns things like {"action": "balance", "runway_hours": 18.0, "estimate_source": "appliances", "tip": "avoid running the water heater"} and the agent turns that into "Stima yako iko na roughly 18 hours. That's tight β avoid running the water heater to stretch your units."
This matters because it means the personality, tone, and language all come from the agent, not hardcoded strings. The same JSON data could be presented differently depending on the SOUL.md persona, the user's language preference, or the chat platform.
Natural language in three languages. The SKILL.md routing table maps natural language to commands. Users can say "stima itaisha lini?" (Sheng), "nimebakisha units ngapi?" (Swahili), or "will my power last until Monday?" (English) and the agent routes all three to the balance check. No translation code in Python. The agent handles it because SKILL.md tells it how to map intent to commands.
SKILL.md routing. The frontmatter and markdown body tell the LLM when to activate this skill. I rewrote the trigger section multiple times. The first version was too broad, catching messages like "what's your power move?" and trying to record them as meter readings. The final version requires the stima prefix for direct commands, auto-detects forwarded KPLC SMS by their distinct Token: / Units: format, and maps natural language about electricity to the right command.
HEARTBEAT.md scheduling. Every 6 hours, the agent checks the burn rate against the current balance. If there's less than 24 hours of power left, it sends a warning. It also checks for planned outages and budget status. Weekly on Monday, it sends a consumption summary with week-over-week comparisons and day-of-week patterns. All of this happens without the user asking.
Appliance-based estimates from day one. During onboarding, the skill asks what appliances you have. It maps each one to realistic consumption using a (wattage Γ typical hours per day) model. A fridge is 150W Γ 24h. A water heater is 3000W Γ 5 minutes. An iron is 1000W Γ 10 minutes. This means the skill can predict how long your tokens will last before you've ever taken a meter reading. As real readings come in, the actual burn rate takes over.
Outage reminders. When the skill detects a planned outage in your area, the agent sets a reminder for the evening before (around 8 PM) so you can charge your devices and plan ahead. The outage data includes ISO dates so the agent can calculate the reminder timing.
M-Pesa top-up instructions. When your balance is low, the agent doesn't just warn you. It tells you exactly how to buy more tokens: M-Pesa Paybill 888880, account number = your meter number, and asks you to forward the confirmation SMS back so it can track the purchase.
Multi-skill composability. Because the skill returns structured JSON, other OpenClaw skills can act on the data. A calendar skill could create events for planned outages. A payments skill could initiate M-Pesa top-ups when balance is critical. The skill doesn't need to know about these. The agent orchestrates.
Household onboarding. On first use, the skill asks how many people live in the house, what area they live in, and what appliances they have. The area is used to match against KPLC's outage schedule. The appliance list powers both consumption estimates and contextual tips when balance is low.
Budget tracking. Users set a monthly electricity budget (stima budget 3000). The skill tracks spending against it and warns at 80% and 100% thresholds.
Usage insights. The skill compares this week's consumption against last week and identifies which days of the week are heaviest. Turns raw data into something actionable.
PDF scraping for outage alerts. The skill downloads KPLC's Power Maintenance Notice PDF directly from their website, splits the two-column layout, and regex-parses all scheduled outages with areas, dates, and times. It then matches against the user's area. The PDF is cached for 1 hour to avoid hammering KPLC's server.
pdf_path = os.path.join(tempfile.gettempdir(), "kplc_schedule.pdf")
urllib.request.urlretrieve(KPLC_SCHEDULE_URL, pdf_path)
with pdfplumber.open(pdf_path) as pdf:
for page in pdf.pages:
# Split two-column layout into left and right halves
left = page.crop((0, 0, page.width / 2, page.height)).extract_text()
right = page.crop((page.width / 2, 0, page.width, page.height)).extract_text()
# Regex-parse AREA, DATE, TIME from each column
for text in [left, right]:
for area, date, time in re.findall(
r'AREA:\s*(.+?)\n.*?DATE:\s*(.+?)\s*TIME:\s*(.+?)[\n\r]',
text, re.IGNORECASE
):
if user_area.lower() in area.lower():
alerts.append({"area": area, "date": date, "time": time})
Sheng personality via SOUL.md. The agent persona ("Stima") speaks casual English with Kenyan Sheng/Swahili flavor. But the personality lives in SOUL.md, not in the Python code. The scripts return data; the agent adds the flavor. This is how OpenClaw skills should work.
Local SQLite storage. All data stays on the user's machine. No cloud sync, no external API beyond the LLM provider. The database auto-initializes on first message with owner-only file permissions.
Security hardening. Parameterized SQL everywhere. User input via stdin heredoc (no shell injection). Profile values sanitized before display (no chat injection). DB error messages don't leak internals. Input length capped. Outbound requests locked to kplc.co.ke HTTPS only.
Demo
Explore KPLC Sentinel on ClawHub
YOUTUBE
Try it yourself
If you have OpenClaw running, install the skill in one command:
clawhub install kplc-sentinel
pip install pdfplumber
If you're starting from scratch:
npm install -g openclaw@latest
openclaw onboard # set up your LLM API key
openclaw channels login --channel whatsapp # scan QR with your phone
clawhub install kplc-sentinel # install the skill
pip install pdfplumber # for outage PDF parsing
openclaw gateway # start the agent
For Telegram instead of WhatsApp:
openclaw channels add --channel telegram --token <your-bot-token>
Message your agent and say stima hi to start onboarding.
What I Learned
The biggest lesson: the agent should be the brain, not the messenger. My first version had the Python script printing fully formatted responses with emoji and Sheng phrases. The agent just parroted them. It worked, but it was a chatbot wearing an OpenClaw costume. The refactor to JSON output changed everything. Now the scripts return data, the agent applies the SOUL persona, and the same skill could work with a different personality or language without touching a line of Python.
SKILL.md routing instructions matter more than the code. If the LLM doesn't know when to activate your skill, the code never runs. I rewrote the trigger section multiple times before the agent reliably caught KPLC messages without false-triggering on unrelated ones. Adding natural language examples in Swahili and Sheng to the routing table was the last iteration, and it made the skill feel native rather than command-driven.
HEARTBEAT.md is what separates a chatbot from an agent. The skill went from "useful when you remember to check" to "warns you before you run out of power at 3 AM." That shift happened with 11 lines of plain English scheduling.
Parsing the KPLC maintenance PDF was harder than expected. The document uses a two-column layout, so extracting text normally merges the columns into garbage. Splitting each page into left and right halves before extraction fixed it. The date formatting is also inconsistent across entries, which broke the parser until I made it case-insensitive.
The appliance-based estimation model needed real-world thinking. My first version assigned flat kWh/day values to each appliance. A water heater got 4 kWh/day. But nobody runs a water heater all day. It runs for 5 minutes. The fix was switching to (wattage Γ typical hours per day): 3000W Γ 0.08h = 0.24 kWh/day. That one change made the estimates actually match what Kenyan households experience.
The Sheng personality makes a real difference. Early versions responded in plain English and felt like a utility. Adding "Sawa! Token imeingia" and "Stima yako iko na roughly 14 hours" made testers actually enjoy using it. But moving the personality from Python strings to SOUL.md was the right call. The agent should own the voice.
Local storage was the right call. Electricity usage data is personal, and Kenyan households shouldn't need to send consumption patterns to a cloud service to get a "you're running low" alert.
Security caught me early. The first version told the agent to run python3 entrypoint.py "<user message>", which meant shell metacharacters in a message could execute arbitrary commands. The fix was switching to stdin heredoc. User input never touches the shell interpreter.
ClawCon Michigan
No did not attend










Top comments (0)