All notable changes to AHTML are documented here. Format follows Keep a Changelog. Versioning follows Semantic Versioning.
Planned post-1.0:
- OpenTelemetry metrics + logs (1.0 ships traces only)
ahtml-py(python/) — Python consumer SDK mirroring@ahtmljs/agent:from_json/from_compactbyte-identical to the TS reference,AHTMLClientwith ETag/TTL caching, detached-JWS +did:webverification,run_actionwith the sameActionRefusedgates, token counting, LangChain loader. 46+ tests including cross-implementation byte-parity; PyPI trusted-publishing workflow ready (project registration pending).@ahtmljs/extract— the extractor pipeline behind every adapter, now a stable plugin API:definePlugin({ match, extract, priority })over a framework-neutralPageModel. Equal priorities and duplicate names are hard errors.@ahtmljs/nextre-exports unchanged (freeze holds); the CLI consumes it; a <100-LOC third-party recipe plugin (examples/recipe-plugin) proves the contract, budget-enforced in CI.@ahtmljs/astroand@ahtmljs/sveltekit— full adapters (.well-known, snapshot routes with negotiation/304/diff, MCP/OpenAPI, llms.txt) with zero framework dependency, both passing the shared adapter matrix (extract → validate → sign → serve → agent-consume) identically to Next.
ahtml init— detects Next/Vite/Hono/Astro/SvelteKit, wires the adapter, generates a validateStrict-clean starter snapshot via the universal extractor. Idempotent; unsupported frameworks exit non-zero leaving the tree untouched.ahtml badge <url>+@ahtmljs/badge— hosted score badge service (Cloudflare-Worker-shaped) serving an SVG + linked report; score is the CLI'scomputeScoreimported, byte-identical to localahtml score; TTL-honoring cache + per-IP rate limit (deployment pending).@ahtmljs/insights— agent-traffic analytics: RFC 9421-verified agent classification (unverifiable ≠ verified, ever), KV-backed event recording with a tested zero-PII guarantee, ≤1 ms p95 middleware overhead (CI-budgeted), offline single-file HTML dashboard, OTel export.
@ahtmljs/conformance— language-agnostic corpus (20 fixtures: canonical round-trips, ETag, diff, signature vectors incl. negatives, validateStrict negatives, dry-run gates) with CI-enforced RFC-2119 MUST traceability, a runner-manifest contract, signed result attestations, andahtml conformance <manifest>. Both the TS reference andahtml-pypass 100% through the same runner — two independent implementations.@ahtmljs/index— the AHTML Index: opt-in submission (validate + score + signature check, rejects with the lint report), TTL/ETag-honoring re-crawl (unchanged sites cost one 304), RSL/policy opt-out delisting within one cycle, per-entry signature status that never upgrades unsigned content, MCP query surface (search_sites,sites_with_action) reusingsnapshotsToMcp, and a dogfood snapshot that scores 100/100 (public deployment pending).ahtml submit <url>— CLI submission to the index.
- SPEC §4.7 dry-run addendum (ADR-0003, additive — pinned 1.0.0 clients
proven unaffected): actions may declare
dry_run.url; simulated responses MUST carrysimulated: true, MUST NOT mutate or charge, and sign like real ones. @ahtmljs/schema/simulate—createSimulateHandler(framework-neutral producer side) +signBytes/verifyBytes.@ahtmljs/agent:POLICY_PRESETS(strictrequires a prior same-parameters dry-run within TTL before irreversible+priced actions),DryRunLedger, and anti-spoofing refusals in both directions — mirrored 1:1 inahtml-py(RUN_POLICY_PRESETS). 100×-dry-run e2e proves zero side effects and zero x402 charges; conformance corpus gained the dry-run gates in the same release.
Stability — the API freeze. Everything shipped through 0.9.5 is stable for the 1.x line; breaking changes now require a full deprecation cycle. No new features.
- SPEC.md is stable. The canonical-JSON serialization rules are now normative (§1.1: fixed top-level key order, no insignificant whitespace, UTF-8, signing input definition), and the detached-JWS signing profile (§6) is documented as the normative profile it became in v0.8.
docs/compare.mdrewritten against the July 2026 field — new sections for WebMCP, CloudflareAccept: text/markdown, Firecrawl/Jina (the typed-output axis), and RSL 1.0 / Content Signals; MCP and llms.txt sections updated with current adoption data; decision tree covers non-adopter sites.- LLM benchmark now includes a
Markdown (auto)column — CDN-style lossy HTML→markdown auto-conversion — answering "why not just let the CDN convert?" in numbers. Report regenerated; reproduce withbash scripts/run-llm-benchmark.sh.
- Doc-import test (
npm run test:docs): every@ahtmljs/*import specifier documented in README/SPEC/docs/package READMEs is resolved against the realexportsmaps (import + require conditions) in CI. Two broken documented imports found and fixed (@ahtmljs/schema/emit,@ahtmljs/schema/canonical).
The snapshot wire format (ahtml: "0.1"), the canonical-JSON form, the compact
text serialization, the discovery chain, and all documented package entry points
are frozen for 1.x. Additive changes only; removals require deprecation in a
minor release and removal no earlier than 2.0.
Verified agents, priced actions — the trust + economics layer. Signed requests, per-agent policy, x402 machine payments, RSL 1.0 licensing, and Content Signals. No breaking changes.
signHttpRequest(request, key, agent, opts?)in@ahtmljs/schema— signs any HTTP Request withSignature+Signature-Input+X-AHTML-Agentheaders per RFC 9421 subset. Covers@method,@authority,@target-uri,content-type,date.verifyHttpSignature(request, keys, opts?)— verifies incoming agent signatures; returnsAgentVerifyResultwith parsedAgentIdentity(id,did,version).signRequest/verifyAgentSignature/buildAgentHeaderre-exported from@ahtmljs/agent.- Hono + Next.js adapters: new
verifyAgents+agentKeysconfig options. When enabled, unverified agents hittingpolicy.verified_agents_only: truesnapshots get a restricted snapshot (actions stripped).X-AHTML-Agent-Verified+X-AHTML-Agent-Idheaders on all responses. Zero overhead when disabled.
buildX402Response(action, opts?)in@ahtmljs/schema— builds a standards-compliant402 Payment Requiredresponse withx-payment-required(base64url-encoded x402/0.2 payload),accept-payment-request: x402/0.2, and optionalx-checkout-url.hasPaymentToken(req)/extractPaymentToken(req)— helpers for verifying paid retries.ActionCost.rails?: ('x402' | 'acp')[]+ActionCost.checkout_url?— new fields in types.Policy.verified_agents_only?+Policy.per_agent_policy?+Policy.content_signals?— new policy fields for agent tiering and crawl signal declarations.- 5 policy presets in
@ahtmljs/schema:publicReadOnly,rateLimited,authRequired,paidAction,trainDeny— andPOLICY_PRESETSnamed map. withPaymentGuard(actions, handler)middleware for Next.js action route handlers — auto-returns 402 when an action requires x402 payment andX-Paymentis absent.
toRsl(snap, opts?)/policyToRsl(policy, siteUrl, opts?)in@ahtmljs/schema— emits a standards-compliant RSL 1.0 file (/rsl.txt) from a snapshot's policy. Sections:[RSL](version, license, republication, attribution) +[content-signals](search, ai-input, ai-train). Defaults to conservative signals whencontent_signalsis unset./.well-known/ahtml.jsonmanifest now includesrsl_urlandcontent_signalswhen set.buildLlmsTxtemits YAML front-matter Content Signals at the top ofllms.txtwhenconfig.policy.content_signalsis set, per the contentsignals.org spec.- After the series completes and bakes two weeks, tag
1.0.0with the API-stability commitment
The browser — AHTML meets the browser tab. Three new surfaces: Accept: text/markdown
negotiation for curl/LLM clients; @ahtmljs/webmcp registering AHTML actions as native
WebMCP tools in Chrome 149+; @ahtmljs/kv pluggable KV backends (memory, Upstash, Cloudflare).
No breaking changes.
- All three adapters (Hono, Next.js, Vite) now serve
text/markdownresponses whenAccept: text/markdownis the highest-weighted type in the request. toMarkdown(snap: Snapshot): stringadded to@ahtmljs/schema— hand-authored structured markdown preserving Products, Documents, Tasks, Profiles, Actions, and Policy in readable sections. Unlike auto-HTML→MD (lossy), this reflects the page's typed AHTML contract.X-AHTML-Tokens: Nresponse header on all snapshot endpoints (JSON, compact, markdown). Approximate (Math.ceil(body.length / 4)) with no external dependency.chooseFormat()return type extended to'json' | 'compact' | 'markdown'; RFC 7231 q-value precedence fully respected.
registerAhtmlTools(snapshot, opts?)— registers all page actions as WebMCP tools, populatingwindow.__AHTML_TOOLS__as the stable fallback and trying both proposed native WebMCP API shapes (navigator.ml.tools.register,window.registerMCPTool) for Chrome 149+ origin trial compatibility.- AHTML's richer metadata (cost, reversibility, confirmation, side-effects) surface as
x-ahtml-*annotations on each tool — more context than plain WebMCP baseline. unregisterAll()for SPA route changes.getBookmarkletHref()/getBookmarkletSource()in@ahtmljs/webmcp/bookmarklet— floating dark panel that reads__AHTML_TOOLS__and fetches/.well-known/ahtml.json. Works in any browser, no origin trial required.- ESM-only (browser), < 6 kB min+br.
@ahtmljs/kv/memory— re-exportsInMemoryKvStoreandInMemoryCacheStorefrom schema (test-friendly, zero deps).@ahtmljs/kv/upstash—UpstashKvStoreandUpstashCacheStore<T>backed by@upstash/redis(optional peer dep). Works in Node.js, Cloudflare Workers, Deno.@ahtmljs/kv/cloudflare—CloudflareKvStoreandCloudflareCacheStore<T>backed by a Cloudflare KV namespace binding. No@cloudflare/workers-typesrequired at compile time (structural interface).RateLimiter(main export) — token-bucket rate limiter built onKvStore.incr(), backend-agnostic.limiter.check(id)→{ allowed, remaining, resetAt, limit }.limiter.enforce(id)throws on rate-limit breach.- Dual ESM + CJS output.
examples/cloudflare-worker/— end-to-end example: Hono v4 +@ahtmljs/hono+@ahtmljs/kv/cloudflarerate limiting + snapshot caching +wrangler.toml.- Demonstrates
Accept: text/markdownnegotiation, per-IP rate limiting from CF KV, and 60-second snapshot cache at the edge.
The agent loop — AHTML inside Claude/Cursor sessions today. Any site becomes typed MCP tools with zero server-side changes. No breaking changes.
- Runs a JSON-RPC 2.0 stdio server compatible with
claude mcp add, Cursor, Cline, and any MCP client. Works on any URL — AHTML adopters or plain HTML. - AHTML adopter path: probes
/.well-known/ahtml.jsonon startup; if found,fetch_pageproxies to the real/ahtml/{path}endpoint (native compact format) andinvoke_actionposts toexecute_url. The full adoption gradient is live inside the agent's session. - HTML fallback path: any non-adopter site auto-extracts via schema-org,
OpenGraph, microdata, and data-attrs — the same pipeline as
ahtml extract. - Four MCP tools (universal):
fetch_page,list_pages,search, plusinvoke_actionfor adopters. list_pagessources from AHTML manifest routes → sitemap.xml → BFS crawl → single page, in preference order, capped at 50.- All debug output goes to stderr; stdout is pure JSON-RPC (Claude Desktop safe).
- Crawls any site and emits a spec-compliant
llms.txtto stdout or--out <file>. - Source priority: AHTML manifest (curated, typed) → sitemap.xml / sitemap_index.xml (cap 200) → BFS crawl (max 30 pages, depth 3, 500 ms delay, respects noindex) → single page fallback.
- Respects
robots.txtDisallow:forUser-agent: *andUser-agent: AhtmlBot. - Zero new dependencies — uses Node's built-in
fs/promises+ globalfetch.
The universal web — every tool works on every site. Value first, adoption second.
The funnel inverts: npx @ahtmljs/cli analyze <any-url> is the release announcement.
No breaking changes.
extractFromSchemaOrg(html)— extracts Product and Article/Document entities from inline<script type="application/ld+json">blocks. Moved from@ahtmljs/next(source of truth now lives in schema).extractFromOpenGraph(html)— extracts entities from OG/Twitter card meta tags. Moved from@ahtmljs/next.extractFromDataAttrs(html)— extracts entities and actions fromdata-ahtml-*attributes. Moved from@ahtmljs/next.extractFromMicrodata(html)— NEW extractor for HTML Microdata (itemscope/itemprop). Mapsschema.org/Product→ Product, Article/BlogPosting → Document. Fills the largest extraction-yield gap in the corpus.mergeExtractions(extractions)— merges multiple extractions in precedence order (data-attrs > schema-org > microdata > opengraph). Moved from@ahtmljs/next.type Extraction— exported from@ahtmljs/schema/extract.Provenance.source?: 'extracted' | 'authoritative'added to theProvenanceinterface — lets downstream code distinguish hand-authored snapshots from auto-extracted ones without a separate channel.
All five files in packages/next/src/extractors/ are thin re-exports from
@ahtmljs/schema/extract. The public API is unchanged; next consumers
continue to import { extractFromSchemaOrg } from '@ahtmljs/next/extractors'.
PageView— typed view over a snapshot with accessors:.products,.documents,.tasks,.profiles,.entities,.actions,.provenance('authoritative' | 'extracted'),.snapshot.AHTMLClient.fetchPage(url)— universal page fetch. SendsAccept: application/ahtml+text, ..., text/html;q=0.5. If the site is an AHTML adopter it returnsPageView { provenance: 'authoritative' }. For any HTML-only site it auto-extracts (schema-org + OG + microdata + data-attrs), builds a snapshot, and returnsPageView { provenance: 'extracted' }. Extracted snapshots never carry actions (untrusted markup).ClientOptions.htmlFallback— documents the html-fallback intent for callers usingfetch()directly (advisory;fetchPage()always applies it).- Exported:
PageView,PageViewOptions,ProvenanceSource.
ahtml extract <url>— fetches any URL, runs all four extractors, prints per-extractor yield and the merged compact snapshot.--jsonflag emitstoJson()output.ahtml analyze <url>— one-run shareable block: HTML byte size, compact size, token estimates (÷4 est.), savings %, entity counts by type, quick agent-readiness probe (JSON-LD, llms.txt, AHTML well-known), nudge toscore. This command is the release announcement.ahtml score <url> [--json]— Lighthouse for agents. Two-tier scoring (0–100, grade A–F): Tier A covers every site (JSON-LD 20pt, OpenGraph 15pt, extraction yield 15pt, token efficiency 10pt, robots AI directives 10pt, llms.txt 10pt); Tier B is AHTML adoption bonus (well-known 10pt, /ahtml 10pt). Output includes a copy-paste fix snippet for the top missing item. Exit 1 if score < 60.ahtml benchmark <url>— format comparison table: raw HTML vs JSON-LD extract vs AHTML compact vs AHTML JSON, in bytes and token estimates. The screenshot markets itself.- Shared
fetch.tshelper in CLI: 30 s timeout, no new runtime dependencies.
packages/agent/tsconfig.cjs.json and packages/next/tsconfig.cjs.json now
add a paths override (@ahtmljs/schema/extract → ../schema/dist/extract/index)
so the module: CommonJS / moduleResolution: Node10 CJS typecheck pass can
resolve the new subpath. Runtime require('@ahtmljs/schema/extract') continues
to work via Node's exports map; this fix is compile-time only.
Close the v0.9 gate — nothing new in scope; this release finishes what v0.9 promised and fixes everything that would otherwise be frozen broken at 1.0. No breaking changes.
require('@ahtmljs/schema')(and every other package) now works. Implemented as a secondtscemit todist/cjs/with a{"type":"commonjs"}marker — not per-entry bundling, which would have duplicatedAHTMLErroracross entries and brokeninstanceof. ESM output is unchanged; thedist/layout is additive.- Fixed broken documented subpaths. These all threw
ERR_PACKAGE_PATH_NOT_EXPORTEDin 0.9.0 and now resolve in both module systems:@ahtmljs/schema/{stream,kv,sign},@ahtmljs/schema/emit/{well-known,mcp,openapi,llms-txt},@ahtmljs/schema/http/{accept,conditional},@ahtmljs/agent/sign(also now re-exported from the@ahtmljs/agentroot). - A
smoke:importsscript verifies every entry point in both module systems; it runs in CI.
enginesfloor drops from>=20to>=18everywhere; CI matrix is now 18 / 20 / 22.sign.ts/did-web.tsgained a guarded dynamicnode:cryptofallback for bare Node 18 (no Web Crypto global). Edge runtimes never reach the import.
- One parameterized suite now runs the full wire surface against Next, Vite, and Hono: well-known, compact/JSON negotiation, ETag/304, diff, NDJSON streaming, encodings, policy 403, mcp.json, openapi.json, llms.txt — plus cross-adapter byte-equality of emitter outputs.
- The v0.5–v0.8 budget tables are now failing tests, not prose:
round-trip medians/p99,
AHTMLErrorconstruction< 50 µs, ES256 sign< 5 ms/ verify< 3 ms, retained-memory ceiling.BUDGET_SCALEenv guards against shared-runner flake.
doctornow checksX-AHTML-Signature(detached JWS) and embeddedprovenance.signature, resolvesdid:websigners, and reports pass/fail with actionable hints. Unsigned snapshots get a WARN, not a FAIL — non-adopters don't regress.
- New spans:
ahtml.validate,ahtml.lint,ahtml.verify_signature(via a new sync-safetraceSync()helper — public APIs stay sync), andahtml.serve_diffin the Next handler. @ahtmljs/honois now fully instrumented (it had zero spans).- New
examples/jaegerdemo: two commands to see the span tree live.
- The Vite plugin now delegates well-known / MCP / OpenAPI / llms.txt /
Accept parsing to
@ahtmljs/schema(411 → 274 LOC). This fixed real drift: a stalegenerated_byfield, wrong MCPserver.name, missing MCP annotations, an invalid OpenAPI oauth2 securityScheme, and llms.txt format divergences. Snapshot endpoint bytes were already identical and remain so.
@ahtmljs/hono: catalog routes were shadowed on the real router./ahtml/mcp.jsonand/ahtml/openapi.jsonwere registered after the/ahtml/*wildcard; real Hono dispatches in registration order, so both returned snapshot 404s. Specific routes now register first, with regression tests on the realhonorouter (new devDependency).chooseEncoding()advertisedbron runtimes that can't produce it (Node ≤ 22), turning a br-only client into an unhandled throw.bris now offered only after a one-timeCompressionStream('br')feature probe.- CI now builds and tests
@ahtmljs/viteand@ahtmljs/langchain(both were publish-only — untested code could ship); stale test-count step names removed.
0.9.0 — 2026-06-02
The production-ready release — the last one before 1.0.0-rc. Adds
OpenTelemetry tracing, a did:web key resolver, two new packages
(@ahtmljs/hono, @ahtmljs/cli), and an external auditor npx ahtml doctor for end-to-end verification of any AHTML deployment.
- OpenTelemetry tracing via a new
trace()helper. Lazy dynamic-imports@opentelemetry/api(declared as optionalpeerDependency). Zero overhead when OTel is not installed — a single null check per span. When present, spans are created via the global tracer with proper status / exception /finallysemantics. Also exportsaddEvent(name, attrs?)andsetStatus(code, message?). did:webresolver (resolveDidWeb(did),verifySnapshotWithDidWeb(snap, jws, did)). Fetches the publisher's.well-known/did.json, imports eachverificationMethod.publicKeyJwkviacrypto.subtle.importKey, and returns aVerifyKey[]ready for signature verification. Caches resolved keys for 5 minutes (CacheStore<VerifyKey[]>, pluggable). Maps JWKalg(andkty/crvfallback) toES256/EdDSA/RS256. Unsupported algs are skipped, not thrown. End-to-end signing- verification now requires zero out-of-band key distribution — just
publish
did.json.
- verification now requires zero out-of-band key distribution — just
publish
@ahtmljs/next/handler—createAHTMLRouteGET wrapped inahtml.serve_snapshotspan; nestedahtml.enforce_policyandahtml.build_snapshotspans. All existing behavior (diff endpoint, streaming, compression, error paths) preserved exactly.@ahtmljs/agent/client—AHTMLClient.fetch()wrapped inahtml.client.fetchspan;streamSnapshot()setup phase wrapped inahtml.client.streamspan. Attributes includeahtml.urlandahtml.format. Retry, coalescing, timeout,onEventhook all unchanged.
-
@ahtmljs/hono@0.9.0— first-class Hono adapter. Single exportmountAHTML(app, config)registers/ahtml/*,/.well-known/ahtml.json,/ahtml/mcp.json,/ahtml/openapi.json,/llms.txton an existing Hono app. StructuralHonoAppLikeinterface — no hard dependency onhono(declaredpeerDependenciesMetaoptional). Runs identically on Node, Bun, Deno, Cloudflare Workers, AWS Lambda. 14 tests covering route registration + per-handler response shapes. -
@ahtmljs/cli@0.9.0—npx @ahtmljs/cli doctor <url>walks the AHTML discovery chain on a live site:/.well-known/ahtml.json— must parse, must declare endpoints./ahtmlsnapshot — must fetch, mustvalidate()clean, must carry ≥1 entity (warn if zero).lint()warnings printed alongside./ahtml/mcp.json— must declareschema_version,server,tools./ahtml/openapi.json— must beopenapi: '3.1.0'./llms.txt— must exist, must start with#(warn if absent). Final report:N PASS, N WARN, N FAIL. Exit 0 on all-pass, 1 if any fail. ANSI-coloured output, zero deps beyond@ahtmljs/schema+@ahtmljs/agent. Exporteddoctor(url, opts?)returns a structuredDoctorReportfor programmatic use. 4 tests covering green/yellow/red paths.
docs/observability.md— OpenTelemetry setup guide. Span catalog, attribute reference, Node + Cloudflare Workers wiring examples, zero-overhead-when-absent guarantee, roadmap to metrics + logs in 1.x.docs/did-web.md— did:web producer + verifier guide. Sampledid.jsonwith ES256 key rotation, threat model (trust anchor = TLS), 5-min cache semantics, roadmap to did:key + did:ion.
- CI workflow (
.github/workflows/ci.yml) — added typecheck + unit test jobs for@ahtmljs/honoand@ahtmljs/cli. - Release workflow (
.github/workflows/release.yml) — added build, typecheck, andnpm publish --provenancesteps for the two new packages. The auto-generated GitHub Release body lists all seven packages with npm links.
- Fully additive. No public API removed. v0.8 callers compile unchanged.
- Wire-compatible. Same compact text, canonical JSON, NDJSON,
diff endpoint,
Accept-Encodingnegotiation as v0.8. - Edge-runtime preserved. No new
node:*imports. OTel + did:web both use Web Standards exclusively. - All seven
@ahtmljs/*packages bumped 0.8.1 → 0.9.0, peer-deps aligned, inter-package deps pinned to exact0.9.0.
- Schema: 170 passing + 1 intentional skip (was 149), 0 todo — +21 tests for OTel no-op behavior and did:web key resolution.
- Agent: 57 passing (unchanged behavior; OTel wrapping is transparent)
- Next: 53 passing (unchanged behavior; OTel wrapping is transparent)
- Vite: 11 passing
- LangChain: 5 passing
- Hono: 14 passing (NEW)
- CLI: 4 passing (NEW)
- UX integration: 30 passing
- Total: 344 passing, 0 todo, 0 failing (was 305 at v0.8.1)
0.8.1 — 2026-05-31
Patch: restore buildLlmsTxt v0.7 back-compat. Adopters who were
calling buildLlmsTxt({title, description, sections, ahtml_manifest_url})
on v0.4–v0.7 hit a typecheck error on v0.8.0 because the signature
moved to {site, ...}. The legacy shape is now restored as a runtime-
discriminated overload — both forms produce their original output. The
canonical v0.8 form is unchanged.
@ahtmljs/schema—buildLlmsTxt()now acceptsLlmsTxtConfig | LegacyLlmsTxtConfig. The legacy{title, description?, sections?, ahtml_manifest_url?}shape is detected by the absence ofsiteand rendered via the v0.4–v0.7 path: rich## H2sections + a## Machine-readableblock driven byahtml_manifest_url. The new{site, title?, description?, routes?}shape still emits the canonical## Pages+## Machine-readablelayout. Zero call-site changes required for v0.7 → v0.8 upgrade.@ahtmljs/next—createLlmsTxtRoute(cfgFn?, configOverride?)widened:cfgFnmay returnAHTMLConfig | LegacyLlmsTxtConfig | LlmsTxtConfig. The route shell detectsAHTMLConfig(by presence ofpolicy/default_ttl/emit_mcp/emit_openapi) and translates toLlmsTxtConfig; everything else is forwarded verbatim tobuildLlmsTxt()which auto-discriminates.examples/landing— the v0.7 rich-sections call site inapp/llms.txt/route.tscontinues to produce its hand-curated## Get started/## Demo/## Machine-readableoutput. The CI typecheck failure that v0.8.0 caused is gone.
@ahtmljs/schema— new exported typeLegacyLlmsTxtConfigfor callers who want to construct the v0.7 shape with type safety.- Tests —
@ahtmljs/next'semitters.test.tsnow covers both shapes: the new{site, ...}Pages layout and the legacy{title, sections, ahtml_manifest_url}rich layout.
- All five packages bumped 0.8.0 → 0.8.1 with peer-deps aligned.
- v0.8.0 callers compile unchanged.
- v0.7 callers (rich
buildLlmsTxtshape) compile unchanged again — the v0.8.0 CHANGELOG note "Direct callers ofbuildLlmsTxt()need a one-line update" no longer applies. Both shapes are first-class.
- Schema: 149 passing
- Agent: 57 passing
- Next: 53 passing (was 51 — adds 2 for the legacy shape coverage)
- Vite: 11 passing
- LangChain: 5 passing
- UX integration: 30 passing
- Total: 305 passing, 0 todo, 0 failing (was 303 at v0.8.0)
0.8.0 — 2026-05-27
The trust release. Signed snapshots land. The duplicated framework emitters consolidate into one canonical implementation. Plus a sweeping README + npm SEO pass across all five packages for AI-agent discovery.
@ahtmljs/schema—signSnapshot(snap, key, opts?)produces a detached JWS overtoJson(snap)using Web Crypto (globalThis.crypto.subtle). SupportsES256,EdDSA,RS256. Nonode:cryptoimport — runs on Cloudflare Workers, Vercel Edge, Bun, Deno.@ahtmljs/schema—verifySnapshot(snap, jws, { trustedKeys })returns{ ok: true, signer: { kid, alg } } | { ok: false, reason }. Tries each trusted key in order. Never throws on mismatch — only on programmer errors (malformed JWS, missing fields).@ahtmljs/schema—verifySnapshotStrict(snap, jws, opts)throwsAHTMLError('SIGNATURE_INVALID')on verification failure.@ahtmljs/agent/sign— re-exports the verifier so adopters writeimport { verifySnapshot } from '@ahtmljs/agent/sign'without reaching into the schema package.@ahtmljs/schema/emit/*— framework-neutral emitter modules.buildWellKnown(config),snapshotsToMcp(server, snaps),snapshotsToOpenApi(opts, snaps),buildLlmsTxt(config, snaps?). These are the canonical implementations used by every framework adapter from v0.8.0 on.@ahtmljs/schema/http/*— pure HTTP helpers.chooseFormat()andparseAcceptEntries()(q-value-aware Accept parsing);isNotModified(),notModifiedResponse(),weakEtagOf()for ETag-based conditional GET on arbitrary bodies.docs/signing.md— JWS signing guide. Producer + verifier code, key distribution options (did:web,.well-known/ahtml-keys.json, out-of-band), threat model, error handling, performance budget.
@ahtmljs/next—well-known.ts,mcp.ts,openapi.ts,llms-txt.tsare now thin adapters (~30-40 LOC each) delegating to@ahtmljs/schema/emit/*. Public exports (buildManifest,snapshotsToMcp,snapshotsToOpenApi,buildLlmsTxt,createXxxRoute) are preserved.buildLlmsTxtsignature — adds a canonical{site, title?, description?, routes?}shape. v0.8.1 restored back-compat for the legacy{title, sections, ahtml_manifest_url}shape, so v0.7 callers compile unchanged on v0.8.1+. If you're pinning exactly v0.8.0, direct callers ofbuildLlmsTxt()need a one-line update — pin v0.8.1 instead and the rich shape continues to work.
- All six READMEs rewritten for npm + GitHub discoverability:
root,
@ahtmljs/schema,@ahtmljs/agent,@ahtmljs/next,@ahtmljs/vite,@ahtmljs/langchain. Each now leads with the one-line value prop, ships copy-pasteable code in the first 20 lines, carries badges (npm version, MIT, MCP-compatible, OpenAPI 3.1, npm provenance), and ends with a Search-keywords section + a suggestednpm keywordsblock calibrated to what AI engineers, RAG operators, Cursor/Continue users, and MCP server builders actually search for. Targets competitor positioning vs Firecrawl, Jina Reader, ScrapingBee, Crawlee, the various MCP SDKs, Schema.org, and hand-rolled llms.txt.
- Fully additive at the package root. Five packages bumped 0.7.0 →
0.8.0 with peer-deps aligned. v0.7 callers compile unchanged for
every public API surface other than
buildLlmsTxt(see above). - Wire-compatible. Compact text, canonical JSON, NDJSON stream, diff endpoint — all unchanged from v0.7.
- Edge runtime preserved. Every new module (sign, emit/, http/)
uses Web Standards only; no
node:*imports. Verified on the schema test suite which runs identically in Node 22.
- Schema: 149 passing (was 137), 0 todo — adds 12 tests for JWS
round-trip across ES256/EdDSA/RS256, tamper detection, multi-key
trusted set,
kidround-trip,SIGNATURE_INVALIDstrict path. - Agent: 57 passing (same)
- Next: 51 passing (same — emitter tests now exercise the new shape via the rewired adapters)
- Vite: 11 passing
- LangChain: 5 passing
- UX integration: 30 passing
- Total: 303 passing, 0 todo, 0 failing (was 291 at v0.7.0)
0.7.0 — 2026-05-26
The scalability release. Snapshots stop being one big buffer.
@ahtmljs/schema/stream— new module.toStream(snap)is anAsyncIterable<string>(one NDJSON line per record).toStreamResponse(snap)returns aReadableStream<Uint8Array>suitable as aResponsebody in any Web-Standards runtime.parseStream(source)is the inverse, yieldingStreamRecords as they arrive.fromStream(source)materializes back to aSnapshotwhen buffering is acceptable. Content-Type:application/ahtml+json-seq(length-delimited NDJSON).@ahtmljs/schema/compress— new module.chooseEncoding(header)parsesAccept-Encodingwith q-values, prefersbr > gzip > identity, honorsq=0refusals, respects*wildcards.compressBuffer(body, enc)andcompressStream(body, enc)useCompressionStream(Web Standard) — nonode:zlibimport.@ahtmljs/schema/kv— new module.KvStoreinterface (get / set / delete / incrwith optionalttlMs) for cross-process rate-limit / idempotency / cache backends.CacheStore<T>interface for object-valued caches (sync or async).InMemoryKvStoreandInMemoryCacheStoreship as the default adapters.InMemoryCacheStoreis a bounded LRU (default 1,000 entries) with lazy TTL expiration.@ahtmljs/next/handler—createAHTMLRoute(builder, config, opts)gains anopts.streamparameter (true/ threshold number / defaultfalse). When triggered, the handler emits NDJSON via aReadableStream. A client can also force streaming by sendingAccept: application/ahtml+json-seq.@ahtmljs/next/handler— every response path now negotiatesAccept-Encodingand wraps the body inCompressionStreamforbr/gzip.Content-Encoding+Vary: Accept, Accept-Encodingemitted accordingly.@ahtmljs/agent—AHTMLClient.streamSnapshot(url)returns anAsyncIterable<StreamRecord>.streamEntities(url)andstreamActions(url)are convenience filters. Caller canbreakout of the iteration to short-circuit — the underlyingReadableStreamis torn down cleanly.@ahtmljs/agent—ClientOptions.cacheaccepts anyCacheStore<CachedSnapshot>(sync or async). The default is theInMemoryCacheStorefrom@ahtmljs/schema. Swap for Redis / Upstash / Cloudflare KV by implementing the four-method interface.docs/streaming.md— the streaming wire format, client patterns, short-circuit behaviour, and error taxonomy.docs/edge.md— the runtime constraint surface, Cloudflare Workers example, multi-replica cache wiring, cold-start budget.
@ahtmljs/agent— the v0.6 snapshot cache was a privateMap, hard-coded. Now defaults toInMemoryCacheStorebut is fully swappable. Existing v0.6 callers (nocache:option) see identical behaviour and a bounded 1,000-entry LRU instead of the old unboundedMap.
@ahtmljs/agent—AHTMLClient.invalidate()now returnsPromise<void>(it wasvoid). Required to support async cache backends. Existing call sites work unchanged in async contexts; sync callers that ignored the return value are unaffected.@ahtmljs/next/handler—Varyheader changes fromAccepttoAccept, Accept-Encoding. Caches that key onVarywill treat this as a cold cache the first time, then settle.
- Fully additive at the API level. No public surface removed.
- Wire-compatible: legacy
application/ahtml+textandapplication/ahtml+jsonpaths are unchanged. - All five
@ahtmljs/*packages bumped 0.6.0 → 0.7.0 with peer-deps aligned.
- Every package in the hot path runs on Cloudflare Workers, Vercel Edge,
Bun, and Deno with no runtime-conditional imports.
computeEtaguses pure-JSdjb2; compression usesCompressionStream. Nonode:*imports.
- Schema: 137 passing (was 112), 0 todo — 25 new tests covering
toStream/fromStreamround-trip,chooseEncodingq-value parsing,compressBufferround-trip viaDecompressionStream,InMemoryCacheStoreLRU + TTL,InMemoryKvStoreasyncincr. - Agent: 57 passing (was 49), 0 todo — 8 new tests for
streamSnapshot,streamEntities, pluggableCacheStore(sync + async), default behaviour. - Next: 51 passing (was 43), 0 todo — 8 new tests for
createAHTMLRoutestreaming + gzip negotiation, threshold triggering,Acceptoverride, byte-size win. - Vite: 11 passing
- LangChain: 5 passing
- UX integration: 30 passing
- Total: 291 passing, 0 todo, 0 failing (was 250 at v0.6.0)
0.6.0 — 2026-05-24
The error story. Every throw across @ahtmljs/* now routes through a
single AHTMLError class with a stable code discriminator, an
actionable hint, and ES2022 cause chaining. Adopters can finally
write a catch block that means something. This is the release we lead
the README with.
@ahtmljs/schema— newerrors.tsmodule exportsAHTMLError,AHTMLErrorCode,DEFAULT_HINTS,makeError(). Re-exported from every package so adopters can writeimport { AHTMLError } from '@ahtmljs/agent'. There is exactly one error type across the stack.@ahtmljs/schema— 13 stable error codes:SCHEMA_INVALID/DIFF_INVALID/COMPACT_PARSE/JSON_PARSE/ETAG_MISMATCH/NETWORK/HTTP_STATUS/AUTH_REQUIRED/POLICY_DENIED/RATE_LIMITED/TIMEOUT/CACHE_POISONED/SIGNATURE_INVALID. Every code has a defaulthintinDEFAULT_HINTS— the error message itself is the documentation.@ahtmljs/schema—AHTMLError.is(e, code?)type guard.AHTMLError.is(err, 'RATE_LIMITED')narrows in one step.@ahtmljs/schema—validateStrict(snap)throwing variant. Keepsvalidate()returningIssue[]for back-compat; use whichever fits the call site.@ahtmljs/schema—lint()warnings now carrycode: 'SCHEMA_INVALID'alongside the existingrulefield, so the samecatch/log path that consumesvalidate()errors also consumes lint warnings without case-splitting.@ahtmljs/schema—fromCompact()andfromJson()now throw typedAHTMLError('COMPACT_PARSE' / 'JSON_PARSE')with the original parse error incause. Previously rawSyntaxErrorleaked through.@ahtmljs/agent—AHTMLClientgains opt-in retry policy: exponential backoff with optional ±25% jitter,Retry-Afterhonoring (seconds form and HTTP-date), per-code retry filter. Default retry is OFF to preserve v0.5 behavior; passretry: { attempts: 3 }to enable.@ahtmljs/agent—AHTMLClientgains per-request timeout (default 30s, abort viaAbortController).@ahtmljs/agent—AHTMLClientgains request coalescing: 100 parallelfetch(url)calls now produce exactly 1 network request. Keyed byformat/bearer/url. On by default; disable per call withcoalesce: false.@ahtmljs/agent—onEventhook onClientOptionsemitsrequest/cache_hit/cache_miss/diff_applied/coalesced/retry/error. Noconsole.loginside library code — adopters wirepino,bunyan, or OTel. A throwingonEventnever breaks a request (logger faults are swallowed).docs/errors.md— every code documented with an examplecatchblock. The error message is the doc; this file is the index.
@ahtmljs/schema—InvalidDiffErroris now a subclass ofAHTMLErrorwithcode: 'DIFF_INVALID'. Bothinstanceof InvalidDiffErrorandAHTMLError.is(e, 'DIFF_INVALID')match the same throw; back-compat preserved for v0.4 / v0.5 callers.@ahtmljs/agent— the old flatAHTMLError(status, message)surface is replaced by the unified class. The new class still has astatusfield, pluscode,hint,retryable,retryAfterMs,path,context, andcause— so existing callers that only inspecterr.statuscontinue to work, and new callers gain the taxonomy.@ahtmljs/agent— the v0.4502thrown for poisoned cache responses is nowAHTMLError('CACHE_POISONED', { status: 502, … })with the validation errors incause. Cache state is untouched — subsequent calls do NOT serve poisoned content; they re-fetch.
- Fully additive. No public API removed or renamed. v0.5 callers compile against v0.6 unchanged.
- Wire compatibility — unchanged from v0.5; same compact and JSON formats.
- All five
@ahtmljs/*packages bumped 0.5.0 → 0.6.0 with peer-deps aligned.
- Schema: 112 passing (was 97), 0 todo — adds 15 tests for the new error taxonomy
- Agent: 49 passing (was 30), 0 todo — adds 19 tests for retry, timeout, coalescing, onEvent, and typed errors
- Next: 43 passing
- Vite: 11 passing
- LangChain: 5 passing
- UX integration: 30 passing
- Total: 250 passing, 0 todo, 0 failing (was 216 at v0.5.0)
0.5.0 — 2026-05-24
The lossless round-trip release. Every field that toCompact() writes
now survives fromCompact(). The SPEC.md claim is finally true.
@ahtmljs/schema— compact-format parser was silently dropping 14 classes of fields documented as supported. The v0.4.0 audit pinned each one astest.todo()inroundtrip.test.ts; v0.5.0 turns every one of them into a passing assertion:- Product —
description,category,list_price,attributes(with typed scalars),images(URL-only inline form and rich form preservingalt/width/height),variantswith their full metadata - Document —
author(single or array),summary,content(multi-line block scalar),tags,chunks(with byte ranges, anchors, headings, prev/next links, embed hints),language,word_count,reading_time - Task —
priority,due_at,labels,description - Profile —
email,homepage,handle,bio,avatar(URL-only and richAssetform),verified,attributes - Dataset entities — previously
parseEntityreturnednulland dataset snapshots were silently dropped. Now fully restored (columns,rows,row_count_total,description). - Conversation entities — same.
messages,participants,message_count_total,titleall round-trip. - Action —
category,execute_url,preview_url,rate_limit,input,output,authin object form ({ scheme, scopes }),targetin array form (multi-target actions) - Top-level blocks —
@links(self / canonical / parent / next / prev / related),@schemas(per-snapshot JSON Schema registry),@meta(now correctly coerces booleans, null, arrays, and objects in addition to numbers),@policy(cachingwithallowed+ttl,actions_require,terms_url,attribution_required,republish),@provenancewith typedsignedboolean
- Product —
@ahtmljs/schema— actionexecute_urlpreviously emittedexecute: ${method ?? 'POST'} ${url}which caused round-trip drift when the original had no method (a phantommethod: POSTfield appeared). Now emits URL-only; legacyexecute: METHOD urlform still parses cleanly for backward compatibility with v0.4 wire output.@ahtmljs/schema—@provenanceblock previously round-trippedsigned: true(boolean) as the string"true"; now correctly typed.
@ahtmljs/schema—Bodyparser model informat-compact.tsseparates scalar lines, nested lists (key:→- item), block scalars (key: |→ multi-line text), and nested sub-bodies (attribute maps). Replaces the flatRecord<string, string>that lost structure.@ahtmljs/schema—coerceTypedScalar()for@metacorrectly recoversnull/true/false/ numbers / arrays / objects from their compact-form spellings; the v0.4 parser only handled numbers.@ahtmljs/schema— chunks, avatars, and images-with-metadata are now serialized in a parseable form (JSON-per-line for chunks / variants / dataset rows / messages; inline-or-rich for images and avatars depending on whether metadata is present).- Tests —
roundtrip.test.tsnow contains a 1000-iteration property fuzz test (buildRandom(seed)→toCompact→fromCompact→ structural equality) and a 200-iteration idempotent re-emit test (toCompact(.) === toCompact(fromCompact(toCompact(.)))). Both green.
- Wire compatibility — v0.5 parser reads v0.4 output without
modification (legacy
execute: METHOD urlform still parses). - API compatibility — fully additive. No public API surface removed or renamed. v0.4 callers continue to work unchanged.
@ahtmljs/next,@ahtmljs/agent,@ahtmljs/vite,@ahtmljs/langchain— bumped to 0.5.0 with peer dep on schema 0.5.0. No behavior change beyond inheriting the fixes above.
- Schema: 97 passing, 0 todo (was 78 passing, 14 todo)
- Agent: 30 passing
- Next: 43 passing
- Vite: 11 passing
- LangChain: 5 passing
- UX integration: 30 passing
- Total: 216 passing, 0 todo, 0 failing
0.1.0 — 2026-05-12
Initial public preview.
-
@ahtmljs/schemav0.1.0:- TypeScript types for
Snapshot, six entity primitives (Product,Document,Task,Profile,Dataset,Conversation),Action,Policy,Provenance,Links,SnapshotDiff snapshot()builder DSL- Zero-dependency runtime validator
- Canonical JSON serializer (
toJson/fromJson) - Token-optimal compact text serializer (
toCompact/fromCompact) — round-trips losslessly - Structural diff (
diff/applyDiff) - Content-addressed
computeEtag - JSON Schema 2020-12 spec at
src/schema.json
- TypeScript types for
-
@ahtmljs/nextv0.1.0:withAHTMLconfig wrappercreateAHTMLRouteroute handler factory — content negotiation (compact / JSON), conditional GET (If-None-Match), diff endpoint (?since=<etag>), TTL-based cache headers, policy enforcementcreateWellKnownRoutefor/.well-known/ahtml.jsonsite manifestcreateLlmsTxtRoutefor/llms.txtcompatibility shim- Three extractors:
extractFromSchemaOrg,extractFromOpenGraph,extractFromDataAttrs snapshotsToMcp— MCP tools manifest emittersnapshotsToOpenApi— OpenAPI 3.1 document emitter withx-ahtml-*extensionsenforcePolicy— token-bucket rate limiter at the route edge
-
@ahtmljs/agentv0.1.0:AHTMLClientfetcher with ETag caching,If-None-Match, diff replay, content negotiation, stale-while-errorrunActionworkflow executor with dry-run viapreview_urlcountTokens/countTokensGpt/countTokensClaude/measure— wrappers aroundgpt-tokenizerand@anthropic-ai/tokenizer
-
Benchmark (
examples/benchmark):- Programmatic corpus (product / article / dashboard) so HTML, llms.txt, AHTML compact, and AHTML JSON derive from the same source data
- Real tokenizer measurements (
cl100k_base,o200k_base, Claude) - Markdown report generator
- Persisted
benchmark-results.md
-
Demo landing site (
examples/landing):- Next.js 15 with App Router
- Editorial cream + ink design with single rust accent
- Hero, dogfood strip, agent-view, problem, fan-out, live benchmark, install, features, comparison, demo strip, roadmap, CTA, footer
- Mini demo store at
/demo/products/[id] - Live AHTML routes at
/ahtml/*,/.well-known/ahtml.json,/llms.txt,/ahtml/mcp.json,/ahtml/openapi.json /api/waitlistaction endpoint
-
Documentation:
README.md— agent-optimized pitch + install + comparison + FAQ + guidance for AI assistantsSPEC.md— formal v0.1 snapshot specPLAN.md— phased build plan, tech selections, risk registerLANGUAGE.md— Phase-2.ahtmlsyntax previewdocs/agents.md— guide for AI code assistantsdocs/faq.md— extended FAQdocs/compare.md— exhaustive comparison vs every adjacent standarddocs/recipes.md— task-oriented cookbookllms.txt— root-level Jeremy Howard convention shimllms-full.txt— self-contained full-text LLM ingestion fileSECURITY.md— threat model + hardening checklistsCONTRIBUTING.md— schema change processLICENSE— MIT
.ahtmlsource language is a Phase-2 deliverable (months 6–12). v0.1 uses the TypeScriptsnapshot()DSL.- Signing (
provenance.signed: true) is a v0.2 deliverable. The field is reserved in v0.1. - Only Next.js 14+ App Router is shipping. Vite / SvelteKit / Astro / Nuxt / Remix are Phase 0 in-progress.
- The Rust core is a Phase-1 deliverable (months 4–9). v0.1 is TypeScript-only.
- Node 20+
- Next.js 14+ (App Router)
- MCP spec version: 2025-11-25
- OpenAPI 3.1
- JSON Schema 2020-12