using leaf

Threads

A comment attaches a conversation to a passage. Replies stay beside that passage, so the discussion and the page can be read together. This page also explains how Leaf keeps each thread on its passage as the page changes.

Comments and replies

Select a passage, then use the comment control or press c to write a comment. The thread keeps that passage as its anchor. Its marker opens the conversation beside the passage, where the agent's reply and the reply box appear.

The Threads tab of Leaf's On view, which the panel's On me mark opens, lists the threads above the panel's row. Select a thread to read its messages and reply in the panel's comment field. Its passage link returns to the anchor on the page.

Try a thread

This fictional exchange shows an agent's reply after revising the importer policy. Open the marker beside the sentence to read the exchange, or open Threads in the sample's top row. Select the sentence to try a comment. The sample has its own temporary log; no agent answers its practice comments. Reset restores the fictional exchange.

Anchors

A thread's anchor is the place on the page it is about. The first comment records it in the event log, and that record never changes; a reply or a revision can later move the thread to a new anchor. A comment can be anchored in these ways:

Anchor How the user makes it What the log stores
Passage Select words, then press c section, the id of the nearest element around the words; quote, the words; and prefix and suffix, up to 24 characters on each side
Element ⌥-click (Alt-click) an element, such as an image or a table, or press s and choose one section alone
Part of a picture ⌥-click a box that a diagram declares commentable section and visual, the part's name
Data value Select words inside one value a widget drew from a data source section and datum, the value's key, and for a value from an external source, that source's revision
Drawing Draw on the page in Draw mode (w) An element anchor, plus the strokes
None Press c with nothing selected, or write in the Threads panel's comment box No anchor: the thread is about the whole page

An agent opens threads with the same anchors, using leaf thread create with --quote, --section, --part, or none of them.

Finding a passage

In the browser, a passage is an ordered list of segments, each {node, start, end}: a text node and a range of characters in it. A selection that crosses a bold word, a link, or two paragraphs is several segments. Text nodes exist only while the page is loaded, so the log stores the passage by its words instead, as the table above shows.

Each time the page is drawn, Leaf searches the anchor's section for the quote. If exactly one occurrence has the stored prefix and suffix around it, that occurrence is the passage. If none has them but the quote occurs only once, that one is the passage. In every other case the anchor finds nothing. Leaf never takes the first of two identical passages, because a comment made on the second would then appear on words it was never made on.

Leaf reads a page's text in two ways. says is the text the rendered page shows, including words a widget draws and declares as part of the page, such as a tab's label. wrote is only the text the author put in the HTML. Anchors and quotes use says, because the user selects what they see. Comparing two versions uses wrote, so words a widget draws do not appear as edits.

The browser makes an anchor from a selection. leaf thread create makes one in Python, from the HTML file. Both apply the same rules: the same whitespace characters collapse to one space, the same elements count as separate blocks, the context is the same 24 characters, and the search is the same.

The file holds the author's HTML, not the words a widget's script draws between its parts, such as a board column's heading or a diagram drawn from its source. A quote may not cross such words on either side. leaf thread create refuses a quote that crosses them, that occurs more than once in the section, or that sits in content the user's decision removed, and says what to quote instead. An agent's anchor is therefore one the user's page can find.

Anchors across revisions

Before publishing a new revision, Leaf checks each open thread's quoted passage against the revised content. The results are shown below.

A highlighted passage and its thread marker in the same place in both revisions Revision 1 Revision 2
Words still thereThe quote still identifies one passage, so the thread stays on it.
The highlighted paragraph is rewritten in revision 2; the thread marker stays beside the paragraph, now outlined as a whole Revision 1 Revision 2
Words changedThe thread moves to the element its anchor names. Its quote keeps the old words, tagged Changed.
The highlighted paragraph is removed in revision 2; the thread is listed as no longer here Revision 1 Revision 2 No longer here
Element removedThe agent moves the thread or detaches it in a reply. A detached thread stays open in Threads.

When the quoted words change but their element survives, Leaf moves the thread to the element its anchor names in section and records the move in the log as a reanchor event, without adding a message. Clicking the quote scrolls to the element. The same happens when a revision repeats the words without context that tells the copies apart.

When the element is gone too, Leaf refuses the revision and names the thread. The agent keeps the element, moves the thread with a reply that names a new passage, or detaches it with a reply passing --detach, which says the subject has left the page. Leaf refuses a revision that removes the element an open element-anchored thread is on in the same way. Only the agent chooses a new passage for a thread, in a reply that says why; Leaf's own move goes only to the element the anchor already named.

A detached thread stays open in Threads, and its first comment keeps its original anchor in the log. Its quote is greyed out and cannot be followed; hovering it shows “This passage is no longer in the version shown”. Threads' No longer here filter lists detached threads. A later reply can move one to a replacement passage.

Anchors are found in whichever version the user is viewing. Opening an older version from the version menu shows threads on passages that version lacks as “This passage can't be identified in the version shown”, also listed under No longer here.

Drawings

In Draw mode, which w turns on and off, the user draws freehand on the page and writes a comment beside the ink. The drawing is anchored on the element under the start of its first stroke, or the nearest one. The comment stores up to 32 strokes as positions within that element's box, the box's size, the words the ink covers, and the window size and colour scheme it was drawn in.

The page draws the ink at the element's current size, so it stays on the element in a narrower window, though text that reflows inside the element can move out from under it. The agent reads the covered words in the comment, and leaf page picture renders that revision again in the same window with the ink over it.

Widgets in messages

An agent's reply can carry a small widget, typically a question with options (--markup on leaf thread reply). Leaf validates the markup when it is posted and stores it in the log with the reply, so the thread shows the same widget in every version of the page. A later leaf page init refuses a vocabulary that could no longer render it.

A widget on the page takes its state from the version being viewed: that version's markup plus the actions taken up to it, minus any a stamped version marked restated. A widget in a message belongs to no version. Its state is its markup plus every action taken on it, whichever version is open, and no revision can retract one. Choosing the answer to its question hands the thread back to the agent.

Titles

A title names a thread in Threads, in a few words of plain text. The agent sets one with --title on the command that first handles the thread, and can rename it later with leaf thread edit --title.

The agent's reply can take minutes, so when the user opens an untitled thread, Leaf also asks the harness's model for a title at once, in a separate request: a claude -p request to Haiku in Claude Code, and an App Server request in Codex; in Pi, the agent names the thread. The request holds only the thread's passage and first message, nothing from the agent's session, and runs in parallel with the agent's work. Leaf uses the first title it receives. While the agent is working on an untitled thread, the panel shows animated dots in place of the title; a thread nobody named shows its opening words.

Thread readings

A thread appears in several places at once: its card in Threads, its entry in the margin, its row in the Page Map, its row in On me, and its line in an activity feed. Each place says the same thing about it, so a thread never goes by two names or shows two statuses.

What it says Where it shows
Its name: the title, else the words of its opening message, else Drawing Threads, On me, the margin, the Page Map, an activity feed
Its status: Your turn, On it, Still to do, Waiting, or how far the agent has got with the latest update Threads and the floating panel, as the card's status; the margin and the Page Map, beside its name
How far the agent has got with an update: Queued for Claude, Up next, Replying, Couldn't answer the message itself; the margin and Page Map entries
How many of the agent's messages the user has not read Threads, the margin, the Page Map
An unsent reply or rewrite its box, wherever the thread is open

Summaries

leaf thread summarize folds a run of consecutive messages in one thread under a disclosure labelled “Earlier discussion” or a label the agent chooses, with a summary of their decisions, reasoning, and open questions. The original messages stay in the log and unfold from the disclosure. A summary answers nothing and closes nothing.

Once a thread is long, Leaf suggests a range to the agent with the user's next message, leaving the newest exchange outside it; the agent decides whether to summarize. Editing a message inside a written summary's range removes the summary, so an outdated summary cannot hide the correction. Progress messages an agent posts while working fold on their own behind “Show N progress messages” when its next ordinary reply arrives.

How a thread ends

The user usually closes a thread with Resolve thread once they have read the answer. Its marker and ink leave the page, and it moves from Threads' Open list to Resolved. Reopen brings it back, as does z when resolving was the user's latest action.

The agent resolves a thread only when the user asks, or when no review could change the outcome; the thread then reads “Resolved by” and the agent's name. A new message from either side reopens a resolved thread. A reaction, a progress message or a failure notice does not.

A reaction is the only message the user can take back. Its mark's undo control, or z while it is the user's latest action, withdraws it: the log gains an undo naming it, and the mark and any thread it started leave the page. This works only while the thread is open and nothing, not even another reaction, has answered it. A message with words cannot be withdrawn.

Words typed but not sent are kept after their box closes. If a new version removes the content they were about, Resume writing (g i) opens them, unsent, in Comment on the page, after any words already there.