A field guide · October 2026 · read the post
Cora 🪸
An AI familiar that lives on a spare MacBook Air in my house, talks to me on Telegram, and quietly turns my email, photos, voice memos and whereabouts into a written record of our life.
Primer
The pieces, in plain English.
Here's every tool this guide mentions, what it is, and how I use it. Each one has a tag for anyone building their own: get it, build your own or optional.
OpenClaw
Get itOpen-source software that turns an AI model into an assistant that lives in your chat apps.
It runs as a background service on one computer. It connects a model to Telegram, gives it tools (files, shell, web search, other services), remembers things in plain text files, and runs jobs on a schedule.
Cora is an OpenClaw agent. The model does the thinking, and OpenClaw gives it a home, a phone number and an alarm clock.
Codex & ChatGPT
Get itOpenAI's subscription and its coding agent. This is the account that powers Cora.
OpenAI lets you sign in to OpenClaw with a ChatGPT plan, so Cora's thinking comes out of a flat monthly subscription instead of a per-token bill. Anthropic doesn't allow a Claude subscription to power OpenClaw, which is why Cora isn't on Claude.
Cora runs on the ChatGPT plan, with metered API keys only as a backup. I also use Codex as a second reviewer on code that Claude writes.
Claude Code
Get itAnthropic's AI agent that works in your terminal: it reads files, runs commands, writes code and fixes what breaks.
Claude Code is awesome at managing things. I don't edit OpenClaw config by hand. Claude Code installs it, upgrades it, writes Cora's scripts, chases down why a job failed, and backs up the config before every change.
Claude Code runs on my Claude subscription, which is allowed because it's Anthropic's own tool. Claude Code maintains Cora, and ChatGPT runs her.
Git & GitHub
Get itGit keeps the full history of a folder of files. GitHub stores that history online and adds a place to plan and review changes.
Getting a GitHub account early is one of the smartest moves in this whole setup. An agent's brain is a folder of text files, so with git every change an agent makes becomes a commit you can read and undo. It's also an off-machine backup: if the Mac dies, you clone the repo onto a new one and you're back.
Claude Code works naturally in repos, issues and pull requests. Cora's workspace, my vault and all my projects are git repos, and every agent edit has a commit message saying who changed what and why.
AWS
Get itAmazon's cloud: storage, servers, email sending, databases, and just about anything else, billed by use.
Having an AWS account ready is great, because when you need something (backups, a small server, a website, sending email) an agent can set it up in minutes. Cora started out on a $12/month AWS Lightsail server.
I use it for backups and for hosting whatever my projects need. Set a billing alert on day one so a runaway experiment can't surprise you.
Cotrugli
Build your ownMy personal data lake: photos, documents, messages, location, health and money records, all in one searchable place that agents can query.
I built it, and it's the reason Cora knows so much context. It's super personal, so it isn't something I share, and I don't have an off-the-shelf equivalent to recommend. The table further down lists what it does, which doubles as the list of jobs you'd give your own agent.
Cora searches it, files tasks in it, and uploads photos to it before linking them from notes.
Obsidian
OptionalA notes app that reads a plain folder of Markdown files. My “vault” is that folder, kept in git.
Obsidian is becoming less and less important to me. It's a nice viewer and an occasional manual editor, but I hardly open it. Cora and Claude Code write straight into the folder, and I mostly read the results through them.
If you want somewhere to browse what your agent writes, it's free and pleasant. You don't need it.
Telegram
Get itThe chat app I use to talk to Cora.
Making a bot is free and takes two minutes, bots work in DMs and groups, and the phone app makes Cora feel like texting a person.
OpenClaw also supports Discord, Slack and others if you'd rather live there.
MCP shows up everywhere below. It's a standard plug that lets an agent use another service's tools. Cotrugli and my heat pumps each expose an MCP server, and Cora plugs into both.
The wiring
One gateway, one bot, everything local.
OpenClaw runs a single gateway process under launchd. Telegram is the only front door. Because the gateway sits on a real Mac instead of a cloud VM, Cora can read the things that only exist on Apple hardware: Messages, Photos, Voice Memos, Reminders and Contacts.
In
- Telegram DMs (allowlist: me)
- A family group chat
- A friends group chat
- Heartbeat every 30 min
- ~20 scheduled jobs
localhost only
token auth from a file
launchd keeps it alive
kicked fresh at 3:40 AM
Out
- Telegram replies, only when worth it
- Obsidian vault commits, signed “Cora on behalf of John”
- Cotrugli tasks and artifacts
- ESPN fantasy moves (judged)
Models
gpt-6-solon my ChatGPT plan (OAuth)- falls back to metered
gpt-5.6-luna - then metered Claude Haiku 4.5
- cheap
lunapinned on high-volume jobs
MCP servers
- Cotrugli: my personal data lake
- Mitsubishi HVAC (6 tools only)
- Tidbits · Brave search
Local Mac sources
- Messages
chat.db+ Contacts - Photos library
- Voice Memos
- Gmail & Calendar via
gog - Apple Reminders
A day in Cora's life
What she does while I'm not looking.
Times Pacific. Plus weekly: skill-collection review, and a Tuesday 6 AM job where Cora upgrades her own fantasy-sports CLI and verifies it with doctor --live. Almost every run ends in NO_REPLY, so I only hear about it when something changed.
The fun part
Things I didn't expect an agent to be good at.
She dreams, and keeps a diary
OpenClaw's memory-core plugin runs a “dreaming” pass at 3:00 AM that promotes the day's short-term notes into long-term memory. Side effect: a 2,200-line DREAMS.md of surreal, oddly tender diary entries, often ending in a haiku.
“a cron job wearing slippers”
Our life, written down automatically
Every 30 minutes she reads new email, texts, photos, voice memos and my phone's location track, and writes what matters into my Obsidian vault: daily notes, people files, trips, purchases, a health log. Photos get uploaded to my artifact lake first so the vault links to the real bytes. Cora transcribes voice memos on the Mac, starting with Apple's own transcript and falling back to local models.
Fantasy football on autopilot, with a judge
She watches my three ESPN leagues and FantasyPros news every heartbeat. For routine moves (benching an OUT player, a $2 waiver claim) she builds a candidate packet and hands it to a second agent, fantasy-judge, which has no shell, no messaging and no channel. Only a strict-JSON APPROVE for that exact move lets her write, and it expires five minutes after the ESPN snapshot. Stars, trades and big bids come to me.
A to-do list that tidies itself
Anything I commit to in an email or voice memo becomes a task. Every two hours a silent sweep dedupes, expires stale errands and closes tasks it has evidence I finished. Twice a day I get one digest with up to five numbered offers, and I reply “yes 1” or “drop 3” right in Telegram.
Every agent session lands in the vault
Hooks on OpenClaw, Claude Code and Codex all call one script that files a session note in that day's folder. Whichever agent did the work, there's a record. A design rule I like: integrations must call the script, so the record never depends on an agent remembering to write it.
House and group chats
She can read and set the Mitsubishi heat pumps, and she plays a careful guest in two group chats. Each group has its own system prompt: help freely, but never volunteer anything from my vault, email, health or finances unless I bring it in.
The data lake underneath
What Cotrugli does so Cora doesn't have to.
Cotrugli runs on its own all day: collectors on the Mac push my messages, photos, voice memos, contacts and files to it, and my phone streams location. It processes everything the moment it lands, so when Cora asks “where was I Tuesday?” or “what did that pharmacy label say?” the answer is already sitting there. If you don't have a data lake, this is the list of jobs you'd hand your agent instead, one scheduled script at a time.
Location
Takes a live feed from the phone (Overland or OwnTracks), keeps a separate track for each person, works out where we stayed and for how long, and gives untagged photos a location from the track.
Poll a location app herself, store the points, and figure out visits from raw GPS jitter on every run.
Medications
Checks every photo for medication. A Claude vision model logs confident doses automatically, and anything uncertain waits in a queue for a human to confirm.
Look at every new photo with a vision model and keep her own dose log.
Messages
Copies iMessage every 30 seconds into a searchable history, one conversation per day, with embeddings for meaning-based search.
Read the Messages database directly and search it from scratch each time.
Photos & faces
Recognizes faces and assigns them to people, keeps track of who appears with whom, reads text in images, decodes QR codes, and tags what's in each picture.
Run face and text recognition on every photo and remember who's who.
Audio & video
Transcribes recordings and video, with a vocabulary rebuilt every week from the names of our people and medications so they're spelled right.
Transcribe everything herself and fix the names by hand.
People
Turns my contacts into people, links phone numbers and emails to them, seeds face recognition from contact photos, and flags conflicting cards for me to merge.
Match every sender and face to a person on her own.
Documents
Sweeps up screenshots and Downloads, turns PDFs into text, reads the dates in them, and writes a summary and search index for every file.
Open and read each file whenever a question touched it.
Money
Syncs bank and card accounts, parses statement PDFs and matches them to transactions, and matches Amazon orders to the card charges that paid for them.
Parse statements and reconcile accounts by hand.
Health
Pulls Oura sleep and readiness every six hours.
Call the Oura API and keep her own history.
House
Records the heat pumps every five minutes. Photos that catch one of my printed QR labels log where that item was last seen.
Poll the thermostat and track where things are kept.
Tasks & notes
Mirrors my open GitHub issues into the task queue every 30 minutes and resyncs the vault every five, so one search covers code work, life tasks and notes.
Check GitHub and the vault separately on every sweep.
Still in progress: a daily narrative of our life and a small AI newsroom that writes articles from all this data. Cora handles the judgment calls Cotrugli leaves for a human, like confirming a medication or merging two contacts, and the triage jobs on the Mac still decide what's worth writing down.
Key decisions
What I chose, and why.
A spare Mac at home, not a $12 cloud VM
My first agents ran on AWS Lightsail. Moving Cora to a MacBook Air unlocked Messages, Photos, Voice Memos, Reminders and Contacts. Sleep is disabled.
One agent, one bot, one owner
Telegram DMs are allowlisted to my user ID. Groups require an @mention unless I've given that group a named prompt. Only I can run owner commands.
Gateway bound to localhost
No Tailscale, and the gateway listens only on localhost, so nothing outside the Mac can reach it. Risky node commands (camera, screen recording, SMS, adding contacts or events) are denied outright.
Secrets live in files, never in config
Every key is a chmod 600 file in ~/.secrets, referenced from openclaw.json as a file SecretRef. I can paste the config anywhere without leaking a token.
Subscription first, metered as backup
The primary model rides my ChatGPT plan through OAuth. If that fails, the chain drops to a metered OpenAI key, then to Anthropic. A provider outage degrades her; it doesn't silence her.
Scripts fetch, the model judges
Each triage job says “Run exactly: scripts/…py”. The script owns cursors, state and dedupe and emits only what's new. The LLM decides what matters. Cheaper, repeatable, and no re-reporting the same email.
Least-privilege tools per job
Every cron job carries its own tool allowlist. The location job gets three Cotrugli tools and file access, nothing else.
Contracts in docs, with a deployment gate
Long-lived behaviors (tasks, location, fantasy autonomy) live in docs/*-contract.md. Changing one means: update the cron payload, check its tool list, run a live smoke test, then let it loose.
Quiet by default
Heartbeat “OK”s are hidden. Jobs answer NO_REPLY when nothing changed, and they check an alert ledger before pinging me twice about the same thing.
Telegram never waits
Slow work (vault pushes, builds, network calls) runs in the background. She answers first and reports the side effect later, so she never seems to go cold.
Ask before anything leaves the house
Reading and organizing are free. Sending email, posting or buying needs my yes. Judged fantasy football moves are the one standing exception. Gmail runs with a no-send guard.
The workspace is a git repo
Her identity, rules, scripts and contracts are versioned, so every change to how she behaves has a commit message. I also save a dated backup of the config before every change.
Build your own
Build your own in an evening.
You need a Mac that can stay on (an old laptop is perfect), a Telegram account, and a ChatGPT plan or an API key.
My recommendation
Don't type these yourself. Hand the whole thing to Claude Code.
Install Claude Code on the agent Mac, open a session, and paste the prompt below. It will run each step, read the errors, check openclaw --help when a flag has changed, and only stop when it needs you: approving macOS prompts, signing in to ChatGPT, talking to BotFather, and DMing your bot to pair. That's how I built and maintain Cora. The steps further down are what it will do, so you can follow along.
Set up OpenClaw on this Mac as an always-on personal agent I talk to on Telegram. Follow John Wulff's Cora Field Guide (I'll paste the setup steps below) and: - run every step yourself; stop only for things I must do (BotFather, ChatGPT sign-in, macOS permission prompts, pairing from my phone) - keep every secret in a chmod 600 file under ~/.secrets, referenced from config - bind the gateway to loopback, DMs allowlisted to me, groups mention-only - back up ~/.openclaw/openclaw.json before every config change - verify with openclaw doctor, openclaw security audit, and a real Telegram reply - then interview me to write IDENTITY.md, SOUL.md, USER.md and a small HEARTBEAT.md, and commit the workspace to git Ask before installing anything not in the guide.
Paste the steps below after the prompt, or just share this page's link with the session.
What the session will run
Make the Mac an always-on server
sudo pmset -a sleep 0 disksleep 0 autorestart 1
Keep FileVault and the login password on. After a power cut, log in once so the agent's background service starts again. If you want to manage the Mac from another computer, turn on Remote Login under System Settings → General → Sharing and use SSH keys only.
Install Homebrew, Node and OpenClaw
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" brew install node git gh npm install -g openclaw@latest --allow-scripts=openclaw openclaw --version
Check
npm --versionfirst. On npm 11.15 or earlier, drop--allow-scripts=openclaw. Newer npm blocks OpenClaw's install scripts without it.Create the Telegram bot
In Telegram, message @BotFather:
/newbot, pick a name and a username ending in_bot, and copy the token. Then under/mybots → Bot Settingsset Group Privacy: off (so it can read groups) and Allow Groups: off (so strangers can't add it). Flip Allow Groups on only while you add it to a group.Put secrets in files
Copy the bot token to your clipboard, then:
mkdir -p ~/.secrets && chmod 700 ~/.secrets pbpaste > ~/.secrets/telegram-bot-token && chmod 600 ~/.secrets/telegram-bot-token pbcopy < /dev/null
Do the same for any API keys later (
openai-api-key,anthropic-api-key,brave-api-key).Run onboarding with ChatGPT sign-in
openclaw onboard --auth-choice openai --agent-name <your-agent-name>
It walks you through the model, gateway, workspace and Telegram. When it asks for the bot token, paste it once. The next step moves it into the secrets file.
Point the token at the file and lock things down
openclaw config set secrets.providers.telegramtoken --provider-source file \ --provider-path ~/.secrets/telegram-bot-token --provider-mode singleValue openclaw config set channels.telegram.botToken --ref-provider telegramtoken \ --ref-source file --ref-id value openclaw config set gateway.bind loopback openclaw config set channels.telegram.groups '{"*":{"requireMention":true}}' --strict-jsonInstall the service and pair yourself
openclaw daemon install openclaw doctor --fix openclaw security audit
DM your bot. It replies with a pairing code and your Telegram user ID, so write the ID down. Approve the code, then close the door so nobody else can ask:
openclaw pairing list openclaw pairing approve <code> openclaw config set channels.telegram.allowFrom '["<your-telegram-user-id>"]' --strict-json openclaw config set channels.telegram.dmPolicy allowlist
Give it a personality and version it
The workspace files are the agent.
IDENTITY.mdis its name and vibe,SOUL.mdits values,USER.mdis about you,AGENTS.mdholds the rules, andHEARTBEAT.mdis the checklist it runs every 30 minutes.cd ~/.openclaw/workspace git init && git add -A && git commit -m "Birth" gh auth login gh repo create <your-agent-name>-workspace --private --source . --push open -e IDENTITY.md SOUL.md USER.md HEARTBEAT.md
If this is the Mac's first commit, git will ask who you are. Set it once with
git config --global user.name "Your Name"andgit config --global user.email you@example.com.That private GitHub repo is your off-machine backup. Run
git statusbefore each push so nothing you didn't mean to share goes up. Your secrets live in~/.secrets, outside the workspace, so they never get committed.Start
HEARTBEAT.mdsmall. Mine began as “check email, tell me if anything is urgent.”Add a fallback model chain
Optional, and it needs pay-as-you-go API keys from OpenAI and Anthropic saved as files in step 4. Merge this into
~/.openclaw/openclaw.json. It defines the two metered providers, points them at your key files, and puts them behind your ChatGPT plan:"secrets": { "providers": { "oaikey": { "source": "file", "path": "~/.secrets/openai-api-key", "mode": "singleValue" }, "anthropickey": { "source": "file", "path": "~/.secrets/anthropic-api-key", "mode": "singleValue" } } }, "models": { "providers": { "openai-metered": { "baseUrl": "https://api.openai.com/v1", "api": "openai-responses", "apiKey": { "source": "file", "provider": "oaikey", "id": "value" }, "models": [{ "id": "gpt-5.6-luna", "name": "GPT-5.6 luna (metered)", "input": ["text", "image"] }] }, "anthropic-metered": { "baseUrl": "https://api.anthropic.com", "api": "anthropic-messages", "apiKey": { "source": "file", "provider": "anthropickey", "id": "value" }, "models": [{ "id": "claude-haiku-4-5", "name": "Claude Haiku 4.5 (metered)", "input": ["text", "image"] }] } } }, "agents": { "defaults": { "model": { "primary": "openai/gpt-6-sol", "fallbacks": ["openai-metered/gpt-5.6-luna", "anthropic-metered/claude-haiku-4-5"] } } }Use the full path (
/Users/you/.secrets/…) if~doesn't resolve. Then runopenclaw models statusto confirm every link authenticates.Schedule your first job
openclaw cron add --name "Morning brief" --cron "30 7 * * *" --tz America/Los_Angeles \ --session isolated --announce --channel telegram --to <your-telegram-user-id> \ --tools "web_search" \ --message "Look up today's weather for <your city>. Send a 2-line brief. If it's an ordinary day, reply NO_REPLY."
Your Telegram user ID is the one from the pairing reply. Use
--modelto put busy jobs on a cheaper model.Turn on dreaming and a nightly restart
openclaw config set plugins.entries.memory-core.config.dreaming.enabled true cat > ~/Library/LaunchAgents/local.openclaw-nightly-restart.plist <<'EOF' <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"><dict> <key>Label</key><string>local.openclaw-nightly-restart</string> <key>ProgramArguments</key><array> <string>/bin/launchctl</string><string>kickstart</string><string>-k</string> <string>gui/501/ai.openclaw.gateway</string> </array> <key>StartCalendarInterval</key><dict> <key>Hour</key><integer>3</integer><key>Minute</key><integer>40</integer> </dict> </dict></plist> EOF sed -i '' "s#gui/501/#gui/$(id -u)/#" ~/Library/LaunchAgents/local.openclaw-nightly-restart.plist launchctl load ~/Library/LaunchAgents/local.openclaw-nightly-restart.plistThe
sedline swaps in your own user ID, in case it isn't the usual501.Then grow it one job at a time
Add an MCP server (
openclaw mcp add), give a group chat its own prompt, or wrap a data source in a small script that only prints what's new. Runopenclaw cron listto see everything at a glance.
Scar tissue
Gotchas I paid for so you don't have to.
A /model pin disables fallbacks
A per-session model override turns off the fallback chain. When that model vanished from the catalog, Cora answered every message with an error while cron kept running. Avoid pins, or clear them from sessions.json.
launchd doesn't see your shell's env
A key exported in .zshrc works in the CLI and is missing in the gateway. Memory search silently went stale until I wired the key as a file SecretRef. Test through the running gateway.
Back up before every config edit
cp openclaw.json openclaw.json.bak-<what>-$(date +%Y%m%d). I have fifteen of these and have used several.
macOS privacy prompts block silently
Messages, Photos and Contacts need Full Disk Access for the process running the gateway. Grant it while you're at the keyboard, or a job will hang on a dialog no one sees.