How We Implemented Agent Mode
- Published on
- — 6 min read (~1.8k tokens)
We introduced AgentMode as part of the website refresh. The idea was to give humans a way to see what a page looks like when browsed by an AI agent, and to make our content easier to inspect, copy, and hand off.
OneOffTech's website is a static site, just HTML generated at compile time. We use Jigsaw by Tighten. How hard could it be to deliver markdown to users, let them copy and paste and interact with agents... Turns out you need a bit more than we anticipated.
Agent Mode on the OneOffTech site is intentionally simple in the interface and slightly more opinionated in the implementation. The goal was to let the same page expose a cleaner, markdown-oriented representation when needed, while users play around with the mode toggle and debate whether they're seeing content in dark mode or not.
Under the hood, Agent Mode is made of five pieces working together:
- a custom Tailwind CSS variant named
agent:and a toggle - a markdown-based format to return
- a hidden markdown representation embedded in the page
- Jigsaw handlers and build listeners that export
.mdvariants alongside the regular HTML output - the web server handles content negotiation when a user requests a page
The Agent mode toggle
The switch works browser-side — we have no backend. We store the mode in sessionStorage, then apply it by adding data-agent to the root HTML element.
All the visual changes come from a Tailwind CSS custom variant:
@variant agent (:is(html[data-agent]) &);
This gives us classes like agent:hidden, agent:block, agent:flex, agent:max-w-none, agent:dark:bg-mist-900 that we can use to style elements differently in agent mode.
That variant turned out to be the most practical approach: progressively adapt existing components rather than maintain a separate agent-only template set. Since we already use Blade components for most parts, applying it meant modifying those components to look different in agent mode, the same way dark or print modes work.
The end result strips decoration and pushes the page toward a markdown-like reading experience:
- switch the font stack to
FiraMono - constrain content width to about
80ch - remove gradients, shadows, rounded corners and ornamental borders
- hide images, videos, iframes and presentation-only elements
- preserve the header and navigation
- prefix headings with
#,##,###, and so on - show link targets inline
- rewrite unordered lists to use
- - wrap inline code and fenced code blocks with markdown markers
- replace horizontal rules with
---
The result is not literal markdown generated by the browser. It is an HTML page visually transformed to resemble what an agent would care about: text structure, hierarchy, links, and code. That distinction matters. We wanted a view that humans can inspect while still approximating an agent-oriented representation.
What markdown to return
Others, like Mintlify, preferred to return the original markdown as-is. We thought we could do better: return markdown tailored for how AI systems actually read it. We wanted to include navigation hints, alt text for images instead of plain links, and support for custom components. In our setup, titles live in frontmatter metadata, so we also needed a way to include them that preserves heading hierarchy.
We created content-md for that. A small frontmatter section lets consumers quickly identify whether the content is relevant without fetching the whole page. The body starts with the page title as a top-level heading and uses specific constructs for image alt text and navigation elements.
As a bonus, each page or post can also serve as an Agent Skill.
Where is the markdown
The page layout does two things at once.
First, it renders the visible agent controls: the mode toggle, the copy button, the share button, and the direct handoff links to ChatGPT and Claude.
Second, it embeds a hidden template:
<template id="markdown-template">
...
</template>
That template contains the markdown version of the current page. The copy button simply reads its text content and places it on the clipboard.
This gives us a clean separation:
- the visible page stays rich and styled
- the hidden template stays plain and portable
Since we store pages and posts as markdown, the processing was straightforward: take the original markdown, resolve the custom Blade components, and clean them up before injecting into the page.
Creating the markdown files
The next part happens during the build. Jigsaw follows two processing paths for pages and posts (called collections).
Jigsaw already knows how to render markdown collection items into HTML, but Agent Mode needed markdown files emitted as actual output artifacts too. For that, we registered a custom collection item handler that delegates normal HTML generation to the existing markdown and Blade handlers, and adds two markdown variants to the output:
path.mdpath/index.md
That mirrors how the HTML output is served and makes the markdown variant easy to target from external tools.
To keep the system extensible, richer content types can define their own export logic, while simpler ones inherit a generic fallback.
That matters because "agent-friendly" does not mean the same thing for every content type. A blog post with custom media components needs normalization. A simpler markdown page may not.
Collections are only half of the site. Top-level pages such as the home page or other Blade pages also need markdown variants.
A listener runs after build, reads the rendered HTML output, looks for #markdown-template, extracts its text content, and writes corresponding .md files next to the generated page.
The markdown sidecar stays in sync with the page and encloses the editorial changes.
Serving the files
With a statically generated site, we have a bunch of files. How do we serve the markdown variant when asked? Turns out HTTP Content Negotiation is exactly what we needed.
Having all files generated at build time in deterministic folders makes things easier. We use Caddy for serving, which lets us adopt rewrites based on the Accept header. To keep maintenance manageable, we built an opinionated Caddy module for content negotiation.
Closing Thought
The question is still open. As agents get better at parsing regular HTML, the distinction between 'agent-friendly' and 'accessible, well-structured HTML' will become increasingly blurred. We believe that making the effort explicit — through the toggle, copy button and .md sidecar — has value beyond the technical. These are the building blocks for human–AI interaction. We need to empower both workflows: humans who want a quick way to include content in chats, and bots that execute activities on behalf of humans.
Human · AI Assisted
The content was produced by humans with AI providing minor help (e.g. grammar, translation) or generated segments (e.g. rephrasing or structuring) integrated by the author.