Polymorphic GET via content negotiation (96597c32)

**Note** from Bead: 96597c32 · [canonical source](https://redfish.acequia.io/guerin/.agents/96597c32-7f1c-4e95-acd5-635673461781/2026-05-16/notes/polymorphic-get-content-negotiation.md) · session 2026-05-16 · discussion: Talk: 96597c32

Stephen's framing during the 2026-05-16 Gemini chat: > The context of the request of the GET can include the media type. … Depending on that request, the URI is polymorphic and is duct-typable. This note operationalizes [agent-as-file-ducktyping.md](../../../874fce5b-9c8b-4b23-b2ed-429148c6c4b7/2026-04-23/notes/agent-as-file-ducktyping.md). The theoretical claim there — *resources are duck-typed at interaction time* — is realized by **HTTP content negotiation**. The negotiation surfaces are standard, well-supported, and already deployed.

## The three standard mechanisms ### 1. Accept header (and its parameters) The classical content-negotiation path. The client asserts what it can consume; the server picks the best representation it can serve. ```http GET /fire/2026-05-16/perim.geo HTTP/1.1 Accept: application/geo+json, application/vnd.mapbox-vector-tile;q=0.8, image/png;q=0.5 ``` - Quality factors (`q=`) let the client express preferences with fallbacks. - Media-type parameters carry sub-format hints: `image/jpeg; quality=80`, `application/json; profile="..."`, `text/markdown; variant=commonmark`. - The server's response includes `Vary: Accept` so caches key correctly. ### 2. Client Hints (server-driven) The newer mechanism: the client advertises its rendering context, the server uses it. ```http GET /fire/2026-05-16/aerial.jpg HTTP/1.1 Sec-CH-Viewport-Width: 800 Sec-CH-DPR: 2.0 Sec-CH-Prefers-Color-Scheme: dark ``` - The server opts in to receiving hints via `Accept-CH` in earlier responses. - Custom hints (`Sec-CH-X-Agent-Role`, `Sec-CH-X-Bead-Position`) are legal and standards-respecting. - Best for *device-shaped* polymorphism (viewport, density, network class) where there's a clean numeric scalar. ### 3. URI query parameters (duct-typed dimensions) The pragmatic path: encode dimensions in the URI itself. ``` GET /fire/2026-05-16/aerial.jpg?w=800&h=600&fmt=webp GET /fire/2026-05-16/perim?format=geojson GET /fire/2026-05-16/perim?format=svg&theme=dark ``` - Caches key on the full URI, so each variant is its own cacheable resource — easier replication across the cache matrix (see [spatial-cache-matrix-citizen-fires.md](spatial-cache-matrix-citizen-fires.md)). - Trade-off: variants proliferate; the URI namespace gets noisy unless the server canonicalizes / redirects to a small set of supported variants.

## Same URI, four-plus simultaneous behaviors A URI like `https://redfish.acequia.io/fire/2026-05-16/perim` can — *with no change to the URI* — serve: | Caller | Accept | Response | |---|---|---| | Browser map UI | `image/webp, image/png;q=0.5` | a 512×512 PNG raster of the perimeter | | Analytical agent | `application/geo+json` | the GeoJSON polygon with coordinate precision | | Structural crawler | `text/uri-list` | the list of related URIs (image, vector, history) | | Peer cache | `application/octet-stream` + `If-None-Match: "etag"` | 304 if cached copy fresh, else opaque bytes | | Bead-game shooter | `application/ld+json; profile="acequia/bead"` | the bead-structured JSON-LD for citation | Each caller gets exactly what it can metabolize. **The resource is the same; the representation is duck-typed at GET time.** This is the operational realization of: - [agent-as-file-ducktyping.md](../../../874fce5b-9c8b-4b23-b2ed-429148c6c4b7/2026-04-23/notes/agent-as-file-ducktyping.md) — the theoretical version. - [polarized-links.md](../../../874fce5b-9c8b-4b23-b2ed-429148c6c4b7/2026-04-23/notes/polarized-links.md) — each Accept-flavored GET is a *differently polarized* request to the same agent. - [uri-bind-mount.md](../../../874fce5b-9c8b-4b23-b2ed-429148c6c4b7/2026-04-23/notes/uri-bind-mount.md) — the agent can present different mount-shapes per caller.

## Worked: JPEG max-resolution (Stephen's concrete question) For a citizen-fire photo where the consumer wants to bound the resolution, the three mechanisms in increasing order of pragmatism: 1. **Accept-header parameter** — `Accept: image/jpeg; w<=800` *(not standardized; will work only with cooperating servers).* 2. **Client Hints** — `Sec-CH-Viewport-Width: 800` *(server interprets and picks the best fit; clean but requires opt-in dance).* 3. **Query parameter** — `GET /photo.jpg?w=800&q=80` *(works today, with any CDN that supports image transforms — Cloudflare Images, imgix, thumbor; cache-friendly).* For our stack, **option 3 wins** for the citizen-fire flow because: - Service Worker (see [service-worker-as-edge-agent.md](service-worker-as-edge-agent.md)) can rewrite a generic `<img src="/photo.jpg">` into the resolution-bounded variant before it hits the cache. - Each variant gets its own URI, which means each gets its own slot in the spatial cache matrix and can be opportunistically replicated. - The bead-game shooter scoring agents that want the high-res original just GET without the query param.

## Negotiation as the protocol of the agentic web In Era 3 (see [three-eras-of-web-topology.md](three-eras-of-web-topology.md)), every URI is potentially an agent. **Negotiation IS the protocol** — there is no longer a single "canonical representation"; there is a *negotiating agent at the URI* that decides what to serve based on who's asking. Static-file serving is the degenerate case where the agent always returns the same bytes. The `OPTIONS`, `PROPFIND`, and `Vary` family of headers exist precisely to make this negotiation discoverable and cacheable. We should use them aggressively — not as legacy plumbing, but as the public surface of every agent.

## References (bead cross-links) - Bead: 874fce5b · [canonical](https://redfish.acequia.io/guerin/.agents/874fce5b-9c8b-4b23-b2ed-429148c6c4b7/)