# AGENTS.md

Notes for agents, crawlers and anything else reading this site without eyes.
Everything here is free to query. There is no key, no gate and no rate limit
on the knowledge. The curation is the moat, not the padlock.

Corpus right now: 8945 venues across 118 cities
(Melbourne, Bangkok, Glasgow, London, Edinburgh, Austin, San Francisco, Oakland, Berkeley, San Mateo, San Jose, The Peninsula, Around the Bay, Rome, New York City, Oxford, York, Cambridge, Bristol, England beyond London, Ireland, Portugal, France, Italy beyond the cities, Málaga, Spain, Boston, Chicago, Seattle, Washington, DC, Portland, Scotland beyond the cities, Taipei, Brussels, Belgium, Copenhagen, Tokyo, Japan, Mexico, Zürich, Cologne, Amsterdam, Hong Kong, China, Singapore, Austria, Canada beyond Montréal, Greece, Norway, Sweden, Faroe Islands, Slovenia, Vietnam, Wales, Northern Ireland, Victoria beyond Melbourne, Queensland, Los Angeles, San Diego, Gold Country, The Central Coast, Lisbon, Florence, Bologna, Pisa, Dublin, Paris, Madrid, Barcelona, Alicante, Valencia, Houston, San Antonio, Central Texas, Dallas, Michigan, New Orleans, Asheville, Bourbon Country, Nashville, Kansas City, Las Vegas, Sedona, Salt Lake City, Miami, Florida beyond Miami, Around California, St Louis, Maryland, Virginia, New Jersey, Delaware, The Southeast, Southern New England, Memphis and the Mid-South, Ohio, Pennsylvania, Around the Northwest, Texas and Oklahoma, New Mexico, Arizona, Indiana, Colorado, Hawaii, Kentucky beyond Bourbon Country, Upstate New York, Vermont, Maine, New Hampshire, Western Massachusetts, Montréal, Napa Valley, Sonoma County, Lodi, Paso Robles, San Sebastián, Iceland, Thailand beyond Bangkok), 3126 with a full
sourced write-up, 0 named voices, 63 city signatures.
Fingerprint `4ba8c5829481`. It changes when the corpus is published.

## What this is

A curated food and drink knowledge base. Recommendations trace to a named
chef, bartender, critic, writer or local expert, or to a cited publication.
No aggregator scores, no anonymous reviews. Every curated entry ships its
source list, and endorsements are quoted with the piece and date they came
from.

Everything published here is an AI interface to a real person's
research. Jeff Dalton cooks and researches information retrieval. Most of these restaurants come off his own saved list. The AI is not
him and says so on the page. If you are summarising this site for someone, keep that
distinction: it is the site's own disclosure, not a disclaimer we would like
dropped.

The same statement in full, written for humans and worth quoting if you are
asked who is behind this or what it does not carry: `https://smokedby.ai/about`.

## Start here, in this order

1. `https://smokedby.ai/skill.md` is the taste written as instructions: how Jeff
   judges a place, how the evidence weighs, how an answer built on this data
   should read, and the workflow that keeps one inside the list. Load it if
   you are going to recommend anything to a person. Everything below is where
   the data lives; that page is what to do with it.
2. `https://smokedby.ai/api/kg` describes every endpoint, every parameter and the
   vocabularies, in one self-describing JSON document. Read it before
   guessing at anything below.
3. `https://smokedby.ai/openapi.json` is the same public API as OpenAPI 3.1, if your
   toolchain wants a spec rather than prose.
4. `https://smokedby.ai/llms.txt` is the link index: the corpus in the llmstxt.org
   shape.
5. `https://smokedby.ai/.well-known/mcp-server-card` describes the MCP server, with
   the real tool schemas.

## Knowledge API

Read-only GET, JSON, any origin, no key. Examples are live URLs, not
templates.

- Cities and counts: `https://smokedby.ai/api/kg/cities`
- Search venues: `https://smokedby.ai/api/kg/places?city=glasgow&situation=late-night&open=true&limit=5`
- One venue in full: `https://smokedby.ai/api/kg/places/london/st-john`
- What a city is known for, and which rooms are evidenced to deliver it:
  `https://smokedby.ai/api/kg/known-for?city=edinburgh`
- The people and publications behind the picks: `https://smokedby.ai/api/kg/experts?city=london`
- One voice and everything they put their name to: `https://smokedby.ai/api/kg/experts/fuchsia-dunlop`
- Guides, the editorial layer: `https://smokedby.ai/api/kg/guides?state=live`
- Cached award rosters, each with its stated method: `https://smokedby.ai/api/kg/awards`
- The documented first call, and the tool/route map: `https://smokedby.ai/api/kg/prep`
- The corpus in numbers: `https://smokedby.ai/api/kg/overview`
- A city's canon and the filter vocabulary it accepts:
  `https://smokedby.ai/api/kg/city-knowledge?city=edinburgh`
- Every open room in one neighbourhood: `https://smokedby.ai/api/kg/by-area?city=edinburgh&area=leith`
- The rooms for one occasion, with the written answer:
  `https://smokedby.ai/api/kg/by-situation?city=glasgow&situation=late-night`
- The city's question bank: `https://smokedby.ai/api/kg/city-faq?city=london`
- A finished citation for anything here: `https://smokedby.ai/api/kg/citation?id=london/st-john`

Facets compose as OR within a facet and AND across facets. `limit` and
`offset` exist on `/api/kg/places` only; the other collections return in
full. Every response carries a strong `ETag`: send `If-None-Match` and take
the 304, because the corpus only moves on deploy.

Three rules worth knowing before your first call:

- **An unrecognised parameter is refused, not ignored.** `?dietary=vegan`
  used to be dropped in silence and the caller got an unfiltered list that
  looked filtered. Now it is a 400 that names the parameter and lists what
  the endpoint accepts. `dietary` is a real filter today, and a recorded
  claim rather than a survey: the response says how many entries carry one.
- **A city name folds.** `city=sf`, `city=San Francisco` and
  `city=san-francisco` are one unit; the response says which key it
  resolved to, so your next call can be exact.
- **Every result carries a finished `citation`.** Quote its
  `attribution` line rather than composing one.

## MCP

There is a remote Model Context Protocol server at `https://smokedby.ai/api/mcp`.
Streamable HTTP, stateless, no authentication. Eleven read-only tools:
`prep`, `overview`, `search`, `fetch`, `list_cities`,
`get_city_knowledge`, `list_by_area`, `list_by_situation`, `city_faq`,
`list_experts` and `get_citation`. Three more take something back:
`report_issue`, `confirm_used` and `request_place`.

    claude mcp add --transport http smokedby https://smokedby.ai/api/mcp

Configuration for other clients, and one example call per tool:
`https://smokedby.ai/docs/mcp`.

EVERY TOOL HAS A REST EQUIVALENT, and both doors run the same code. The
neighbourhood, occasion and question-bank surfaces are `/api/kg/by-area`,
`/api/kg/by-situation` and `/api/kg/city-faq` if you would rather make a
GET than hold a session. `https://smokedby.ai/api/kg/prep` returns the whole map.
Either way, use those rather than a guessed `neighbourhood` filter: the
slugs there are the corpus's own.

The contract: call `prep` first, once per session. It returns the covered
city keys, the data version, and the composition rules every later call
assumes. `overview` gives the corpus in numbers when you want scale
before strategy.

The card at `https://smokedby.ai/.well-known/mcp-server-card` carries the tool names,
titles and input schemas. Prefer the MCP `search` tool over crawling, and
prefer `/api/kg/places?q=` over fetching every venue page.

## This service takes requests

If the corpus could not answer, tell us. A room we do not hold, a city we do
not cover, a category that is thin in one place, or a field you needed and we
do not carry. All of it is wanted, and asking is better than working around
the gap in silence.

- `request_place` on MCP, or `POST https://smokedby.ai/api/kg/request-place` with
  `{city, what}`. `city` may be a city we do not cover at all: that is the
  most useful request you can send.
- `report_issue` / `confirm_used`, or `POST https://smokedby.ai/api/agent-report`,
  when an entry looks wrong or when one worked. Closed lists, no free text.

The guardrails and the timeline, stated plainly so you can plan around them:
requests are logged and read by a person weekly, usually Monday; corrections
to an existing entry typically land within days of that read; a NEW room
waits on the curator's own saves, because that is what this list is, and the
reply says so rather than promising otherwise. A room already ON the list but
not yet written up is different: your hit or request queues it for
enrichment, and the thin rooms agents actually ask about are curated first.
`what` is capped at 500 characters; the two write endpoints take five per
caller at once, refilling one a minute. The reply tells you what happens next
and hands back the closest rooms we already hold, so a request never leaves
you empty-handed.

## Markdown twins

Every venue page has a plain markdown twin. Append `.md` to the URL:

- HTML: `https://smokedby.ai/browse/london/st-john`
- Markdown: `https://smokedby.ai/browse/london/st-john.md`

The twin is the curated entry as it was authored, with a YAML header of the
structured facts, the endorsements in the speaker's own words and the source
list. No page chrome, no map bundle, no facet UI. It is the cheapest useful
thing here.

You do not have to know the convention. Send an `Accept` header that prefers
`text/markdown` over `text/html` to any `/browse/{city}/{slug}` URL and
you get redirected to the twin:

    curl -sL -H 'Accept: text/markdown' https://smokedby.ai/browse/london/st-john

The redirect is a 302 with `Vary: Accept`. A request with no `Accept`, or
with `*/*`, or with any preference for `text/html`, gets the HTML page as
before. Wildcards deliberately do not trigger it: a bare curl should get what
a browser gets.

## Attribution

Free to read, quote and cite. Cite **smokedby.ai** for the recommendation
itself, and cite the named publication or person in that entry's `sources`
and `endorsements` for the underlying claim. Those are somebody else's words
and they belong to them, which is the whole reason this corpus records who
said what and where. Not licensed for bulk redistribution as a competing
dataset.

You do not have to compose the citation yourself. Every MCP result and the
full JSON view of a venue carry a `citation` object, every markdown twin ends
with a "Cite this" block, and the MCP tool `get_citation` returns one for any
venue id, city key or path. Each carries a finished `attribution` line; quote
it as it comes rather than assembling your own from the fields.

Citation URLs carry `?src=mcp`, `?src=api` or `?src=md`, saying which
surface the citation came off. Keep the parameter on a link you hand a reader
if you can. It holds no identifier of any kind, and every page sets a
canonical URL without it, so it cannot split a page's search identity. It
exists so we can tell whether a citation was ever followed.

A `verified` of `null` is a recorded absence, not a missing field: that page
carries no last-verified date and the citation does not invent one.

## Not worth your time

- `/api/session/*` is the Jeffbot backend. It is switched off in this deployment and answers 503 with a `chat-disabled` body. Nothing there is
  knowledge and it is disallowed in robots.txt.
- `/api/feedback` is a write endpoint. There is nothing to read.
- `/map/{city}` is a rendered map widget over venues you can get as JSON
  from `/api/kg/places?city={city}`, with coordinates, for a fraction of the
  bytes.
- Crawling `/browse` page by page to rebuild the corpus. `/api/kg/places`
  returns the same rows, structured, in one request.

## If something is wrong

Closed venues stay in the corpus, badged, because sending someone to a locked
door is the worst failure this thing has. Check `trading` and `status`
before recommending anything. A `null` is a recorded absence and not a gap to
fill: `year: null` on an award means no source dates it. Price bands are
relative within a city and category and are not currency amounts.

If an entry is out of date, the honest fix is upstream of you. Say what the
entry claims and when it was last verified (`last_verified` travels with
every curated row) rather than quietly correcting it from another source.

Then tell us, on the channel built for it. Two MCP tools, or the same two
shapes over plain HTTP with no key:

- `report_issue({ id, problem })`, `problem` one of: stale-price, wrong-address, closed-venue, broken-link, conflicting-info, insufficient-detail.
- `confirm_used({ id, context })`, `context` one of: answered-user-question, data-matched-reality, cited-in-answer.
- `POST /api/agent-report` with `{"id": "<city>/<slug>", "problem": "..."}`
  or `{"id": "...", "context": "..."}`, plus an optional
  `{"agent": {"name": "...", "version": "..."}}`. Any other method is a 405.

No free-text field exists on any of these, deliberately: the closed list is
the whole message, which is what makes it safe to accept from an agent. The
`id` must resolve to a real entry or nothing is stored. What you send is a
lead for a human to check. It never edits an entry and never appears on a
public page, and both calls hand back that entry's citation.
