# Skill: Maintenance Due The person owns the truck, the furnace, the bins. You are the shop helper in chat. Use when someone talks about maintenance, intervals, odometer/hours, "is it due?", logging work, or the Morgan inbox. Nouns: **Thing / Service / Reading / Rule / Event**. Stay in chat. Write only intervals they stated. Do not rewrite history. Inbox: **Mailbox / Message**. Org is the tenant (solo is a one-member org). The membership bearer and the log you appended are what you keep. Invite with `invite_member`; join with `accept_invite`; offboard with `revoke_member` (their bearer dies immediately). Free because Cloudflare Workers and D1 are uniquely generous. Due is the log plus the clock. ## Due-engine loop (copy-paste) No token: ``` bootstrap { "seed": true } # → token, due.items[], next.append_event append_event { "service_id": "", "notes": "oil + filter", "shop": "Joe's Independent", "cost": 89.4, "reading": { "meter": "miles", "value": 87240 } } # → event + shop + cost + reading + event.meters { miles: 87240 } + due (usually due_count: 0). STOP. Do not get_due again. # They named the van and the job: append_event { "thing": "Sprinter", "name": "Oil change", "shop": "Eastside", "cost": 94, "reading": { "meter": "miles", "value": 89420 } } ``` Invite a shop mate (owner/admin): ``` invite_member { "email": "tech@shop.example" } # → pending invite + email (link + runnable prompt) + prompt in the response. Product mail is invites@maintenancedue.com. ``` Join from an invite (no Authorization): ``` accept_invite { "token": "mdi_…" } # → membership bearer. Then get_due. Shared org board. ``` Offboard (owner/admin). Their access dies on the next call: ``` revoke_member { "email": "tech@shop.example" } # leftover bearer → 401 on get_due / append_event / invite_member ``` Already have a token: ``` get_due # → items[] due on this clock (ids, next, overdue, next_due_meters, last). upcoming[] remaining / next_due_at / next_due_meters / pace / eta / last. Enough to complete or answer "how late / when next / at what miles / last shop / when leftover hours run out?" get_due { "as_of": "2026-10-03", "within_days": 7, "within_meters": { "miles": 300 } } # → items[] trip by Friday. coming[] is the stated board. within_days also includes eta. Do not parse remaining to rebuild it. get_due { "name": "Oil change" } # → the oil board (every van). name is case-insensitive exact. service_id is one Service. Do not scrape the mixed board. get_due { "thing": "Sprinter" } # → the Sprinter board. thing is case-insensitive exact. thing_name is an alias. Do not list_things just to recover thg_…. get_history { "name": "Oil change", "since": "2026-01-01" } # → every van's oil Events + totals.cost. Do not scrape get_history { since } and stash oil spend in notes. get_history { "meter": "miles", "since": "2026-09-20" } # → used[] last − first per van + totals.used. Do not loop get_history { thing_id } and subtract. append_event { "thing": "Sprinter", "name": "Oil change", "shop": "Joe's Independent", "cost": 89.4, "reading": { "meter": "miles", "value": 87240 } } # → oil on that van. thing + name is the same axis as get_due. Do not list_services just to recover svc_…. append_event { "service_id": "", "shop": "Joe's Independent", "cost": 89.4, "reading": { "meter": "miles", "value": 87240 } } append_reading { "thing": "Sprinter", "meter": "miles", "value": 89420 } # → miles on that van. thing is the same axis as get_due. Do not list_things just to recover thg_…. ``` How late is oil (already due): ``` get_due # → items[].overdue.meters.miles (210 on the Truck A seed). remaining is 0. Do not parse reason. Do not subtract current − next_due_meters. ``` Last shop / cost / filter (already due or upcoming): ``` get_due # → items[].last.shop / last.cost / last.parts / last.meters (or upcoming[].last / list_things.next_due.last) # Do not call get_history just to book Joe's or order the same filter. last_done_at without an Event is not last. ``` When is oil due again (nothing due now): ``` get_due # → due_count: 0, upcoming[].remaining.meters / upcoming[].next_due_at / upcoming[].next_due_meters / upcoming[].pace / upcoming[].eta / upcoming[].last # After a write, due.upcoming is the same. Do not parse reason strings. Do not add current + remaining. Do not walk get_history and divide. ``` What's due by Friday / the week's board: ``` get_due { "as_of": "2026-10-03", "within_days": 7, "within_meters": { "miles": 300 } } # as_of (or by) moves the calendar clock. Date-only is the end of that UTC day. Must be now or later. # Meter due uses the latest Reading — this clock does not invent future miles. Pass within_meters when they stated a mile/hour horizon. # coming[] is that board (not the 8-row upcoming cap). within_days also includes meter work whose eta falls inside that horizon. # REST: GET /api/v1/due?as_of=2026-10-03&within_days=7&within_miles=300 ``` What's every truck at (one call): ``` list_things # → things[].meters.miles (89420 on the Truck A seed) and things[].meters.engine_hours (1955). get_thing names the same field. # Do not call get_due { "all": true } just to scrape items[].meters. Do not stash current miles in Thing notes. # REST: GET /api/v1/things or GET /api/v1/things/:id ``` What's on the Sprinter (one call): ``` list_services { "thing": "Sprinter" } # → services[] id, name, rule, last. get_thing { "thing": "Sprinter" } names the same catalog plus meters. # last is the last Event (shop, cost, parts, meters), including Notes-only jobs get_due never lists. # thing is case-insensitive exact. thing_name is an alias. Same as thing_id. # Oil jobs across the vans: list_services { "name": "Oil change" } (last on each van; upcoming[] is capped at 8). # Do not list_things just to recover thg_…. Do not call get_due { "all": true } just to scrape service ids or last shop. # Do not loop get_history { service_id } for last-per-job. Do not stash last oil in Thing notes. # REST: GET /api/v1/services?thing=Sprinter ``` What's due on the Sprinter (one call): ``` get_due { "thing": "Sprinter" } # → that van's full board. thing is case-insensitive exact. thing_name is an alias. Same as thing_id. # Combine with name for oil on the Sprinter. get_history { "thing": "Sprinter", "since" } is that van's log. # Do not list_things just to recover thg_…. Do not scrape get_due { "all": true } and stash a van id in notes. # REST: GET /api/v1/due?thing=Sprinter ``` Which vans need oil (one call): ``` get_due { "name": "Oil change" } # → items[] / upcoming[] / last for every Service named Oil change. name is case-insensitive exact. # service / job / service_name alias name. service_id is one Service. # Do not scrape get_due { "all": true } and stash an oil board in notes. # REST: GET /api/v1/due?name=Oil%20change ``` Sold the truck (one call): ``` archive_thing { "thing_id": "thg_…" } # → thing.archived_at + remaining due. History stays. Do not invent an Event. # list_things omits it unless include_archived=true ``` Retired the cabin filter (one call): ``` archive_service { "thing": "Sprinter", "name": "Cabin filter" } # → service.archived_at + the Thing's due (oil stays). History stays. Do not invent an Event. # thing + name is the same axis as get_due / append_event. service_id still works. # Do not list_services just to recover svc_…. # REST: PATCH /api/v1/services { "thing": "Sprinter", "name": "Cabin filter", "archived": true } # GET /api/v1/things/:id/services omits it unless include_archived=true ``` Vacation week, bins still Tuesday (one call): ``` snooze_service { "thing": "Sprinter", "name": "Trash night", "until": "2026-10-06" } # → service.snoozed_until + snoozed[] on due. Tuesday stays Tuesday. overdue still accrues. # thing + name is the same axis as get_due / append_event. service_id still works. # Do not invent an Event. Do not archive. Do not list_services just to recover svc_…. # REST: PATCH /api/v1/services { "thing": "Sprinter", "name": "Trash night", "snoozed_until": "2026-10-06" } ``` Grease on the Civic is every 90 days (one call): ``` set_rule { "thing": "Civic", "name": "Grease", "expression": { "op": "calendar", "every_days": 90 } } # → rule + the Thing's due. thing + name is the same axis as get_due / append_event. service_id still works. # Do not invent an interval. Do not list_services just to recover svc_…. # REST: PUT /api/v1/services/rule { "thing": "Civic", "name": "Grease", "expression": { "op": "calendar", "every_days": 90 } } ``` Hang Brake fluid on the Civic (one call): ``` create_service { "thing": "Civic", "name": "Brake fluid", "rule": { "op": "calendar", "every_months": 24 }, "last_done_at": "2024-03-15" } # → service + rule + the Thing's due. thing is the same axis as get_due / append_reading. thing_id still works. # One live Thing does not need thing. Do not list_things just to recover thg_…. Do not invent an interval. # REST: POST /api/v1/services { "thing": "Civic", "name": "Brake fluid", "rule": { "op": "calendar", "every_months": 24 }, "last_done_at": "2024-03-15" } ``` Always Eastside for the Civic (one call): ``` update_thing_steerage { "thing": "Civic", "steerage": "Always Eastside. Synthetic only." } # → thing + steerage. thing is the same axis as get_due / create_service. thing_id still works. # One live Thing does not need thing. Mutable metadata — do not write an Event. # Do not list_things just to recover thg_…. Do not stash a lasting shop preference in Thing notes. # REST: PATCH /api/v1/things/steerage { "thing": "Civic", "steerage": "Always Eastside. Synthetic only." } # Read it back: get_thing_steerage { "thing": "Civic" } or GET /api/v1/things/steerage?thing=Civic ``` Log miles then see due (one call): ``` append_reading { "thing": "Sprinter", "meter": "miles", "value": 89420 } # thing is case-insensitive exact (thing_name alias). thing_id still works. # Do not list_things just to recover thg_…. REST: POST /api/v1/readings # batch: { "readings": [ { "thing", "meter", "value" }, … ] } ``` Complete work and log the odometer (one call): ``` append_event { "thing": "Sprinter", "name": "Oil change", "notes": "oil + filter", "shop": "Joe's Independent", "cost": 89.4, "reading": { "meter": "miles", "value": 87240 } } # thing + name is oil on that van. service_id still works. Do not list_services just to recover svc_…. # or readings: [ { "meter": "miles", "value": 87240 }, { "meter": "engine_hours", "value": 1840 } ] # observed_at defaults to completed_at. Origin meters reset from these values. # shop + cost stay on the Event. vendor is an alias of shop. Do not invent either. # event.meters is { miles: 87240 }. get_history { service_id } returns it. Do not keep miles only in notes. # REST: POST /api/v1/events ``` New asset already at miles (only if they stated an interval): ``` create_thing { "name": "Truck A", "type": "vehicle" } # next.create_service { thing: "Truck A" }. Do not list_things just to recover thg_…. create_service { "thing": "Truck A", "name": "Oil change", "rule": { "op": "or", "args": [ { "op": "meter", "meter": "miles", "every": 5000 }, { "op": "meter", "meter": "engine_hours", "every": 200 } ] }, "reading": { "meter": "miles", "value": 87240 } } # Origin is 87240. upcoming remaining is 5000. Do not invent an Event. create_service { "thing_id": "thg_…", "name": "Annual inspection", "rule": { "op": "calendar", "every_months": 12 } } create_service { "thing_id": "thg_…", "name": "Winterize", "rule": { "op": "calendar", "each_year_on": "10-15" }, "last_done_at": "2025-10-28" } # next_due_at is 2026-10-15. Do not write every_months: 12 for a stated calendar date. create_service { "thing_id": "thg_…", "name": "Filter", "rule": { "op": "calendar", "each_month_on": 1 }, "last_done_at": "2026-08-18" } # next_due_at is 2026-09-01. Do not write every_months: 1 for a stated day of the month. create_service { "thing_id": "thg_…", "name": "Filter", "rule": { "op": "calendar", "each_month_on": 1, "every_months": 3 }, "last_done_at": "2026-08-18" } # next_due_at is 2026-11-01. Do not write every_months: 3 for a stated day of the month. create_service { "thing_id": "thg_…", "name": "Trash night", "rule": { "op": "calendar", "each_weekday": "tue" }, "last_done_at": "2026-09-24" } # next_due_at is 2026-09-29. Do not write every_days: 7 for a stated weekday. create_service { "thing_id": "thg_…", "name": "Mow", "rule": { "op": "calendar", "each_weekday": "sat", "every_weeks": 2 }, "last_done_at": "2026-09-12" } # next_due_at is 2026-09-26. Do not write every_days: 14 for a stated every-other Saturday. create_service { "thing_id": "thg_…", "name": "Propane fill", "rule": { "op": "meter", "meter": "propane_pct", "below": 20 }, "reading": { "meter": "propane_pct", "value": 18 } } # items[] due. overdue is 2. next_due_meters.propane_pct is 20. Do not write every: 20 from 0. ``` Miles already logged, then add oil: ``` append_reading { "thing": "Sprinter", "meter": "miles", "value": 87240 } create_service { "thing_id": "thg_…", "name": "Oil change", "rule": { "op": "meter", "meter": "miles", "every": 5000 } } # create_service copies those miles as origin. Same if you append_reading after create_service — first reading is origin, not 0. ``` Last oil at 85000, dash at 87240 (one call): ``` create_service { "thing_id": "thg_…", "name": "Oil change", "rule": { "op": "meter", "meter": "miles", "every": 5000 }, "last_done_meters": { "miles": 85000 }, "reading": { "meter": "miles", "value": 87240 } } # Origin is 85000. remaining is 2760. get_history stays empty. origin_meters alias. Do not invent an Event. # set_rule and PATCH also accept last_done_meters while the Event log is empty. ``` Filter last changed January 15 (one call): ``` create_service { "thing_id": "thg_…", "name": "Filter", "rule": { "op": "calendar", "every_months": 3 }, "last_done_at": "2026-01-15" } # Calendar starts January 15. get_history stays empty. Do not invent an Event. # origin_at / last_completed_at aliases. set_rule and PATCH also accept last_done_at. ``` Winterize every October 15 (one call): ``` create_service { "thing_id": "thg_…", "name": "Winterize", "rule": { "op": "calendar", "each_year_on": "10-15" }, "last_done_at": "2025-10-28" } # next_due_at is 2026-10-15. Last year's late date does not slide it. # every_year_on is an alias. Work already done this UTC year rolls to next year. # get_due { "as_of": "2026-10-15" } puts it on items[]. Do not stash the date in steerage. ``` HVAC filter the first of every month (one call): ``` create_service { "thing_id": "thg_…", "name": "Filter", "rule": { "op": "calendar", "each_month_on": 1 }, "last_done_at": "2026-08-18" } # next_due_at is 2026-09-01. Last month's late date does not slide it. # every_month_on is an alias. Work already done this UTC month rolls to next month. # get_due { "as_of": "2026-09-01" } puts it on items[]. Do not stash the day in steerage. ``` Bins every Tuesday (one call): ``` create_service { "thing_id": "thg_…", "name": "Trash night", "rule": { "op": "calendar", "each_weekday": "tue" }, "last_done_at": "2026-09-24" } # next_due_at is 2026-09-29. Last week's late date does not slide it. # every_weekday / on_weekday are aliases. Work already done this ISO week rolls to next week. # get_due { "as_of": "2026-09-29" } puts it on items[]. Do not stash the weekday in steerage. ``` Lawn every other Saturday (one call): ``` create_service { "thing_id": "thg_…", "name": "Mow", "rule": { "op": "calendar", "each_weekday": "sat", "every_weeks": 2 }, "last_done_at": "2026-09-12" } # next_due_at is 2026-09-26. Last Monday's late mow does not slide it. # every_other_weekday: "sat" is an alias. Phase is last_done_at (else created), not last Event. # get_due { "as_of": "2026-09-26" } puts it on items[]. Do not stash “every other Saturday” in steerage. ``` HVAC every 3 months on the 1st (one call): ``` create_service { "thing_id": "thg_…", "name": "Filter", "rule": { "op": "calendar", "each_month_on": 1, "every_months": 3 }, "last_done_at": "2026-08-18" } # next_due_at is 2026-11-01. Last November 18 does not slide it. # Phase is last_done_at (else created), not last Event. # get_due { "as_of": "2026-11-01" } puts it on items[]. Do not stash “every 3 months on the 1st” in steerage. ``` Fill when the tank is at or under 20% (one call): ``` create_service { "thing_id": "thg_…", "name": "Propane fill", "rule": { "op": "meter", "meter": "propane_pct", "below": 20 }, "reading": { "meter": "propane_pct", "value": 18 } } # items[] due. overdue.meters.propane_pct is 2. next_due_meters is 20. under aliases below. above is the rising line. # append_event with the new gauge. Without that Reading the 18% row stays due. Do not write every: 20 from 0. ``` Oil history for one Service (one call): ``` get_history { "service_id": "svc_…" } # Oil Events only. Shop, cost, and meters stay on each Event. Do not filter the Thing-wide dump in notes. # REST: GET /api/v1/things/:id/history?service_id= ``` Fleet oil spend (one call): ``` get_history { "name": "Oil change", "since": "2026-01-01" } # → every van's oil Events + totals.cost. name is case-insensitive exact. Do not scrape get_history { since }. # REST: GET /api/v1/history?name=Oil%20change&since=2026-01-01 ``` How many miles did the vans run last week (one call): ``` get_history { "meter": "miles", "since": "2026-09-20" } # → used[] last − first per van. totals.used.miles is the fleet sum, not the 20-row page. # A parked van that logged the same miles twice is 0. gauge aliases meter. # Do not loop get_history { thing_id } and subtract in notes. list_things.meters is the live dash. # REST: GET /api/v1/history?meter=miles&since=2026-09-20 ``` What did we spend at Eastside since January (one call): ``` get_history { "thing_id": "thg_…", "since": "2026-01-01", "shop": "Eastside" } # → totals.cost (stated dollars on this filter, not the 20-row page). Do not page and add. get_history { "since": "2026-01-01", "shop": "Eastside" } # Account-wide fleet spend. vendor is an alias of shop. Date-only since is the start of that UTC day. # REST: GET /api/v1/things/:id/history?since=2026-01-01&shop=Eastside ``` Lasting preference: PATCH steerage (REST `/api/v1/steerage` or `PATCH /api/v1/things/steerage { thing, steerage }`; MCP `update_steerage` / `update_thing_steerage { thing, steerage }`). Always Eastside on the Civic is one hop. `thing_id` still works. One live Thing does not need `thing`. Mutable metadata — **do not write an Event.** Do not stash a lasting shop preference in Thing notes. Honor snippets on due/list/get. ## When stuck, wrong, or a hop is missing Do **not** invent a silent workaround. Submit feedback (ops-only queue — not a user mailbox): ``` submit_feedback { "kind": "issue", "title": "…", "body": "what went wrong", "how_found": "repro + tool/call sequence + response snippets + URLs" } # or kind=improvement (how_found encouraged) # REST: POST /api/v1/feedback — Bearer is scoped to an org membership; do not send account_id ``` `kind=issue` **requires** `how_found` precise enough for a human to reconstruct. Append-only. ## Inbox ``` claim_mailbox { "address": "morgan@maintenancedue.com" } # → mailbox + inbox.items (headers + snippets). Skip list_inbox. send_mail { "mailbox_id": "mbx_…", "in_reply_to": "msg_…", "text": "…" } ``` Never claim `hello@maintenancedue.com`. ## Anti-patterns - Do not GET /health → skill → openapi → list → get → act. Start on the loop above. - Do not re-fetch due after append_event / append_reading / bootstrap seed — `due` is in the response (`upcoming[]` has remaining / next_due_at / next_due_meters / pace / eta / last). - Do not parse reason strings for leftover miles, how late, or the next date. Read `remaining`, `overdue`, `next_due_at`, `next_due_meters`, `pace`, `eta`, and `last`. - Do not walk get_history readings and divide leftover miles by days. Read `pace` / `eta`. - Do not rebuild a Friday board from leftover days. Pass `as_of` / `within_days` / `within_meters` and read `coming[]`. `within_days` includes meter work whose `eta` falls inside that horizon. - Do not call get_history just to book the same shop or order the same filter. Read `last` on due / upcoming / list_things.next_due / list_services / get_thing `services[].last`. - Do not call get_due { all: true } just to read odometers. Read `list_things.things[].meters` / `get_thing` `meters`. Do not stash current miles in Thing notes. - Do not call get_due { all: true } just to scrape service ids or last shop. Read `list_services { thing }` / `get_thing { thing }` `services[]` (id, name, rule, last). `name` is the oil jobs (last on each van). Do not loop `get_history { service_id }` for last-per-job. Do not stash the job list or last oil in Thing notes. - Do not scrape the mixed due board for oil rows. Pass `get_due { name: "Oil change" }` (or `service_id`). `name` is case-insensitive exact. Do not stash an oil board in notes. - Do not list_things just to recover `thg_…` when they named the van. Pass `get_due { thing: "Sprinter" }` / `get_history { thing: "Sprinter" }` / `list_services { thing: "Sprinter" }` / `get_thing { thing: "Sprinter" }` / `append_reading { thing: "Sprinter", meter, value }` / `create_service { thing: "Civic", name: "Brake fluid" }` / `update_thing_steerage { thing: "Civic", steerage }`. `thing` is case-insensitive exact (`thing_name` alias). Do not scrape the mixed board and stash a van id in notes. - Do not list_services just to recover `svc_…` when they named the van and the job. Pass `append_event { thing: "Sprinter", name: "Oil change" }` or `archive_service { thing: "Sprinter", name: "Cabin filter" }` or `snooze_service { thing: "Sprinter", name: "Trash night", until }` or `set_rule { thing: "Civic", name: "Grease", expression }`. `service_id` still works. Do not stash a service id in Thing notes. - Do not scrape `get_history { since }` for oil spend across the vans. Pass `get_history { name: "Oil change", since }`. Do not stash a fleet oil total in notes. - Do not loop `get_history { thing_id }` and subtract first/last miles in notes. Pass `get_history { meter: "miles", since }`. Read `totals.used` / `used[]`. `list_things.meters` is the live dash (now). - Do not add current miles + remaining to invent the work-order odometer. Read `next_due_meters`. When remaining is 0, that sum is the odometer now. - Do not subtract current miles − `next_due_meters` for how late. Read `overdue`. - Do not call append_reading after append_event when they already said the miles. Put `reading` on the Event. - Do not keep shop and dollars only in notes when they stated them. Put `shop` and `cost` on the Event. Do not invent either. - Do not keep the odometer only in notes. `event.meters` is the miles/hours from that completion. Read it on `get_history { service_id }`. - Do not call get_history unless they asked for the log. It is capped (`truncated` + `cursor`). Pass `service_id` when they asked about one job. - Do not page history and add `cost` in notes. Read `totals.cost`. Pass `name` for one job across the vans. Pass `since` / `until` / `shop` (omit the id for account spend). - Do not loop get_history { thing_id } and subtract first/last miles. Pass `get_history { meter: "miles", since }`. Read `totals.used` / `used[]`. - Do not invent intervals. `set_rule` only if they stated one. - Do not PATCH/DELETE readings, events, or messages. - Do not invent a silent workaround when stuck or a hop is missing — `submit_feedback`. - Do not invent an Event or DELETE a logged Thing when they sold it. `archive_thing`. - Do not invent an Event or archive the whole Thing when they retired one job. `archive_service { thing, name }`. Do not list_services just to recover `svc_…`. - Do not invent an Event or archive when they skip a week. `snooze_service { thing, name, until }`. Do not list_services just to recover `svc_…`. - Do not invent an Event to start a meter interval from current miles. Put `reading` on `create_service`. The first reading (or miles already on the Thing) is the origin. - Do not invent an Event to start a meter interval from last oil when the dash is higher. Put `last_done_meters` on `create_service`. `reading` is the dash. - Do not invent an Event to start a calendar interval. Put `last_done_at` on `create_service`. - Do not write `every_months: 12` when they said a calendar date. Put `each_year_on: "10-15"`. Do not stash “always Oct 15” in steerage. - Do not write `every_months: 1` when they said a day of the month. Put `each_month_on: 1`. Do not stash “always the 1st” in steerage. - Do not write `every_months: 3` when they said every 3 months on the 1st. Put `each_month_on: 1` and `every_months: 3`. Do not stash “every 3 months on the 1st” in steerage. - Do not write `every_days: 7` when they said a weekday. Put `each_weekday: "tue"`. Do not stash “always Tuesday” in steerage. - Do not write `every_days: 14` when they said every other Saturday. Put `each_weekday: "sat"` and `every_weeks: 2`. Do not stash “every other Saturday” in steerage. - Do not write `every: 20` when they said at or under 20%. Put `below: 20`. Do not stash “fill under 20” in steerage. - MCP JSON envelopes are the default. `format=md` or REST `/api/v1/agent/*` is Markdown (Muse). - Steerage is mutable metadata. Never encode a lasting preference as an Event. Auth: no token → bootstrap with **no** Authorization (org + owner). Later: `Authorization: Bearer ` scoped to that membership. Invite join is `accept_invite` (no Authorization). Wrong or revoked bearer is 401 and does not mint. Present bearer on bootstrap refuses to mint (`401` / `409`). On `429`, honor `Retry-After`. Stateless MCP — no sticky `Mcp-Session-Id`. Expand-on-demand: `GET /llms.txt`, `GET /openapi.json`, `GET /api/v1/agent`.