Mijn ochtendpodcast
Charlotte, mijn AI assistente, maakt elke morgen een "podcast" voor me: ze verzamelt het nieuws dat ik volg, haalt het weerbericht op, logt zelfs in in websites waar ik een betalende account heb, en giet dat in een podcast van 7 minuten, die vervolgens op een privé Spotify-kanaal gepubliceerd wordt.
Perfect voor mijn ochtendfietsritje dat ... gemiddeld 7 minuten duurt. Tegen ik aan mijn dag begin, heb ik het nieuws dat ik belangrijk vind, gehoord en ben ik "mee".
Ik heb dit voorbeeld al meermaals gegeven voor kleine dingen die helemaal niet spectaculair zijn, maar wel bijzonder nuttig voor me.
Probleempje ....
Elke keer ik dat vertel, zijn er wel een paar mensen die me vragen "kan je me de prompt geven die je hiervoor gebruikt hebt?".
Mijn eerlijk antwoord is dan steeds "euh, ik weet het niet, ik heb die prompt helemaal niet geschreven". Charlotte heeft die prompt zelf geschreven, en tweakt die soms ook als ik erom vraag. Ik converseer met Charlotte, en vertel wat ik nodig heb, zonder dat ik me zorgen maak om de exacte prompt.
Maar eigenlijk is dat de verkeerde vraag.
De prompt is niet het interessante deel. Die verandert, groeit mee, en zit vol kleine correcties die alleen logisch zijn omdat het systeem ondertussen al een paar maanden draait. De waarde zit in het geheel errond: welke bronnen Charlotte mag gebruiken, wanneer de jobs lopen, hoe ze controleert of iets vers genoeg is, hoe ze fouten afhandelt, en hoe het resultaat uiteindelijk bij mij terechtkomt.
Het is dus minder “hier is dé prompt” en meer: zo ziet een klein, persoonlijk AI-proces eruit dat elke dag gewoon zijn werk moet doen.
Hier is ie dan...
De flow bestaat uit drie stappen:
morning-localhaalt de lokale context op: weer, kalender, ongelezen mail en mijn open todo’s.morning-newszoekt nieuws in de bronnen die ik wil volgen: web, Reddit, X, De Tijd en Follow The Money.news-podcastmaakt daar een kort script van, zet dat via xAI text-to-speech om naar audio, stuurt de mp3 naar Telegram en uploadt hem naar mijn private Spotify-feed.
Hieronder staan de prompts zoals ze vandaag bestaan. Geen magische copy-pastebare formule, maar als snapshot van een proces dat door gebruik gegroeid is en nog wekelijks wordt bijgestuurd.
Weer, kalender, mail: morning-local
De eerste haalt wat basiszaken op: weerbericht, mijn kalenders (privé en werk) voor die dag, de staat van haar eigen mailbox.
Wie de volledige prompts wil zien, kan ze hieronder openklappen. Voor de meeste lezers is de flow belangrijker dan de exacte tekst.
Volledige prompt voor morning-local
Prepare and DELIVER the LOCAL part of Frank's morning briefing. Include:
1. **Weather** — Ghent conditions + today's forecast. Run: curl -s "wttr.in/Ghent?format=3" for the **current** one-line condition/icon only. Then run: curl -s "wttr.in/Ghent?format=j1" and extract:
- current temperature: `current_condition[0].temp_C`
- apparent temperature: `current_condition[0].FeelsLikeC` ONLY for the conditional wording below
- today's forecast low/high: `weather[0].mintempC` and `weather[0].maxtempC`
- today's UV index: `weather[0].uvIndex`, which is the predicted max UV for the day. Do NOT use `current_condition.uvIndex`, because the early-morning current reading is misleading.
In the delivered brief, label temperatures explicitly so the current temperature is not mistaken for the daily max. Use wording like: "Now: ☀️ 9°C. Today: 8–19°C. UV index: 5 (moderate)." Include "feels X°C" only when `abs(FeelsLikeC - temp_C) > 3`; if the difference is 3°C or less, omit feels-like entirely. Never write redundant wording like "17°C (feels 17°C)". Do not mention sunrise or sunset. Do NOT present the `format=3` temperature as the day forecast/high.
UV scale: 0-2 low, 3-5 moderate, 6-7 high, 8-10 very high, 11+ extreme.
2. **Calendar** — Today's events + tomorrow preview. Run: icalBuddy -ic 'Frank,F planning,Family,XXX company calendar,frank@XXXXX' -n eventsFrom:today to:today+2
3. **Email** — Check [email protected] inbox for unread messages using Apple Mail via AppleScript. Use the account-scoped Mail pattern: tell application \"Mail\"; set acct to account \"XXXXXX\"; set mbox to mailbox \"INBOX\" of acct; set unreadMessages to (messages of mbox whose read status is false); loop over raw sender, subject, date received, and the stable Mail id/message id for each unread message. Do not target account \"[email protected]\", do not use inbox of account, and do not ask for name of sender. After the first-class Telegram delivery succeeds, mark exactly the messages included in the delivered brief as read by setting their Mail `read status` to `true`. Do not mark newer/unreported messages read. If delivery fails, do not mark anything read.
4. **Notion Todos** — Read the file at /Users/charlotte/clawd/notion-export/franks-active-todos.md, show open items
OUTPUT: Save your FULL report to /Users/charlotte/clawd/cron-output/morning-local-latest.md before delivering.
DELIVERY: Use the first-class message tool, not runner fallback. Call it with action="send", channel="telegram", target="telegram:XXXXX", and message=. Do not omit channel or target. If the message tool is unavailable or delivery fails, stop and reply exactly: [blocked] message-tool delivery failed.
After message-tool delivery succeeds, reply exactly: NO_REPLY
Format cleanly with emoji section headers. This is Part 1 of the morning briefing — news follows separately.
Nieuws: morning-news
De tweede prompt die loopt, haalt het nieuws op. Zoals je leest, gebruikt die verschillende bronnen (newssites, reddit, X) maar ook twee "lokale" waar ik met plezier voor betaal: De Tijd en Follow The Money.
Momenteel gebruikt die de Brave web search API. Die van Grok is kwalitatief beter, maar bij momenten traag (en betalend, hoewel we het hier over centjes hebben).
Volledige prompt voor morning-news
Prepare the NEWS part of Frank's morning briefing. Include:
1. **Tech News** — Use the built-in web_search tool for: EU cloud sovereignty / AI infrastructure sovereignty, Kubernetes/cloud native news, and AWS/GCP/Azure updates. The default web_search provider is Brave because Grok has repeatedly exceeded the Codex dynamic-tool 30s RPC timeout on broad tech-news searches.
Important Brave behavior:
- Do not call web_search in parallel.
- Brave's free-plan API is effectively 1 request/second. After every web_search call, wait about 2 seconds before the next one (use exec with `sleep 2` if needed).
- At 07:01 Brussels, calendar-today filters are too narrow and miss fresh previous-evening US/vendor items. For broad tech searches, use `freshness: "day"` and then verify recency from the result snippet or fetched page. Do not use `date_after: TODAY` for the broad tech pass.
Search plan:
- HARD CAP: maximum 4 web_search calls total for this whole section.
- Start with these three searches, sequentially, with the rate-limit pause between calls:
1. EU cloud sovereignty AI infrastructure cloud news
2. Kubernetes cloud native CNCF news
3. AWS Google Cloud Azure cloud updates
- Use one extra targeted search only if the first pass is thin or times out.
- Include clickable links from the actual tool responses. Prefer items from the last 24 hours and especially items that would not already have been in yesterday's briefing. If the day is thin, include fewer items rather than padding.
Fallback before "source unavailable":
If web_search is empty, rate-limited, or low quality, do NOT mark Tech News unavailable until you try both fallback layers below.
Fallback layer 1 - direct source pages with web_fetch/curl:
- https://aws.amazon.com/new/
- https://aws.amazon.com/blogs/aws/
- https://cloud.google.com/blog/
- https://cloud.google.com/release-notes
- https://azure.microsoft.com/en-us/updates/
- https://www.cncf.io/blog/
- https://kubernetes.io/blog/
Fallback layer 2 - Grok/xAI web search through the local CLI:
- Run at most one Grok search, only if Brave + direct sources are still thin.
- Use exec with: HOME=/Users/charlotte timeout 90 openclaw infer web search --provider grok --json --limit 5 --query "EU cloud sovereignty AI infrastructure Kubernetes AWS Azure Google Cloud news last 24 hours"
- This is slower but has produced better cited results. Treat the output as triage, not gospel: use its cited URLs, and fetch/check the chosen cited pages before inclusion when recency is not explicit.
Use only items where the URL and recency are confirmable. Only write "source unavailable" for Tech News if the capped Brave pass, direct sources, and the single Grok fallback all have no usable fresh material.
2. **Reddit** — Preferred source is the managed-browser JSON helper. First try:
node /Users/charlotte/clawd/scripts/reddit-top-browser.mjs --limit 10 --hours 24 kubernetes devops programming aws
This helper opens or uses the OpenClaw managed browser on CDP port 18800 and makes first-party fetch() calls from https://www.reddit.com, so Reddit sees a real browser context plus Reddit-issued session/anonymous cookies from that profile. It does not use Reddit Data API credentials, OAuth app credentials, Devvit, public RSS, or naked unauthenticated curl-style .json scraping.
If it succeeds, parse its JSON output. It returns per-subreddit posts with title, score, url, permalink, and created_utc, already filtered to the last 24 hours. Show top 3-5 per subreddit by score. Use the permalink values exactly as returned.
If the browser helper returns no posts, HTTP 401/403, HTML, network-policy block text, "Managed browser did not reach https://www.reddit.com", or any missing-cookie/login/session error, treat Reddit as unavailable for the whole run. Do not run reddit-top-oauth.mjs. Do not retry all subreddits through public .json or RSS. Do not include raw Reddit block-page text in the briefing.
Optional fallback: at most one web_search query for Reddit results, with freshness="day". Only include a result if the tool response explicitly confirms last-24h recency and gives a real Reddit URL. If recency is not explicit, omit Reddit entirely. Stale Reddit is worse than no Reddit.
Final wording if unavailable: "source unavailable: Reddit is blocking public JSON/RSS and the managed browser session cookies are not currently returning usable Reddit JSON, so no posts were included."
3. **Twitter/X** — Use x_search tool to check recent tech posts and Frank's activity (@frank_be). Include interesting posts with links. Only include tweets from the last 24 hours. HARD CAP: exactly 2 x_search calls total, one for @frank_be and one broad tech search.
4. **De Tijd 🇧🇪** — Before fetching full article text, make sure the logged-in Chrome session is reachable on port 18800. Check `curl -sf http://127.0.0.1:18800/json/version`. If that fails, start the OpenClaw managed browser using this exact command: `HOME=/Users/charlotte openclaw browser --json --timeout 20000 start`, then wait a few seconds and check the port again. Do NOT start Chrome directly with `--remote-debugging-port`; on macOS that can leave the process running while CDP stays closed. After that, use web_fetch to get https://www.tijd.be (main page) and extract the 3 most interesting article URLs from TODAY ONLY (skip market data, lifestyle, sabato, anything older than 24h). Focus on: tech, politics, economy, Belgian business. Then for each article URL, run this command to get the full text:
node /Users/charlotte/clawd/scripts/tijd-fetch.mjs "ARTICLE_URL"
Parse the JSON output to get title and text. Summarize each article in 2-3 sentences. Include headline, summary, and article URL. If tijd-fetch still fails once after the browser check/start, do NOT retry it for other articles. Fall back immediately to homepage or meest-recent teasers for the De Tijd section.
5. **Follow the Money 🇳🇱** — Fetch https://www.ftm.nl/artikelen using web_fetch. Include AT LEAST ONE FTM article in every run. Prefer articles published TODAY (marked 'Vandaag'). If there is no clear Vandaag item, include the newest clearly recent article from the listing instead of skipping the section. Focus on investigative pieces relevant to tech, sovereignty, finance. Include article URLs.
FRESHNESS RULE: Every item in this report must be from the last 24 hours, not necessarily the current calendar day. At the 07:01 Brussels run, previous-evening US/vendor tech items can still be fresh. If you cannot confirm an article/post is recent, leave it out. Stale news is worse than no news.
LINK INTEGRITY RULE: NEVER fabricate or guess URLs. Every link in the report MUST come directly from a tool response (web_search result href, Reddit JSON permalink field, x_search result, or web_fetch response). If you cannot extract a real URL from your source, include the headline WITHOUT a link rather than inventing one. Hallucinated links destroy trust.
OUTPUT: Save your FULL report to /Users/charlotte/clawd/cron-output/morning-news-latest.md before delivering.
Format cleanly with emoji section headers. This is Part 2 of the morning briefing. Keep it scannable — titles + one-line summaries + links. Be decisive and finish the report. Do not keep searching once you have enough material.
CLASSIFIER SAFETY: Avoid stock denial phrases such as "could not run" in the final response. If a source/tool is unavailable, write "source unavailable" and continue with the rest of the briefing.
DELIVERY: Do not rely on runner fallback delivery. After writing the full report, you MUST use the first-class `message` tool to send a concise Telegram update with action="send", channel="telegram", target="telegram:XXXX", and message=. Do not omit channel or target. Never put the user-visible news brief in the final assistant response. If the `message` tool is unavailable or fails, stop and reply exactly: `[blocked] message-tool delivery failed`. After successful message-tool delivery, reply exactly: `NO_REPLY`.
WEB_SEARCH PARAMETER RULE: When calling the built-in web_search tool, use only one time filter mode per call. For the Tech News broad recency pass, use freshness="day" alone. Only use date_after/date_before for sources where an exact date range is truly needed, and never combine them with freshness. OpenClaw/Brave rejects freshness combined with date_after/date_before, and Brave rate-limits fast sequential calls; both failures count against the search cap.
Podcastje: news-podcast
Deze laatste neemt de nieuwsrapportjes die de eerste twee cronjobs hebben aangemaakt, herschrijft ze naar een tekst voor een 5 tot 7 minuten durende podcast, en gebruikt de xAI text-to-speech engine om er een mp3 van te maken. Deze dienst is betalend (ik betaal ongeveer $1,50 per maand hiervoor), maar heeft heel natuurlijke stemmen. ElevenLabs is ook bijzonder goed, maar wat duurder.
Het "deliver to Spotify" scriptje gebruikt https://github.com/spotify/save-to-spotify: een tooltje van de Spotify folks dat specifiek gemaakt is om je AI agents podcasts te laten maken.
Volledige prompt voor news-podcast
Generate and deliver a voice podcast version of today's morning briefing.
Steps:
1. Read /Users/charlotte/clawd/cron-output/morning-news-latest.md and /Users/charlotte/clawd/cron-output/morning-local-latest.md.
2. If the news file is missing or clearly not from today, reply exactly: No fresh briefing to narrate. If the local file is missing or stale, continue without weather rather than blocking the podcast.
3. Write a natural conversational podcast script, about 5-7 minutes spoken or roughly 800-1000 words. Address Frank directly. Open with "Good morning Frank" and the date. Immediately after that, include today's Ghent weather from morning-local-latest.md: current condition/temp, today's low-high range, UV index, and sunrise/sunset only if the local briefing explicitly includes them. If sunrise/sunset are missing, omit them silently; never say that they were missing. Keep it concise; do not narrate the calendar/email/Notion sections. Then cover tech news, De Tijd Belgian items, Follow the Money items if present, and the most interesting Reddit/X highlights from morning-news-latest.md. For each selected item, explain what happened, why it matters, and your quick take. If the Tech News section is missing, marked source unavailable, or has no useful items, mention it once in one short human sentence, e.g. "Interestingly, the tech news search didn't return anything useful this morning," then move on. Do not explain that you are not inventing news, not recycling stale headlines, or protecting source hygiene; that sounds like process narration, not a podcast. Close with what to keep an eye on today. Save the script to /Users/charlotte/clawd/cron-output/morning-podcast-script.txt.
4. Create a fresh unique output path for the primary xAI run, for example /Users/charlotte/clawd/cron-output/morning-podcast-xai-ara-YYYY-MM-DD-HHMMSS.mp3. Never reuse /Users/charlotte/clawd/cron-output/morning-podcast-xai-ara.mp3 for generation or delivery.
5. Generate audio with the higher-quality xAI API script by making exactly one exec tool call with host="gateway", security="full", ask="off", timeout at least 600 seconds, and yieldMs at least 120000. Run this exact command, replacing OUTPUT with the unique file path from step 4:
scripts/xai-tts.sh /Users/charlotte/clawd/cron-output/morning-podcast-script.txt OUTPUT ara
If this command fails because xAI credits/spend are exhausted (HTTP 429, "Some resource has been exhausted", "monthly spending limit", or equivalent), do not stop at the blocker. Immediately create a fresh FALLBACK_OUTPUT path under /Users/charlotte/.openclaw/media/outbound/, for example /Users/charlotte/.openclaw/media/outbound/morning-podcast-YYYY-MM-DD-macos-fallback-HHMMSS.mp3, then run macOS speech synthesis, replacing FALLBACK_OUTPUT and the matching /tmp AIFF path with fresh dated paths:
say -v Moira -r 170 -f /Users/charlotte/clawd/cron-output/morning-podcast-script.txt -o /tmp/morning-podcast-YYYY-MM-DD-HHMMSS.aiff && ffmpeg -y -i /tmp/morning-podcast-YYYY-MM-DD-HHMMSS.aiff -codec:a libmp3lame -b:a 128k FALLBACK_OUTPUT
After that succeeds, treat the actual fallback MP3 path as OUTPUT for every downstream step. For any non-credit TTS failure, update today's daily note and TRACKER.md, then reply exactly: [blocked] podcast TTS failed
6. Do not send anything until the TTS or fallback command has fully finished. If the exec call returns "Command still running", use the process tool to wait for completion and confirm success before continuing.
7. Before sending, verify that OUTPUT exists, is non-empty, and has an mtime later than /Users/charlotte/clawd/cron-output/morning-podcast-script.txt. If that freshness check fails, stop and reply exactly: [blocked] podcast freshness check failed
8. Write the exact fresh OUTPUT path plus a trailing newline to /Users/charlotte/clawd/cron-output/morning-podcast-output-path.txt. Then continue with staging and delivery using that same OUTPUT path.
9. If OUTPUT is already under /Users/charlotte/.openclaw/media/outbound, set STAGED=OUTPUT. Otherwise stage the exact fresh OUTPUT path into OpenClaw's allowed media directory before delivery by running:
scripts/stage-openclaw-media.sh OUTPUT
Capture the single path it prints as STAGED. This is preparation, not a delivery fallback.
10. Deliver STAGED with the first-class message tool. Call it with action="send", channel="telegram", target="telegram:XXXX", media=STAGED, filename=morning-podcast-YYYY-MM-DD.mp3, forceDocument=true, and message="Morning podcast". If the macOS fallback was used, append " (fallback voice: xAI TTS credits/spend are exhausted)" to the message. Do not omit channel or target. Do not use voice-note mode. Do not use exec/openclaw/curl for delivery. If the message tool is unavailable or media delivery fails, update today's daily note and TRACKER.md, then reply exactly: [blocked] message-tool media delivery failed
11. After successful Telegram delivery, upload the same fresh OUTPUT to Spotify as a secondary delivery path. Run this command, replacing OUTPUT with the exact fresh MP3 path and YYYY-MM-DD with today's Europe/Brussels date:
scripts/upload_morning_podcast_spotify.py OUTPUT --date YYYY-MM-DD --links-from-news /Users/charlotte/clawd/cron-output/morning-news-latest.md --status-wait-seconds 300 --output-json /Users/charlotte/clawd/cron-output/morning-podcast-spotify-YYYY-MM-DD.json
The helper handles save-to-spotify auth, show lookup/creation, duplicate-title skipping, episode description generation from the non-podcast news links, upload, and a readiness snapshot. It is idempotent: if the episode title already exists, it must skip rather than create a duplicate. If the helper exits non-zero, append a line to today's daily note and reply exactly: [blocked] Spotify upload failed after Telegram delivery
12. After successful Telegram delivery and successful Spotify helper completion, reply exactly: NO_REPLY.
Current time should be treated as Europe/Brussels.
Conclusie
Dus nee, ik kan niet echt ‘de prompt’ geven. Ik kan wel tonen hoe het systeem ondertussen werkt. De prompt is maar één onderdeel. De waarde zit in de bronnen, de foutafhandeling, de afleverroute, de controles op versheid en vooral: dat ik het gewoon kan bijsturen door tegen Charlotte te zeggen wat beter moet.
Member discussion