Design ·
How we built Fern Agent
Fern Agent is one agent for your docs. It reads your analytics, search, and feedback, then edits the pages it finds problems with, from your Dashboard, Slack, or your own agent over MCP. This post covers what it does and how we built it.
Running a docs site on Fern involves many different kinds of tasks: rewriting a paragraph, reorganizing the nav, updating the CSS, reading through user feedback, and finding out which pages people abandon. Each one usually means leveraging a tool to pull the data and another tool to make the changes.
More tasks has always meant more tools, but agents are changing that arithmetic: now, one person can do more tasks in parallel with fewer tools. That’s why we built Fern Agent: one agent for the content, styling, information architecture, and analytics behind your docs site. For our customers, it’s live today in your Dashboard.
PlayThe Fern Agent launch video.
Meet Fern Agent
| You ask | What Fern Agent does |
|---|---|
| “Which pages do people leave without finding what they came for?” | Reads your usage data and answers |
| “Move the SDK guides under Reference and fix the links.” | Restructures navigation in docs.yml |
| “My custom component overflows on mobile.” | Evaluates your React and CSS |
| “Rewrite the authentication quickstart to cover the OAuth flow.” | Edits the markdown |
| “Read the last quarter of feedback and tell me what’s broken.” | Groups feedback by theme |
Before it edits anything, Fern Agent reads what you’ve already published, searching the same index behind Ask Fern. This keeps its edit consistent with the pages around it. It fetches URLs when it needs context beyond your docs, and it screenshots pages when it needs to look at a design.
Fern Agent is also an expert on Fern itself. When you ask how to improve a page, it flags the component you hand-rolled and suggests a Fern component instead. If your API Explorer is sending empty optional parameters, Fern Agent tells you it’s because send-optional-defaults is off. Because Fern Agent knows how your site is configured, the changes it makes are ones you can actually ship.
The rest of this post is the engineering story behind it.
One agent, three surfaces
We wanted Fern Agent to live where our customers (and us too) were already working:
- Slack to kick off PRs based on internal team discussions or customer troubleshooting
- MCP for when someone’s already working in their own coding agent or pipeline and wants to pull analytics or ship a fix without leaving the terminal
- The Dashboard, which is still where plenty of customers, especially non-technical teams like support or marketing, go first
The goal of building Fern Agent was to make these three surfaces behave like one agent, while letting each keep the ergonomics native to it. To us, equal surfaces doesn’t mean identical UI. Instead, it means the same tools and the same conversation underneath, with each surface customized to fit how people already use it. Slack and MCP both install from the Dashboard chat panel with a single click.
The Slack loop: ask in the thread where the problem was raised, and the answer is ranked by evidence. The agent opens the PR for the fix it can make cleanly and stops short on the two that need your judgment.In the Dashboard, Fern Agent is a docked, persistent panel. Chat state is held at the organization level, so the conversation follows you across navigation instead of resetting every time you change pages.
The panel sits beside your docs site rather than on top of it. Expand it to a full page, close it, come back: the conversation is where you left it. Fern Agent using a recommended prompt in the Dashboard chat panel.Building for durability
A turn is your message and everything the agent does in response. Every agent turn and every docs edit runs as a durable workflow on DBOS, using pydantic-ai’s integration with it. DBOS checkpoints workflow state into the same Postgres Fern Agent already uses, and each model request and tool call is a recoverable step. When a replica crashes or a deploy rolls, the workflow picks up where it left off. Completed steps replay from their checkpoints instead of re-executing, so a tool that already committed a branch doesn’t commit it twice.
The agent streams its response over Vercel’s AI SDK, with one connection carrying both the text and the tool progress. This is what makes equal surfaces possible: a conversation belongs to you, not to the panel it started in.
One toolset, ours and yours
Every Fern Agent capability is a tool in a registry that we built to serve both Fern Agent and our customers’ agents. The MCP server reads the same registry, so Claude Code, Cowork, or whatever agent you point at Fern gets the same tools that Fern uses, with the same behavior. Your agent can pull the data, spot the problem, and hand the fix to our docs-editor subagent, so the full loop happens directly in your terminal.
Claude Code with our MCP server connected, answering from your analytics in the terminal you already work in. Same tools, same behavior, no dashboard.The registry finds tools on its own: it walks the tools directory and collects whatever each package declares, and the runtime asks the registry what exists. Adding a capability is appending one object to one list. The conversation loop, persistence, and streaming protocol stay untouched.
Editing docs is a subagent with real git
Editing your published docs is an organization-specific task that requires Git access, so that work goes to its own subagent behind one tool, edit_docs, with its own prompt, tools, and limits.
There are two editing lanes:
- The inline lane reads the repository through the GitHub API in-process and returns in seconds. It tries first, since nobody should wait minutes for a typo fix.
- The session lane gets a full repository checkout and a shell, which takes minutes to set up. It only kicks in when the inline lane can’t finish cleanly, so a deep edit takes the extra time and a small one doesn’t.
Sessions persist per conversation, so a follow-up edit reuses the same session instead of setting up again.
The agent and the subagent talk to each other, not through you. edit_docs dispatches the subagent and returns in seconds, so the conversation stays free while the edit runs in the background. When it’s done, the agent reports the outcome and can act on it. The subagent never sees your conversation, only a summary of the task so far, so a follow-up edit picks up where the last one left off.
The agent proposes, you dispose
Delivery is separate from editing, and the agent owns it end to end, no matter which lane made the edit: path confinement, size limits, branch naming. Where the edit lands depends on whether you have the visual editor open, and the subagent swaps its prompt and tools to match.
With the visual editor open, edits stream directly into your draft. The page dims while the edit runs, and the editor locks your keystrokes until it lands, so you watch the change happen in place instead of reviewing a wall of text afterward.
With no editor session open, the subagent commits straight to GitHub: to a fern-agent/ branch cut from your configured branch, reused across turns so a follow-up stacks on the first commit. It then opens a pull request, draft by default from Slack and MCP, or pushes onto one it already opened, and reports back the PR’s status, review comments, and CI’s preview link.
With the editor open, the edit lands in your draft and is highlighted in place: the intro paragraph rewritten, the rest of the page untouched, the commit still yours to make.
Either way, Fern Agent’s edits are restrained by default. We built it to modify only the lines that need changing rather than regenerate the page, so a small fix shows up as a small diff instead of a rewritten file you have to review line by line. Preview links and pull request comments work with the review process you already use.
Securing an agent that can edit your docs
Fern Agent never runs with a privileged service identity. Whether you reach it through the Dashboard, an MCP client, or Slack, the turn executes as you: your Auth token is carried through to every internal and external service the agent touches. Because every request runs with your token, the agent automatically respects RBAC and multi-tenancy. It can only read the docs sites your org owns, and search results are scoped to your roles: an admin sees admin-only content, a developer sees developer content, and an anonymous caller sees only what’s public.
By design, Fern Agent has no write access to your API keys, member invites and permissions, plan, or organization settings. When you ask it to update billing information or add a new teammate, it points you to the right Dashboard page so you can make those changes yourself.
Everything the agent reads is screened or labeled. Fern treats anything the agent didn’t write as potentially hostile. Your request is screened on the way in, and any content the agent pulls in during a turn (a fetched web page, user feedback, reviewer comments) is passed through a dedicated screening guardrail before the model sees it. Clean content arrives explicitly labeled as untrusted data, not instructions; content that tries to hijack the agent is withheld, down to the individual record where possible so a single poisoned row doesn’t blackhole a whole result set. Screening is chunked so an injection buried deep in a large page is still caught.
Who built it
We built Fern Agent together. The shared decisions came first:
- Putting the agent in the Python backend
- Refactoring routes into services
- The auth model and the prompt-injection guardrail
- Building one toolset for our agent and yours
From there, Clay Tondreau led the hosted agent, MCP, and Slack. I led design and UX across the Dashboard, Slack, and the visual editor.
That design work started as one mega pull request: a working prototype of the whole experience, built directly in the Dashboard instead of in Figma. Static mocks couldn’t be pointed at a real docs site or show what a streaming turn feels like. The coded prototype could, so we demoed it against real docs before chunking it into individual features and rebuilding them properly on top of the backend work.
Devin authored 87 of this project’s 93 commits, and every one of them was reviewed by one of us before it landed. Devin was good at the middle of the work, turning a decided design into a lot of correct, consistent code across a large surface. It was not deciding what to build, or whether the result was safe to ship.
Open your Dashboard
Fern Agent is live now. Open your Dashboard and ask it to review and fix your underperforming pages. Mention it in Slack, or run claude mcp add and close the loop from your own agent.
New to Fern? Try it for free or book a demo to see the agent on your docs first.
Comments