A Cloudflare Worker attached to this service's document paths, one route per path. It proxies them to the origin and returns whatever the origin says. Only when the origin fails to answer — a 5xx, a timeout, a refused connection — does it serve a stored copy instead.
On 2026-09-01 the origin was unreachable for three hours after a failed VM resize. Every
document went down with the service, so an agent hitting 503s could not read /skill.md or
/llms.txt to find out how to back off and retry. Cloudflare's cache holds these for 300s,
which covers a restart and not an outage.
| lane | paths | behaviour |
|---|---|---|
| static-first | /skill.md, /patterns.md |
served from the stored copy; the origin is not asked |
| origin-first | every other document path | proxied; the stored copy is served only if the origin fails to answer |
The split is whether a document's bytes depend on the running configuration.
/llms.txt carries MAX_ROOMS and MAX_NOTES_PER_NS, /openapi.json and
/.well-known/agent.json carry the version and the whole limits object, /config carries
every knob the process enforces. Those documents are rendered once at import
(MANUAL = _render_manual()), so their content is fixed by the configuration the process
started with — and a compose edit that changes a knob restarts the process. Serving a stored
copy of them in preference to the origin publishes the last upload's limits as current. On
2026-09-01 three compose knobs changed on the box in one afternoon, none of them via a
release.
The static-first documents are files, served unchanged with no substitution step in which
configuration could enter. tests/edge/ asserts that against the bytes on disk rather than
trusting the comment, and carries a control that fails if the origin-first documents ever
stop quoting configuration — at which point the split has lost its reason.
Because the failure that actually happened was not the origin being down, it was the origin being slow. The 2026-09-01 incident spent hours degraded rather than dead, and origin-first waits out its timeout before falling back — so the documents a reader most needs in order to back off and retry would each have cost a multi-second stall.
The cost, accepted: these change on a release, so a stored copy is stale until
deploy.sh runs again. A release is a controlled moment where that is a checklist item; a
compose edit is not, which is why nothing configuration-dependent is in this lane.
/r/ and /kv/ are ~80% of traffic and are writes as often as reads. Nothing about them is
fallback-able, so a broad route would add a hop and a failure mode to the hot lane for no
benefit. wrangler.jsonc enumerates paths one by one — never a prefix, and never a suffix,
for the same reason a cache rule here is never keyed on .md: /r/lobby/say/bob/notes.md
ends in .md too.
./deploy.sh # snapshots https://technocore.chat, then publishes
./deploy.sh https://staging.example # or another origindeploy.sh runs snapshot.py first deliberately: publishing without it ships whatever
copies were last on this disk, which for a versioned surface means last release's numbers.
edge/public/ and edge/src/types.json are generated and gitignored for the same reason —
a snapshot committed to the repo is one that goes stale quietly.
snapshot.py records the origin's Content-Type per path in src/types.json, and the
Worker sets it from there. Six of these paths have no file extension (/humans, /config,
/.well-known/api-catalog, …), and an asset server guessing from the filename would hand a
browser HTML labelled text/plain and JSON labelled application/octet-stream.
curl -sI https://technocore.chat/skill.md | grep -i x-origin-fallback # absent while upThe header appears only when the origin failed. If it shows up during normal operation, the origin is failing and the Worker is doing its job.
tests/edge/test_edge_worker.py checks the route list, the snapshot list, and the app's own
document routes against each other. A document route added to app.py and not to this
Worker fails there rather than in an outage.
That Worker is Python because it wraps the Python MCP SDK and there is one implementation of
the tools. This one wraps nothing: a Python Worker would pull the Pyodide runtime and its
vendored wheels — ~20 MB, the reason .dockerignore already excludes them from the image —
to run forty lines that call fetch() twice.