Zum Hauptinhalt springen
AI

Context Engineering – your frontmatter is the starting point

Context engineering in practice: how structured frontmatter metadata from Astro content collections becomes a precise, budgeted context source for an LLM – with TypeScript examples instead of naive full-text search.

Alain Ritter 6
Context Engineering – your frontmatter is the starting point
Cover image: AI-generated

Context engineering is the discipline of filling an LLM’s context window at runtime so that the model has exactly the information it needs for a good answer – no more and no less. Sounds abstract? It isn’t. The Markdown files this blog is built from are already a case study in it. That is the thread this post follows.

Prompting ends where context begins

Prompt engineering optimises one message: wording, tone, a good example. That carries well-scoped tasks. But as soon as a system has to stay reliable across external knowledge sources, history and tools, the question shifts from “How do I phrase this?” to “What needs to be in the context window for the request to be answerable at all?” – and in which form, which order, which budget.

Your frontmatter is already context engineering

The blog uses Astro content collections. The schema in content.config.ts is nothing other than a context contract: it forces every post into a structured, type-safe shape.

// src/content.config.ts (excerpt)
const blogCollection = defineCollection({
  loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
  schema: z.object({
    title: z.string(),
    lang: z.enum(['de', 'en', 'fr']).default('de'),
    translationKey: z.string().optional(),
    description: z.string().optional(),
    tags: z.array(z.string()).default([]),
    publishDate: z.coerce.date(),
    draft: z.boolean().default(false),
  }),
});

And the frontmatter of this very post – that is already curated, compressed context:

---
title: 'Context Engineering – your frontmatter is the starting point'
description: 'Context engineering in practice: how structured …'
lang: 'en'
translationKey: 'context-engineering'
tags: ['tutorial', 'Context Engineering', 'prompting', 'KI', 'LLM']
---

title, description and tags compress a 900-word article into ~30 tokens. translationKey links language variants. lang enables filtering. None of this is accidental – it is exactly the raw material an LLM feature needs.

Structured metadata beats full text

Say you want a “related posts” feature or a chat over your blog. The naive reflex: embed every MDX body and dump it in via vector search. Let’s do the maths:

ApproachContext per post15 posts
Full Markdown body~1,200 tokens~18,000 tok
Frontmatter only (title + desc)~30 tokens~450 tok

A factor of 40. And those 450 tokens are not just cheaper but more precise: no boilerplate, no noise, no dilution. To answer “which posts match X?” the model does not need prose – it needs the curated essence that already sits in the frontmatter.

Assembling context in code

Here is a budgeted context assembly right on top of the content collections – no vector DB, just what the frontmatter gives you:

import { getCollection } from 'astro:content';

// Rough but workable heuristic: ~4 characters per token.
const estimateTokens = (s: string) => Math.ceil(s.length / 4);

export async function buildBlogContext(query: string, budget = 1500) {
  const posts = await getCollection('blog', ({ data }) => !data.draft && data.lang === 'en');

  // 1. Rank by tag overlap – simple, transparent, no embeddings.
  const terms = new Set(query.toLowerCase().split(/\W+/).filter(Boolean));
  const ranked = posts
    .map((post) => ({
      post,
      score: post.data.tags.filter((t) => terms.has(t.toLowerCase())).length,
    }))
    .filter((r) => r.score > 0)
    .sort((a, b) => b.score - a.score);

  // 2. Build context ONLY from frontmatter – and respect the token budget.
  const lines: string[] = [];
  let used = 0;
  for (const { post } of ranked) {
    const line = `- ${post.data.title} [${post.data.tags.join(', ')}]: ${post.data.description ?? ''}`;
    const tokens = estimateTokens(line);
    if (used + tokens > budget) break; // a hard limit, not "let's see"
    lines.push(line);
    used += tokens;
  }
  return lines.join('\n');
}

The decisive lines are not the ranking but if (used + tokens > budget) break. Context engineering is half about leaving things out.

Deduplicate translations

Every post exists three times (de/en/fr) – same content, same translationKey. Unfiltered, you would serve the model the same information three times: triple the cost, zero added value. The frontmatter solves that too:

function dedupeByTranslation<T extends { data: { translationKey?: string }; id: string }>(posts: T[]): T[] {
  const seen = new Set<string>();
  return posts.filter((p) => {
    const key = p.data.translationKey ?? p.id;
    if (seen.has(key)) return false;
    seen.add(key);
    return true;
  });
}

Structure beats prose: delimiters & order

Once the context is assembled, the packaging decides. Models separate instruction from data far more reliably when both are clearly marked up – which incidentally reduces the risk of prompt injection from retrieved content:

const prompt = `
<instructions>
Answer the question USING ONLY <context>.
If the information is missing there, say so explicitly – do not guess.
</instructions>

<context>
${await buildBlogContext(userQuery)}
</context>

<question>
${userQuery}
</question>
`;

Two principles are baked in:

  • Delimiters (<context>, <question>) cleanly separate data blocks.
  • Positioning: instructions early, the concrete question late. Models attend to the beginning and end more reliably than the middle – the “lost in the middle” effect is measurable, so put the critical bits at the edges.

Token budget instead of “everything in”

Why budget at all when windows are huge? Because more context degrades three things at once:

  • Cost & latency grow linearly with every token.
  • Dilution – the more irrelevant material, the harder the model weighs what matters.
  • Recall drop-off – past a certain fill level, hit rates on facts in the middle of the context fall noticeably.

A hard budget like the one above (budget = 1500) is therefore not penny-pinching but a quality feature.

For agents: load context dynamically

With multi-step agents, tool results and intermediate steps pile up. Instead of loading everything up front, give the model a tool that pulls in only the body of a specifically chosen post – frontmatter first, full text only on demand:

// Tool definition: decide via frontmatter first, then load selectively.
const getPostBody = {
  name: 'get_post_body',
  description: 'Loads the full text of a blog post by its id.',
  input_schema: {
    type: 'object',
    properties: { id: { type: 'string' } },
    required: ['id'],
  },
};

This keeps the window lean: the model first sees only the compact frontmatter overview and fetches the expensive full text solely for the one post it actually needs (just-in-time retrieval). On top of that, scratchpads hold intermediate results outside the window and sub-agents encapsulate sub-tasks with their own fresh context.

Common mistakes

  1. Too much context – “just in case, put it all in” degrades quality, cost and latency at once.
  2. Translations not deduplicated – the same info in de/en/fr, three times in the window.
  3. Instruction and data mixed – without delimiters you risk prompt injection and the model confuses instruction with content.
  4. Full text where metadata suffices – the frontmatter is often the better, cheaper source.
  5. Position ignored – burying critical information in the middle of a long context.

Conclusion

Context engineering is the step from “asking correctly” to “informing correctly”. And the starting point is closer than you think: if you structure your content cleanly – a typed schema, a meaningful description, precise tags, stable translationKeys – you have done half the work already. The frontmatter you maintain anyway is the most compact context source you own. The rest is assembly: rank, deduplicate, budget, structure.

Related: the LLM wiki à la Karpathy case study shows the same principle at scale – structured, accumulated notes instead of naive vector search.

[Top]

Published on 26. Juli 2026 by Alain Ritter