Fomo: a small agent for everyday things

I wanted to try making an agent with Apple's Foundation Models. Give it tools and web search, keep it small, use it for routine things. A morning report, a reminder, something from my mailbox.

I also wanted it to have a terminal UI. Then I wanted to talk to it from my phone. Telegram seemed easier than building another app.

So now I have Fomo.

Fomo, a small coral robot beside a retro computer, with everyday tools in orbit
Small agent. Everyday orbit.

Start with the boring tasks

The request I kept coming back to was: give me today's briefing based on my emails, calendar and weather. It sounds simple. Three sources, a short answer. Something I would actually use.

Fomo can search and read Gmail, look up calendar events, search the web, and prepare reminders and events. It asks for confirmation before saving those. There is a terminal UI when I'm at the Mac and a Telegram bot when I'm away from it.

The service runs in OrbStack. The model runs on the Mac, along with a small Swift helper for Calendar and Reminders. TypeScript handles the agent, task queue, Telegram, and schedules. SQLite remembers what is running, what needs approval, and which replies have been delivered.

I used Pi for the agent core. I wanted to build the small thing around it, rather than adopt a whole coding assistant and try to turn it into a personal one.

The model needed some help

Apple's fm utility has an HTTP server, which made connecting the model fairly straightforward. Getting it to use tools reliably took more work.

In this setup, the model returns guided JSON. Fomo asks which tool to use, then asks for arguments matching that tool's schema. Pi validates and executes the call. The result goes back to the model.

That works for focused requests. Broader ones can wander. The morning briefing was a good example: I already knew which three sources it needed, so asking the model to work out the plan every morning wasn't helping. We made that a fixed collection routine.

It collects a few recent emails, today's events, and Berlin weather. If one source fails, it can still return the others and say what was unavailable. It is a small snapshot. I wouldn't rely on it to decide which email is most important.

I also asked it to try at least three rounds before bringing a failure back to me. Then it failed three times with the same context-budget error. The guard was counting tool definitions that weren't even being sent to the model. Retrying couldn't fix that. We had to fix the harness.

Remind me an hour ago

At one point it saved reminders one and two hours in the past.

That is quite a basic thing for a personal assistant to get wrong. The model was mixing UTC and Berlin time. Now a request like “in one hour” is calculated in code from the original request timestamp. The model doesn't get to improvise the offset. If I don't ask for a time, the reminder stays undated.

Calendar access was another detour. macOS showed that Fomo had permission, but the background helper couldn't read any events. Rebuilding the helper had changed its code signature. The permission on screen belonged to an older build.

We refreshed the permission and checked the actual background service. It could finally read the events. A green switch in Settings wasn't enough.

Less bot noise

The first Telegram version replied with something like “Task 003ca596 queued. Use /status to check progress.” Then it prefixed the answer with “completed:”.

I don't need a task management ceremony to ask about the weather. It now says “Working” and deletes that message when the answer arrives. Replies have short paragraphs, some formatting, and links where useful.

There was also a detour into connecting the bot to a desktop AI chat instead of the local model. I wanted the better reasoning without another model API. We tried a polling bridge, then switched back to FM. For this experiment, I wanted to see how useful the local version could become.

The latest addition is recurring tasks:

/schedule 08:00 Give me today's briefing based on my emails, calendar and weather.

The schedule survives a restart and uses Berlin time, including daylight-saving changes. The Mac still needs to be running. If it has been off for days, Fomo skips old reports instead of sending a pile of them.

Inference is local, but the whole system isn't offline: Gmail, Telegram, weather, and web search still use external services. Telegram bot chats aren't end-to-end encrypted either.

It's a toy project, and the small model is still the small model. But I can give it a concrete job from my phone, check what it did, and have it run the same job tomorrow morning. That's enough to keep experimenting.

← All notes