$PILOOP_HOME/projects/<id>/workspace. It is linked into every agent’s
worktree as ./workspace/. Agents leave notes, logs, evaluation outputs and analysis arrays there. On a long campaign
it grows to hundreds of gigabytes, much of it copies.
piloop stores workspace files in a content-addressed artifact store once nothing needs them any more. It removes
the local copies later, and piloop restore brings them back.
What is archived, and when
The daemon archives in the background, one job at a time. Its scheduling loop never waits for an archiving.- When an idea is done (its last run is judged), the entries that belong to it are archived:
workspace/idea<id>…(idea12/,idea12-canary.log). Workers are told to put their output there. - When a campaign stops and none of its agents is alive any more, everything left in its workspace is archived.
- Never archived:
NOTES.mdandresearch/at the top of the workspace. These small knowledge files stay where research sweeps read them.
archives table. It removes nothing: the local copies stay, so an
agent or a human that still reads a file, for example to answer an escalation, finds it.
When local copies go
The daemon removes the workspace copies of an archiving later, and records when inarchives.pruned_at:
- By age: 7 days after the archiving.
- Under disk pressure: while the disk of the store has less than
min_free_gb([storage], 20 GB by default) free, the oldest archivings first.
min_free_gb is stored in batches: before each batch, the daemon removes older local
copies while the disk is short, then measures the free space again. When not one more file fits, the rest is not
stored, and the daemon writes an anomaly.disk event on the project, with the bytes needed, the bytes free and the
reserve. Only the last batch of a job that stored everything marks the job as done (archives.complete). A job that
stops short, because of the disk, a failure or a stop of the daemon, stays due, and its next run stores the rest.
After a failed or deferred archiving the daemon waits 10 minutes before the next one.
Which archivings are due is computed from the database. A daemon that stops in the middle of a backlog picks it up
where it was when it starts again. Content that was already stored is found by its hash and is not stored twice. The
first daemon start after an upgrade archives every done idea and stopped campaign that is already there.
piloop store status shows how many are still due.
Workers are told to put their output under workspace/idea<id>/, so it is archived with their idea. They are also
told not to copy the code tree into the workspace (every candidate’s tree is in git) and to leave evaluator output in
the project’s own cache.
Getting files back
piloop restore reads the files from the local store, or downloads them from the remote. A file that is already in
the workspace with other content is kept and listed, never overwritten. Agents may run it too.
How the store works
One store serves every project of aPILOOP_HOME, in $PILOOP_HOME/store.
- Content addressing. A blob is addressed by the BLAKE3 hash of its whole content. A file that appears in many places, or in two campaigns, is stored once.
- Compression. Blobs are kept zstd-compressed. JSON and logs shrink about tenfold; arrays that are already
compressed (
.npz) barely shrink. - Streaming. Files of any size are read and written in 1 MiB pieces, never held in memory.
- Manifests. A manifest lists the paths of one archiving, each with its blob, size and mode (or a symlink’s
target). It is a blob too, and the
archivestable records its address.
A remote
Set[storage.remote] in $PILOOP_HOME/config.toml to mirror the store. Its kind picks the backend, and the
backend owns the other keys of the table. s3 and gcs are built in; gcs needs the gcs extra
(uv tool install --reinstall 'piloop[gcs]'). A package can add a backend: see
storage backends. The keys of [storage] itself hold for every
backend. The configuration reference lists every key and shows the same store on S3 and on
GCS.
[storage.remote] against its backend when it opens the store: piloop serve, piloop restore and
piloop store fail on a key the backend does not know.
- Mirror. With a remote, every blob is uploaded as it is stored. For S3, boto3 finds the credentials (environment, profile or instance role).
- Storage class. Each object gets Glacier Instant Retrieval (millisecond reads at a fraction of the price of
STANDARD), except objects under
standard_below: Glacier IR bills each object as at least 128 KB, so small ones are cheaper as STANDARD. - Eviction. Every hour, before the next archiving, the daemon drops the local copies of blobs that the remote
holds: those older than
evict_after_days, then the oldest ones until the local store fitslocal_max_gb.piloop store evictdoes the same on demand. It first uploads any blob that was stored before you set the remote. - Remote only. With
evict_after_days = 0, the local store is only a staging area: each blob’s local copy is dropped as soon as it is uploaded, andpiloop restoredownloads it. - One remote per store. The first upload writes the remote’s URL to
$PILOOP_HOME/store/remote.url. A store whose blobs went to one remote refuses to open with another remote, or with none: the blobs it marked as uploaded are only in the first one. To move to a new remote, copy the objects there, then write its URL into that file.
piloop store verify checks that every blob of every archive is still there. It hashes the local copies again and
asks the remote for the evicted ones.