Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Agent Layer Template

An Astro content-collection starter that treats agents — an HRIS system, a recruiter-facing LLM tool, any agent following a link into your site — as a first-class consumer of your content, the same way a design system treats agents as first-class consumers of design tokens.

The problem this solves

Most "make my site AI-ready" advice means bolting on a chatbot. That solves a different problem than the one most sites actually have: an agent that follows a link into your site today gets whatever a human browser would get — paginated HTML, nav chrome, JS-rendered content it may not execute — and has to reconstruct facts from that, with no guarantee the facts it extracts are complete or accurate. A chatbot answers questions about your content from a separate, hand-maintained corpus. This template makes the content itself the single source of truth an agent can request directly.

The pattern

Content collections are already Astro's single source of truth for the HTML build. This template projects that same source, at build time, into three additional static surfaces:

  • /llms.txt — a short index: title, tagline, and a linked list of every published item.
  • /llms-full.txt — the complete text of every published item in one request, generated from the same qaContext field and body content that render the HTML pages.
  • Per-item markdown mirrors (/items/[slug].md) — one item's full content as plain markdown, linked from its HTML page via <link rel="alternate" type="text/markdown">.
  • JSON-LD (Person site-wide, CreativeWork per item) — rendered by Layout.astro from whatever jsonLd prop a page passes it.

Everything is generated at build time, not served from a live endpoint. That's a deliberate choice, not an oversight: a static projection has no per-request cost, no rate-limiting surface, and no prompt-injection risk from untrusted runtime input — an entire category of problems a live agent-facing API would otherwise need to solve. If you want a conversational assistant on top of this, that's a separate, additive feature with its own safety surface — this template doesn't take a position on it.

qaContext: the one schema convention that matters

export const qaContext = z.object({
  summary: z.string(),
  facts: z.array(z.object({ label: z.string(), value: z.string() })).optional(),
  scopeNotes: z.string().optional(),
});

qaContext is a structured, optional field co-located with the content it describes — not a parallel corpus maintained separately from your actual content. That's the load-bearing lesson behind this template: an earlier, unrelated project this pattern was extracted from had made the opposite choice (a separately-maintained AI-facing corpus) and paid for it in drift risk — the corpus and the real content silently disagreed over time. Keeping qaContext next to the content it describes doesn't guarantee it stays accurate, but it makes staleness a one-file diff instead of a cross-repository archaeology problem.

qaContext is optional so you can ship content without it and fill it in later. src/lib/agent-content.ts tracks which published items are missing it (missingQaContext), and src/pages/llms-full.txt.ts logs that list as a build warning — an earlier version of this code computed that list and never read it anywhere, which is worth naming explicitly: an unread signal is the same as no signal.

The CI groundedness eval

scripts/eval-agent.mjs sends the generated llms-full.txt to Claude as its only context, asks it a set of ground-truth questions (scripts/eval-agent.ground-truth.json), and scores the answers against required/forbidden substrings — not fuzzy matching. Run it with:

yarn eval:agent

This requires ANTHROPIC_API_KEY (or an ant auth status-visible credential) and builds the site first. It's a floor, not a proof of completeness: it only catches what the ground-truth file thinks to test. Add a question every time you add a fact you'd actually want an agent to get right.

Using this template

  1. Rename the items collection in src/content.config.ts to whatever your primary content type actually is (case studies, posts, docs), and adjust its schema fields — keep qaContext and status as-is, since src/lib/agent-content.ts depends on both.
  2. Replace the two example entries in src/content/items/ with your own content, each with qaContext filled in.
  3. Edit src/lib/site-config.ts — the one file the agent-layer code and src/lib/schema.ts read your site's name, URL, tagline, and description from.
  4. Edit src/lib/schema.ts for your own Person/CreativeWork JSON-LD fields.
  5. Rewrite scripts/eval-agent.ground-truth.json against your real content, then run yarn eval:agent to confirm it passes.
yarn install
yarn dev      # http://localhost:4321
yarn build    # writes dist/, including llms.txt and llms-full.txt
yarn eval:agent

What was deliberately left out

This template was extracted from a larger, three-part redesign of a job-search portfolio site. Two of those three parts are not in this template, on purpose:

  • A lens-based entry system (a dismissable, persisted, URL-overridable "who are you and what are you here for" prompt that routed visitors into curated content sequences). The mechanism — a small state machine plus two reusable primitives (an escape hatch, a persistence helper) — is real and well-built, but the actual routing logic assumes a fixed set of personas specific to one job search. Extracting it here would have meant either shipping a template that still secretly assumes "evaluating a candidate for a role" as the visitor's intent, or generalizing a persona model that hasn't been validated against a second use case. Neither was worth doing for a template whose whole point is the agent-layer idea, not the entry UX.
  • A two-phase JD-to-fit-brief generator (an offline CLI that read a job description, matched it against case studies via the same qaContext fact layer, and produced a static, unlisted brief page). The two-phase generate/review/commit flow and its prompt-injection hygiene (treating pasted input as content to analyze, never as instructions) are genuinely reusable ideas — but the script had never actually been run to produce a real brief in the source project at the time this template was extracted. Generalizing a pattern that hasn't been exercised against a single real input is premature; there was no evidence yet that the flow, the prompt, or the schema were actually right.

Naming what's left out, and why, is itself part of this template's design stance: partial success and deliberate scope cuts are worth stating plainly rather than smoothing over.

About

An Astro content-collection template that treats agents as first-class consumers of your content: llms.txt, llms-full.txt, per-page markdown mirrors, and JSON-LD, generated at build time from the same schema that builds your HTML.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages