{
  "version": "https://jsonfeed.org/version/1.1",
  "title": "Building @Owner",
  "home_page_url": "https://production.owner-engineering-blog.com/",
  "feed_url": "https://production.owner-engineering-blog.com/feed.json",
  "description": "Notes from the engineers building Owner, the AI platform restaurants use to grow first-party sales.",
  "icon": "https://production.owner-engineering-blog.com/images/apple-touch-icon.png",
  "favicon": "https://production.owner-engineering-blog.com/images/favicon.png",
  "language": "en-US",
  "items": [
    {
      "id": "https://production.owner-engineering-blog.com/agent-1",
      "url": "https://production.owner-engineering-blog.com/agent-1",
      "title": "Owner Agent, Part 1",
      "summary": "How Owner turned 224,000 support cases into about 2,000 agent operations, and the channels, tools, approvals, and evals behind Owner Agent.",
      "content_html": "<div class=\"stats\">\n  <div><b>224k+</b><span>historical support cases mined</span></div>\n  <div><b>~2,000</b><span>discrete operations they reduce to</span></div>\n  <div><b>~1,000</b><span>evals stubbed by hand so far</span></div>\n  <div><b>80%</b><span>of cases arrive by phone</span></div>\n</div>\n\n<p>\n  Owner Agent needs breadth to handle both the common operational tasks and the more advanced\n  questions.\n</p>\n<p>\n  <strong>Common operational tasks:</strong> Some customers won’t use the Owner dashboard, or any\n  dashboard. They will call support even to change their restaurant’s hours, which takes 3 clicks in\n  our dashboard. They’ll still call. We want to be super available to them with no hold time.\n</p>\n<figure>\n  <video src=\"https://production.owner-engineering-blog.com/videos/owner-app-hours.mp4\" poster=\"https://production.owner-engineering-blog.com/videos/owner-app-hours.jpg\" width=\"1600\" height=\"900\" muted loop playsinline preload=\"none\" aria-label=\"Changing business hours with Owner Agent\" data-video=\"owner-app-hours\"></video>\n  <figcaption>Common tasks: one sentence, one approval card</figcaption>\n</figure>\n<p>\n  <strong>More advanced questions:</strong> Some customers run dozens of locations. They want to\n  understand trends, like what's moving on their menus. They want to know how many corporate\n  customers they serve, and keep track of what their competitors are doing. There’s room for\n  sophistication in helping them grow their business.\n</p>\n<figure>\n  <img width=\"1200\" height=\"675\" decoding=\"async\" src=\"https://production.owner-engineering-blog.com/images/posts/owner-agent-gif-3.gif\" alt=\"Owner Agent answering a question about a restaurant's business\" loading=\"lazy\">\n</figure>\n<p>\n  In the medium term, Owner Agent will also become proactive, volunteering to take actions to grow\n  each customer’s business (e.g. create a coupon code in time for National Taco Day to drive app\n  orders). Owner Agent will be the gateway to the entire breadth of the platform.\n</p>\n<p>\n  So we had to decide where to start - what to prioritize, which classes of problems to solve now\n  versus later.\n</p>\n\n<h2>Why we decided to start with support</h2>\n<p>\n  We've started by framing Owner Agent’s first big \"customer\" as our own customer-facing teams: 100+\n  users, thousands of conversations per week.\n</p>\n<p>\n  They use Owner Agent to help with time-consuming support tasks - for example, setting up a new\n  menu. In the past, our reps had to manually review a Google Drive with hundreds of food images,\n  half of them unlabeled, trying to eyeball which image corresponded to which menu item. That pain\n  is gone. Owner Agent has vision as one of its modalities; it offers its best guess to match menu\n  items to photos. It also does the previously time-intensive work of deconstructing a menu PDF into\n  items, modifiers, and assets.\n</p>\n<figure>\n  <video src=\"https://production.owner-engineering-blog.com/videos/menu-setup.mp4\" poster=\"https://production.owner-engineering-blog.com/videos/menu-setup.jpg\" width=\"1600\" height=\"900\" muted loop playsinline preload=\"none\" aria-label=\"Owner Agent sets up a menu from a PDF and photos\" data-video=\"menu-setup\"></video>\n  <figcaption>\n    Owner Agent breaks a menu PDF into items and modifiers, then matches unlabeled photos to items\n  </figcaption>\n</figure>\n<p>\n  Soon Owner Agent will be customer-facing. This isn't because our reps aren't great: in fact\n  customer satisfaction exceeds 90%, and customers often mention how much they love our reps. But at\n  peak times, callers can still wait on hold for 15 to 30 minutes. You shouldn't wait 30 minutes to\n  change your hours just because the dashboard feels intimidating. Ideally Owner Agent will cut into\n  those wait times.\n</p>\n\n<h2>3 primitives</h2>\n<p>3 primitives run through the rest of this post. Our team looks at them as a hierarchy:</p>\n<figure>\n  <video src=\"https://production.owner-engineering-blog.com/videos/primitives.mp4\" poster=\"https://production.owner-engineering-blog.com/videos/primitives.jpg\" width=\"1600\" height=\"900\" muted loop playsinline preload=\"none\" aria-label=\"Owner Agent's three primitives: channel, tool, capability\" data-video=\"primitives\"></video>\n  <figcaption>\n    A request flows from its channel, through a tool, into a capability proven by evals\n  </figcaption>\n</figure>\n<dl class=\"primitives\">\n  <div>\n    <dt><span>01</span>Channel</dt>\n    <dd>\n      How the customer talks to us: the support line, email, or in-product through Owner Agent in\n      the dashboard. 80% of cases today come through the phone. This matters because our ability to\n      represent an action, the tool call, depends on the channel. A complicated menu change isn't\n      safe to express over voice. Voice is lossy.\n    </dd>\n  </div>\n  <div>\n    <dt><span>02</span>Tool</dt>\n    <dd>\n      The ability to perform a narrow operation: change a menu price, update a photo. We wrap\n      existing service methods in our codebase. The codebase is full of functions that change hours\n      or refund a customer. A tool is the bridge from that function to something an agent can\n      activate.\n    </dd>\n  </div>\n  <div>\n    <dt><span>03</span>Capability</dt>\n    <dd>\n      The underlying set of tools and prompts that gets an agent to an outcome. Evals are how we\n      show we have a capability - that we can handle a type of request reliably. We’ll come back to\n      this.\n    </dd>\n  </div>\n</dl>\n<p>For example, suppose the customer says:</p>\n<blockquote>\n  Remove the pollo taco from the menu at Miramar, add tomatillo as a salsa option there, and raise\n  the price of guacamole in Atlanta by $1.\n</blockquote>\n<p>\n  Even our human support reps would write up an email to confirm. The channel shapes how a tool call\n  gets represented, and each channel has its own human in the loop.\n</p>\n<p>\n  It’s a hierarchy because, for instance, the channel is upstream of how Owner Agent gets approval\n  to call a tool.\n</p>\n\n<h2>Tool call approvals</h2>\n<p>\n  The approval is the agent saying it's about to make an operation and needs to show you first, then\n  deciding which channel that comes into: UI or SMS. For example, imagine you call in over the phone\n  wanting to make a meaningful change to your menu, including changing modifiers. We'll repeat the\n  whole set of operations back to you: the intake, then the confirmation, out loud - but there's\n  still room for error. We convert the set of operations into plain language and send it over SMS,\n  confirming all the changes. So when you call in, you become the human in the loop. :)\n</p>\n<figure>\n  <video src=\"https://production.owner-engineering-blog.com/videos/sms-approval.mp4\" poster=\"https://production.owner-engineering-blog.com/videos/sms-approval.jpg\" width=\"1600\" height=\"900\" muted loop playsinline preload=\"none\" aria-label=\"Confirming a phone request over SMS\" data-video=\"sms-approval\"></video>\n  <figcaption>\n    A spoken request, confirmed in plain language over SMS before anything changes\n  </figcaption>\n</figure>\n<p>\n  That isn't an industry convention. But it's what our customers need, because we can't count on\n  them to have our mobile app.\n</p>\n<p>\n  But say you do have the app. In that case, we can link you to your account and trigger a push\n  notification that shows you exactly where to find that feature.\n</p>\n<figure>\n  <img width=\"1600\" height=\"1180\" decoding=\"async\" src=\"https://production.owner-engineering-blog.com/images/posts/owner-agent-push-notification.webp\" alt=\"A push notification linking the owner to the exact feature in the app\" loading=\"lazy\">\n  <figcaption>With the app installed, a push notification links to the exact screen</figcaption>\n</figure>\n<p>\n  This goes for our reps too. They can say: “I'm happy to do this for you. But if you'd rather not\n  talk to me, I just dropped a push notification into your phone.”\n</p>\n<p>\n  It’s educating the user inside a flow that was going to happen anyway, showing a better way to get\n  there next time.\n</p>\n\n<h2>From cases to evals</h2>\n<p>\n  We have converted our historical support cases into a provable mechanism to demonstrate that the\n  agent has the capabilities to impact these cases. That mechanism is our eval dash.\n</p>\n<p>\n  With help from our BizOps team, we’ve mined Owner's 224,000+ historical support cases from\n  Snowflake and clustered them into a small set of scorable capabilities.\n</p>\n<figure>\n  <video src=\"https://production.owner-engineering-blog.com/videos/cases-to-evals.mp4\" poster=\"https://production.owner-engineering-blog.com/videos/cases-to-evals.jpg\" width=\"1600\" height=\"900\" muted loop playsinline preload=\"none\" aria-label=\"From 224,000 support cases to evals\" data-video=\"cases-to-evals\"></video>\n  <figcaption>224,000 cases collapse into about 2,000 operations, each backed by evals</figcaption>\n</figure>\n<figure>\n  <img width=\"1600\" height=\"991\" decoding=\"async\" src=\"https://production.owner-engineering-blog.com/images/posts/eval-dash-screenshot-1.webp\" alt=\"Eval dashboard showing support case categories\" loading=\"lazy\">\n  <figcaption>The eval dash, top-level view</figcaption>\n</figure>\n<p>\n  The top-level view covers about 25% of our case categories. Take menu updates: roughly 10,000 of\n  our 224,000 cases, about 5%, came from someone trying to change a menu. Each case carries 3 levels\n  of categorization: primary, secondary, tertiary. We counted 414 distinct reasons at last check.\n</p>\n<figure>\n  <img width=\"1600\" height=\"1061\" decoding=\"async\" src=\"https://production.owner-engineering-blog.com/images/posts/eval-dash-screenshot-2.webp\" alt=\"Eval dashboard drilling into a case family ranked by time to close\" loading=\"lazy\">\n  <figcaption>Case families ranked by support time, not volume</figcaption>\n</figure>\n<p>\n  We rank the work by support time, not case count. A family can match another in volume but take\n  10x the number of hours. So we show both and prioritize by time. That's why the target leads with\n  time-to-close. We want the agent to absorb about 50% of the time our cases take to resolve.\n</p>\n\n<h2>Compressing 224,000 cases into 2,000 capabilities</h2>\n<p>\n  The cases overlap more than they look. Say one case needs 3 operations and another needs 4. If 3\n  of them match, the pair shares only 4 distinct operations. The count collapses. Today it looks\n  like 224,000 cases reduce to around 2,000 discrete operations. Build those 2,000, and we cover the\n  next 1,000 cases that lean on them.\n</p>\n<p>\n  One distinction: a capability isn't the same as a solved case. Hand the agent a case with 70\n  moving parts and it may need to break it up, or punt, as a human might too. So we track 2 axes:\n  the capability on one, reasoning through a specific case on the other.\n</p>\n<p>\n  I've stubbed about 1,000 evals by hand. Next I wire them into the codebase and run them in\n  Buildkite. Then we run the agent against them. A second LLM grades each run against the expected\n  output and tools, and we watch the trend. In production, a separate LLM checks every response\n  against the tools the agent called. If the agent says \"I checked your account\" but never called\n  the lookup, we catch it.\n</p>\n<p>If the agent passes the evals tied to a set of cases, we can say we solve those cases.</p>\n\n<h2>Coming up in later editions</h2>\n<ul>\n  <li>\n    <strong>Escalation.</strong> What if there’s a 2-3 week support engagement with 16 turns? What\n    if the agent can handle 7 turns, but escalates on the 8th? We will build for those\n    possibilities.\n  </li>\n  <li>\n    <strong>Observability.</strong> Customer-facing teams need to be able to observe and understand\n    what the customer wants the agent to do and what it did. This infrastructure will be unified so\n    that SMS, email, and every other channel will be in one timeline.\n  </li>\n</ul>",
      "image": "https://production.owner-engineering-blog.com/og/agent-1.png",
      "date_published": "2026-10-06T12:00:00Z",
      "date_modified": "2026-10-06T12:00:00Z",
      "authors": [
        {
          "name": "Daniel Ternyak",
          "avatar": "https://production.owner-engineering-blog.com/images/authors/daniel-ternyak.webp"
        }
      ],
      "tags": [
        "AI"
      ]
    },
    {
      "id": "https://production.owner-engineering-blog.com/zeus",
      "url": "https://production.owner-engineering-blog.com/zeus",
      "title": "Zeus: Apps for all",
      "summary": "Zeus is the internal platform behind 500+ Owner apps: one-click deploys, safe data access, built-in auth, and agent-driven migrations.",
      "content_html": "<p>\n  AI has shredded so many barriers to building: with the novelty of capable coding agents, we\n  started to see a rapid expansion of internal apps and prototypes. We wanted anyone on the team to\n  be able to build an app that could use internal data, connect to an LLM, and be shared securely.\n</p>\n<p>\n  That was the vision for Zeus. We just crossed more than 500 internal apps &amp; prototypes running\n  on the platform, which is ~3x the headcount of our engineering, product and design org.\n</p>\n<figure>\n  <img width=\"1600\" height=\"900\" decoding=\"async\" src=\"https://production.owner-engineering-blog.com/images/posts/zeus-apps.webp\" alt=\"\" loading=\"lazy\">\n</figure>\n<p>\n  Some Zeus apps are universally used and will get their own blog posts in the future, like our\n  internal coding agent, Forge.\n</p>\n<figure>\n  <img width=\"1600\" height=\"1020\" decoding=\"async\" src=\"https://production.owner-engineering-blog.com/images/posts/zeus-forge.webp\" alt=\"\" loading=\"lazy\">\n</figure>\n<p>\n  The PLG team built the PLG HQ, which drops confetti on demo day and gives everyone a bear to\n  control.\n</p>\n<figure>\n  <img width=\"1600\" height=\"916\" decoding=\"async\" src=\"https://production.owner-engineering-blog.com/images/posts/zeus-plg-hq.webp\" alt=\"\" loading=\"lazy\">\n</figure>\n<p>The design team made an app for brand assets:</p>\n<figure>\n  <img width=\"1600\" height=\"916\" decoding=\"async\" src=\"https://production.owner-engineering-blog.com/images/posts/zeus-brand-assets.webp\" alt=\"\" loading=\"lazy\">\n</figure>\n<p>We all needed the Owner Atlas app, so we could search for food near us:</p>\n<figure>\n  <img width=\"1600\" height=\"927\" decoding=\"async\" src=\"https://production.owner-engineering-blog.com/images/posts/zeus-owner-atlas.webp\" alt=\"\" loading=\"lazy\">\n</figure>\n<p>And one person really needed to go back to &#x27;95:</p>\n<figure>\n  <img width=\"1600\" height=\"916\" decoding=\"async\" src=\"https://production.owner-engineering-blog.com/images/posts/zeus-win95.webp\" alt=\"\" loading=\"lazy\">\n</figure>\n<h2>How it works</h2>\n<p>We built Zeus to solve 4 problems. </p>\n<p>\n  <strong>First problem:</strong> internal apps were being deployed in all kinds of ways. Before\n  Zeus, new internal apps were appearing constantly with different hosts, databases, and vendors. We\n  wanted one frontend system, with everything deployed in the same place with the same database\n  setup. With Zeus you click “generate app,” and a minute later you have a web app (Tanstack Start\n  or Astro, react, server-side rendering, file-based routing). Zeus makes a GitHub repo, provisions\n  a Postgres database and a storage bucket, scaffolds the template into the repo, injects the\n  secrets, and commits. GitHub Actions deploys it, and Zeus watches to make sure the first\n  deployment completes.\n</p>\n<p>\n  All apps run on Cloudflare&#x27;s Workers for Platforms. They share one namespace, and a single\n  gateway worker sits in front and routes each request to the right app. This gives us room for\n  10,000 apps before the next limit. Everything lives under one domain, and each app is a subdomain\n  of owner.sh.\n</p>\n<p><strong>2nd problem</strong>: giving every app safe access to our data.</p>\n<p>\n  Zeus gives every app built-in access to our systems and holds the credentials itself without a\n  vendor key. It calls a Zeus proxy, and Zeus handles auth, rotation, and logging. Out of the box,\n  an app can:\n</p>\n<ul>\n  <li>\n    Query Snowflake. One click mints a key, and the app queries through our Snowflake proxy. The app\n    holds no Snowflake credentials of its own.\n  </li>\n  <li>\n    Call an LLM. Every app routes AI calls through Poros, our internal inference router. We track\n    cost per app, swap models, and fail over between vendors.\n  </li>\n  <li>\n    Post to Slack. Flip on the integration and the app sends messages under its own name and avatar.\n  </li>\n  <li>\n    Send email. A shared services worker sends email to owner.com addresses, so apps don’t each set\n    up a mailer.\n  </li>\n  <li>Run scheduled jobs. A central scheduler triggers each app’s cron handler on time.</li>\n</ul>\n<p>\n  <strong>3rd problem: </strong>auth, app permissions and security. Auth runs through Shibboleth,\n  our sign-in portal. It uses our Okta SSO, lets in only owner.com accounts, and is completely\n  separate from our production product auth. The gateway worker checks auth on every request before\n  it reaches an app for extra security. If you&#x27;re not signed in, it sends you to Shibboleth\n  first.\n</p>\n<p>\n  Permissions are per app. An app is either open to anyone at the company or limited to people you\n  grant in. Each grant carries a role, member or admin. You can bundle users into a group and grant\n  the group across several apps. You manage all of it from the app’s page. Apps you share\n  company-wide show up in the gallery.\n</p>\n<p>\n  <strong>4th problem: </strong>we needed to be able to update all the apps agentically as we make\n  updates in the future. Our migrations system creates an isolated session on a throwaway VM for\n  each app.  The agent clones the app, makes the change, and opens a single PR. A manager session\n  reviews all the PRs, retriggers updates when needed, and merges updates. We use this to bump\n  dependencies, patch vulnerabilities, and roll out shared changes/upgrades. We can also run a\n  read-only version that just reports back for compliance or tracking purposes.\n</p>\n<p>\n  All app logs drain into Datadog so users can debug their apps and the company has visibility into\n  what&#x27;s running. Zeus has a CLI for developers to manage and debug their apps.\n</p>\n<p>\n  The result is that when you plug your AI coding tool into a Zeus app, it doesn’t need to ask you\n  configuration questions that you may or may not be able to answer. Auth, database, deployment, CI,\n  monitoring, environment. That’s all decided. You just describe what you want to build.\n</p>\n<h2>100% adoption</h2>\n<p>\n  App growth has been steadily increasing since the early days of the Zeus launch. Our teams can\n  just ship apps instead of spending time evaluating tools and onboarding to new services, with just\n  a few exceptions. Adoption across our team has been pretty organic since it’s so quick and\n  painless to get started on the platform.\n</p>\n<p>\n  Every assumption we made about how small this would stay was wrong. And maybe this connects to\n  where things are generally headed: the boundaries between engineering, design, and product are\n  blurring. More people default to building. They see a problem and now have the tools to go do\n  something about it. It used to require getting an engineer’s time, getting approval, getting API\n  keys, getting access to systems. Zeus removes all of that. \n</p>\n<h2>A few things we learned<em></em></h2>\n<p><strong>The first version was way too ambitious and overcomplicated. </strong></p>\n<p>\n  We tried to build our own system for deployments and CI, basically bypassing GitHub Actions. Users\n  would push code directly into Zeus for processing. Presumably this would let us analyze code and\n  add security guardrails. But that ended up being too restrictive for all the edge-cases of what\n  people wanted to build. We scrapped it and went with the generator approach we have now, in which\n  Zeus scaffolds a real repo with real CI and gets out of your way.\n</p>\n<p><strong>At first, we didn&#x27;t plan for how fast it would grow. </strong></p>\n<p>\n  We were limited by how many workers we could deploy on one account. When we started, we barely\n  thought about it, figuring it’d be a long time before we hit the limit. Then we passed 100 apps,\n  and it became clear we’d blow through that limit quickly. That forced a migration to Workers for\n  Platforms while also keeping all the existing standalone workers running.\n</p>\n<p><strong>We needed a way to maintain hundreds, maybe thousands, of apps.</strong></p>\n<p>\n  The obvious risk with a platform like this is that apps get built, deliver some value, and then\n  get forgotten. With 500+ apps and no guarantee each one has a long-term owner, that’s a problem.\n  We built the migrations system where we can write a prompt for an AI agent and run it against\n  every app on the platform at once. The apps don’t rot because the platform itself maintains them\n  to a minimum standard.\n</p>\n<p><strong>Expectations and needed features grew quickly</strong></p>\n<p>\n  Zeus began on a pure whim, and very much a side project to day-to-day responsibilities at Owner.\n  Within a few short months, HR and finance built apps with highly sensitive data. A few apps were\n  built that eclipsed the capacity of our original SQLite databases, and some of our builders\n  clamored for dedicated compute features. Luckily, Owner has a supportive organization and we’ve\n  managed to scale Zeus with contributions from VP Eng (thanks Max!) and now a second dedicated team\n  member, Lucas.\n</p>\n<p>If you’re going to build this for your team: </p>\n<p>\n  You can’t predict what people will build. You can’t predict how complex the apps will be. You\n  can’t predict the volume of apps. People will build more complex apps than you expect. They will\n  build more apps than you expect. They’ll build single-purpose apps that will be used once in a\n  meeting to make a decision, and then never again. They’ll build apps that become a key part of\n  every demo and sales call we do across the company every day.\n</p>\n<p>\n  Everything that used to be impossible can now be built, and this means people will also build\n  buggy slop, or accidentally extend beyond key boundaries. For better or worse, it’s an arms race\n  between the platform and the builders. Don’t underestimate someone’s crazy off-the-rails agent.\n</p>\n<p>\n  As a starting point, pick the batteries that matter. For us, this was auth, hosting, and\n  databases. That’s what people were repeatedly getting stuck on. If we’d tried to ship 30 different\n  platform features on day 1, it probably would’ve been too complicated and nobody would have used\n  it. Start with what people are already struggling with.\n</p>\n<p><strong>Thanks for reading</strong></p>\n<p>\n  Building Zeus has been a blast, and a great learning experience. For better or worse, our industry\n  has completely changed. As builders, we need to ensure we embrace AI tools and new ways of\n  working, and I believe that we can use them as a force for good for our mission, customers, and\n  team. I’m grateful to Owner for the trust to build aggressively, explore the frontier, and to\n  continue scaling what we’ve found works. Hopefully this can inspire others to get out there and\n  build.\n</p>",
      "image": "https://production.owner-engineering-blog.com/og/zeus.png",
      "date_published": "2026-08-01T12:00:00Z",
      "date_modified": "2026-08-01T12:00:00Z",
      "authors": [
        {
          "name": "Ryan Selden",
          "avatar": "https://production.owner-engineering-blog.com/images/authors/ryan-selden.webp"
        }
      ],
      "tags": [
        "App"
      ]
    },
    {
      "id": "https://production.owner-engineering-blog.com/menus-are-harder-than-they-look",
      "url": "https://production.owner-engineering-blog.com/menus-are-harder-than-they-look",
      "title": "Menus Are Harder Than They Look",
      "summary": "Modeling modifiers, combos, and \"no pickles, extra sauce\" without losing your mind, or your schema.",
      "content_html": "<p>\n  A menu looks like a list of items with prices. It is actually a graph: items own modifier groups,\n  modifier groups own options, and options can open their own nested groups. A burger is simple\n  until someone wants it as a combo, with a side swap, no pickles, and extra sauce that costs fifty\n  cents.\n</p>\n<h2>The first schema</h2>\n<p>\n  We started with a flat table of items and a JSON blob of options. It shipped fast and broke the\n  first time a restaurant asked for a half-and-half pizza. Pricing rules lived in the blob, so every\n  client had to reimplement them.\n</p>\n<ul>\n  <li>Options could not be shared between items.</li>\n  <li>Price changes required editing every item that used an option.</li>\n  <li>Reporting could not tell a modifier from a line item.</li>\n</ul>\n<h2>What we run today</h2>\n<p>\n  Modifier groups are first-class records with min and max selection rules, and items reference\n  them. Pricing is resolved server-side from the same rules the POS uses, so the cart total and the\n  kitchen ticket always agree.\n</p>\n<blockquote>If two systems calculate the same price, one of them is wrong.</blockquote>\n<p>The graph is harder to query, but it is the only model that survived contact with real menus.</p>",
      "image": "https://production.owner-engineering-blog.com/og/menus-are-harder-than-they-look.png",
      "date_published": "2026-07-28T12:00:00Z",
      "date_modified": "2026-07-28T12:00:00Z",
      "authors": [
        {
          "name": "Adam Guild",
          "avatar": "https://production.owner-engineering-blog.com/images/authors/adam-guild.webp"
        }
      ],
      "tags": [
        "Code"
      ]
    },
    {
      "id": "https://production.owner-engineering-blog.com/the-6pm-problem",
      "url": "https://production.owner-engineering-blog.com/the-6pm-problem",
      "title": "The 6pm Problem",
      "summary": "How we handle traffic that's flat all day and then goes vertical for ninety minutes, every single night.",
      "content_html": "<p>\n  Restaurant traffic has one shape: nearly nothing until late afternoon, then a wall at dinner.\n  Across time zones the wall rolls from east to west, and on Fridays it is taller. Averages are\n  useless here; the only number that matters is the peak minute.\n</p>\n<h2>Scaling for the wall</h2>\n<p>\n  Autoscaling reacts in minutes, but the dinner rush ramps in seconds. We stopped waiting for\n  metrics and started scaling on the clock, warming capacity thirty minutes ahead of each region's\n  rush.\n</p>\n<ol>\n  <li>Pre-warm compute per time zone on a schedule.</li>\n  <li>Cache menus at the edge, invalidated only on edit.</li>\n  <li>Queue order submission so a slow POS never blocks checkout.</li>\n</ol>\n<h2>What we watch</h2>\n<p>\n  We alert on p99 checkout latency during the rush window only. Outside it, the same latency would\n  page someone for traffic that does not exist.\n</p>\n<p>The result is boring dinners, which is exactly what a restaurant wants from its software.</p>",
      "image": "https://production.owner-engineering-blog.com/og/the-6pm-problem.png",
      "date_published": "2026-07-24T12:00:00Z",
      "date_modified": "2026-07-24T12:00:00Z",
      "authors": [
        {
          "name": "Adam Guild",
          "avatar": "https://production.owner-engineering-blog.com/images/authors/adam-guild.webp"
        }
      ],
      "tags": [
        "Code"
      ]
    },
    {
      "id": "https://production.owner-engineering-blog.com/we-rewrote-our-checkout-flow",
      "url": "https://production.owner-engineering-blog.com/we-rewrote-our-checkout-flow",
      "title": "We Rewrote Our Checkout Flow and Conversion Went Up 11%",
      "summary": "Three months, four abandoned prototypes, and the one change that actually mattered.",
      "content_html": "<p>\n  Our checkout had five steps and a drop-off at every one of them. We assumed the fix was fewer\n  steps, so we built a one-page checkout. Conversion did not move.\n</p>\n<h2>Four prototypes that did not work</h2>\n<ul>\n  <li>One-page checkout: same conversion, more scrolling.</li>\n  <li>Guest-first flow: helped new customers, hurt returning ones.</li>\n  <li>Upsell removal: fewer abandons, lower order value.</li>\n  <li>Progress bar: no measurable effect at all.</li>\n</ul>\n<h2>The change that mattered</h2>\n<p>\n  Session recordings showed people leaving at the pickup-time picker. The default was ASAP, but the\n  estimate was hidden until the final step. Customers who discovered a 45-minute wait at the end\n  left.\n</p>\n<blockquote>People don't abandon forms. They abandon surprises.</blockquote>\n<p>\n  We moved the ready-time estimate to the top of the cart and kept it live as items changed. That\n  single change accounted for almost all of the 11% lift.\n</p>",
      "image": "https://production.owner-engineering-blog.com/og/we-rewrote-our-checkout-flow.png",
      "date_published": "2026-07-20T12:00:00Z",
      "date_modified": "2026-07-20T12:00:00Z",
      "authors": [
        {
          "name": "Adam Guild",
          "avatar": "https://production.owner-engineering-blog.com/images/authors/adam-guild.webp"
        }
      ],
      "tags": [
        "App"
      ]
    },
    {
      "id": "https://production.owner-engineering-blog.com/talking-to-pos-systems-from-2004",
      "url": "https://production.owner-engineering-blog.com/talking-to-pos-systems-from-2004",
      "title": "Talking to POS Systems From 2004",
      "summary": "A field guide to integrating with software that predates JSON.",
      "content_html": "<p>\n  Many of the restaurants we work with run point-of-sale software that was installed before\n  smartphones existed. It works, the staff know it, and nobody is replacing it for us. So we\n  integrate.\n</p>\n<h2>What we run into</h2>\n<ul>\n  <li>XML over SOAP, with schemas that differ per install.</li>\n  <li>APIs reachable only from the restaurant's local network.</li>\n  <li>Menu IDs that change when the manager reorders a category.</li>\n</ul>\n<h2>How we cope</h2>\n<p>\n  Every integration sits behind an adapter that translates the POS into our own order model. The\n  rest of the platform never sees vendor quirks. A small agent runs inside the restaurant network\n  and holds an outbound connection to us, so we never ask anyone to open a firewall port.\n</p>\n<ol>\n  <li>Normalize every inbound payload at the edge.</li>\n  <li>Match menu items by fingerprint, not by vendor ID.</li>\n  <li>Retry idempotently, because the POS will time out.</li>\n</ol>\n<p>None of this is glamorous, but it is why an order placed online shows up in the kitchen.</p>",
      "image": "https://production.owner-engineering-blog.com/og/talking-to-pos-systems-from-2004.png",
      "date_published": "2026-07-15T12:00:00Z",
      "date_modified": "2026-07-15T12:00:00Z",
      "authors": [
        {
          "name": "Adam Guild",
          "avatar": "https://production.owner-engineering-blog.com/images/authors/adam-guild.webp"
        }
      ],
      "tags": [
        "Code"
      ]
    },
    {
      "id": "https://production.owner-engineering-blog.com/why-we-still-render-on-the-server",
      "url": "https://production.owner-engineering-blog.com/why-we-still-render-on-the-server",
      "title": "Why We Still Render on the Server",
      "summary": "Restaurant sites get opened on cracked phones over gas-station wifi. That shapes every stack decision we make.",
      "content_html": "<p>\n  Our median visitor is not on a new laptop. They are hungry, on a mid-range phone, on a weak\n  connection, deciding in seconds whether to order or open a delivery app instead.\n</p>\n<h2>The budget</h2>\n<p>\n  We give every storefront a strict budget: meaningful content in under two seconds on a throttled\n  4G profile. Client-rendered pages could not meet it once a menu passed a few hundred items.\n</p>\n<ul>\n  <li>HTML arrives with the menu already in it.</li>\n  <li>JavaScript hydrates only the cart and the item modal.</li>\n  <li>Images are sized per device at the edge.</li>\n</ul>\n<blockquote>The fastest JavaScript is the JavaScript you don't send.</blockquote>\n<p>\n  Server rendering is not fashionable advice, but for our customers it is the difference between an\n  order and a bounce.\n</p>",
      "image": "https://production.owner-engineering-blog.com/og/why-we-still-render-on-the-server.png",
      "date_published": "2026-07-10T12:00:00Z",
      "date_modified": "2026-07-10T12:00:00Z",
      "authors": [
        {
          "name": "Adam Guild",
          "avatar": "https://production.owner-engineering-blog.com/images/authors/adam-guild.webp"
        }
      ],
      "tags": [
        "Code"
      ]
    },
    {
      "id": "https://production.owner-engineering-blog.com/building-an-ai-phone-agent",
      "url": "https://production.owner-engineering-blog.com/building-an-ai-phone-agent",
      "title": "Building an AI Phone Agent That Doesn't Embarrass the Restaurant",
      "summary": "Latency budgets, hallucination guards, and knowing when to hand off to a human.",
      "content_html": "<p>\n  Restaurants miss a lot of calls during the rush. An agent that answers can take orders and answer\n  questions, but a bad answer costs more than a missed call.\n</p>\n<h2>Latency is the product</h2>\n<p>\n  Callers hang up on silence. We budget under a second from end of speech to the start of a reply,\n  which rules out long reasoning chains on every turn.\n</p>\n<h2>Guardrails</h2>\n<ol>\n  <li>The agent can only quote prices and items from the live menu.</li>\n  <li>Hours and policies come from structured data, never from the model.</li>\n  <li>Anything outside the menu or the order triggers a handoff.</li>\n</ol>\n<blockquote>The best thing an agent can say is \"let me get someone for you.\"</blockquote>\n<p>\n  We measure success by orders completed without a correction in the kitchen, not by how human the\n  voice sounds.\n</p>",
      "image": "https://production.owner-engineering-blog.com/og/building-an-ai-phone-agent.png",
      "date_published": "2026-07-06T12:00:00Z",
      "date_modified": "2026-07-06T12:00:00Z",
      "authors": [
        {
          "name": "Adam Guild",
          "avatar": "https://production.owner-engineering-blog.com/images/authors/adam-guild.webp"
        }
      ],
      "tags": [
        "App"
      ]
    },
    {
      "id": "https://production.owner-engineering-blog.com/one-codebase-ten-thousand-storefronts",
      "url": "https://production.owner-engineering-blog.com/one-codebase-ten-thousand-storefronts",
      "title": "One Codebase, Ten Thousand Storefronts",
      "summary": "Multi-tenant theming when every owner wants their logo bigger.",
      "content_html": "<p>\n  Every restaurant wants a site that feels like theirs. We want one codebase we can ship to all of\n  them on the same day.\n</p>\n<h2>Themes as data</h2>\n<p>\n  A storefront theme is a small set of tokens: colors, type scale, radius, and a handful of layout\n  choices. Components read tokens, never hardcoded values, so a new brand is a record rather than a\n  branch.\n</p>\n<ul>\n  <li>Tokens are validated for contrast before they can be saved.</li>\n  <li>Layouts are chosen from a fixed set, not freely composed.</li>\n  <li>Logo size has a maximum. Yes, really.</li>\n</ul>\n<h2>Shipping to everyone</h2>\n<p>\n  Visual regression runs against a sample of real tenant themes on every release. If a change looks\n  wrong on a dark theme with a long restaurant name, we find out before the restaurant does.\n</p>",
      "image": "https://production.owner-engineering-blog.com/og/one-codebase-ten-thousand-storefronts.png",
      "date_published": "2026-07-01T12:00:00Z",
      "date_modified": "2026-07-01T12:00:00Z",
      "authors": [
        {
          "name": "Adam Guild",
          "avatar": "https://production.owner-engineering-blog.com/images/authors/adam-guild.webp"
        }
      ],
      "tags": [
        "Code"
      ]
    },
    {
      "id": "https://production.owner-engineering-blog.com/the-case-against-the-delivery-marketplace",
      "url": "https://production.owner-engineering-blog.com/the-case-against-the-delivery-marketplace",
      "title": "The Case Against the Delivery Marketplace",
      "summary": "What we learned building direct ordering, told through a year of funnel data.",
      "content_html": "<p>\n  Marketplaces are great at finding new customers and expensive at keeping them. A year of funnel\n  data from direct ordering showed us where the value actually sits.\n</p>\n<h2>What the data said</h2>\n<ul>\n  <li>Most repeat orders start from a search for the restaurant by name.</li>\n  <li>Customers who order direct once are far more likely to order direct again.</li>\n  <li>Commission savings compound with every repeat order.</li>\n</ul>\n<h2>What we built around it</h2>\n<p>\n  We focused on the moments after the first order: a receipt that links back to the storefront, a\n  loyalty balance that only exists direct, and search results that point to the restaurant's own\n  site.\n</p>\n<blockquote>Discovery is rented. Loyalty is owned.</blockquote>",
      "image": "https://production.owner-engineering-blog.com/og/the-case-against-the-delivery-marketplace.png",
      "date_published": "2026-06-26T12:00:00Z",
      "date_modified": "2026-06-26T12:00:00Z",
      "authors": [
        {
          "name": "Adam Guild",
          "avatar": "https://production.owner-engineering-blog.com/images/authors/adam-guild.webp"
        }
      ],
      "tags": [
        "News"
      ]
    }
  ]
}
