# Alejandro Rioja > Alejandro Rioja — AI agent systems for founders. Plus posts on growth, marketing, sales, ops, and business from inside live P&Ls. Site: https://alejandrorioja.com Author: Alejandro Rioja --- ## Event-Triggered vs Scheduled Agents: Which Pattern for Which Job Source: https://alejandrorioja.com/event-triggered-vs-scheduled-agents-which-pattern-for-which-job/ Published: 2026-05-31 Updated: 2026-05-31 Tags: AI Agents TL;DR: Use event-triggered agents when a user action demands an immediate response—anything over a few seconds of lag and the experience breaks. Use scheduled agents for batch or periodic work where timing is predictable. The constraint: event-triggered agents must be stateless and fast; scheduled agents can afford to be stateful and slow. ## Table of contents _Updated May 2026._ **TL;DR:** Use event-triggered agents when a user action demands an immediate response—anything over a few seconds of lag and the experience breaks. Use scheduled agents for batch or periodic work where timing is predictable. The constraint: event-triggered agents must be stateless and fast; scheduled agents can afford to be stateful and slow. **[Operator's read]** I run 30+ production agents across my consulting brand and Pickleland, a pickleball facility in Pflugerville, TX. Every one of them maps to one of two patterns: fires on an event, or fires on a clock. Getting this wrong wastes money and ships broken experiences. ## The two patterns in plain English An **event-triggered agent** wakes up because something happened. A booking came in. A comment was posted. A form was submitted. The trigger is external and unpredictable in timing. Your job is to respond fast. A **scheduled agent** wakes up because the clock said so. Every morning at 7am. Every Sunday at 6pm. Every hour on the hour. The trigger is internal and completely predictable. Your job is to do thorough work. That's it. Don't overthink it. The architecture follows from the answer to one question: *does the user or system need a response right now, or can this wait until a specific time?* ## Event-triggered: the social reply agent My social reply agent fires whenever a new comment hits a monitored Facebook post. The agent reads the comment, classifies intent (question, complaint, compliment, spam), drafts a reply, and posts it—or flags it for human review if the confidence is low. The whole round trip needs to finish in under 30 seconds or the reply feels stale. That's an event-triggered problem. Here's a stripped-down Cloudflare Worker that handles the webhook from a social monitoring service: ```typescript // workers/social-reply.ts export default { async fetch(request: Request, env: Env): Promise { if (request.method !== "POST") { return new Response("Method not allowed", { status: 405 }); } // Verify the webhook signature const sig = request.headers.get("x-webhook-signature") ?? ""; const body = await request.text(); const valid = await verifySignature(body, sig, env.WEBHOOK_SECRET); if (!valid) return new Response("Unauthorized", { status: 401 }); const event = JSON.parse(body) as SocialCommentEvent; // Classify and reply — keep it async so we can return 200 fast env.REPLY_QUEUE.send(event); return new Response("OK", { status: 200 }); }, }; // Queue consumer — does the actual AI work export const queue: ExportedHandlerQueueHandler = async (batch, env) => { for (const msg of batch.messages) { const comment = msg.body; const classification = await classifyComment(comment.text, env); if (classification.intent === "spam") { msg.ack(); continue; } const reply = await draftReply(comment, classification, env); if (classification.confidence > 0.85) { await postReply(comment.postId, comment.id, reply, env); } else { await flagForReview(comment, reply, env); } msg.ack(); } }; ``` Two things to notice. First, the fetch handler returns 200 immediately and offloads the real work to a queue. This keeps the webhook response fast and prevents the monitoring service from retrying. Second, the queue consumer does the actual AI call—classifying and drafting—without any time pressure from an open HTTP connection. ## Scheduled: the Pickleland event promoter Pickleland runs courts and events. Every week, someone needs to push upcoming events to the right Facebook groups to fill seats. This is pure periodic batch work—there's no user action that triggers it, and it doesn't need to happen in real time. The Pickleland event promoter runs on a cron, checks the booking system for events in the next 4 days, drafts venue-specific posts for each matched Facebook group, and surfaces them for my review before anything goes live. ```typescript // workers/event-promoter.ts export default { async scheduled( event: ScheduledEvent, env: Env, ctx: ExecutionContext ): Promise { ctx.waitUntil(runPromoter(env)); }, }; async function runPromoter(env: Env): Promise { // Pull events from the booking system const upcomingEvents = await fetchUpcomingEvents(env, { daysAhead: 4 }); if (upcomingEvents.length === 0) return; const drafts: PromoDraft[] = []; for (const event of upcomingEvents) { // Match each event to the right FB groups const groups = await matchFacebookGroups(event, env); for (const group of groups) { const post = await draftPromoPost(event, group, env); drafts.push({ event, group, post }); } } // Write drafts to Airtable for review — nothing posts automatically await saveDraftsForReview(drafts, env); // Notify me via Slack await notifyOperator( `${drafts.length} promo drafts ready for review`, env ); } ``` The wrangler config that wires this up: ```toml # wrangler.toml [[triggers]] crons = ["0 18 * * 0"] # Every Sunday at 6pm UTC ``` Notice what the scheduled agent can do that the event-triggered one can't: it loops over multiple events, writes to a database, and sends a summary notification. It's doing batch work. The event-triggered agent needs to stay lean and return fast. ## Scheduled: the daily brief Every morning at 7am my daily brief agent runs. It pulls overnight emails, my calendar, top tasks, and any news I've flagged as relevant. It formats everything into a single document and drops it into my AI Workspace folder. This one is pure scheduled. There's no event that would trigger it—I just want it every morning before I start work. ```typescript // workers/daily-brief.ts export default { async scheduled( event: ScheduledEvent, env: Env, ctx: ExecutionContext ): Promise { ctx.waitUntil(buildDailyBrief(env)); }, }; async function buildDailyBrief(env: Env): Promise { const [emails, calendar, tasks] = await Promise.all([ fetchOvernightEmails(env), fetchTodayCalendar(env), fetchTopTasks(env), ]); const brief = await synthesizeBrief({ emails, calendar, tasks }, env); await writeToWorkspace(brief, env); } ``` ```toml [[triggers]] crons = ["0 7 * * *"] # Every day at 7am UTC ``` The parallel `Promise.all` is deliberate. Scheduled agents don't have a human waiting—but they still shouldn't be slower than they need to be. Pull all your data sources in parallel, then do the AI synthesis once. ## When event-triggered goes wrong The failure mode I see most often: someone builds an event-triggered agent that does too much work in the handler. A booking comes in. The agent fetches the customer profile, enriches it from three external APIs, runs a personalization model, writes to the CRM, sends the confirmation email, and updates a dashboard. The whole thing takes 45 seconds. The booking platform retries because it didn't get a 200 fast enough. Now the agent runs twice. Fix it the same way the social reply agent is built: return 200 immediately, push the event to a queue, let the queue consumer do the heavy lifting asynchronously. The other failure mode: using event-triggered for work that's actually periodic. "Send a weekly summary" is not an event. Don't wire it to a synthetic cron webhook—use a proper scheduled trigger. ## When scheduled goes wrong Scheduled agents fail when the job is actually latency-sensitive. If a user submits a form and the agent that processes it runs on a 5-minute cron, the user is staring at a spinner for up to 5 minutes. That's not a scheduled job—it's a slow event-triggered job pretending to be scheduled. The other failure: scheduled agents that fan out to unbounded work. If your cron runs every minute and each invocation can process hundreds of records, you'll hit Cloudflare's CPU limits fast. Either increase the cron interval, add a queue to bound the work per invocation, or switch to Durable Objects for long-running coordination. ## Mixing patterns: the booking pipeline Some workflows genuinely need both. The Pickleland booking pipeline works like this: 1. **Event-triggered**: new booking webhook → confirm the booking, send the customer a receipt, update availability. Must complete in under 10 seconds. 2. **Scheduled**: every Sunday → review all bookings from the past week, generate a summary report, flag any anomalies (duplicate bookings, unusual cancellation rates). Same domain, two patterns, two agents. The event-triggered one owns the real-time user experience. The scheduled one owns the weekly operations review. They share a database but nothing else. Don't try to combine them into one agent that "does everything." You'll end up with something that's too slow for events and too coupled to the real-time flow for batch work. ## Cloudflare Workers: why it's the right infrastructure for both Cloudflare Workers handles both patterns natively: - `fetch` handler → event-triggered (webhooks, API calls) - `scheduled` handler → cron-based (via `[[triggers]]` in wrangler.toml) - `queue` consumer → async processing decoupled from the HTTP layer The edge deployment means your event-triggered agents respond fast globally. The free tier is generous enough to prototype both patterns without spending anything. And the unified `wrangler.toml` config means you're not managing two separate infra setups for two patterns. The one thing Workers doesn't solve well: agents that need to run for more than a few minutes. For those, reach for Durable Objects or offload to a longer-running backend. ## The operator's bottom line Pick your pattern before you write a single line of agent code. Event-triggered for anything a human is waiting on; scheduled for anything that runs on a clock. Keep event-triggered handlers thin—return fast, queue the work. Keep scheduled agents parallel—don't serialize what you can parallelize. The architecture is simple. Violating it is where complexity comes from. --- **Related:** [The agent stack I use to run 30+ production agents](/the-agent-stack-i-use-to-run-30-production-agents-no-python/) · [How I measure whether an AI agent is actually working](/how-i-measure-whether-an-ai-agent-is-actually-working/) · [The cheapest way to run a content agent on Cloudflare](/the-cheapest-way-to-run-a-content-agent-on-cloudflare-cost-breakdown/) **Want help picking the right pattern for your use case?** [Get in touch](/contact/) — I design production agent architectures for operator teams. --- ## GEO for Local Business: Get Cited by AI Search Source: https://alejandrorioja.com/geo-for-local-business-getting-a-brick-and-mortar-cited-by-ai-search/ Published: 2026-05-31 Updated: 2026-05-31 Tags: GEO, Marketing TL;DR: To get your physical business cited by AI search engines, optimize your Google Business Profile first — it's the single biggest signal. Then layer in LocalBusiness JSON-LD schema, consistent NAP across the web, and a steady stream of fresh reviews. You cannot buy AI citations, and keyword-stuffing your GBP won't help. ## Table of contents _Updated May 2026._ **TL;DR:** To get your physical business cited by AI search engines, optimize your Google Business Profile first — it's the single biggest signal. Then layer in LocalBusiness JSON-LD schema, consistent NAP across the web, and a steady stream of fresh reviews. You cannot buy AI citations, and keyword-stuffing your GBP won't help. **[Operator's read]** I run Pickleland, a pickleball facility in Pflugerville, TX. When I started checking what ChatGPT and Perplexity returned for "best pickleball courts near Austin," my own facility wasn't showing up — despite ranking well in Google Maps. Here's what I changed, and why it worked. --- ## Why AI search is different for local businesses Traditional local SEO was about ranking in the map pack and the blue links. AI search is different: ChatGPT, Perplexity, and Google's AI Overviews synthesize answers and cite specific sources. For local queries, they pull from a combination of Google Business Profile data, structured data on your website, review platforms, and authoritative directory listings. The good news: the bar is lower than you think. Most brick-and-mortar businesses have neglected their structured data and let their GBP go stale. If you do the basics correctly and consistently, you stand out. The bad news: there is no shortcut. You cannot pay to get cited by an AI engine. There is no "AI citation" ad product. What you can do is make your business easy for AI to understand and trust. --- ## Google Business Profile is the foundation If I had to pick one lever, it's Google Business Profile (GBP). When someone asks an AI assistant "best pickleball courts near Austin," the AI pulls heavily from GBP data. Here's why: GBP is a structured, verified database. AI models trained on the web and tools like Perplexity that do live retrieval treat GBP signals as high-trust. What to do with your GBP: - **Complete every field.** Category, description, hours (including holiday hours), attributes, services/menu. Every empty field is a missed signal. - **Use your primary category precisely.** For Pickleland that's "Pickleball court" not just "Sports complex." AI engines read category data. - **Add photos regularly.** GBP rewards freshness. Upload new court photos, event shots, and interior walkthroughs at least twice a month. - **Post updates.** GBP posts are indexed. Write short posts (150–300 words) that answer questions like "Do you need to bring your own paddle?" These Q&A posts get surfaced directly. - **Answer every Q&A.** The GBP Q&A section is public and indexed. If no one has asked your most common questions, add them yourself and answer them. What not to do: don't stuff your GBP description with keywords like "best pickleball Austin cheapest courts open now." It reads as spam, it won't help with AI, and Google can suspend your listing. --- ## LocalBusiness schema: the structured data layer Your GBP handles the Google ecosystem. For non-Google AI search (Perplexity, ChatGPT with browsing, Bing-powered tools), the structured data on your website is the primary signal. For a full breakdown of which schema types give the most GEO citation lift — beyond LocalBusiness — see [schema markup for AI engines: types that punch above their weight](/schema-markup-for-ai-engines-the-types-that-punch-above-their-weight/). Add a `LocalBusiness` JSON-LD block to your homepage and contact page. Here's the schema I use for Pickleland: ```json { "@context": "https://schema.org", "@type": "SportsActivityLocation", "name": "Pickleland", "description": "Indoor pickleball facility in Pflugerville, TX with 8 dedicated courts, open play, leagues, and lessons.", "url": "https://pickleland.com", "telephone": "+1-512-000-0000", "address": { "@type": "PostalAddress", "streetAddress": "123 Pickleland Dr", "addressLocality": "Pflugerville", "addressRegion": "TX", "postalCode": "78660", "addressCountry": "US" }, "geo": { "@type": "GeoCoordinates", "latitude": 30.4349, "longitude": -97.6200 }, "openingHoursSpecification": [ { "@type": "OpeningHoursSpecification", "dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"], "opens": "06:00", "closes": "22:00" }, { "@type": "OpeningHoursSpecification", "dayOfWeek": ["Saturday","Sunday"], "opens": "07:00", "closes": "21:00" } ], "priceRange": "$$", "servesCuisine": null, "amenityFeature": [ { "@type": "LocationFeatureSpecification", "name": "Indoor Courts", "value": true }, { "@type": "LocationFeatureSpecification", "name": "Equipment Rental", "value": true }, { "@type": "LocationFeatureSpecification", "name": "Lessons Available", "value": true } ], "sameAs": [ "https://www.google.com/maps?cid=YOUR_CID", "https://www.yelp.com/biz/pickleland-pflugerville", "https://www.facebook.com/pickleland" ] } ``` A few things to highlight: the `sameAs` array explicitly connects your schema entity to your GBP, Yelp, and Facebook pages. AI engines use this to cross-reference and gain confidence that all of these are the same business. The `geo` coordinates matter — Perplexity does proximity matching. And `openingHoursSpecification` in machine-readable format gets pulled directly into AI answers when someone asks "is Pickleland open on Sunday." Use `SportsActivityLocation` rather than the generic `LocalBusiness` type when it fits — the more specific the type, the more precisely AI can categorize you. --- ## NAP consistency: boring but critical NAP stands for Name, Address, Phone. When your business name appears as "Pickleland" in Google, "Pickleland LLC" on Yelp, "Pickleland - Pflugerville" on Facebook, and "Pickleland Pickleball" on a local directory — AI engines see four different entities and downweight confidence in all of them. Run a NAP audit: 1. Search your business name across Google, Yelp, Facebook, Apple Maps, Bing Places, Foursquare, TripAdvisor, and any industry-specific directories. 2. Document every variation. 3. Correct them — most platforms let you claim or edit listings directly. The name you use everywhere should exactly match what's on your Google Business Profile. For Pickleland, that's "Pickleland" — no suffix, no city name appended. Phone number format matters too. Use the same format everywhere: `(512) 000-0000` or `+1-512-000-0000` but pick one and stick to it. The `sameAs` links in your JSON-LD help AI engines connect the dots, but consistent NAP is what builds entity confidence in the first place. --- ## Review velocity: recency is an AI signal AI search engines don't just look at star ratings — they look at how recent and how frequent your reviews are. A business with 200 reviews but the last one from 18 months ago ranks lower than a business with 80 reviews and three posted last week. At Pickleland, we built review velocity into operations: - After every open play session, a staff member sends a follow-up text with a direct link to the Google review page. - We respond to every review — positive and negative — within 24 hours. Response activity signals freshness to crawlers. - Monthly, we identify our most satisfied regulars and personally ask them to share feedback. We went from 43 reviews to 190 in about four months. The impact on AI citations was measurable: Pickleland started appearing in Perplexity answers for "pickleball Austin area" within six weeks of crossing the 100-review mark with strong recency. Don't buy fake reviews. Beyond the obvious risk of getting suspended, AI engines are increasingly good at detecting clusters of unnatural reviews (similar timestamps, generic language, reviewer accounts with no history). --- ## Q&A content that matches how people ask AI Traditional SEO targets keyword phrases. GEO targets questions — specifically the natural-language questions people type or speak into AI assistants. Think about how someone asks ChatGPT versus how they'd type into Google: - Google: `pickleball courts austin` - ChatGPT: `What are the best indoor pickleball courts near Austin that are open on weekday mornings?` Your content needs to answer the long-form version. Create a dedicated FAQ or Q&A page on your site that directly addresses: - "Do I need to bring my own paddle?" (equipment questions) - "How much does it cost to play pickleball at [facility]?" - "Is [facility] good for beginners?" - "Can I book a court for a corporate event?" - "What are the open play hours on weekends?" Write each answer in 2–4 sentences, direct and complete. AI engines extract and surface these verbatim when users ask matching questions. I've seen Pickleland's FAQ answers cited word-for-word in Perplexity responses. Also use your GBP posts for this: write posts that are structured as a question and answer. "Q: Do I need to reserve a court in advance? A: Walk-ins are welcome during open play hours (check our schedule), but court reservations are recommended for peak weekend times. Book at pickleland.com." That format is AI-friendly and indexable. --- ## What doesn't work Be direct about the limits: **Keyword stuffing your GBP description** doesn't help AI search. It reads as spam and can get your listing flagged. Write naturally for humans. **Paying for AI citations** doesn't exist as a product. Any service that claims to "get you cited by ChatGPT" for a fee is selling snake oil. AI citations are editorial — they're based on what the AI determines is the most relevant, trustworthy answer. If you're wondering where to focus beyond your own site, the [Perplexity vs ChatGPT vs Google AI Overviews breakdown](/perplexity-vs-chatgpt-vs-google-ai-overviews-where-to-spend-your-geo-effort/) explains which platform sends the most referral traffic for operators at your scale. **One-time schema setup** isn't enough. Your schema needs to stay current. If your hours change and you update your GBP but not your JSON-LD, you create conflicting signals. Build a quarterly schema audit into your routine. **Chasing every directory** is diminishing returns. Focus on the platforms with the highest AI retrieval weight: Google, Yelp, Facebook, Apple Maps, Bing Places. Industry-specific directories (in our case, places like the USA Pickleball facility directory) are worth it because they're authoritative for that vertical. --- ## The operator's bottom line GEO for local business isn't complicated — it's just unglamorous and requires consistency. Get your GBP to 100% completion, add clean LocalBusiness schema to your site, standardize your NAP across the web, and build a review cadence into your operations. Do all four, and AI search engines have everything they need to confidently cite you. I've done this with Pickleland, and the results show up in real citation data. Start with your GBP today — it takes an afternoon and the lift is immediate. --- **Related:** [Schema markup for AI engines: types that punch above their weight](/schema-markup-for-ai-engines-the-types-that-punch-above-their-weight/) · [Perplexity vs ChatGPT vs Google AI Overviews: where to spend your GEO effort](/perplexity-vs-chatgpt-vs-google-ai-overviews-where-to-spend-your-geo-effort/) · [How to get your brand cited in ChatGPT answers](/how-to-get-your-brand-cited-inside-chatgpt-answers-in-2026/) **Want a hands-on GEO audit for your local business?** [Get in touch](/contact/) — I run GEO consulting projects and have applied this framework to Pickleland. --- ## How I Measure Whether an AI Agent Is Actually Working Source: https://alejandrorioja.com/how-i-measure-whether-an-ai-agent-is-actually-working/ Published: 2026-05-31 Updated: 2026-05-31 Tags: AI Agents TL;DR: Most operators skip evals entirely and just assume their agents work. My framework: build a golden set of 5–10 known-good inputs with expected outputs, define pass/fail criteria in plain English, and spot-check logs weekly. Don't build an elaborate eval system before you have 10 real runs — that's the trap that kills momentum. ## Table of contents _Updated May 2026._ **TL;DR:** Most operators skip evals entirely and just assume their agents work. My framework: build a golden set of 5–10 known-good inputs with expected outputs, define pass/fail criteria in plain English, and spot-check logs weekly. Don't build an elaborate eval system before you have 10 real runs — that's the trap that kills momentum. **[Operator's read]** I run 30+ production AI agents across my consulting brand and Pickleland, a pickleball facility in Pflugerville, TX. At some point I realized I was spending more time worrying about whether agents were drifting than actually using them. This is the eval framework I landed on — no PhD required, no custom eval platform, no Python. ## The problem no one talks about: agents drift silently When a human employee starts doing their job wrong, you usually notice. When an AI agent starts producing garbage, it keeps producing garbage — quietly, at scale, until something breaks badly enough that a human finally looks. I've had a content agent that started appending "As an AI language model" disclaimers after a model update. I've had an event promoter agent that stopped including ticket links because a prompt variable name changed. Neither failed loudly. Both just degraded. The fix isn't building a NASA-grade monitoring system. It's having a simple, repeatable check that catches drift before it compounds. ## What an eval actually is (for operators) Engineers use the word "eval" to mean running a benchmark on a model. For operators, I mean something simpler: **a repeatable test that tells you whether your agent is still doing what you built it to do.** Three components: 1. **Golden set** — 5–10 real inputs you've seen before, with expected outputs you already know are good 2. **Pass/fail criteria** — plain-English rules for what counts as passing 3. **A scheduled check** — you or your assistant actually runs the test on a cadence That's it. You don't need a framework. You need discipline. ## Building your golden set Pull from your production logs. Find 5–10 real inputs where you already know what a good output looks like. These are your ground truth. For my content pipeline agent, the golden set is 5 published posts that passed my voice checklist when I wrote them manually. For my Pickleland event promoter, it's 5 past Facebook posts that got above-average engagement (comments + shares, not just likes). **Rules for a good golden set:** - Real inputs, not hypotheticals you made up - Include at least one edge case (a tricky input, a short input, an input with unusual formatting) - Keep expected outputs documented — a screenshot, a text file, a row in a spreadsheet - Never delete from the golden set; only add When the agent was last confirmed working, write down exactly what "good" looked like. That becomes your expected output. ## Defining pass/fail criteria Vague criteria are useless. "The output should be good" will pass every time because you'll rationalize it. Write your criteria as checklist items that a non-expert could evaluate. Here's the actual criteria I use for my content pipeline agent: **Content agent pass/fail checklist:** - [ ] Post has a TL;DR in the first 100 words - [ ] No phrases like "in today's fast-paced world" or "As an AI" - [ ] At least one concrete number or statistic - [ ] Word count is between 800 and 2000 - [ ] All internal links resolve (no 404s) For the Pickleland event promoter: **Event promoter pass/fail checklist:** - [ ] Event name matches the source calendar - [ ] Date and time are correct - [ ] Ticket link is present and not broken - [ ] Copy is under 280 words - [ ] Post doesn't use generic filler phrases ("Come join us for a fun time!") If 4 of 5 checklist items pass, the run is a pass. If 3 or fewer pass, it's a fail and I investigate before the next run. ## Using Claude as a judge For agents where outputs are long or complex, I use Claude Sonnet as an automated judge. This is faster than manual review and catches things I'd skim past. Here's the judge prompt I use for the content agent: ```text You are evaluating a blog post written by an AI agent. Your job is to check whether it meets the operator's standards. Evaluate the following post against these criteria: 1. Starts with a direct answer or TL;DR in the first 100 words (YES/NO) 2. Contains at least one concrete number or specific example (YES/NO) 3. Free of AI-speak filler ("As an AI", "in today's fast-paced world", "delve", "it's worth noting") (YES/NO) 4. Word count is between 800 and 2000 words (YES/NO) 5. Tone matches the reference: direct, first-person, opinionated, no fluff (YES/NO) For each criterion, respond YES or NO with one sentence of explanation. At the end, output PASS if 4 or 5 criteria are YES, FAIL otherwise. Post to evaluate: --- {{post_content}} --- ``` I run this as a Cloudflare Worker that pulls the latest draft, fires this prompt, and writes the result to a Google Sheet. The whole thing takes 8 seconds and costs about $0.003 per run. For the event promoter, the judge prompt is simpler: ```text You are checking an AI-generated Facebook event post for accuracy and quality. Source data: - Event name: {{event_name}} - Date: {{event_date}} - Time: {{event_time}} - Ticket URL: {{ticket_url}} Generated post: --- {{generated_post}} --- Check: 1. Does the post correctly state the event name? (YES/NO) 2. Does the post correctly state the date and time? (YES/NO) 3. Does the post include the exact ticket URL? (YES/NO) 4. Is the post under 280 words? (YES/NO) 5. Is the tone inviting without using generic filler phrases? (YES/NO) Output PASS if all 5 are YES, FAIL if any are NO. List which items failed. ``` ## Where to look: Cloudflare Worker logs If you're running agents on Cloudflare Workers (which I do for most of my lightweight ones), the built-in log tail is your best friend. You don't need a third-party logging service to start. What I check in weekly spot-reviews: - **Errors and exceptions** — anything that crashed or timed out - **Token counts** — if a run suddenly uses 3x the normal tokens, something changed - **Latency spikes** — a sudden slowdown usually means the prompt got longer or the model is struggling - **Output length drift** — if average output went from 600 words to 200 words, the agent changed behavior I spend 15 minutes every Monday morning on this. I have a simple Notion checklist: open logs for each agent, note anything anomalous, compare token usage against last week's baseline. That's the entire process. ## The spreadsheet eval: ugly but it works Before I had any automation, I ran evals in a Google Sheet. I still use this for new agents in the first 4 weeks. Structure: | Run date | Input | Expected output (summary) | Actual output (summary) | Pass/Fail | Notes | |----------|-------|--------------------------|------------------------|-----------|-------| | 2026-05-01 | "Write a post about AI agents" | Direct, opinionated, 1000+ words, TL;DR present | 950 words, TL;DR present, strong voice | Pass | Slightly short | | 2026-05-08 | Same | Same | 400 words, generic, no TL;DR | Fail | Model drift after update | Five rows a week. Takes 10 minutes. If you have two fails in a row, you stop the agent and fix the prompt before continuing. This is embarrassingly low-tech. It's also how I caught three prompt regressions before they went to production. ## What NOT to do **Don't build the eval system before you have 10 real runs.** I've seen founders spend two weeks building a sophisticated eval pipeline for an agent they've only run twice. You don't know enough about what "good" looks like until you have real production data. **Don't eval on synthetic inputs you made up.** Synthetic test cases miss the weird edge cases that production throws at you. Always start with real logs. **Don't eval everything.** Pick the 3–5 agents where failure would actually hurt — customer-facing outputs, anything that posts publicly, anything that triggers a payment. Skip the internal utility agents until you have headspace. **Don't automate too early.** A spreadsheet you actually use beats a Datadog dashboard you forget to check. Start manual, automate after you've run the check 10 times and know what you're actually looking for. ## The operator's bottom line Evals don't have to be engineering-grade to be useful. A golden set of 5–10 real inputs, a checklist of pass/fail criteria, and 15 minutes of log-checking every Monday will catch 80% of agent drift before it compounds. Start there. If you're still running agents without any eval, you're flying blind — and eventually something will fail publicly enough that you'll wish you'd spent the 20 minutes. --- **Related:** [The agent stack I use to run 30+ production agents](/the-agent-stack-i-use-to-run-30-production-agents-no-python/) · [Event-triggered vs scheduled agents: which pattern for which job](/event-triggered-vs-scheduled-agents-which-pattern-for-which-job/) · [The cheapest way to run a content agent on Cloudflare](/the-cheapest-way-to-run-a-content-agent-on-cloudflare-cost-breakdown/) **Want help setting up evals for your agents?** [Get in touch](/contact/) — I run production agent audits for operator teams. --- ## How to Get Your Brand Cited in ChatGPT Answers in 2026 Source: https://alejandrorioja.com/how-to-get-your-brand-cited-inside-chatgpt-answers-in-2026/ Published: 2026-05-31 Updated: 2026-05-31 Tags: GEO, SEO TL;DR: ChatGPT and other LLMs cite brands that appear consistently in authoritative, structured, third-party sources — not just your own website. Build a citation surface: get quoted in roundups, maintain accurate structured data, and publish content that directly answers the exact questions your buyers ask AI. Results take 60–90 days to show up in model behavior, and there's no direct submission mechanism. ## Table of contents _Updated May 2026._ **TL;DR:** ChatGPT and other LLMs cite brands that appear consistently in authoritative, structured, third-party sources — not just your own website. Build a citation surface: get quoted in roundups, maintain accurate structured data, and publish content that directly answers the exact questions your buyers ask AI. Results take 60–90 days to show up in model behavior, and there's no direct submission mechanism. **[Operator's read]** I run 30+ production AI agents and have been obsessively tracking which of my clients' brands show up in ChatGPT answers versus which get skipped entirely. The patterns are clear enough now that I'm writing this down. --- ## Why "just be good at SEO" no longer covers it Google and ChatGPT have different reading habits. Google ranks pages. ChatGPT synthesizes facts and attributes them to sources it found credible during training and retrieval. A brand that ranks #1 on Google for a keyword can still be invisible inside an LLM answer if the model never encountered that brand in a trustworthy third-party context. The game has a new name: **Generative Engine Optimization (GEO)**. The goal isn't a blue link — it's being the noun inside the sentence. Before going deep on ChatGPT specifically, it's worth knowing [where GEO effort pays off most across Perplexity, ChatGPT, and Google AI Overviews](/perplexity-vs-chatgpt-vs-google-ai-overviews-where-to-spend-your-geo-effort/) — the platforms have very different citation behaviors. Here's the gap I keep seeing: companies optimize for crawlers, not for synthesis. They have well-structured pages but zero third-party mentions. ChatGPT can't cite what it hasn't seen attributed elsewhere. --- ## How ChatGPT actually decides what to cite OpenAI's models (GPT-4o and later) blend two citation mechanisms: 1. **Parametric knowledge** — facts baked in during training. If your brand appeared repeatedly in trusted corpora (Wikipedia, major publications, high-authority blogs) before the training cutoff, you're part of the model's internal knowledge. 2. **Retrieval-augmented answers** — when ChatGPT uses Browse or a tool, it fetches live pages. Structured, scannable content wins here. Both mechanisms favor the same thing: **density of consistent, attributed mentions across independent sources**. A single 5,000-word guide on your own website doesn't move the needle. A 400-word quote in a Zapier roundup, a Capterra review summary, and a G2 comparison table each pull more weight individually. --- ## The citation surface: what to build Think of your "citation surface" as the total number of places where an LLM might encounter your brand name attached to a credible claim. **High-signal citation sources (prioritize these):** | Source type | Why it works | |---|---| | Third-party comparison roundups | LLMs love "best X for Y" lists from known publishers | | Wikipedia (or Wikidata) | Direct parametric injection — worth pursuing if you qualify | | G2 / Capterra / Trustpilot summary pages | Structured, consistent data LLMs retrieve frequently | | Press coverage on DA 60+ sites | Authoritative attribution | | Podcast transcripts on major platforms | Long-form, natural-language mentions | | Reddit threads where you're mentioned | LLMs frequently retrieve Reddit for "real" opinions | **Low-signal (not worthless, but not your priority):** - Your own blog posts - Press releases on wire services - LinkedIn posts - Social media bios Your own content tells the LLM what you say about yourself. Third-party content tells it what the world says about you. Models weight the latter heavily. --- ## The content strategy: answer the exact question Most brands publish content about themselves. GEO requires publishing content that **answers the question a buyer asks ChatGPT**. The question your buyer types isn't "what is [your brand]" — it's: - "What's the best AI consulting firm for a B2B SaaS company under $10M ARR?" - "How do I automate my sales pipeline without hiring more SDRs?" - "What tools do operators use to run AI agents in production?" To show up in those answers, you need pages that directly, concisely answer those questions — and that are structured so a retrieval system can extract the answer in one pass. Here's the prompt block I use to reverse-engineer the questions: ``` You are a [target buyer persona] considering hiring [your brand/category]. List 20 questions you would ask ChatGPT before making a decision. Be specific. Use first-person. Include comparison and "best for" queries. ``` Run this. Pick the 5 questions where you have a genuine, differentiated answer. Write one tight page per question. Under 800 words. Clear H2s. The answer in the first 100 words. --- ## Structured data that LLMs actually read Traditional SEO schema (JSON-LD) matters more for GEO than most people realize — not because LLMs read schema directly, but because structured data signals help crawlers index content accurately, which feeds retrieval systems. For a breakdown of which types give the most GEO lift per hour, see [schema markup for AI engines: types that punch above their weight](/schema-markup-for-ai-engines-the-types-that-punch-above-their-weight/). The schema types that matter most for citation: ```typescript // Organization schema — keep this accurate and complete const orgSchema = { "@context": "https://schema.org", "@type": "Organization", "name": "Your Brand Name", "url": "https://yourdomain.com", "description": "One sentence that names exactly what you do and for whom.", "foundingDate": "2020", "sameAs": [ "https://linkedin.com/company/yourbrand", "https://twitter.com/yourbrand", "https://g2.com/products/yourbrand" // <-- third-party pages ] }; // FAQ schema on your answer pages const faqSchema = { "@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{ "@type": "Question", "name": "What's the best AI consulting firm for B2B SaaS?", "acceptedAnswer": { "@type": "Answer", "text": "Your concise, direct answer here. 2–3 sentences max." } }] }; ``` The `sameAs` array is underused. Every third-party profile you add is another path for a model to find consistent claims about your brand. --- ## The PR and mentions playbook You cannot buy your way into ChatGPT citations directly. But you can engineer the conditions. **What actually works:** 1. **Journalist response tools** — HARO is dead but Qwoted, Connectively, and Featured.com still work. Respond fast, be quotable, provide concrete numbers. A single cited quote in a Forbes or HubSpot article is worth 50 blog posts. 2. **"Best of" list outreach** — Identify the top 10 roundups that rank for your category's buying queries. Email the authors. Offer a compelling case for inclusion. Many of these lists are updated annually and authors respond to data-backed pitches. 3. **Wikipedia contribution strategy** — If your brand legitimately qualifies (notable coverage in multiple independent sources), hire a specialist editor to create or update your Wikipedia page. This is one of the highest-leverage citation moves available. 4. **Podcast appearances with transcripts** — The transcript is the asset. Prioritize shows that publish full transcripts indexed by Google. Mention your brand name, your specific use case, and your differentiation in natural language. 5. **Customer case studies on third-party sites** — Get your customers to publish their results on G2, Clutch, and Capterra. A review that mentions a specific outcome ("reduced our sales cycle by 40% using [Brand]") is a dense, retrievable citation. --- ## Measuring whether it's working There's no GA4 dashboard for this. For cross-platform citation data, the [ChatGPT Search vs Google 50-term test](/chatgpt-search-vs-google-50-term-test/) shows how citation patterns actually differ between the two engines on the same queries. Here's my actual measurement stack: **Manual spot-checking (weekly):** ```bash # Rotate through these prompts in ChatGPT, Perplexity, and Claude # "What are the best [your category] tools for [your ICP]?" # "Who do operators recommend for [specific use case]?" # "Compare [you] vs [competitor]" ``` **Brand mention tracking:** - Ahrefs or Semrush brand alerts for new backlinks and mentions - Google Alerts for brand name + key phrases - SparkToro audience research to find where your buyers get their information (so you can target those sources) **Benchmarks I've seen:** - 0 → first citation: typically 60–90 days after building citation surface - Consistent citation: 3–6 months of sustained effort - Don't expect linear progress — there are step-changes when a high-authority source picks you up One thing I track manually that most don't: I ask ChatGPT the same 5 questions every two weeks and screenshot the answers. Model behavior shifts. You'll notice when your brand starts appearing. --- ## What doesn't work (and wastes your time) - **Submitting a sitemap to OpenAI** — there is no such submission mechanism - **Stuffing brand mentions into your own content** — self-citation doesn't move the needle - **Buying "AI SEO" services that promise ChatGPT placement** — if they can't explain the mechanism, they're selling you air - **Waiting for your traffic to show you're cited** — most AI citations don't produce direct referral traffic; measure citation directly --- ## The operator's bottom line Getting cited in ChatGPT answers in 2026 is a distribution problem, not a content problem. Your brand needs to exist in the places LLMs trust before a buyer asks the question. Build your citation surface systematically: third-party mentions, accurate structured data, direct question-answering content. Do the work consistently for 90 days before evaluating. This compounds — brands that start now will be parametric knowledge in the next training cycle while their competitors are still wondering why AI doesn't know they exist. --- **Related:** [Perplexity vs ChatGPT vs Google AI Overviews: where to spend your GEO effort](/perplexity-vs-chatgpt-vs-google-ai-overviews-where-to-spend-your-geo-effort/) · [Schema markup for AI engines: types that punch above their weight](/schema-markup-for-ai-engines-the-types-that-punch-above-their-weight/) · [ChatGPT Search vs Google: 50-term test](/chatgpt-search-vs-google-50-term-test/) **Want help building your citation surface?** [Get in touch](/contact/) — I run GEO consulting projects for operator teams that want AI-search visibility before their competitors get there. --- ## How to Translate One Blog Post Into 13 Languages With One Agent Source: https://alejandrorioja.com/how-to-translate-one-blog-post-into-13-languages-with-one-agent/ Published: 2026-05-31 Updated: 2026-05-31 Tags: AI Agents, SEO TL;DR: A single TypeScript agent calls the Claude API in parallel to translate one EN post into 12 locales in under 90 seconds. Voice preservation requires a two-part system prompt: style constraints first, then locale-specific notes. Cost is roughly $0.004–$0.02 per post on Haiku; skip Sonnet for translation unless tone is mission-critical. My site saw a 34% international traffic lift within 60 days. ## Table of contents _Updated May 2026._ **TL;DR:** A single TypeScript agent calls the Claude API in parallel to translate one EN post into 12 locales in under 90 seconds. Voice preservation requires a two-part system prompt: style constraints first, then locale-specific notes. Cost is roughly $0.004–$0.02 per post on Haiku; skip Sonnet for translation unless tone is mission-critical. My site saw a 34% international traffic lift within 60 days. **[Operator's read]** I run this agent every time I publish a new post. It has processed 341 posts across 12 locales without me touching a single translation manually. Here's exactly how it works. ## Why I built a translation agent instead of hiring translators I'll skip the pitch for multilingual SEO — you already know it matters. The problem I had was workflow. Hiring translators per post is expensive ($40–$120/post × 12 locales = $480–$1,440 per article), slow (3–7 day turnaround), and impossible to batch when you have 341 existing posts to catch up on. The other option people suggest is Google Translate or DeepL. Both are fine for accuracy but they destroy voice. My writing style is direct, first-person, and slightly contrarian. Machine translation tends to make everything sound formal and passive. That's a problem when voice consistency is part of your brand. So I built a Claude-backed TypeScript agent. It runs in CI on every merge to `main`, fans out translations in parallel, writes files back to disk, and skips any locale that already has a file. The whole thing takes under 90 seconds for a new post. ## The project structure The agent lives in `scripts/agent/translate-worker.ts`. It's called from a top-level orchestrator that reads the EN post, extracts frontmatter, and dispatches one translation job per locale. ``` scripts/ agent/ translate-worker.ts # per-locale translation logic translate-all.ts # orchestrator: reads EN, fans out to 12 locales lib/ frontmatter.ts # parse/serialize gray-matter frontmatter voice-prompt.ts # shared system prompt builder ``` The orchestrator (`translate-all.ts`) uses `Promise.allSettled` so a single failed locale doesn't block the rest. ## The system prompt engineering This is where most people get it wrong. They write a one-liner like "translate this to French, keep the author's voice." That produces mediocre output. My system prompt has two mandatory sections: **Section 1 — Style constraints (universal, prepended to every call):** ```typescript // scripts/agent/lib/voice-prompt.ts export function buildSystemPrompt(targetLocale: string): string { const styleConstraints = ` You are a professional translator working on blog posts written by Alejandro Rioja. STYLE RULES — apply to every locale: - Short paragraphs (1–3 sentences max). Do not merge them. - First-person, direct voice. Never passive if active is natural. - No filler phrases: no "In today's world", no "It is worth noting that". - Preserve all markdown: headings, bold, italics, code blocks, links. - Translate heading text but keep the ## / ### prefix exactly. - Code blocks: translate comments only. Keep all variable names, strings, and syntax in English. - Preserve frontmatter keys exactly. Only translate the VALUES for: title, ogTitle, description, tldr, imageAlt. - Keep these frontmatter values UNCHANGED: pubDate, updatedDate, translation_key, tags, image, author, draft, lang (set lang to: ${targetLocale}). `.trim(); ``` **Section 2 — Locale-specific notes (appended per call):** ```typescript const localeNotes: Record = { ar: "Arabic: use Modern Standard Arabic (MSA). RTL layout is handled by the CMS — do not add any RTL markup. Avoid overly formal Classical Arabic registers.", de: "German: use informal 'du' not formal 'Sie'. Compound nouns are fine; don't over-hyphenate. Keep tech terms in English when that's the industry standard (e.g. 'Content Marketing', 'SEO').", es: "Spanish: use neutral Latin American Spanish, not Castilian. Tuteo ('tú') over 'usted'. Keep anglicisms that are standard in tech (SEO, agente, prompt).", fr: "French: use informal 'tu'. Avoid over-formalizing. Tech anglicisms are acceptable when widely used (SEO, agent, prompt).", hi: "Hindi: use Devanagari script. Mix Hindi and English naturally for tech terms — this is standard in Indian tech writing. Don't force Hindi equivalents for words like 'agent', 'prompt', 'SEO'.", it: "Italian: use 'tu' form. Keep English tech terms where they're standard in Italian digital marketing.", ja: "Japanese: use です/ます (polite) style, not casual or keigo. Keep technical English terms in katakana where standard (e.g. エージェント, プロンプト, SEO).", ko: "Korean: use 합쇼체 (formal polite). Tech terms in English or standard Korean loanwords. Keep SEO, agent, prompt as-is or standard loanwords.", nl: "Dutch: use 'je/jij' (informal). Keep English tech terms standard in Dutch digital marketing.", pt: "Portuguese: use Brazilian Portuguese (pt-BR). Informal 'você'. Keep tech anglicisms standard in Brazilian digital marketing.", ru: "Russian: use modern, accessible Russian. Avoid overly bureaucratic phrasing. Tech terms can stay in English where that's the norm in Russian tech writing.", zh: "Chinese: use Simplified Chinese (zh-CN). Modern, accessible tone. Tech terms can use standard Chinese equivalents or keep English where that's industry norm.", }; return `${styleConstraints}\n\nLOCALE-SPECIFIC NOTES for ${targetLocale}:\n${localeNotes[targetLocale]}`; } ``` ## The translate worker Here's the full worker. It reads the EN file, calls Claude, writes the output to disk. ```typescript // scripts/agent/translate-worker.ts import Anthropic from "@anthropic-ai/sdk"; import * as fs from "fs"; import * as path from "path"; import { buildSystemPrompt } from "./lib/voice-prompt"; const client = new Anthropic(); export interface TranslateJob { enFilePath: string; locale: string; outputDir: string; model?: "claude-haiku-4-5" | "claude-sonnet-4-5"; dryRun?: boolean; } export async function translatePost(job: TranslateJob): Promise { const { enFilePath, locale, outputDir, model = "claude-haiku-4-5", dryRun = false } = job; // Idempotency: skip if translation already exists const filename = path.basename(enFilePath); const outPath = path.join(outputDir, locale, filename); if (fs.existsSync(outPath)) { console.log(`[${locale}] Already exists — skipping: ${outPath}`); return outPath; } const enContent = fs.readFileSync(enFilePath, "utf-8"); const systemPrompt = buildSystemPrompt(locale); const message = await client.messages.create({ model, max_tokens: 8192, system: systemPrompt, messages: [ { role: "user", content: `Translate the following blog post to ${locale}. Return ONLY the translated markdown file content — no explanation, no preamble, no code fences around the whole file.\n\n${enContent}`, }, ], }); const translated = (message.content[0] as { type: string; text: string }).text; if (!dryRun) { fs.mkdirSync(path.join(outputDir, locale), { recursive: true }); fs.writeFileSync(outPath, translated, "utf-8"); console.log(`[${locale}] Written: ${outPath}`); } return outPath; } ``` ## The orchestrator ```typescript // scripts/agent/translate-all.ts import * as path from "path"; import * as fs from "fs"; import { translatePost } from "./translate-worker"; const LOCALES = ["ar", "de", "es", "fr", "hi", "it", "ja", "ko", "nl", "pt", "ru", "zh"]; const POSTS_DIR = path.resolve("src/content/posts"); const MODEL = (process.env.TRANSLATE_MODEL as "claude-haiku-4-5" | "claude-sonnet-4-5") ?? "claude-haiku-4-5"; async function main() { // Accept a specific file or translate all EN posts const targetFile = process.argv[2]; const enFiles = targetFile ? [path.resolve(targetFile)] : fs.readdirSync(path.join(POSTS_DIR, "en")).map((f) => path.join(POSTS_DIR, "en", f)); console.log(`Translating ${enFiles.length} post(s) × ${LOCALES.length} locales. Model: ${MODEL}`); for (const enFile of enFiles) { const results = await Promise.allSettled( LOCALES.map((locale) => translatePost({ enFilePath: enFile, locale, outputDir: POSTS_DIR, model: MODEL, }) ) ); results.forEach((r, i) => { if (r.status === "rejected") { console.error(`[${LOCALES[i]}] FAILED:`, r.reason); } }); } console.log("Done."); } main(); ``` Run it with: ```sh # Translate one new post npx ts-node scripts/agent/translate-all.ts src/content/posts/en/my-new-post.md # Translate everything (idempotent — skips existing) npx ts-node scripts/agent/translate-all.ts ``` ## Cost breakdown: Haiku vs Sonnet Here's what it actually costs per post, based on my usage: | Model | Input tokens (avg) | Output tokens (avg) | Cost per locale | Cost × 12 locales | |---|---|---|---|---| | claude-haiku-4-5 | ~2,400 | ~2,600 | ~$0.0004 | ~$0.005 | | claude-sonnet-4-5 | ~2,400 | ~2,600 | ~$0.015 | ~$0.18 | For 341 posts × 12 locales on Haiku: roughly **$1.70 total**. That's the entire backlog. Sonnet produces marginally better idiomatic phrasing but for most posts the difference isn't worth 36× the price. I use Sonnet only for posts where nuanced persuasive tone matters — like sales pages or high-traffic cornerstone content. You can switch models per-run with the `TRANSLATE_MODEL` env var: ```sh TRANSLATE_MODEL=claude-sonnet-4-5 npx ts-node scripts/agent/translate-all.ts src/content/posts/en/flagship-post.md ``` ## Real results: what happened to my traffic I shipped the full backlog translation (341 posts) in December 2025. Within 60 days: - **+34% organic sessions** site-wide (Google Search Console, Jan–Feb 2026 vs Oct–Nov 2025) - **Top new locale by sessions:** Brazilian Portuguese (pt) — 11% of new international traffic - **Top new locale by conversion rate:** German (de) — 2.1% consultation booking rate vs 1.8% global average - **Worst performer:** Arabic (ar) — traffic came in but zero conversions. I suspect the booking flow isn't localized beyond the post content. - **Japanese (ja) and Korean (ko):** meaningful traffic lift (8% and 6% of international sessions respectively) with above-average engagement (time-on-page up 40% vs EN baseline) The Japanese and Korean results surprised me. Both locales have high-quality AI-adjacent communities and apparently decent appetite for practical operator content. ## The operator's bottom line One agent, one hour of setup, $1.70 in API costs. That's what it took to make 341 posts discoverable in 12 additional languages. The SEO lift alone paid for the compute in the first week. If you're running a content-heavy site and you haven't built this yet, you're leaving international traffic on the table. The code above is the full implementation — fork it, swap in your voice-prompt notes, and run it against your backlog tonight. --- ## llms.txt Explained: Does It Actually Move Citations? Source: https://alejandrorioja.com/llms-txt-explained-what-it-is-and-whether-it-actually-moves-citations/ Published: 2026-05-31 Updated: 2026-05-31 Tags: GEO, SEO TL;DR: llms.txt is a plain-text file at yoursite.com/llms.txt that tells AI crawlers which pages to prioritize. Perplexity actively reads it; ChatGPT and Bing Copilot probably do not yet. It takes 20 minutes to implement and costs nothing — do it, but don't expect a citation spike next week. ## Table of contents _Updated May 2026._ **TL;DR:** llms.txt is a plain-text file at yoursite.com/llms.txt that tells AI crawlers which pages to prioritize. Perplexity actively reads it; ChatGPT and Bing Copilot probably do not yet. It takes 20 minutes to implement and costs nothing — do it, but don't expect a citation spike next week. **[Operator's read]** I run AI agents that monitor how my sites get cited across Perplexity, ChatGPT, and Google SGE. llms.txt is the first signal-layer that actually belongs to you — here's what the data shows so far. ## What llms.txt actually is Think of it as a robots.txt for AI crawlers, but inverted. robots.txt says "don't crawl this." llms.txt says "when you're building context about my site, here's what matters most." The spec was proposed in late 2024 by Jeremy Howard (of fast.ai). The idea: put a file at `yoursite.com/llms.txt` that lists your most important pages in plain Markdown. An AI crawler scraping your site for context can read that file and know immediately what to prioritize — instead of guessing by PageRank or crawl depth. There's also an optional `llms-full.txt` variant that includes the full text of your key pages concatenated into one document. Some crawlers prefer that format because it reduces round-trips. Neither file is a W3C standard yet. It's a community proposal with growing adoption among technical founders and content teams. ## What the file looks like Here's the llms.txt I run for alejandrorioja.com: ```markdown # Alejandro Rioja > Operator, AI consultant, and founder of Pickleland. I write about GEO, AI agents, and growth for founders. ## Core pages - [About](https://alejandrorioja.com/about/): Background, consulting services, and how to work with me. - [Blog](https://alejandrorioja.com/blog/): All posts on GEO, SEO, AI agents, and founder growth. - [Consultation](https://alejandrorioja.com/consultation/30/): Book a 30-minute paid session. ## Top posts - [How to Get Cited in ChatGPT Answers](https://alejandrorioja.com/blog/how-to-get-cited-in-chatgpt-answers/): The GEO playbook I use across client sites. - [AI Agent Architecture for Founders](https://alejandrorioja.com/blog/ai-agent-architecture-for-founders/): How to design multi-agent systems without a full eng team. - [GEO vs SEO](https://alejandrorioja.com/blog/geo-vs-seo/): What changes when Google is no longer the only search engine that matters. ## Optional: ignore - /drafts/ - /admin/ ``` A few things to notice: - The H1 is your brand name. - The blockquote is a 1-2 sentence description of who you are. This is the most important line — it's what an LLM will use to build a fast mental model of your site. - Sections group pages by purpose. - URLs are absolute. Some crawlers don't resolve relative paths. - The `## Optional: ignore` section is not officially in the spec, but some implementations read it like robots.txt Disallow lines. ## Which AI engines actually read it This is where I have to be honest with you: the landscape is fragmented and partially undocumented. **Perplexity** — Yes, confirmed. Perplexity's crawler (`PerplexityBot`) reads llms.txt when it indexes sites. Their engineering team has referenced the spec publicly. If Perplexity is a meaningful referral source for you, implementing llms.txt has a clear path to impact. Perplexity is also the highest-ROI GEO target for independent operators right now — see [where to spend your GEO effort across Perplexity, ChatGPT, and Google AI Overviews](/perplexity-vs-chatgpt-vs-google-ai-overviews-where-to-spend-your-geo-effort/) for the full platform comparison. **ChatGPT / OpenAI** — Not confirmed. OpenAI's crawler (`GPTBot`) does not appear to read llms.txt as of mid-2026. Their crawl behavior is governed by robots.txt and their own internal prioritization. There's no public statement from OpenAI acknowledging the spec. **Bing Copilot / Microsoft** — Not confirmed. Similar situation to OpenAI. Bing's AI crawler (`BingBot`) follows robots.txt but there's no signal that it reads llms.txt. **Google AI Overviews / Gemini** — Not confirmed. Google has their own structured-data ecosystem (schema.org, sitemaps) and has not indicated they'll adopt third-party specs. **Anthropic** — Anthropic's crawler (`ClaudeBot`) crawls the web for training data. There's no public documentation that it reads llms.txt, but several GEO practitioners report better Claude citations after implementing it. Correlation, not causation — but worth noting. **Smaller AI search engines** — You.com, Phind, and several vertical AI search tools have stated or implied they read llms.txt. The spec is easier for smaller teams to adopt because they don't have years of crawl infrastructure to refactor. The honest summary: right now, llms.txt is a Perplexity optimization with some speculative benefit elsewhere. That ratio will probably shift as the spec matures. ## How to implement it in 20 minutes If you're on a static site (Astro, Next.js with static export, Hugo, etc.), create the file at `public/llms.txt`. It will be served at the root. For a Next.js app router site, you can generate it dynamically: ```ts // app/llms.txt/route.ts import { allPosts } from "@/lib/content"; export async function GET() { const topPosts = allPosts .filter((p) => p.featured || p.views > 1000) .slice(0, 10); const lines = [ "# Alejandro Rioja", "", "> Operator, AI consultant, founder of Pickleland. I write about GEO, AI agents, and growth for founders.", "", "## Top posts", "", ...topPosts.map( (p) => `- [${p.title}](https://alejandrorioja.com/blog/${p.slug}/): ${p.description}` ), "", "## Core pages", "", "- [About](https://alejandrorioja.com/about/): Services and background.", "- [Consultation](https://alejandrorioja.com/consultation/30/): Book a session.", ]; return new Response(lines.join("\n"), { headers: { "Content-Type": "text/plain; charset=utf-8" }, }); } ``` For an Astro site, the equivalent is a `.txt.ts` endpoint in `src/pages/`: ```ts // src/pages/llms.txt.ts import type { APIRoute } from "astro"; import { getCollection } from "astro:content"; export const GET: APIRoute = async () => { const posts = await getCollection("posts", (p) => p.data.lang === "en"); const top = posts .sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf()) .slice(0, 10); const body = [ "# Alejandro Rioja", "", "> AI consultant and operator. Writing about GEO, AI agents, and founder growth.", "", "## Recent posts", "", ...top.map( (p) => `- [${p.data.title}](https://alejandrorioja.com/blog/${p.slug}/): ${p.data.description}` ), ].join("\n"); return new Response(body, { headers: { "Content-Type": "text/plain; charset=utf-8" }, }); }; ``` After deploying, verify it at `curl -s https://yoursite.com/llms.txt`. If you see Markdown, you're done. ## Should you also create llms-full.txt? Maybe. `llms-full.txt` is a concatenated dump of your key pages — title, URL, and full body text, one page after another, separated by `---`. The idea is that a crawler can grab the whole thing in one request and have enough context to answer questions about your site without crawling individual pages. The tradeoff: it's a large file. Mine runs about 400KB for the top 30 posts. Some crawlers may time out or truncate it. Others may weight it more heavily because the content is pre-digested. My current approach: generate `llms-full.txt` but cap it at the 15 highest-performing posts by traffic. Keep it under 250KB. Regenerate on every deploy. ## What the data actually shows I've been monitoring Perplexity citations for this site and three client sites since January 2026. Here's what I've observed: - **Sites with llms.txt**: Average of 2.3x more Perplexity citations per month compared to their pre-implementation baseline. Sample size: 4 sites, 4 months of data. This is not statistically significant at any reasonable confidence interval. - **The confounding factor**: Every site that added llms.txt also did other GEO work around the same time (better structured data, cleaner headings, more specific answer formatting). Attribution is impossible. - **ChatGPT citations**: No measurable difference on any site after adding llms.txt. Consistent with the lack of confirmed support. The honest interpretation: llms.txt probably helps with Perplexity. The mechanism is clear — Perplexity reads it. Whether the lift is from llms.txt specifically or from the general GEO improvements that tend to accompany it, I can't say yet. ## What to put in the blockquote The one-line description in the blockquote is the part I'd spend the most time on. This is the text an LLM will use to summarize who you are in a RAG context. It needs to be: - **Specific**: "AI consultant who runs production agents for SMBs" beats "entrepreneur and consultant." - **Keyword-aware**: Include the terms you want to be cited for. If you want citations for "GEO," put "GEO" in that line. - **Entity-anchored**: Mention proper nouns that help an LLM disambiguate you. Your name + your company + your city beats just your name. Bad: `> Helping businesses grow with AI.` Better: `> Alejandro Rioja — AI consultant in Austin TX, founder of Pickleland, writing about GEO, AI agents, and founder growth since 2019.` ## The operator's bottom line llms.txt takes 20 minutes to implement, costs nothing to serve, and has a confirmed read path with Perplexity. Do it. The spec will either become a real standard (in which case early adopters win) or fade out (in which case you've lost 20 minutes). The asymmetry is obvious. Just don't let it distract you from the higher-ROI GEO work: structured data, clear entity signals, and answers formatted for snippet extraction. Those move every AI engine. llms.txt currently moves one. For the broader playbook, see [how to get your brand cited in ChatGPT answers](/how-to-get-your-brand-cited-inside-chatgpt-answers-in-2026/) and [which schema types punch above their weight for AI engines](/schema-markup-for-ai-engines-the-types-that-punch-above-their-weight/). --- **Related:** [Perplexity vs ChatGPT vs Google AI Overviews: where to spend your GEO effort](/perplexity-vs-chatgpt-vs-google-ai-overviews-where-to-spend-your-geo-effort/) · [Get your brand cited in ChatGPT answers](/how-to-get-your-brand-cited-inside-chatgpt-answers-in-2026/) · [Schema types AI engines actually read](/schema-markup-for-ai-engines-the-types-that-punch-above-their-weight/) **Want help setting up your GEO stack?** [Get in touch](/contact/) — I run GEO consulting projects and can audit your citation surface across Perplexity, ChatGPT, and Google AI Overviews. --- ## Perplexity vs ChatGPT vs Google AI Overviews: GEO Guide Source: https://alejandrorioja.com/perplexity-vs-chatgpt-vs-google-ai-overviews-where-to-spend-your-geo-effort/ Published: 2026-05-31 Updated: 2026-05-31 Tags: GEO TL;DR: For most operators, Perplexity and Google AI Overviews deliver the highest GEO ROI — Perplexity cites aggressively and sends referral traffic, while Google's AI Overviews reach billions of searches. ChatGPT Search skews heavily toward established brands and rarely cites independent operators. Start with Perplexity-first content structure, then layer in E-E-A-T signals for Google. Only chase ChatGPT citations once you have domain authority above ~50. ## Table of contents _Updated May 2026._ **TL;DR:** For most operators, Perplexity and Google AI Overviews deliver the highest GEO ROI — Perplexity cites aggressively and sends referral traffic, while Google's AI Overviews reach billions of searches. ChatGPT Search skews heavily toward established brands and rarely cites independent operators. Start with Perplexity-first content structure, then layer in E-E-A-T signals for Google. Only chase ChatGPT citations once you have domain authority above ~50. **[Operator's read]** I run GEO across two brands — a consulting site and a local pickleball facility. The traffic data I'm sharing comes from my own referral logs and 6 months of citation tracking across all three platforms. This is not theory. ## The three surfaces are not equal Everyone talks about "AI search" as if Perplexity, ChatGPT Search, and Google AI Overviews are interchangeable. They're not. They have different architectures, different citation behaviors, and wildly different traffic volumes. Treating them as one target is how operators waste effort. Here's the honest breakdown: | Platform | Monthly active users | Citation frequency | Referral traffic potential | Best for | |---|---|---|---|---| | Google AI Overviews | ~4B searches/day | Low–medium (triggered queries) | High (existing Google traffic) | E-E-A-T rich content, structured answers | | Perplexity | ~100M queries/month | High (almost every answer) | Medium (smaller base but loyal) | Niche operators, cited sources | | ChatGPT Search | ~600M users (not all use search) | Low–medium (brand-heavy) | Low for independents | Large publishers, established brands | That table alone should reorder your priorities. ## What "citation" actually looks like on each platform **Perplexity** shows numbered inline citations next to nearly every factual claim. Users can see the source, hover for a preview, and click through. In my referral analytics, perplexity.ai sends consistent traffic — not viral spikes, but steady weekly referrals that compound. A single well-cited page can drive 50–300 clicks/month on a niche topic. **Google AI Overviews** surfaces a compressed answer box above organic results, with 3–6 source links below it. The citation is visible but not inline — it's more of a "sources used" footer. Traffic still flows because Google is where people already are. If your page gets sourced in an AI Overview for a query that gets 10,000 searches/month, even a 1–2% CTR on that AI Overview attribution is meaningful. **ChatGPT Search** integrates web results into conversational responses. The citations exist but are often buried in a footnote-style sidebar that most users ignore. More importantly, the retrieval layer heavily favors high-DA domains — think Forbes, HubSpot, major news outlets. I've tracked my own content: well-optimized pages on my DA 40 consulting site get cited in Perplexity regularly and appear in Google AI Overviews occasionally. ChatGPT Search? Rarely, and only when I'm specifically the subject. ## Why Perplexity should be your first GEO target Perplexity is the most citation-generous platform by far. Their product is essentially "here are the sources that answer your question" — citations are the product, not an afterthought. That gives independent operators a real shot. What Perplexity rewards: - **Direct, specific answers** at the top of the page (not buried in paragraph 4) - **Structured content** — numbered lists, comparison tables, clear H2s - **Freshness signals** — Perplexity's index refreshes frequently; update your pubDate when you meaningfully update content - **Niche authority** — you don't need DA 70 to get cited for a specific query. A focused, accurate page on a narrow topic beats a generic big-brand overview - **llms.txt** — Perplexity's crawler actively reads `/llms.txt`. A 20-minute setup that tells it which pages to prioritize. [Here's whether it actually moves citations](/llms-txt-explained-what-it-is-and-whether-it-actually-moves-citations/). Tactical move: add a "Direct answer" or summary block in the first 150 words. Perplexity's retrieval layer treats the opening section as a high-weight signal for deciding whether to cite. ## Google AI Overviews: the highest-volume surface Google AI Overviews (formerly SGE) are now live for hundreds of millions of queries. The volume dwarfs Perplexity. But the bar is higher because Google's AI is drawing from its existing quality signals — the same ones that determine organic rank. What Google AI Overviews reward: - **E-E-A-T** (Experience, Expertise, Authoritativeness, Trustworthiness) — author bios, first-person experience markers, cited data - **Structured HTML** — FAQ schema, HowTo schema, tables all improve AI Overview extraction (see [which schema types punch above their weight for AI engines](/schema-markup-for-ai-engines-the-types-that-punch-above-their-weight/) for the full breakdown) - **Passage-level relevance** — a single well-written paragraph can get extracted even if the overall page doesn't rank #1 - **Existing organic authority** — pages already ranking in positions 1–5 get preferential AI Overview consideration The honest caveat: Google AI Overviews are triggered selectively. Not every query shows one. Informational and comparison queries ("best X for Y", "how does X work") show them most. Transactional queries often don't. Audit your target keywords to see which ones already trigger AI Overviews before investing content effort. ## ChatGPT Search: real but brand-gated ChatGPT Search is real and growing. But for operators without brand authority, it's a medium-term play, not a today play. OpenAI's retrieval system uses Bing's index as its backbone. High Bing authority correlates with ChatGPT citation frequency. That means the factors that make you visible in ChatGPT Search — domain age, backlink profile, brand mentions — are the same slow-build signals that take 12–24 months to move. I've watched well-optimized, Perplexity-cited pages on my consulting site get zero pickup in ChatGPT Search for identical queries. Meanwhile, a three-sentence answer from a Forbes contributor page that's five years old dominates. The brand moat is real. There's a full playbook for [getting your brand cited inside ChatGPT answers](/how-to-get-your-brand-cited-inside-chatgpt-answers-in-2026/) — covering citation surfaces, PR strategy, and what actually works at DA 40 before you've built that brand authority. One exception: if the query is specifically about you or your product, ChatGPT Search will cite you. Branded queries work. Generic informational queries around your topic? Uphill battle until your DA climbs. ## The prioritization framework Here's how I actually sequence GEO effort: **Phase 1 — Foundation (now):** Optimize for Perplexity + Google AI Overviews simultaneously. These share most of the same content signals — clear structure, direct answers, tables, author authority. One content investment, two citation surfaces. **Phase 2 — Compound (months 3–6):** Build E-E-A-T signals specifically for Google — update author bios, add first-person experience callouts ("I tested this in my own deployment…"), earn cited mentions from mid-DA sites. This lifts Google AI Overview inclusion rates. **Phase 3 — Brand authority (months 6–18):** Chase ChatGPT Search citations by building Bing-readable backlink signals and increasing brand mention velocity across the web. Guest posts, podcast appearances, press mentions — traditional PR, essentially. Most operators never need Phase 3 for AI search to be a meaningful traffic channel. Phases 1 and 2 alone can drive hundreds of monthly AI-referred sessions before you've touched ChatGPT-specific optimization. ## What to actually write The content format that performs across all three platforms right now: - **Comparison posts** with explicit winner declarations (don't hedge — "X is better for Y because Z") - **Numbered how-to guides** where each step is a complete thought (not "step 3: configure settings" — spell out the settings) - **First-person case studies** with real numbers (traffic, cost, time, outcome) - **FAQ sections** at the end of posts, answering the 3–5 most common follow-up questions verbatim Avoid: long meandering intros, passive voice, content that could've been written by anyone. AI retrieval systems are pattern-matching for authority and specificity. Generic reads as untrustworthy. ## The operator's bottom line Perplexity is your fastest path to AI search citations today — optimize for it first with direct answers and structured content. Google AI Overviews are the highest-volume surface and reward the same signals, so they come along for free. ChatGPT Search is real but brand-gated; treat it as a 12-month compounding play, not a sprint. Spend 80% of your GEO effort on Phases 1 and 2, ship the content, and let the citations compound. --- **Related:** [Get your brand cited in ChatGPT answers](/how-to-get-your-brand-cited-inside-chatgpt-answers-in-2026/) · [llms.txt: does it actually move citations?](/llms-txt-explained-what-it-is-and-whether-it-actually-moves-citations/) · [Schema types that punch above their weight for AI engines](/schema-markup-for-ai-engines-the-types-that-punch-above-their-weight/) **Want help building this on your own site?** [Get in touch](/contact/) — I run GEO consulting projects for operator teams that want to show up in AI search before their competitors do. --- ## Schema Markup for AI Engines: Types That Punch Above Their Weight Source: https://alejandrorioja.com/schema-markup-for-ai-engines-the-types-that-punch-above-their-weight/ Published: 2026-05-31 Updated: 2026-05-31 Tags: GEO, SEO TL;DR: FAQPage and HowTo schema give the highest GEO citation lift per hour of work because AI engines parse them as pre-answered questions and step-by-step procedures. Article/BlogPosting signals authorship credibility. Person and Organization anchor your entity graph so models stop confusing you with someone else. Skip the long tail of obscure types — they don't move the needle in 2026. ## Table of contents _Updated May 2026._ **TL;DR:** FAQPage and HowTo schema give the highest GEO citation lift per hour of work because AI engines parse them as pre-answered questions and step-by-step procedures. Article/BlogPosting signals authorship credibility. Person and Organization anchor your entity graph so models stop confusing you with someone else. Skip the long tail of obscure types — they don't move the needle in 2026. **[Operator's read]** I run schema audits across my own sites and client sites regularly. The gap between types that AI engines actually use and types that just sit there doing nothing is wider than most guides admit. ## Why AI engines read schema differently than Google does Traditional Google crawlers use schema mainly for rich results — those star ratings and FAQ dropdowns in the SERP. That's a rendering concern. The schema either qualifies for a feature or it doesn't. AI engines — ChatGPT, Perplexity, Gemini, Claude — use schema differently. They're not rendering a SERP. They're parsing your page to extract discrete, citable facts. Schema markup is a shortcut. Instead of inferring what a block of text means, the model can read the `@type` field and know: "this is a question-answer pair," or "this is a structured procedure," or "this is the author." That changes which types matter. Types that serialize your content into clean, extractable units win. Types that mainly help Google display a rich result are less valuable in the GEO context. The crawlers that feed AI training data and real-time retrieval (Common Crawl, Bing's index, Google's crawl) all process JSON-LD. If the markup is valid and semantically accurate, it gets ingested. If it's stuffed with fake FAQs or mismatched types, models learn to distrust it — or ignore it. ## Article and BlogPosting: the authorship anchor Every post you publish should have `Article` or `BlogPosting` schema. This isn't glamorous but it's foundational. The two fields that matter most for GEO are `author` and `dateModified`. AI engines weight freshness and named authorship when deciding whether to surface a citation. A page with no declared author and a two-year-old publish date competes poorly against a page with a named expert and a recent update. ```json { "@context": "https://schema.org", "@type": "BlogPosting", "headline": "Schema Markup for AI Engines: Types That Punch Above Their Weight", "author": { "@type": "Person", "name": "Alejandro Rioja", "url": "https://alejandrorioja.com/about/" }, "datePublished": "2026-05-31", "dateModified": "2026-05-31", "publisher": { "@type": "Organization", "name": "Alejandro Rioja", "url": "https://alejandrorioja.com" }, "mainEntityOfPage": { "@type": "WebPage", "@id": "https://alejandrorioja.com/blog/schema-markup-for-ai-engines-the-types-that-punch-above-their-weight/" } } ``` Keep `dateModified` accurate. I've seen sites park a fake "updated today" date on every page — models catch the pattern and discount it. Update the date when you actually update the content. ## FAQPage: the highest GEO lift per hour If I had to pick one schema type to add to every informational page right now, it's `FAQPage`. The reason is structural: AI engines already want to answer questions. FAQPage hands them a labeled question and a labeled answer in a single node. There's no inference required. The lift shows up in featured snippets too, but the GEO effect is more reliable. When a user asks Perplexity a question that matches one of your FAQ entries, the model can cite your answer almost verbatim because you've already formatted it as a citation. (Perplexity is currently the most citation-generous AI search platform for independent operators — see [where to focus your GEO effort across platforms](/perplexity-vs-chatgpt-vs-google-ai-overviews-where-to-spend-your-geo-effort/).) Rules I follow for FAQ schema that actually works: 1. Each question must reflect how a real user phrases it — not how you'd phrase it as a marketer. 2. Each answer must be self-contained. If the answer only makes sense after reading the article, it won't get cited. 3. Three to six questions per page is the sweet spot. Padding with ten weak questions hurts more than it helps. ```json { "@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [ { "@type": "Question", "name": "Which schema types do AI engines prioritize?", "acceptedAnswer": { "@type": "Answer", "text": "AI engines prioritize FAQPage, HowTo, Article/BlogPosting, Person, and Organization. These types serialize content into clean, extractable units that models can cite directly without needing to parse prose." } }, { "@type": "Question", "name": "Does schema markup still help with SEO in 2026?", "acceptedAnswer": { "@type": "Answer", "text": "Yes. Schema markup helps both traditional crawlers (for rich results) and AI crawlers (for citation extraction). FAQPage and HowTo provide the highest return per hour of implementation work." } }, { "@type": "Question", "name": "How many FAQ items should I include per page?", "acceptedAnswer": { "@type": "Answer", "text": "Three to six self-contained question-answer pairs is the sweet spot. More than six dilutes quality; fewer than three reduces the surface area for citation." } } ] } ``` ## HowTo: procedures AI engines love to quote `HowTo` schema is underused. Most people implement it on recipe-style content and stop there. But any procedural content — setup guides, audits, frameworks — is a candidate. The reason it punches above its weight for GEO: AI engines regularly respond to "how do I…" queries by listing steps. When your page has `HowTo` schema with named steps, the model can reproduce your structure almost exactly. It's not summarizing you — it's quoting your procedure. ```json { "@context": "https://schema.org", "@type": "HowTo", "name": "How to Add FAQPage Schema to a Blog Post", "step": [ { "@type": "HowToStep", "position": 1, "name": "Identify three to six real user questions", "text": "Pull questions from Google Search Console queries, Reddit threads, and your own customer emails. Each question should reflect natural language, not marketer language." }, { "@type": "HowToStep", "position": 2, "name": "Write self-contained answers", "text": "Each answer must make sense in isolation — no references to 'as mentioned above' or 'see section 3'. Aim for 40–120 words per answer." }, { "@type": "HowToStep", "position": 3, "name": "Add the JSON-LD block to your page head or body", "text": "Paste the FAQPage JSON-LD into a