Skip to content

Notes on the Documentation, Revised by Agents

When you read these pages inside Field, you can leave a note on any sentence: point out something unclear, suggest an example, or ask for a correction. The notes form a queue that agents can work through, editing the Markdown and recording what they did. You can review their changes, answer questions and run the queue again.

Let's start with leaving a note.

Writing a note

Open the documentation pane in Field (Cmd+/), or go to https://localhost:9092/field-docs/ on the machine Field is running on. A DOC NOTES panel sits beside the page.

  • Select some text and press Ctrl+Shift+A, type the note, and press Enter. With nothing selected, the note is pinned to the part of the page you are looking at.
  • Put @tags in a note to group it: @voice, @accuracy, @example. You can run the agents on a single tag.
  • Select the sentence itself, not the heading or the navigation around it. A note on the site's header or menu does not belong to any one page's text, and the panel records it as such.

Each note in the panel shows its state, where it now sits in the source (for example public/docs/sound.md:42), and its thread: your replies, and the agents' questions, resolutions and memories.

state meaning
open waiting for an agent
agent asks an agent needs a decision from you. Type an answer in the reply box and press Enter, and the note goes back in the queue. Questions for you at the top of /docs-pass.html lists every waiting question, on notes and handoffs alike, each with its own reply…
resolved an agent (or you) dealt with it; the thread says how, and reopen puts it back
memory something an agent wrote down for the next agent on this page. Shown when show resolved notes and memories is ticked
handoff a larger task an agent filed instead of editing; see Handoffs
waits on a handoff held until that handoff is closed, then back in the queue

Notes are kept in ~/.afield/docs-annotations.json, or in whatever file AFIELD_DOCS_ANNOTATIONS_STORE names.

Running the agents

Everything below runs from the root of a Field checkout.

:::static
# what is waiting, grouped the way the agents will divide it up
uv run python tools/docs_notes.py queue

# the plan, without running anything
uv run python tools/docs_notes.py revise --dry-run

# one agent per page, four at a time, then one commit per page
uv run python tools/docs_notes.py revise --jobs 4 --commit

revise takes a few options:

  • --agent claude or --agent codex; the default is the machine's configured agent. --agent-command runs anything else, and replaces {prompt_file} in it with the path of a file holding the prompt.
  • --tag voice or --page sound.md to run only part of the queue.
  • --commit makes one commit per page once every agent has finished. Each commit has a Docs-Note: line for each note it resolves. Without --commit the edits stay in your working tree for you to review.

A run keeps each agent's prompt, complete log and recorded outcomes in .field-backdoor/docs-notes/runs/<run>/.

The queue can also hold the KDoc that completion drafts for undocumented declarations. import-drafts adds each draft as a note on its declaration, tagged @kdoc-draft, and from then on it is worked like one of yours. See AI documentation polish.

From the welcome page

Revision pass, in the Documentation notes card of /welcome's AI panel, opens /docs-pass.html, which runs a whole pass without a terminal:

  1. Start a pass makes a worktree on a new docs-pass/… branch from main. Agents edit there, never in the checkout you work in.
  2. Drain the queue runs one agent per page. Stop after current pages lets the running agents finish and starts no more. Stop now ends them. Either way, whatever they had recorded is kept, and every other note stays in the queue for the next drain.
  3. Handoffs lists what the agents filed, and each document can be read in place and marked taken, done or dropped.
  4. Changes shows every file's diff against where the pass started. Discard throws one file's edit away and puts the notes it resolved back in the queue.
  5. Rebuild docs builds the site in the worktree and links each changed page to its rebuilt copy. Commit makes one commit listing every resolved note. Enqueue for landing hands the branch to the repository's landing queue (tools/land.py), whose lander merges main in, runs the checks once and fast-forwards main. The page shows when it has landed, or the reason it bounced. Finish removes the worktree once the pass has landed.

Everything on the page is also docs_notes.py pass … (create, revise, stop, diff, discard, rebuild, commit, land, finish). The page accepts requests only from a browser on the machine Field is running on, and only from Field's own pages. It needs no switch or token.

What an agent is given

Each agent is given one page, or several pages when they would have to edit the same file, which happens with reference pages generated from the same Kotlin file. The agent is told which files it may edit, and it gets a brief. You can print the brief yourself:

:::static
uv run python tools/docs_notes.py brief public/docs/sound.md

For each open note, the brief has the note, the quoted text, the section it is in, the current lines of source with their line numbers, and the thread so far. Then it lists the memories left on the page and the most recent resolutions there, so an agent does not undo what the previous one did.

The agent must record an outcome for every note it is given: resolve it with a sentence saying what changed, question it when the note needs a decision from you, hand it off when it is bigger than the page (see Handoffs), or leave it open. It can also leave a memory, either on a note or on the page as a whole. A memory is for whatever the next agent would otherwise have to work out again: a constraint, a trap, the reason a sentence is worded the way it is.

Generated pages carry a DO NOT EDIT header, and the reference section is one of them. A note on one of those is traced back to the KDoc it was generated from, and the agent edits the Kotlin. At the end, the run prints the command that regenerates those pages.

Handoffs

Some notes ask for more than a page agent should do on its own. Merge this page into that one, this example needs a new function in the library, the figure is wrong because the renderer is. For those, the agent files a handoff instead of an edit. A handoff is a document in dev_docs/handoffs/ that states what was asked, why it is bigger than a docs edit, the evidence with file and line references, a proposed approach, and the questions only you can answer. The notes it came from are listed at the top, with their quotes.

A handoff has one of two scopes:

  • project: real engineering, such as a code change, a new tool, a bug in Field, or a capture that cannot come out until the runtime is fixed.
  • crosscutting: anything that reaches past one page's files, such as merging, splitting, moving or renaming pages, reorganising the navigation, or a change of terms across several pages.

A handoff can block further work on its page—for example, if the page is about to be merged into another. The agent stops, and revise skips the page until the handoff is dealt with. Notes added in the meantime wait too. A non-blocking handoff holds only the notes that led to it, so the agent can continue with the rest.

Handoffs are meant to be worked through separately, in an interactive session where the questions can be answered as they come up:

:::static
uv run python tools/docs_notes.py handoffs                       # blocking ones first
uv run python tools/docs_notes.py handoff-close 7f3a91c2 "merged into sound.md in 1234abc"
uv run python tools/docs_notes.py handoff-close 7f3a91c2 --state dropped "keeping both pages"

Closing a handoff releases the notes it held. The next revise picks them up with the closing sentence in the page's brief, and so brings the page in line with whatever was decided. dev_docs/handoffs/README.md covers the interactive side.

How a note keeps its place

A note refers to the page you saw, while agents edit its Markdown source. The page may have changed in between, so Field first finds the selected text and then follows its source lines as the file changes.

Finding it. Every documentation build records the commit it was built from, and the panel stores that commit with each note. docs_notes.py sync looks for the quoted text in that version of the page's Markdown, which is the text you actually read. Link syntax, emphasis and code markup do not get in the way. If the quote appears more than once, the text around your selection and the heading above it decide which one you meant. A quote of one short word whose surroundings match nothing in the source is not placed at all.

Following it. Once found, a note is tied to its lines by what git blame says about them: the commit and line each one came from. A line keeps that identity however the lines around it are edited, moved or added to, so later syncs find it again by identity rather than by searching for the text. That matters when a sentence appears twice: the note stays on the copy you annotated. It also follows a paragraph moved within its file, and a file that has been renamed.

sync reports how each note fared:

status meaning
anchored, exact, moved the note's lines are intact, possibly at new line numbers or in a renamed file
edited some of its lines were changed; it covers the ones that were not
reanchored all of its lines were rewritten, but the quote can still be found in the new text; the old text is kept for the agent
orphaned the lines and the quote are both gone; where it last was is kept
unanchored never placed. Usually the quote came from a figure or output that the documentation build captured, which is not in the Markdown, or from the site's header
uncommitted found only in edits that have not been committed yet; the next sync after a commit places it properly

revise syncs before it starts and again at the end, so resolved notes follow the rewritten text too. Run sync on its own after editing pages by hand. If a note has been placed on the wrong lines, sync --reanchor <id> discards its history and places it again from the quote.

Answering from the command line

The panel's controls are also commands, for when you would rather not open a browser:

:::static
uv run python tools/docs_notes.py show 4159b698          # a note in full (an id prefix is enough)
uv run python tools/docs_notes.py reply 4159b698 "keep Field·4 in prose"
uv run python tools/docs_notes.py resolve 4159b698 "left as is"
uv run python tools/docs_notes.py reopen 4159b698
uv run python tools/docs_notes.py memory --page sound.md "the second example is pinned by a test"

Limits

  • Nothing is verified. A run neither builds the documentation nor recaptures its figures. Build the pages it touched before trusting them: tools/build_zensical_docs.py --page <page>.
  • Agents share your checkout. Each agent is told which files it may edit. After a run, any change to a file no agent was given is listed, and it is never committed.
  • Tables and HTML-heavy passages are matched approximately, and sometimes not at all. Check the sync output for unanchored notes before a long run.
  • Each agent is limited to a set of tools. Claude may read and edit files, run the notes command, and run read-only git and grep. Codex runs in its workspace-write sandbox.
Agents: where the parts live

The panel is src/commonMain/resources/docsAnnotations.js, the store and its routes are src/jvmMain/kotlin/afield/app/server/docsAnnotations.kt, and everything on this page that runs from a terminal is tools/docs_notes.py, with its tests in tools/test_docs_notes.py. The server and the tool both rewrite the same JSON file, so any field the tool writes must also be declared on the Kotlin side or the server drops it; KotlinSchemaTest checks that. The security posture of the welcome-page button is dev_docs/DOCS_IMPROVEMENT_AGENT.md.