DEV Community

Cover image for open-doc: Letting Antigravity and Other Coding Agents Fully Own Document Layout and Generation

open-doc: Letting Antigravity and Other Coding Agents Fully Own Document Layout and Generation

GitHub — open-doc
https://github.com/simonliu-ai-product/open-doc

1. Introduction

Over the past year, I think most of us have handed more and more work over to coding agents — writing code, looking things up, running tests. They do all of that pretty well. But there is one thing I never got right: asking an agent to produce a report I could hand off as-is.

Agents are actually good writers. Ask one for a quarterly review, a technical evaluation, or a project proposal and the content quality is fine. The problem starts right after the words — the layout. Every approach I tried got stuck in the same place:

  • Have the agent write Markdown, then convert to PDF. The output has no concept of a "page". Tables get sliced in half across a page break, captions get separated from their figures, the table-of-contents page numbers don't line up. You end up tuning CSS instead of reading content.
  • Have the agent write HTML, then print it. Every document reinvents the page layout from scratch. Where are the A4 margins, when should it break, how do page numbers carry across — you re-derive all of it every time, and then the next document starts over.
  • Have the agent produce Word. No need to elaborate here. The odds of the formatting falling apart are roughly 100%.

After thinking about it long enough, you realize the problem isn't that the agent isn't smart enough — it's that the division of labor is wrong. What agents are genuinely good at is content, and layout happens to be the half that isn't allowed to be wrong. A report's page size, margins, break positions, and consecutive page numbers leave no room for creativity: they're either right or wrong. Handing those to an agent that has to guess afresh every time was never a reasonable idea.

So the sensible arrangement is to let the framework lock down the parts that can't be wrong, and let the agent handle only what it's actually good at. The project that made me see this clearly was somebody else's.

2. A Look at open-slide

To be clear up front: open-slide is @1weiho's work, not mine. I'm a user.

GitHub - 1weiho/open-slide: A slide framework built for agents.
https://github.com/1weiho/open-slide

It bills itself as "a slide framework built for agents". Every slide is a fixed 1920 × 1080 canvas written as a React component, and the framework takes care of scaling, navigation, hot reload, presenter mode, and speaker view. You don't have to learn a restrictive DSL, because the page is a component — want a chart, drop in a chart; want an animation, write an animation.

npx @open-slide/cli init my-slide
Enter fullscreen mode Exit fullscreen mode

I heard about the project at COSCUP and went home to try it. In practice, the feeling is: as long as you can describe the slide you want in plain language, the agent writes React and the result shows up in your browser immediately.

But what actually made me stop and dig in wasn't "writing slides in React" — that isn't new. What I found interesting was how it handles the question of how the agent is supposed to know how to use this tool.

When you init an open-slide project, the folder contains more than code: there's an AGENTS.md, plus a handful of skill documents under .agents/skills/. When your coding agent opens the project, it already knows what the file contract looks like, how much content safely fits on one slide, and what it shouldn't touch.

In other words, the manual travels with the project, not with the conversation. That design became the starting point for all of open-doc.

3. What open-slide Taught Me — Building Tools, and Making Coding Agents Understand How to Use Them

I put open-slide through its paces and read the source. This section is about what it taught me regarding the difference between tools built for agents and tools built for people.

I. The manual belongs in the repo, not in the prompt

This is the most counterintuitive point and the one with the biggest impact. We're used to writing "how to use this tool" as a prompt pasted at the top of a conversation. The problem is that prompts go stale, get truncated, and never get updated — and a different person or a different agent means pasting it all over again.

open-slide's approach is to turn that knowledge into files in the repo. A skill is just a Markdown file that says "when you write this kind of file, here's what you must follow", and the scaffolder generates it into the user's project. Three benefits fall out of this: it's versioned (the framework changes, the skill changes with it), it's project-scoped (anyone who opens the repo can see it), and it's reviewable (a skill is part of the source, so it goes through PRs).

II. Lock the parts that can't be wrong into the framework

open-slide's canvas is always 1920 × 1080. That isn't a limitation — that is the product. Because the canvas is fixed, the agent never has to guess how big the slide is, how large the text should be, or whether the content will fit. It just writes content, and "does it fit" gets answered by the framework through measurement, which is vastly more reliable than an agent guessing.

I'd put it even more bluntly: every choice you take away from the agent is one class of error you no longer have to verify. Layout is right-or-wrong with no creative latitude — it was never something to leave to guesswork.

III. The agent needs to know where you're currently looking

This one I only appreciated after using it for real. You're looking at slide 7 in your browser, you turn to the agent and say "the spacing on this page is too tight" — which page is "this page"? The agent doesn't know. It can only ask you back, or guess, and a wrong guess means it edits something else entirely.

open-slide's fix is direct: on every navigation, the dev server writes "where the user is right now" into a file, paired with a skill that tells the agent to read it. Deictic references like "this page" or "this element" then resolve into a concrete file path and line number. It looks like a small thing, but it's the bridge between the screen the human is looking at and the file the agent is editing.

Those three lessons came straight from open-slide. But once I actually built a system of my own, I hit two more problems it hadn't shown me — humans and agents editing the same file at the same time, and the same operation having more than one entry point. I'll cover both in the next section.

4. My Open Source Project: open-doc

Carrying those three lessons, I spent the last few days building open-doc — and the two problems I just teased are exactly what I ran into along the way.

open-doc

If open-slide is Google Slides for agents, then open-doc is Google Docs. Same concept, different medium: a deck is a 1920 × 1080 canvas, while a document is a stack of A4 sheets that has to survive contact with a printer.

npx @open-document/cli init my-docs
cd my-docs
pnpm dev
Enter fullscreen mode Exit fullscreen mode

(Side note: the @open-doc scope on npm was already taken, so the packages are `@open-document/` — but the CLI command and the project name are still open-doc.)*

open-doc document viewer
open-doc's document viewer — page thumbnails on the left, a real A4 sheet in the middle, footer and page number filled in by the framework

Real page dimensions

Every page component renders as an actual sheet of paper. A4 (794 × 1123 px @96dpi), Letter, A5, and Legal are all supported, in portrait or landscape. What you see on screen is what's in the PDF, because the @page size matches — nothing gets re-scaled at print time.

This is something I called out explicitly when writing the skills: authors write CSS px, but paper is measured in mm. 1pt is roughly 1.333px, so 14px body text prints at about 10.5pt — while 11px, which looks okay on screen, prints at 8pt and is unreadable.

The file contract

A document is a folder plus an index.tsx:

// docs/q3-review/index.tsx
import type { DocMeta, DocPage } from '@open-document/core';

const Cover: DocPage = () => <div></div>;
const Summary: DocPage = () => <div></div>;

export const meta: DocMeta = {
  title: 'Q3 Review',
  pageSize: 'A4',
};

export default [Cover, Summary] satisfies DocPage[];
Enter fullscreen mode Exit fullscreen mode

No front-matter DSL, no hierarchy of config files. One component is one printed sheet.

Auto-pagination that knows what can't be split

Fixed pages are right for covers, tables of contents, and section dividers — pages where the layout is the content. Body text is the opposite: paginating it by hand usually yields eleven pages that are each 60% full. So body content can be wrapped in flow(), and the framework measures every block in the real DOM before packing it into pages: headings don't get stranded at the bottom, captions stay with their figures, tables move as a unit. That pagination logic is a pure function, so it has unit tests. Pagination rules should be verifiable, not folklore.

A table of contents and page numbers that maintain themselves

Just write real <h1> / <h2> and you get an outline sidebar automatically. Drop in <TableOfContents /> and the TOC page fills itself in, with page numbers that are correct in both the viewer and the exported file. Footers use useDocPageNumber() / useDocPageCount(), so nothing has to be renumbered by hand.

Edit right on the page, or leave a note for the agent

Inspect mode lets you click an element on the page and edit its text directly; the change is written back to source via AST replacement. But the feature I use more often is the other one: you can leave a note for the agent right on the page. It's stored in the source as an @doc-comment marker, and later you tell the agent "apply comments" — the skill walks through them one by one, makes the edits, and clears the markers. This is far more precise than screenshotting a page and saying "fix this bit", because the annotation is anchored to a source line number.

Inspect mode
Inspect mode — click any element to rewrite its text, or leave a note for the agent

Humans and agents will edit the same file

This is one I only discovered by building it, and one traditional tools mostly don't have to face. While you're clicking around in the browser editing text, the agent is editing the same source. Whoever writes last clobbers the other — silently, with no warning at all.

My fix is that every write API takes an expected parameter. You send along "what I just read", and if what's on disk no longer matches, the write is rejected (409) rather than applied. Once the agent gets a 409, it re-reads and re-decides — which it's perfectly capable of doing, as long as you give it the chance.

Export and deploy

PDF goes through the browser's print pipeline at real page dimensions, and waits for fonts and images to load and the TOC to be filled before serializing — so you don't get the classic "chart is blank after export" problem. HTML export is self-contained and printable (documents with assets are bundled into a zip). open-doc build also produces a fully static site you can drop onto Vercel, Cloudflare Pages, or any static host.

An MCP server for any agent framework

pnpm add -D @open-document/mcp
open-doc dev --mcp
# ➜ MCP:   http://localhost:5273/mcp
# ➜ Local: http://localhost:5273/
Enter fullscreen mode Exit fullscreen mode

There are 19 tools in total, covering document CRUD, precise single-paragraph text replacement, themes, assets, and folders. It's stateless Streamable HTTP — clients just call it, no session handshake required.

Which brings me to the second thing I ran into while building this: the same operation has more than one entry point. open-doc can be driven from the browser UI or from MCP, and I nearly ended up writing two sets of logic. I eventually pulled every operation that touches disk into src/ops/, leaving the dev server's routes and the MCP tools as thin adapters over it.

The payoff is that rules are written once — the 409 conflict check, path safety, id validation are all identical — so you never get a gap where "the UI blocks it but MCP doesn't". That matters especially in systems with agents in them, because the path an agent takes is often the one you manually test least.

Built-in skills

The project you init ships with these skill documents, placed in both .agents/skills/ and .claude/skills/ so either flavor of agent can see them:

Skill What it does
create-doc Draft a document from scratch: establish the topic, the audience and the source material first, ask a few scoping questions, plan the pages, and only then start writing
doc-authoring Technical reference: the file contract, the page canvas, print-safe type sizes, the vertical budget that decides where pages break, tables, charts, assets
create-theme Generate a reusable family style, including a palette, a type scale, and components you can drop straight in
apply-comments Walk through the notes you left in Inspect mode, complete each one, and clear the markers
current-doc Resolve "this page" and "this element" by reading the cursor file the dev server writes

5. Demo: Driving open-doc with Antigravity

Everything above is design. Now let's actually run it. I'm using Google Antigravity as the coding agent environment here, for a simple reason: open-doc's interface to agents is a generic convention (AGENTS.md plus .agents/skills/, with MCP as an option), so it isn't tied to any one vendor's agent. The flow is the same with other tools.

If you want background on Antigravity, here's something I wrote earlier — this article won't cover any AI-coding details, just the content and the results:

Experiencing AI Building a Personal Website Directly with the Google Antigravity IDE
https://medium.com/@simon3458

Step 1: Create a workspace

Run the following to scaffold the folder:

npx @open-document/cli init q3-report
cd q3-report
pnpm install
pnpm dev
Enter fullscreen mode Exit fullscreen mode

open-doc workspace
The open-doc workspace created by the CLI

Open http://localhost:5273 and you'll see an empty workspace plus a getting-started document.

empty workspace

Step 2: Open the folder in Antigravity

The first thing Antigravity does after opening the project is read AGENTS.md, which sits in the root of the scaffolded project. It says that only docs/<id>/ may be written to, that no new dependencies should be added, and lists the available skills and what each is for. In other words, you don't have to explain what open-doc is to the agent — the project explains itself.

Antigravity reading AGENTS.md
Antigravity reading the project's AGENTS.md and skills

Step 3: Ask for a report in one sentence

My prompt was roughly:

Write me a Q3 infrastructure review report for an audience of engineering leads.
The data is in ~/workspace/data/q3-metrics.csv — don't make up any numbers.
Enter fullscreen mode Exit fullscreen mode

Antigravity prompt

The create-doc skill first pins down where the material comes from. That behavior is deliberately written into the skill: when no data is provided it should ask, not invent a set of plausible-looking numbers. Then it asks a few scoping questions (page limit, TOC or not, tone), plans out the pages, and only then starts writing files. The browser hot-reloads the moment it's done, and you're looking at that stack of A4 pages directly.

Antigravity generating the document
Antigravity generating the document

generated document

Step 4: Leave a note on the page and let the agent come back to it

This is the loop I use most. Rather than describing "the paragraph under the subheading on page three" in a chat box, just open Inspect, click that paragraph, leave a note, then go back to Antigravity and say "apply comments".

inspect note

inspect note

applied result
Leaving a note in Inspect mode, and the result after the agent applies it

applied result

Step 5: Export the PDF

Pick PDF from the Download menu in the top right, and out comes a real A4 file with consecutive page numbers and correct TOC page references — no round trip through Word to fix things up.

export PDF

exported PDF
The exported PDF

Optional: hook up MCP

If you'd rather have another agent framework (Google ADK, for instance) drive it through tool calls than edit files directly, add --mcp to start the MCP service at http://localhost:5273/mcp. The agent can then use tools like list_documents, read_document, and write_text, and the page in the browser updates live, because the MCP server runs on the dev server itself.

6. Conclusion

The biggest thing I got out of building open-doc wasn't the tool itself — it was getting clear on one idea: the hard part of building tools for agents isn't the features, it's making the agent know how to use them.

And "making the agent know how to use them" isn't about writing a longer prompt. It's three concrete pieces of engineering work: put the knowledge in the repo so it's versioned, project-scoped, and reviewable; lock the parts that can't be wrong into the framework so the agent only has to handle what it's actually good at; and give the agent the context it's missing, like which page you're currently looking at, or whether this file has been changed by someone else since it last read it.

None of these three have much to do with how smart the LLM is. They're design problems — and I expect more and more tools will have to face the same ones.

Finally, thanks again to @1weiho for open-slide. Virtual-module-based document discovery, the scaffolder, and treating skills as documentation were all learned from there. Good design gets picked up and carried forward by the next person.

open-doc is at v0.2.0, MIT licensed. Try it, open issues, send PRs — and I'd love to hear what kind of documents you end up producing with it.

GitHub — open-doc
https://github.com/simonliu-ai-product/open-doc


I am Simon

Hi, I'm Simon Liu, an AI solutions specialist and a Google Cloud AI Google Developer Expert (GDE). I hope to help enterprises solve problems by adopting AI technologies. If this article was useful to you, please give it a reaction and follow me so you can catch what I write next. Feel free to leave a comment on my LinkedIn and discuss AI topics with me — I hope this was helpful!

My Personal Website: https://simonliuyuwei.my.canva.site/link-in-bio


This article was originally published in Traditional Chinese on Medium.

Top comments (0)