lazy documentationv0.91

Builder scratch dir

A writable, host-accessible directory the builder can use for artifacts it wants to hand to you — and nothing else.

~/.lazy/scratch/<project-slug>/

The builder sees the path in $LAZY_SCRATCH_DIR. For a builder you launch with lazy builder, you see it at the same absolute path on the host. A builder started by Lazy Teams sees it inside its container at /lazy-builder/scratch; read its files with lazy scratch or the Teams Files view. On Lazy Teams the directory, like each member's builder settings and conversations, lives on the project's persistent disk, so it survives the project's machine being replaced during an upgrade.

Why it exists

The repository is bind-mounted read-only into builder containers. That is deliberate: the builder's job is prompts and review, not implementation, so it must not be able to do work in the git tree.

The side effect was that the builder had nowhere to put anything. Yet artifacts are genuinely useful:

The scratch dir is that place.

The boundary: builder ↔ human, never builder ↔ agent

This directory is a scratchpad for the builder and for data exchange with you. It is not a channel to agents. If the builder had a writable place task agents could also see, the builder would start writing code there and telling agents to copy it in — dissolving the builder/agent separation the whole system depends on. An agent-visible scratch dir is worse than no scratch dir.

There is no builder→agent handoff area, and none is planned.

Two properties enforce this, structurally rather than by convention:

  1. It can never be committed. It is not inside any git tree — not .lazy/, not the worktree, not the repo at all. There is no .gitignore entry to forget, edit, or override with git add -f.
  2. No agent can read it. No agent launch path mounts it, and nothing is reachable in a container that is not mounted. A custom [[mounts]] entry in lazy.toml whose source is (or contains) the scratch base dir is refused with an error naming the entry — the same treatment lazy's daemon state directory gets, and for the same kind of reason.

Container builder contract

container runner (docker / podman)
Availability bind-mounted read-write — at the identical host path for lazy builder, at /lazy-builder/scratch for a Lazy Teams builder
Path $LAZY_SCRATCH_DIR

The path is derived from the project root alone — no config key, no lazy.toml setting — so every launch computes the same path.

The directory is created mode 0777 before every builder launch. A builder container writes as the image's user account, whose uid need not match yours; the bind mount carries host ownership through, so a 0700 dir would be silently unwritable inside the container on any host where the uids differ. Repair only ever widens the bits — if you set the sticky bit (chmod 1777) by hand on a multi-user machine, lazy leaves it alone.

It is captured into the project store

The directory on disk is the working area; lazy's storage holds the durable copy. The builder supervisor captures the directory on its regular cadence and again when the session ends, so artifacts outlive the host, travel with the project, and are readable by later builders — not just by whoever was at that machine.

lazy scratch list              # captured files, newest first
lazy scratch show <path>       # one file's content
lazy scratch sync              # capture now instead of waiting for a builder
lazy scratch rm <path>         # drop a captured file from the store
lazy scratch path              # the live directory, its entry count and size
lazy search 'in:scratch <text>'   # search inside captured content

Builders read the same files through the lazy_scratch MCP tool and search them with in:scratch. Task agents cannot — both the tool and the search scope are rejected for an agent caller, server-side, from its task identity. The builder↔human boundary above is not weakened by making scratch searchable.

The web dashboard shows the same captured files under Scratch (in Lazy Teams, Builders → Files, and each builder's own files under Files this builder left on its page): grouped by the builder that last wrote them, with capture time, markdown rendered (with a raw view), and a search box. A file recorded by name only is labelled as such with its reason — over 1 MiB, binary, or over the sandbox budget — rather than shown empty. A system message that names a captured scratch path links to it. The web pages are read-only; removal stays lazy scratch rm.

What capture does and does not do

Finding it

lazy scratch path      # the live path, item count and size
lazy system status     # names the path
lazy doctor            # names the path, item count and total size

The builder also prints the path at launch, and its system prompt tells it to give you the full path whenever it leaves something there.

Lifecycle

Persistent, never auto-wiped. You may read an artifact hours or days after the builder session that wrote it has ended, so nothing in lazy prunes this directory — not lazy doctor, not lazy system status, not a new builder session.

Cleanup is yours, and it is two places — the live directory and the store:

rm -rf ~/.lazy/scratch/<project-slug>/*     # the live working area
lazy scratch rm <path>                      # the captured copy

Clearing only the directory leaves the captured copies readable (deliberately — that is what "outlives the host" means). Clearing only the store means the next capture puts the file back.

lazy doctor reports the size on every run and adds that hint once the directory passes 100 MB.

Overriding the location

LAZY_SCRATCH_BASE_DIR relocates the base directory for every project at once — the same seam, and for the same reason, as LAZY_DAEMON_BASE_DIR for daemon state. There is no lazy.toml key: as described under Container builder contract, the path stays a pure function of the project root, so every launch computes the same one.