how it works

How leaf works

A leaf is a page an agent hands you, and the loop that carries what you do on it back to the agent.

Your feedback goes back to the agent

Leaf serves the agent's page beside an append-only event log, and your browser combines the two into the current document, your choices, and every thread. What you do there goes back to the agent session that owns the page.

sequenceDiagram
  participant Agent as agent session
  participant Leaf as leaf server
  participant Browser as your browser
  Agent->>Leaf: authored files · commands
  Leaf->>Browser: page + current state
  Browser->>Leaf: comments · drags · picks
  Leaf->>Agent: leaf wait carries the event batch

What you see after you send

Select a passage and comment on it, pick an option, or drag a card. The page shows your move at once, and a status beside it follows the agent's side: Sent once the server has it, Picked up once an agent confirms receipt, Working while it acts. The status clears once a reply or revision answers the move. A move that stays at Sent has reached no agent yet. Updates lists every label.

One turn, from the agent's side

This delivery comes from a page asking which of two ways to shed load when a queue backs up. Each wait runs in the background, so the agent can answer while the next input is being watched.

$ leaf page init ./review
initialized /home/you/review

# the agent writes ./review/index.html — an ordinary file write

$ leaf version check ./review
✓ index.html: parses, widgets, authored modules, and theme validate, protected ids and decisions carried over, nothing overflows the 720px column

$ leaf version stamp ./review --text "Two ways to shed load — which?"
stamped v1 — Two ways to shed load — which?

$ leaf wait ./review
# the background task completes when the user sends input, printing the
# delivery as one line of JSON; here it is indented as `leaf delivery read` shows it
{
  "format": "leaf-delivery-v3",
  "id": "2d5bdcc4",
  "created_at": 1790100943.144468,
  "carrier": "wait",
  "acknowledge": "Whoever ran the `leaf wait` that printed this delivery acknowledges it; until then the user's moves read Sent rather than Picked up. If the output was cut off, acknowledge nothing and rerun the wait with room for the whole envelope. If you handle it, acknowledge before any other work; if you forward it, acknowledge once it durably arrives there. To acknowledge, start `leaf wait --ack 2d5bdcc4` as the next background task: it confirms this delivery and waits for the next.",
  "batches": [
    {
      "page": "/home/you/review",
      "through_seq": 3,
      "threads": [
        {
          "id": "ebe8f367",
          "title": null,
          "anchor": {
            "section": "shed-premise",
            "quote": "Callers get a 429 immediately"
          },
          "detached_from": null,
          "resolved": null,
          "elided": {
            "messages": 0,
            "actions": 0
          },
          "messages": [],
          "summaries": [],
          "actions": []
        }
      ],
      "handling": {
        "h1": "The user already sees the action applied.",
        "h2": "If you need clarification, ask in a thread on the picked option. To override the pick in a later version, mark the element `restated` and say why in the version note.",
        "h3": "It answers the page's Ask, so the page shows it as waiting on you until the markup records it. Name the work it selects or unblocks with `leaf status <page> working \"<the work, in a line>\"` before starting it, then do it, write it in, and stamp a version.",
        "h4": "This event's thread has no title yet: name it with `leaf thread title <page> <thread-id> --text \"<a few words>\"`, so the thread panel lists its subject rather than its opening words.",
        "h5": "Answer with `leaf thread reply <page> --for <event-id>` before any other work, including the work this event asks for: give the answer, or say what you are about to do and where the result will appear. When work follows, name it on the page in the same command, as in `leaf thread reply <page> --for <event-id> --text \"…\" && leaf status <page> working \"<the work, in a line>\" --on <address>`, where `<address>` is the event's `answer.to`: the line stands beside the thread and in the banner until your next reply there. Once that work lands, put its result on the page, with whatever shows it (captures, a figure, the numbers), then report it with `leaf thread reply <page> --to <address>`: what changed, in a sentence or two, linking the section that shows it. The page carries the work, and the thread only the conversation about it. `leaf thread reply` checks any saved page edit and makes it live before posting. If a revision removes what the thread is anchored on, move the thread in the reply that reports it with `--section <id>` (narrowed by `--quote` or `--part` if needed), or `--detach` it if its subject has left the page. Add `--awaits` only if your reply's prose asks the user something.",
        "h6": "Your reply renders as Markdown, so show code or markup in a fenced block."
      },
      "events": [
        {
          "kind": "action",
          "revision": 1,
          "widget": "shed",
          "action": "choose",
          "detail": {
            "options": [
              "opt-drop"
            ]
          },
          "author": "user",
          "meaning": {
            "scope": "page",
            "unit": "shed",
            "depends": [
              "opt-drop",
              "shed"
            ],
            "answer": null
          },
          "id": "0b0fd3b7",
          "ts": "2026-09-22T11:15:42-07:00",
          "seq": 2,
          "subject": {
            "kind": "widget",
            "id": "shed"
          },
          "threads": [],
          "says": {
            "opt-drop": "Drop the oldest Keep accepting, and discard the oldest queued work."
          },
          "handling": [
            "h1",
            "h2",
            "h3"
          ],
          "answer": {
            "kind": "markup",
            "action": "0b0fd3b7"
          }
        },
        {
          "kind": "comment",
          "revision": 1,
          "text": "Rejecting at the door is right for the API, but the batch importer retries blindly — it would hammer us. Drop oldest there.",
          "anchor": {
            "section": "shed-premise",
            "quote": "Callers get a 429 immediately"
          },
          "author": "user",
          "id": "ebe8f367",
          "ts": "2026-09-22T11:15:42-07:00",
          "seq": 3,
          "subject": {
            "kind": "thread",
            "id": "ebe8f367"
          },
          "threads": [
            "ebe8f367"
          ],
          "handling": [
            "h4",
            "h5",
            "h6"
          ],
          "answer": {
            "kind": "reply",
            "to": "ebe8f367",
            "for": "ebe8f367"
          }
        }
      ]
    }
  ]
}

# after the complete envelope reaches the agent, start the next background task
$ leaf wait --ack 2d5bdcc4

# while that wait runs, answer the comment and name the work it starts
$ leaf thread reply ./review --for ebe8f367 --text "I’ll use drop-oldest for the batch importer and keep rejection at the API boundary." && leaf status ./review working "splitting the shed policy between the API and the importer" --on ebe8f367

# revise ./review/index.html; a valid save goes live at once
$ leaf thread reply ./review --to ebe8f367 --text "Done: [the shed section](#shed-premise) now splits the policy by caller."
$ leaf status ./review waiting "Does the API/importer split match what you need?"

The batch carries a pick and a comment. The comment names the passage it was left on, so the agent receives the objection with its context, and each event's handling says what that kind of feedback asks for. The agent answers in the comment's thread and revises index.html. A valid save goes live at once; the agent stamps it as v2 once the revision is worth pointing at.

Each host delivers the same envelope

Claude Code receives feedback through leaf wait. In Codex a detached adapter queues it as the task's next message, or, where Leaf is a client of the task's Codex App Server, starts a turn with it. Every route carries a delivery of the same shape, which names the route and says who acknowledges it. The host owns delivery, so an open page does not prove that an agent received your comments.

Behind those words, Leaf tracks three separate facts about each move:

  • Receipt: which input reached the agent. In the direct loop, leaf wait --ack <delivery-id> confirms the whole envelope and waits for the next one; printed output can be lost or cut off, so unconfirmed events stay available to the next wait. On other hosts the adapter acknowledges, and the agent follows its route rather than starting a competing wait.
  • Work: what the agent is doing. A delivery entering the agent's turn marks it working; leaf status names the operation, and leaf delivery claim <id> --event <event-id> --detail "…" names the move being worked on.
  • Response: what settled the move: a reply in its thread, a revision, or a request's outcome. Receipt and work leave it open. Once it is answered, the agent names your next move with a waiting status.

A command never edits the document

The agent writes the document to index.html. Replies, comments, and version notes go through the leaf command, which appends them to the event log. Browser gestures enter through the same validation and append boundary, giving every event a stable identity and order.

The browser redraws when the server announces a change: replies appear in the thread panel beside their passages, and the banner and tab icon say whether an agent is working, waiting for you, or absent.

The agent says what it is doing before it does it

While an agent works on a page, it keeps a status: one sentence naming the operation and its subject, written before the step starts, so what you read is what is happening now. The status stands in the banner at the top of the page and beside the thread or widget the work is about, and replies in the thread carry the rest.

Sent You comment on the lede. The page shows your comment at once, marked Sent. Picked up The agent confirms receipt with leaf wait --ack. Your comment reads Picked up, and until the agent writes its own words the banner reads Claude working — on your update. A reply says what comes next Before doing the work, the agent replies in your thread: I’ll cut the lede to two sentences. In the same command, leaf status … working "cutting the lede to two sentences" --on <thread> puts that line beside the thread and in the banner. Each new phase gets a new line When the work moves from editing to checking, the agent writes the status again: Claude working — checking the page at phone width. Answered The revision goes live, a second reply reports it, and a waiting status names what the agent wants back: Claude awaits — does the shorter lede keep enough?

Every update, and where you read it

Some updates the agent writes with a command. The rest Leaf writes itself, from what it can observe: how far your input has got, and whether the agent's turn and watcher are still running. A status and the agent's turn each show in two places; the dotted line is a status written with --on.

flowchart LR
  subgraph Sources["Who writes it"]
    Reply["agent: leaf thread reply · leaf thread open"]
    Awaits["agent: leaf thread reply --awaits"]
    Stamp["agent: leaf version stamp"]
    Receipt["agent: leaf experimental report · receipt"]
    Status["agent: leaf status"]
    Pickup["Leaf: how far your input got"]
    Turn["Leaf: the agent's turn and watcher"]
  end
  subgraph Reads["Where you read it"]
    Thread["the thread"]
    Asks["the Asks tray"]
    Versions["the page and its versions menu"]
    Widget["the widget"]
    Banner["the banner, tab icon and Leaves tray"]
    Beside["the label beside your comment or widget"]
  end
  Reply --> Thread
  Awaits --> Asks
  Stamp --> Versions
  Receipt --> Widget
  Status --> Banner
  Status -.-> Beside
  Turn --> Banner
  Turn --> Beside
  Pickup --> Beside
  classDef leaf fill:var(--ok-tint),stroke:var(--ok),color:var(--ok-ink)
  class Pickup,Turn leaf

Written by the agent

  • leaf status … working "…" What it is doing, in the banner: Claude working — running the browser suite. With --on, the same line stands beside that thread or widget until the agent answers there. The tab icon takes the banner's colour, and the page's row in the Leaves tray repeats the line.
  • leaf status … waiting "…" What it wants from you, as in Claude awaits — pick one of the two layouts.
  • leaf status … idle The agent has finished with the page, and the banner reads Leaf closed. It cannot close while your input is unanswered.
  • leaf thread reply --for … An answer in your thread, which clears the label beside your comment.
  • leaf thread reply --to … A later message in a thread already answered, such as the result of work the first reply promised.
  • leaf thread reply … --awaits A question for you. The thread reads On you, and the question joins the Asks tray.
  • leaf thread open, leaf thread edit, leaf thread resolve A thread the agent opens on a passage, a message it corrects, marked edited, or a thread it closes (✓ Resolved by Claude).
  • leaf version stamp --text "…" A revision. The page updates in place, the status line says Updated to v4, and the text joins the versions menu as that revision's note. While you read an earlier revision, a chip offers the new one: New page available → open v4.

leaf experimental report and leaf experimental receipt let optional packages' widgets show a worker's progress and the outcome of a request you made. Both may change.

Written by Leaf beside your input

Beside each comment, pick, or drag, a label follows your input through the agent's turn. The label clears once a reply or revision answers it.

Label What Leaf knows
Sending Your browser is still sending it to the server.
Sent The server has your input.
Queued The agent's host queued it for the agent's next turn.
Picked up The agent's turn has taken it up.
Working A status names this input's thread or widget.
Replying The answer is streaming into the thread.
Waiting for pickup Two minutes after sending, no agent has taken it up.
Turn ended The turn that picked it up closed before the agent said anything about it.
Update stale The status about it went a quarter of an hour without renewal, or the turn behind that status ended.
Interrupted, Not answered The host stopped the turn, or reported that it failed, before it answered.

Queued, Replying, and the failure labels need the agent's host to report more than a pickup, so a host that doesn't report them never shows them. The same holds for the banner's step readings. Today Codex reports all of these, and Claude Code none.

When the agent writes one

  • When your input arrives. Your comment comes before the work in hand: the agent confirms receipt, answers or says what it is about to do, and names that work, all before starting it. Each delivery repeats these steps to the agent beside the events they apply to.
  • When the work changes. A new subject, or a new phase such as reading, editing, testing, or waiting on a result, gets a new line. leaf status refuses working with no sentence, since the banner already says that the agent is working.
  • During long work. Work longer than a few minutes goes to background workers, so the agent stays free to answer your next comment. Its status names what is still running, and it writes it again when a worker reports.
  • When it is your move. A waiting status names the answer or decision the agent wants, rather than saying that it is waiting.

The conversation loop reference is the agent's copy of these rules.

What the banner says without an update

The banner states only what the page directory can prove, so a quiet agent reads differently from a busy one. Once an update is about a minute old, the banner shows its age, as in Claude working · 5m ago — running the browser suite.

Banner What Leaf knows
Claude working — on your update The agent's current turn picked up your input and has not yet said what it is doing.
Codex thinking, Codex using a tool The agent's host reported its current step.
Claude’s turn ended 3m ago The agent's turn finished without renewing its last update. The disclosure keeps that update's words.
Claude last checked in 20m ago Nothing has renewed the update for about a quarter of an hour.
Claude away No watcher is running. Your comments are saved, and the agent picks them up on its next turn.
Nudge Claude in terminal No watcher is running, and nothing has renewed the update for about a quarter of an hour. Your comments are saved until a message in the agent's terminal starts its next turn.

What the status line announces

News that arrives without you doing anything shows for a few seconds in the status line at the bottom of the page, and a screen reader hears the same words. News arriving together shares one notice, as in Claude replied; Input needed. What was already there when you opened the page isn't announced, except messages you haven't read.

Notice When
Claude replied, Claude commented, Claude updated a reply, 3 replies in 2 threads An agent message you haven't read arrives, or one is edited.
Input needed, 2 items need your input Something new waits on you: an Ask on the page or in a thread, or a thread whose turn is yours.
Response failed, Response interrupted The agent's host reports that an answer to you failed or stopped.
Request succeeded, Request failed A request you made from a widget gets its outcome.
Agent active on this page An agent starts watching or working on the page after a time with none.
Updated to v4 A new revision replaces the page in place.

Every valid save is a revision

Each changed, valid save of index.html becomes an immutable revision, including the document and its local dependencies, and goes live at once. leaf version stamp marks a revision as a public version, served at an address such as /versions/v2.html: the live address follows revisions, and a tab pinned to v1 stays there.

A live update waits while you are composing, dragging, or undoing, have a gesture still on its way to the server, or have the version menu open, and it keeps your reading position.

A page that asks for approval declares <meta name="lf-review" content="sign-off">. A stamped version that declares it offers the banner's approval control, enabled once you have answered every Ask, the questions the page puts to you.

Checked before it goes live

Before activating a revision or stamping a version, Leaf checks the source's structure, vocabulary, dependencies, and your standing decisions. An invalid save leaves the last valid revision live and shows its diagnostic. For a finished record, leaf version check --render also opens the page in Chrome in both color schemes, catching console errors, invisible widgets, and horizontal overflow.

Your choices survive revisions

Dragging a card or choosing an option records a structured action. The browser applies it immediately, the agent receives it alongside comments, and the log keeps it across reloads, other tabs, and later revisions. The event log reference shows the records a pick and its undo write, and how they fold into state.

To override a choice, the agent marks the rewritten element restated and explains the change in the version note. Without that declaration the recorded choice still applies, and a revision that contradicts it is refused.

z withdraws your last undoable gesture; pressing it again steps further back. The log records the withdrawal, and the browser redraws the state without that gesture, so a reload gives the same result.

A reaction attaches brief feedback to a passage or agent reply without opening a thread. The default tokens ask the agent to keep, change, clarify, shorten, support, or prioritize something; packages can declare others.

What a page is made of

An authored HTML document supplies the page's title, description, and content. Leaf adds the security policy, identity, theme, runtime, and canonical address when serving it. Authors reach for four things, in this order:

  1. Plain semantic HTML. The theme styles bare elements, so headings, prose, tables, code, and details carry the page's voice unclassed. Most of any page is this.
  2. Class idioms. A small closed set the theme defines for shapes HTML has no element for, among them .eyebrow, .lede, .callout, aside.sidenote for a note in the page's margin, and ol.steps, which numbers this list.
  3. lf-* widgets. Custom elements that say what things are: <lf-options> for a question the user answers, <lf-board> for work in columns. A JSON-Schema registry keeps the renderer, the linter, and the agent's documentation agreeing about the vocabulary.
  4. Bespoke presentation. A page-local <style> and hand-drawn inline SVG, both using the theme's tokens: the last escape hatch for presentation only one page needs, never a second palette. The drawings on the Layout tab are this.

Interactive elements load their declared behavior modules when the page uses them. Page-specific behavior can use inline module scripts or local modules under page/; reusable behavior belongs in a package. The page-authoring reference defines the module and dependency rules.

Widgets you can operate

This page runs the plugin's own theme and widget modules. Each example sits in an lf-specimen, the gutter that marks quoted material, so an example decision never reads as one the page is asking you to make.

effort: highrisk: medRewrite the importer A clean slate on the new schema; the old importer keeps running until parity. effort: lowrisk: highShim the old schema Fastest to ship; every future field pays the translation tax. effort: medrisk: lowMigrate in stages New fields land on the new schema while reads fall back, table by table. My take: this keeps the rollback boundary small.

Whichever way that goes, which of the three jobs behind it are worth starting now?

Backfill last month's rows Contract tests against the old schema A read fallback behind a flag
The form follows the options, and nothing declares it: options that argue their own case render as cards, bare labels as rows — a question about the page, where each row can name the section that makes its case. In a live leaf either form takes your pick by click, the next version ships with your choice marked, and the group ends with one more option: an empty one you write yourself, for the answer nobody listed.
Flaky auth test Fails one run in twenty on CI only. Dark-mode contrast pass Rate-limit the export Tokenize the palette
In a live leaf, cards drag between columns by a grip, and each drop reaches the agent as a structured move.

The catalog runs wider: diagrams, diffs with per-file collapse, annotated code walkthroughs, file trees, timelines, metric rows, plans with milestones, and dense command surfaces. The examples gallery opens each complete page in its own view.

Each page owns its layer

page init composes the kernel and selected packages into a checked layer in the page directory. Revisions capture the layer they use, so changing the installed defaults cannot change a version already approved. The package contract owns composition order and replacement rules.

The kernel owns the shared runtime, chrome, and collaboration protocol. Packages supply reusable elements, modules, styles, and guidance: the default package provides common shapes, and optional packages add capabilities such as diagrams and diffs. Project packages use the same format and can replace or extend the selected layer. The extension guide lists the bundled packages and builds a small one.

Every page is a stack of blocks

A Leaf page has one kind: blocks read top to bottom in the reading column. A block changes the layout by declaring what it needs. A wide block grows past the column, data-width on main widens the whole page, and a workspace that is the page's only block holds the window.

Prose in the reading column, with a wide block growing past it wide block
Prose and wide blocksNothing is declared on the page. Text keeps the column, and a block that declares a width, such as a diagram, grows past it.
A wide page: main declares a width, and its regions share one left edge main data-width body status
A wide pagedata-width on main widens the whole page. Every block starts at one left edge, and text keeps its measure.
A workspace alone in main holds the window, and each pane scrolls on its own queue detail
A workspace aloneAn lf-workspace that is main's only block holds the window, and each pane scrolls on its own.

A page grows without changing kind: a report that gains live status gains a grid, and its comments and anchors stay where they were.

Page tabs don't make another kind. They are navigation over sections of one page: the strip sticks under the banner, one panel shows at a time, and each panel takes the page's width like any other block, so data-width on main widens every tab.

The room a page has depends only on the window

The room a page has is the window less three fixed reservations, and the surfaces Leaf opens while you read stand over the page. So the page is laid out as the agent wrote it, and opening Threads or a thread card never moves what you were reading.

The banner, the shortcut band and the rail are reserved at fixed sizes Banner Shortcut band Rail
Three fixed reservationsThe banner, the shortcut band and the rail each have a fixed size, so the room a page has depends only on the window.
The Asks tray and the Threads panel open over the page's edges Asks tray Threads panel
Surfaces open over the pageThe Asks tray and the Threads panel open one at a time over its edges. The page under them keeps its width, its wrapping and your place.
A thread card stands in the rail beside its marker Thread card
A card stands in the railIt opens beside its marker, crosses into the column only by what the rail lacks, and pushes nothing down.

A touch screen draws no shortcut band, and a window too narrow for the rail draws no rail. A margin entry with no rail to stand in, such as one for a passage inside a pane, sits in the text beside what it marks.

Elements name a role, attributes size a block, classes only style

Leaf reads a page's markup to lay it out and to act on it. What it has to recognise is an element, what sizes a block is an attribute, and everything else is a class for the theme.

Elements: a workspace holding a grid of two panes lf-workspace lf-grid lf-pane lf-pane
An element when Leaf needs the roleA workspace holds the window, reading keys and scroll restore find each pane, and the render check compares a page's grid tracks.
Attributes: data-width on main and on one block main data-width figure data-width
An attribute to size a blockdata-width on any block, or on main for the whole page. A widget declares its own default width the same way.
Classes: a sidebar and two panels the theme styles aside .sidebar section .panel section .panel
A class only to stylesection.panel, aside.sidebar, .callout. The theme styles these, and a page can write its own CSS in their place.

A wide page lays its regions on one set of tracks

When the regions are the page, such as a release in progress, a board and its status, or a review whose verdict stays beside the diff, <main data-width="available"> widens the whole page. Every block, the title included, starts at one left edge, one gutter in from the window, and text inside a region keeps the reading measure.

A wide page reads as one structure when its regions stand on the same vertical lines. Give it one set of tracks, usually a wide body beside a narrow column of status, and stack each track's regions as panels. A new grid per row puts each row's split somewhere new, and leaf version check --render advises when it finds that.

One set of tracks: every region starts on one of two lines body status
One set of tracksEvery region starts on one of two lines. The body holds what you work through; the status column holds what you keep an eye on.
A grid per row: each row splits somewhere new
A grid per rowEach row splits somewhere new, so the regions line up with nothing and the page reads as a pile of parts.
<main data-width="available">
  <h1>Release 4.2 rollout</h1>
  <lf-grid columns="2fr 1fr">
    <lf-grid columns="1">…</lf-grid>  <!-- body: panels you work through -->
    <lf-grid columns="1">…</lf-grid>  <!-- status, counts, contents -->
  </lf-grid>
</main>

Examples: Live progress, Triage board, Command hub.

A workspace holds the window

An lf-workspace keeps regions in view together while each scrolls on its own: a queue beside the item it opens, a playground's controls beside its preview. A page whose only block is a workspace is a wide page without declaring one, and the workspace holds the window's height as well as its width: each lf-pane's body scrolls under the pane's header, and actions in a pane's footer stay in reach. The title goes in the workspace's header.

Alone in main, a workspace holds the window and its panes scroll queue detail footer
Alone in mainThe workspace holds the window. Each pane scrolls under its header, and a footer's actions stay in reach.
Among other blocks, a workspace's panes take their natural height and the page scrolls queue detail
Among other blocksThe panes take their natural height and flow with the text, and the page scrolls them.
<main>
  <lf-workspace>
    <header><h1>Payments alerts</h1></header>
    <lf-grid columns="1fr 2fr">
      <lf-pane label="Queue">…</lf-pane>
      <lf-pane label="Alert">…</lf-pane>
    </lf-grid>
  </lf-workspace>
</main>

Examples: Notification playground, Merge a worktree.

On a phone, every page is one column

A grid's tracks stack where its narrowest would fall below 14rem, and a workspace's panes flow once the window cannot hold them, so on a phone every page becomes one scrolling column in source order. Write regions in an order that reads top to bottom.

A wide page's regions, numbered in source order, stack into one column on a phone 1 2 3 4 1 2 3 4
Source order becomes reading orderThe status column stacks under the body, so region 3 follows region 2.

The page directory is the record

Everything a page is lives in its directory: the mutable index.html, an immutable copy of each revision with the layer it used, the append-only events.jsonl, and one JSON file per external data source under data/. Copying the directory copies the page, its history, and every decision on it.

A server is only a way to reach that directory. One started by an agent session retires when no live session claims the page; one started from your shell, or with --standing, stays up until leaf server stop.

Served where you are

Leaf serves on the session's SSH destination, or on loopback for a local session, where the URL opens only on the serving machine. The URL carries a machine-wide access key, which the browser keeps in a cookie, so sharing that key grants access to every Leaf page on the machine.

When the default address is unreachable, the agent re-serves the page with leaf server start --host NAME on a hostname you can reach. Leaf uses the machine's existing networks and opens no public tunnel. The serving reference covers addresses, authentication, and server lifetimes.