Back to the work

Civic intelligenceLiveMCP server

Zoning Signal

Zoning and planning intelligence for Central Florida.

Zoning Signal reads the agendas, minutes and staff reports of the planning boards in 25 Central Florida jurisdictions, and publishes what each decision actually changes, for a parcel, a corridor, or a whole city’s growth pipeline. I built it as a public specimen of the studio’s craft. A ChatGPT extension prototype explores how to bring its planning records into a conversation, with a watch sidebar and dossier search.

Live at zoningsignal.com · 25 jurisdictions · 425 meeting readings observed 2026-10-16

What it reads

The local planning record

Agendas, minutes, staff reports, ordinances, comprehensive plans, transportation improvement schedules: every decision a city makes about its future passes through this paper trail before a permit is pulled, a rezoning is approved or a corridor lights up. The documents are public. They are also fragmented across municipal portals, dense with technical language, and almost never read across time, across cities, or across boards. The pipeline reads where each city posts, whether that is CivicClerk, CivicEngage, a city’s own website, PDFs linked straight from a city page, Laserfiche, MuniCode Meetings, AV Capture All or Granicus, and standardizes each document into one shape, one Markdown file per meeting with a stable record of jurisdiction, board, date and items.

Every figure on the site is derived from that corpus when the site is built, never typed in, and the site says so. The figures below are the site’s own observed on 2026-10-16.

The field · 5 counties · 25 jurisdictions · 425 readingsAs of 2026-10-16

Lake County222 readings

  • Clermont39
  • Leesburg36
  • Groveland30
  • Minneola30
  • Mascotte18
  • Mount Dora17
  • Eustis16
  • Lake County (Unincorporated)16
  • Tavares12
  • Montverde8

Orange County114 readings

  • Winter Park36
  • Winter Garden22
  • Apopka17
  • Ocoee15
  • Orlando13
  • Maitland11

Polk County45 readings

  • Lake Wales19
  • Davenport14
  • Haines City12

Seminole County24 readings

  • Sanford10
  • Winter Springs7
  • Lake Mary5
  • Altamonte Springs1
  • Oviedo1

Osceola County20 readings

  • Kissimmee20
Every jurisdiction the observatory reads, with the meeting readings zoningsignal.com published for it as of 2026-10-16; counties in order of coverage, places most-read first.One scale: Clermont, 39, is the full bar
Counties
Lake, Orange, Polk, Seminole and Osceola, Florida.
Jurisdictions
25 cities, towns and one unincorporated county, each with its own place dossier.
Readings
425 meeting readings, beginning with meetings held in January 2024.
Records
600 in all: readings, place dossiers, corridor readings, briefs, patterns, entities and open questions.
Sources
Agenda packets, meeting minutes, staff reports, ordinances, comprehensive plans and their supporting attachments.

ChatGPT extension · Research prototype

From a planning question to the record behind it.

I’ve built a watch sidebar for pending decisions, hearings and deadlines, alongside dossier resources and search for places, people and organisations. The aim is to let someone investigating a planning question find the relevant record and bring it into the conversation.

The tools loaded in a supervised ChatGPT Desktop test. The interfaces have also had a walkthrough in a test host.

Research prototype using a local stdio connection. ChatGPT Desktop tool loading was observed on 9 October 2026. The retained work includes implemented interfaces and a test-host walkthrough; it does not establish the full sidebar flow, composer mention interaction or current public availability of the extension.

How it reads a meeting

Reading a meeting

The meeting is the unit. Each agenda item passes through a plain-English summary, signal extraction, a source trail, the delta from the prior sitting, and why it matters. The reading is what an analyst would write the morning after, and it links to the previous and next sitting of the same board. A reading can carry a lens for each kind of reader, developer, investor, broker, attorney, business, resident and civic leader, beside the synthesis; the switcher is eight radio inputs and one CSS rule, no JavaScript, so every lens stays in the page and can be cited.

A reading never says commercial pressure is rising without saying which agenda item, which staff report, which minutes and which prior reading produced the signal. Each reading carries a confidence grade, low to very high, that the method page defines as assessed by the operator against a stated evidence standard, not computed, and a horizon that is a stated expectation, not a forecast model. The definitions readers see and the ones agents pin to are rendered from the same file, so they cannot drift apart.

  • Harvest. Agenda packets, staff reports, minutes, ordinances, comprehensive plans and attachments, pulled from each city’s own portal. Refresh adapters check CivicClerk, AgendaCenter, Granicus and direct PDF feeds; other publishing systems keep their own collection paths.
  • Standardize. One Markdown file per meeting, with a stable front-matter contract for jurisdiction, board, date and items.
  • Synthesize. The synthesis procedure primes sub-agents with one lens each, corridor and infrastructure, governance, regulatory and a wild card, to read the standardized meetings at the city and corridor scale and follow their leads. A convergence pass then reads every stream, and the entity’s track record and the lessons of resolved watches are read before the synthesis is written. Patterns that recur become briefs. Every dossier and brief publishes after a review that walks the citation chain back to the source document.
  • Publish. Markdown is the canonical source. Front matter governs the URL grammar, the structured data and the lens content. The build compiles it into one artifact that the site and the agent server read directly. A checksum-keyed sync, run by an operator, mirrors it into a Postgres store with 1024-dimension embeddings, and that store backs one tool, the agents’ semantic search.

Forward calls, scored in public

Tracking an expected decision

The observatory’s watch surface states decisions it expects, each with a trigger and a stated expectation. When a watched decision resolves, the outcome is recorded against the original call and graded on three questions: did the call point the right way, did it land inside the stated window, and was the weight right. A call can be directionally correct and still late; both are recorded. The misses are published beside the hits at the same weight, not tucked into a footnote, because that is the whole reason to trust the ones that were right. As of 2026-10-16 the site lists 18 open questions and 8 resolved, with the grades on its track record.

For AI agents

Agent access to the corpus

Zoning Signal publishes an MCP server at zoningsignal.com/mcp, listed in the official registry as com.zoningsignal/observatory: public, read-only, no sign-in, with tools to describe a place, a corridor, a meeting, a pattern or a watch, to trace the connection between two named things, to read the track record, and to search the corpus by meaning, with queries embedded by Voyage against a 1024-dimension index. The server runs as its own Cloudflare Worker with the corpus compiled in, lifted out of the website so that a redesign cannot take it down. The site also publishes llms.txt, RSS and JSON feeds, and a status document that states the build time and every count.

Every tool call against the server writes one row to an append-only log, with no raw addresses stored, and the agent observatory publishes what the log shows, recomputed at each build rather than at each request. In the page’s own words, the observatory reads itself being read. Agents can leave feedback through one tool, the only one that writes.

Engineering

Harvesting, publishing and search

Twenty-five jurisdictions post their planning agendas and minutes in eight different ways, from vendor portals such as CivicClerk and Laserfiche to a city’s own pages and bare PDFs, each in its own shape. Everything below turns that scatter into one readable set of files, the same ones the build checks and the agent server queries. The aim is that every figure on the site comes from the build.

Harvest

CivicClerk REST API · Laserfiche WebLink · Playwright Chromium · pdfplumber · tesseract at 300 DPI

Python tools pull agendas and minutes from where each city posts them and extract the text, reading scanned pages with tesseract at 300 DPI.

How it works

Python tools pull agendas and minutes from where each city posts them, the CivicClerk API for meeting metadata, the Laserfiche WebLink API page by page and headless Chromium for portals that are JavaScript applications; pdfplumber extracts the text, and when a scanned packet yields none, each page is rendered at 300 DPI for tesseract to read. Of the 376 meeting readings in the build of 2026-09-21, 360 name their platform, all 355 harvested records among them: CivicClerk 153, CivicEngage 54, city websites 52, direct PDFs 45, Laserfiche 29, MuniCode Meetings 16, AV Capture All 9, Granicus 2.

Corpus

YAML front-matter validator · SHA-256 per document · content-addressed version · fails on duplicate (type, slug)

An agent writes one Markdown record per meeting, a validator refuses malformed records, and one build step compiles, hashes and names the whole corpus by its own hash.

How it works

An agent reads the extracted text and writes one Markdown record per meeting from a template; a validator refuses a record that lacks one of eight front-matter fields, uses a value outside its enumeration or misses one of three required sections. One build step then compiles every Markdown and MDX source into a single artifact, hashes each document, names the corpus by the first sixteen hex characters of its own hash, and fails on malformed front matter or a duplicate (type, slug).

Entity graph

162 nodes, 479 edges · three evidence layers · authored_on provenance · byte-compare gate · 13 golden fixtures

A second build step compiles the entity dossiers into a typed graph of 162 nodes and 479 edges, and each edge records which dossier states it.

How it works

A second build step reads the front matter of the 69 entity dossiers and compiles a typed graph of 78 meetings, 69 entities and 15 named patterns, joined by 479 edges in three layers, where a dossier places the actor in a meeting record (250), names a related entity (169) or says it exhibits a pattern (60), and each edge records which dossier states it. Geographic and editorial links were excluded as hops by measurement (in the full front-matter graph one corridor node carries 118 links and 94.5% of 2,346 entity pairs sit within two steps, so a route through them would distinguish nothing); verify:graph rebuilds the graph in memory and byte-compares it with the emitted file, and 13 golden queries pin what trace answers.

Graph query

trace_connection · BFS · shortest-path DAG · minimax bottleneck DP · base rates for 7 layer subsets

trace_connection finds every shortest route between two named things and ranks a route through a hub below one through rarely linked nodes.

How it works

trace_connection runs a breadth-first search from one named thing to another, counts every shortest route through the predecessor DAG, and computes for each route the degree of its busiest stop; it ranks by that degree, lowest first, then by hops, then by how many equally short routes exist, so a route through a hub ranks below one through rarely linked nodes. The build measures base rates for all seven combinations of the evidence layers (component counts, degree profiles, distance and uniqueness histograms), and every answer ships the rates for the layers it traversed, with its own reach: 78 of 376 meeting readings across 11 of 25 jurisdictions.

Retrieval

voyage-4, 1024 dimensions · pgvector halfvec(1024) · Postgres full-text, simple config · weighted reciprocal rank fusion, k = 3 · held-out MRR 0.683 to 0.887 · scope and evidence

A search ranks every record three ways, by meaning, by the words in its text and by its title, and fuses the lists; on 222 held-out questions, mean reciprocal rank rose from 0.683 to 0.887.

How it works

Each record is embedded whole, title, subtitle and body up to 32,000 characters, with voyage-4 and stored as a half-precision pgvector column. One database function, run as its caller, ranks the published rows three ways to a depth of 50: exact cosine against the query’s embedding; Postgres full-text search in the simple configuration, which keeps an identifier such as 2026-021R as written, over title and subtitle weighted above the body; and the same search over title and subtitle alone. Weighted reciprocal rank fusion (k = 3; weights 1, 1.25 and 3, taken from the middle of the development plateau) merges the lists, and every hit reports its true cosine and its rank in each. On a known-item benchmark of 524 questions split by answer record, tuned on 302 and frozen before the other 222 were read, mean reciprocal rank rose from 0.683 for the previous precedence cascade over the same complete index to 0.887, a paired difference of +0.204 (95% CI 0.154 to 0.254), 68 questions better and 6 worse. Identifiers rose from 0.326 to 0.840 and proper nouns from 0.402 to 0.905. Against the cascade with its one threshold tuned on the development split (0.816), fusion leads by +0.071 (0.032 to 0.109). Every response states its scope (a covered place, a place outside coverage, or no place it recognises) and its evidence, strong when one of the top three records contains the query’s terms or resembles it at cosine 0.5 or above.

Agent server

MCP 2025-11-25 · stateless Streamable HTTP · com.zoningsignal/observatory · tool annotations, outputSchema · resources, prompts, completion · Worker with the corpus inlined

The MCP server is one stateless handler in a Cloudflare Worker with the corpus and graph bundled in, listed on the official registry since April 2026.

How it works

The server is one Request-in, Response-out handler in its own Cloudflare Worker, with corpus.json and graph.json bundled as modules through esbuild and a build that refuses to bundle them if they disagree on document count; sixteen of the 18 tools its discovery card listed at version 2.3.0 on 2026-09-26 answer from that inlined corpus, semantic search calls Voyage and Postgres, and the feedback tool writes one row. It speaks JSON-RPC over stateless streamable HTTP, is listed on the official registry through eleven versions since April 2026, and uses the whole protocol surface: every tool carries annotations (all closed-world; all but the feedback tool read-only and idempotent) and an output schema, beside resources, resource templates, prompts, argument completion and logging.

Security and telemetry

RLS on every table · SECURITY INVOKER search · anon key · SECURITY DEFINER aggregates · salted SHA-256 IP hash · ok_empty

Row-level security is enabled on every table, and each message writes one log row with the client address kept only as a salted hash.

How it works

Row-level security is enabled on every table: the public reads published rows only, and the vector search function runs as its caller, with the anon key, so that rule still applies inside it; the invocation and feedback logs have no public read policy, and the agent observatory reads them only through definer functions, five returning aggregates and one returning feedback an operator marked for publication. Each message writes one row after the response is sent, with the client address kept only as a salted SHA-256 hash truncated to 32 characters, and a success that returned nothing, or only a weak match, recorded as ok_empty, so demand the corpus does not yet meet is kept on record.

How semantic_search answered through 2.2.0Tiers by precedence, replaced by fusion

  1. EmbedThe query, up to 500 characters, becomes a voyage-4 vector of 1024 dimensions with input_type query. The records were embedded whole with the same model as documents.
  2. ScoreOne database function scores up to 500 published rows by cosine, with no floor. Beside it, two substring passes look for the query in a title or subtitle and in a body (50 rows at most).
  3. Merge
    1. Title or subtitle contains the queryordered by true cosine · matched: title
    2. Cosine at or above the floor, 0.28the calibrated floor · matched: semanticCosine from 0.23 to 0.39. 0.2741: best hit for an unrelated sentence. 0.2866: correct record for a two-word venue name, just above the floor. 0.3459: weakest correct answer found by cosine alone. The floor is at 0.28.0.230.39floor 0.280.27410.28660.3459
    3. Body contains the queryfills the remaining slots · matched: body
    Hits are deduplicated on (type, slug), types with no public route are dropped, and the list is capped at 12. Every hit carries its true cosine and how it matched.
  4. FlagIf the first-ranked hit’s cosine is below 0.46, the tool keeps every hit, sets weak_match and adds a coverage note, so the agent is told the corpus may not cover the question.Cosine from 0.42 to 0.5. 0.447: highest first hit among out-of-scope queries. 0.469: lowest first hit among in-scope queries. The advisory is at 0.46.0.420.5advisory 0.460.4470.469
The path a question took through semantic_search until version 2.3.0 replaced it on 2026-09-26, read from the observatory’s search module, its MCP tool and its Voyage client as of 2026-09-20. This is the cascade the benchmark above measured at 0.683. The 0.28 floor scale shows three measurements recorded in the search module; the 0.46 advisory scale shows the edges of eleven live queries. Both are calibrations from a handful of live queries, short of an evaluation.Cosine similarity, marks placed to scale

Limits. The verify gates for the graph, the trace fixtures, the URL contract and the live site are run by hand. The vector mirror is synced by an operator, outside the build, so semantic search reads what was last synced, which can trail the corpus as built. The benchmark’s questions are the project’s own: identifiers and names mined from the records, paraphrases written by a model, and 29 logged agent queries labelled by hand. The title list was added after some test questions had been seen, so its share of the gain is partly in-sample; without it, fusion scores 0.863. Most of the gain comes from the two text lists. On paraphrase alone, fusion trails the cascade (0.911 to 0.880 on the test split, 0.927 to 0.909 on 66 fresh paraphrases), a difference that is not significant. The scope gazetteer and the evidence threshold were set against the logged queries they are scored on. Latency was measured locally, not in production. Citation review is human. At the September source checkpoint the graph linked 78 of 376 then-published meeting readings in 11 of 25 jurisdictions. Confidence grades are assessed by the operator, not computed, and the track record observed on 2026-10-16 holds eight resolved calls, too few for a percentage.

Code read at the observatory repository on 2026-09-20 and the harvest repository on 2026-09-16; counts recomputed from the corpus build of 2026-09-21; registry listing and discovery card fetched 2026-09-26; retrieval measured in the observatory’s benchmark on 2026-09-26.

Who it is for

Readers and research limits

The readings are for the people who act on local planning: developers, investors, business operators, residents and local leaders. They are research artifacts, distinct from legal, investment, financial or planning advice, and the site says so. I read every meeting on the site, as its About page says, and the harvest and the synthesis run on a corpus I can walk back to the source. The site shows how I read a dense set of public records and make its sources accessible.

Coverage by jurisdiction

The figure on this page is the field the observatory reads: its 25 jurisdictions by county, each with the readings published for it, drawn as an index on one scale. A function of the data draws it, so when the field grows the figure grows with it, and a test pins every name and count to what the site publishes. It is an index rather than a map because a map would have had to vouch for shapes and distances at a glance, and the page draws only what it can stand behind. The site holds itself to the same rule: a corridor’s locator is a strip diagram in the transit-map tradition, drawn from what the record knows and nothing it does not.

Publishing from one source

The site’s content is Markdown with front matter, kept in git as the canonical source, and the build emits it as one artifact that every surface reads. The published pages, the feeds, the machine contracts and the agent server read the same file, and nothing queries a database to render a page. Any generator that can read that file could rebuild the publication.

That is what lets the observatory serve as plain static files from Cloudflare’s edge, the home page arriving in 26 kilobytes, with the agent surface running as its own Worker with the corpus compiled in, so a redesign of the site leaves the server untouched. The cutover in August 2026 was designed to be reversible, routes bound on top of the old origin’s record, and when I asked afterward whether every address had come across, the check found six families the sitemap does not list, feeds and static assets among them, and restored them the same day.

Questions

Why its own domain, and not a page here?
By the product’s own decision: the civic-intelligence audience is not the AI-studio audience, and a subdomain of an AI studio reads as a marketing surface, not a destination. The studio is attributed in the site’s structured data, its footer and its About page, not in the address.
Does it need an account?
No. Every reading is public. Sign-in exists only for watchlists and the newsletter, and by the product’s own invariant it never gates content.
Where do the numbers come from?
From the corpus at build time. The site’s status document publishes the build time, the date of the newest record and the count of every record type, so a figure on the site can be checked against the build that produced it.
Is it advice?
No. The readings are research artifacts, cited and sourced, and distinct from legal, investment, financial and planning advice.
Who built it?
David Jones, under his own name, as a public specimen of Mainthread’s practice. The harvest pipeline is a sibling natural-language agent application; the observatory is its publishing surface.

Changelog

What shipped, newest first.

24 releases since 27 Apr 2026, the latest on 26 Sep 2026. From Zoning Signal’s changelog.

  1. 0.31.0

    Search finds names and numbers, and says what it covers

    semantic_search now ranks every record three ways at once, by the words in its text, by its title and by meaning, and fuses the three lists.

  2. 0.30.0

    Maps of the whole region, measured rather than drawn

    Every covered county now has a map: 25 jurisdictions across Lake, Orange, Osceola, Polk and Seminole, projected from Census TIGER boundaries, with the worst deviation from the source at 0.62 rendered pixels.

  3. 0.29.0

    trace_connection: routes between named things

    A new MCP tool finds the routes between two entities, meetings or named patterns.

  4. 0.28.0

    Private residents are no longer named

    People who appear in the record only as public-comment speakers, commenters or neighbouring owners are now "a resident", and their home addresses "[address withheld]", across every reading and meeting record.

Earlier releases (20)
  1. 0.27.0

    Six more cities, SB 180 read again, and a faster site

    Orlando, Kissimmee, Mount Dora, Eustis, Tavares and Montverde join the corpus: 83 harvested documents, six place dossiers, 25 cities in all.

  2. 0.26.0

    The Water Line, and an agent surface rebuilt

    The corridor's binding constraint moves from roads to water.

  3. 0.16.0

    The atlas widens: Winter Park, Mascotte, communities

    Ten corpus releases in two days.

  4. 0.15.0

    The Mirror: The Observatory Observes Itself Being Observed

    The agent surface now logs every tool call, accepts feedback from agents through a new MCP tool, and publishes what it reads at /agent-observatory.

  5. 0.14.0

    The Attorney + Broker Projections: Lens Architecture Reconciled to Canon

    The lens values agents use to request a stakeholder's view had drifted from the canonical roster, and this release restores it: synthesis plus seven stakeholder lenses, including broker and attorney.

  6. 0.13.0

    The Atlas Doubles: Constellation Expansion Across Three Counties

    The corpus doubles in size while the architecture deepens.

  7. 0.12.1

    The Visible Surface: Closing the Discoverability Loop for Browser-Class Agents

    Every page now ends with a row linking the five machine surfaces, such as /llms.txt and the MCP manifest, so browsing agents that fetch only linked URLs can reach them.

  8. 0.12.0

    The Mirror Tools: The Tool Surface Mirrors the Content Surface

    The tool surface catches up to the content surface.

  9. 0.11.0

    The Citable Contract: State, Definitions, and Claims Become Citation-Stable

    The contract layer between content and AI agents catches up to the prose layer.

  10. 0.10.0

    The Decoder Index: Every Named Entity Becomes Citable

    Every named entity in the South Lake County corpus, from board members, applicants and attorneys to agencies, legislation and projects, gets its own page at /entities/{slug}, linked both ways to every meeting, brief, watch and pattern that mentions it.

  11. 0.9.0

    The Pattern Atlas: Named Patterns Become First-Class

    Named patterns become first-class artifacts.

  12. 0.8.0

    The Watch Resolves: Forecast Accuracy Surfaces

    The Watch now closes its loop.

  13. 0.7.0

    The Watch: The Field's Forward Calendar

    The observatory now publishes what it is tracking.

  14. 0.6.0

    The Meeting Record Opens

    The full standardized meeting record from January 2024 through May 2026 is now readable: 92 meetings of the Clermont, Leesburg, Minneola and Groveland planning and zoning boards, each at its own URL with its items, a public-comment summary, key signals and a source trail.

  15. 0.5.0

    Your Feed: Curation as Conversion Primitive

    Subscriptions now drive a personalized feed.

  16. 0.4.0

    Watchlists + Auth: Phase 1 Complete

    The observatory now recognizes returning readers.

  17. 0.3.0

    Semantic Search + MCP Agent Discovery

    Two new ways into the civic intelligence graph: semantic search for readers and MCP discovery for AI agents.

  18. 0.2.0

    The Field: Index Pages and Lens System

    Four index pages and the core navigation system that structures how the observatory is traversed.

  19. 0.1.5

    The Corpus Opens: Depth + Design System

    The launch surface shipped with one place dossier and one corridor.

  20. 0.1.0

    Zoning Signal Observatory: First Publish

    The Zoning Signal Observatory is live.