DEV Community

Cover image for I thought a family calendar bot should run everything until I realized AI is way better at intake than decisions
Lars Winstand
Lars Winstand

Posted on • Originally published at standardcompute.com

I thought a family calendar bot should run everything until I realized AI is way better at intake than decisions

I went down a rabbit hole after reading a thread on r/openclaw about building a family Gmail/calendar assistant.

One line captured the whole use case:

“I want it to accept email (or later, telegram msgs) and when appropriate create calendar items in its own calendar, and send an invitation with a reminder to the requester (and whoever else).”

That’s such a good automation target.

Not “build an autonomous household agent.”

Just: take a messy request, turn it into a valid event, invite the right people, and don’t make a mess.

Then someone in the thread asked the best possible question:

“What’s the benefit if, in the end, you just get invited?”

That question kills a lot of bad architecture.

Because if the end result is a calendar invite, then the hard part is not Google Calendar. The hard part is the messy human input before Google Calendar.

That’s where GPT-5, Claude Opus 4.6, or another strong model helps.

Everything after that should be deterministic.

The pattern: use AI for intake, not side effects

If you’re building this in n8n, OpenClaw, Make, Zapier, or your own Node/Python service, the clean split looks like this:

  • Use an LLM to extract structured fields from messy text
  • Use normal code to decide whether to create the event
  • Use the Google Calendar API to perform the side effect

That sounds less exciting than “AI calendar agent.”

It’s also the design that won’t randomly create duplicate dentist appointments.

Google Calendar is the easy part

The Google Calendar API is boring in the best way.

events.insert is not asking for deep reasoning. It wants valid fields.

At minimum, you really just need a usable start and end. After that, you can add:

  • attendees
  • reminders
  • recurrence
  • location
  • conferenceData
  • custom event IDs
  • extendedProperties

The hard part is the input you get from humans.

Examples:

  • “Can you put Sam’s dentist appointment next Thursday at 3 with a 1 hour reminder and invite me + dad?”
  • “Book soccer practice every Tuesday at 6 for the next month.”
  • “Lunch with Priya after my 2pm, maybe 3:30-ish if traffic is bad.”

Humans are bad at schemas.

LLMs are pretty good at turning that mess into structure.

Where the model helps

Use GPT-5, Claude, or Grok for:

  • extracting summary
  • inferring start and end
  • identifying attendees
  • spotting recurrence intent
  • assigning a confidence score
  • deciding when to ask a follow-up question

Where code should take over

Use deterministic logic for:

  • duplicate prevention
  • reminder defaults
  • invite policy
  • conflict checks
  • RRULE formatting
  • retries
  • idempotency
  • rejecting incomplete requests

That split is the whole design.

The 3 approaches, ranked

Here’s the comparison I wish more people made before wiring an agent directly to production APIs:

Approach What actually happens
LLM extraction + deterministic calendar logic Best default. Flexible input handling, reliable side effects, easy to validate and debug.
Fully agentic calendar assistant Feels smart in demos, gets weird in production, harder to audit, easier to trust too early.
Rule-only parser Cheap and predictable, but breaks the moment humans stop following your template.

If this post only leaves you with one thing, make it that table.

Why I don’t trust fully agentic calendar bots

I’m not anti-agent.

I’m anti-agent-with-side-effects-when-the-task-is-actually-narrow.

A calendar bot is not a place where you want “creative exploration.”

You want bounded interpretation.

The model should answer questions like:

  • What date did the user probably mean?
  • Is this recurring?
  • Who should be invited?
  • Is confidence high enough to proceed?

The model should not freestyle the entire workflow and then mutate a real calendar directly.

That’s how you get systems that work just enough to feel trustworthy right before they fail in the most annoying way.

A practical architecture for n8n or custom code

If I were building this today, I’d treat it as structured intake plus deterministic execution.

Flow

  1. Watch a dedicated Gmail label or Telegram bot inbox
  2. Pull the message text
  3. Send it to an LLM with a strict schema
  4. Validate required fields
  5. If confidence is low, ask a follow-up
  6. If valid, create the event via Google Calendar API
  7. Store metadata for dedupe and later updates

That architecture works in:

  • n8n
  • OpenClaw
  • Make
  • Zapier
  • plain Node.js
  • plain Python

Use structured outputs, not “please return valid JSON”

This is the part that saves you from writing retry glue for months.

If you’re using OpenAI-compatible APIs, use structured outputs or schema-constrained responses.

A minimal Node example:

import OpenAI from "openai";
import { z } from "zod";
import { zodResponseFormat } from "openai/helpers/zod";

const client = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
  baseURL: process.env.OPENAI_BASE_URL
});

const CalendarIntent = z.object({
  summary: z.string(),
  start: z.string().nullable(),
  end: z.string().nullable(),
  timezone: z.string().default("America/New_York"),
  attendees: z.array(z.string()).default([]),
  recurrence: z.string().nullable(),
  reminderMinutes: z.number().nullable(),
  confidence: z.number().min(0).max(1),
  needsClarification: z.boolean()
});

const completion = await client.beta.chat.completions.parse({
  model: "gpt-4o-2024-08-06",
  messages: [
    {
      role: "system",
      content: "Extract calendar event intent from user messages. If required fields are ambiguous, set needsClarification=true."
    },
    {
      role: "user",
      content: "Can you put Sam's dentist appointment next Thursday at 3 with a 1 hour reminder and invite me + dad?"
    }
  ],
  response_format: zodResponseFormat(CalendarIntent, "calendar_intent")
});

const intent = completion.choices[0].message.parsed;
console.log(intent);
Enter fullscreen mode Exit fullscreen mode

That’s the useful part of AI here.

Not “run my calendar.”

Just “extract intent into a shape my code can trust.”

If you use n8n, this maps cleanly

n8n’s Information Extractor node is a strong fit for this pattern.

You define the fields you want, give it descriptions or a schema, and let the model fill the structure.

Then the rest of the workflow stays normal.

Example shape:

{
  "summary": "Sam dentist appointment",
  "start": "2026-05-28T15:00:00-04:00",
  "end": "2026-05-28T16:00:00-04:00",
  "timezone": "America/New_York",
  "attendees": ["mom@example.com", "dad@example.com"],
  "recurrence": null,
  "reminderMinutes": 60,
  "confidence": 0.93,
  "needsClarification": false
}
Enter fullscreen mode Exit fullscreen mode

After that, it’s just workflow logic.

Gmail intake: keep it narrow

A family bot does not need access to your entire mailbox.

Use a dedicated label or inbox lane.

That gives you two sane operating modes:

  • polling with messages.list
  • push notifications with users.watch

Example watch request:

POST https://gmail.googleapis.com/gmail/v1/users/me/watch
Content-Type: application/json
Authorization: Bearer YOUR_ACCESS_TOKEN

{
  "topicName": "projects/my-project/topics/my-topic",
  "labelIds": ["INBOX"],
  "labelFilterBehavior": "INCLUDE"
}
Enter fullscreen mode Exit fullscreen mode

That design matters more than people think.

If the bot only reads a narrow lane, your failure modes get much smaller.

Then create the event with normal code

Here’s a stripped-down Node example using googleapis:

npm install googleapis
Enter fullscreen mode Exit fullscreen mode
import { google } from "googleapis";

const auth = new google.auth.OAuth2(
  process.env.GOOGLE_CLIENT_ID,
  process.env.GOOGLE_CLIENT_SECRET,
  process.env.GOOGLE_REDIRECT_URI
);

auth.setCredentials({
  refresh_token: process.env.GOOGLE_REFRESH_TOKEN
});

const calendar = google.calendar({ version: "v3", auth });

async function createEvent(intent) {
  if (intent.needsClarification) {
    throw new Error("Intent requires clarification");
  }

  if (!intent.start || !intent.end) {
    throw new Error("Missing required start/end");
  }

  return calendar.events.insert({
    calendarId: "primary",
    requestBody: {
      id: "msg_abc123_sam_dentist",
      summary: intent.summary,
      start: {
        dateTime: intent.start,
        timeZone: intent.timezone
      },
      end: {
        dateTime: intent.end,
        timeZone: intent.timezone
      },
      attendees: intent.attendees.map(email => ({ email })),
      reminders: intent.reminderMinutes
        ? {
            useDefault: false,
            overrides: [{ method: "popup", minutes: intent.reminderMinutes }]
          }
        : { useDefault: true },
      recurrence: intent.recurrence ? [intent.recurrence] : undefined,
      extendedProperties: {
        private: {
          source: "gmail",
          confidence: String(intent.confidence),
          intakeMessageId: "abc123"
        }
      }
    },
    sendUpdates: "all"
  });
}
Enter fullscreen mode Exit fullscreen mode

That is much safer than giving an agent broad Google Calendar access and hoping for the best.

Three implementation details that matter a lot

These are the boring parts that make the system reliable.

1. Use your own event IDs

Set a custom event ID when inserting.

That gives you idempotency.

If your workflow retries, you don’t create duplicate events.

2. Store metadata in extendedProperties

This is underrated.

Store things like:

  • original Gmail message ID
  • source channel (gmail, telegram)
  • extraction confidence
  • internal workflow run ID

That makes future reconciliation and debugging much easier.

3. Respect etags on updates

People edit calendar entries manually.

They always will.

If your workflow later updates the event, use etags / If-Match semantics so you don’t overwrite human changes blindly.

That’s how you avoid becoming the household menace that keeps “fixing” events back to the wrong version.

Telegram might actually be the better intake layer

Email is messy.

Telegram is often cleaner for this exact use case.

Messages are shorter, more direct, and less polluted by signatures, quoted threads, and forwarded junk.

Example:

“Book soccer practice every Tuesday at 6 for the next month”

That’s almost ideal LLM input.

The flow is the same:

  • receive message
  • extract fields
  • convert recurrence intent into a real RRULE
  • create event
  • ask follow-up only if something critical is missing

Again, the model interprets.

Your code decides.

A tiny CLI prototype is enough to prove the design

You don’t need a full agent framework to validate this pattern.

You can prototype it with a single script.

npm install openai zod googleapis dotenv
Enter fullscreen mode Exit fullscreen mode
OPENAI_API_KEY=your_key
OPENAI_BASE_URL=https://api.openai.com/v1
GOOGLE_CLIENT_ID=your_client_id
GOOGLE_CLIENT_SECRET=your_client_secret
GOOGLE_REDIRECT_URI=your_redirect_uri
GOOGLE_REFRESH_TOKEN=your_refresh_token
Enter fullscreen mode Exit fullscreen mode

Then feed it test strings and inspect the extracted intent before enabling writes.

That gets you much closer to production reliability than starting with “multi-agent autonomous family assistant.”

This pattern generalizes really well

The reason I like this architecture is that the family calendar bot is just a friendly version of a serious ops workflow.

Same pattern works for:

  • support triage
  • recruiting coordination
  • inbound scheduling
  • CRM updates
  • internal request queues
  • lead qualification

Humans send messy text.

AI extracts structure.

Code performs the side effect.

That’s the repeatable pattern.

One more practical point: cost gets weird fast if you over-agent everything

This is also why I’m skeptical of turning narrow workflows into sprawling agent systems.

If a task is mostly extraction plus a deterministic API call, then burning huge amounts of tokens on agent loops is the wrong trade.

That’s especially true for teams running automations all day in n8n, Make, Zapier, OpenClaw, or custom workers.

You want the model where it adds leverage, not everywhere by default.

That’s part of why products like Standard Compute are interesting for this category: you can keep the useful LLM step in the workflow without treating every automation run like a token-budget emergency. Flat monthly pricing is a much better fit for always-on agents and automations than staring at per-token costs every time a workflow spikes.

My default recommendation

If you’re building a calendar assistant, do this:

  • LLM for extraction
  • schema for structure
  • code for validation
  • Google Calendar API for side effects
  • custom IDs for idempotency
  • metadata for auditability
  • follow-up questions when confidence is low

Do not start with a fully agentic bot unless you have a very specific reason.

The funny part is that the best version of this system feels less like AI magic and more like good plumbing.

That’s a compliment.

Because the real win is not that AI “runs your family calendar.”

The real win is that it cleans up the messy intake layer and hands the rest to logic you can trust.

Top comments (0)