harn artifacts
harn artifacts <subcommand> [options]Working artifacts sit between the journal and tracked project files.
Use them for screenshots, exports, browser captures, audit dumps, experiments,
and rollback inputs that need to survive a session but should expire later.
Each workspace lives under .harnery/artifacts/ with a versioned manifest.
Subcommands
Section titled “Subcommands”harn artifacts create <slug> --purpose <text> [--days <n> | --minutes <n>] [--big]harn artifacts adopt-unmanaged [--days <n>] [--big] [--yes]harn artifacts listharn artifacts show <ref>harn artifacts delivery-card <ref> [--title <text>] [--url <label=url>...] [--path <label=path>...]harn artifacts renew <ref> (--days <n> | --minutes <n>) --reason <text>harn artifacts release <ref>harn artifacts discard <ref> --reason <text> [--minutes <n>]harn artifacts capabilities --jsonharn artifacts create <slug> --purpose <text> --hold <id> --hold-reason <text> --actor <instance-id>harn artifacts hold <ref> --id <id> --reason <text> [--actor <instance-id>]harn artifacts unhold <ref> --id <id> [--actor <instance-id>]harn artifacts migrate [--yes]harn artifacts repair-activity [--yes]harn artifacts clean [--yes]<ref> can be an artifact ID, a direct-child directory name, or a path to one
direct child of .harnery/artifacts/.
Typical workflow
Section titled “Typical workflow”# Create a workspace. The result includes an absolute path and artifact ID.harn artifacts create checkout-audit \ --purpose "Compare checkout screenshots across viewports"
# Write screenshots and reports under the returned path.
# Stop treating the creating agent's heartbeat as active-use protection.# Keep the normal window when review, a handoff, or final evidence needs it.harn artifacts release <artifact-id>
# For reviewed disposable captures, use discard instead of release.harn artifacts discard <artifact-id> --reason "Reviewed; superseded by the final capture"
# Preview cleanup. This never changes the filesystem.harn artifacts clean
# Delete only entries still classified managed-expired after a final recheck.harn artifacts clean --yesRetire disposable evidence after review
Section titled “Retire disposable evidence after review”After inspecting temporary captures, discard them when they are disposable or superseded and no unresolved failure, handoff, final evidence, or user review depends on them:
harn artifacts discard <artifact-id> --reason "Reviewed; newer run contains the final evidence"This records the reason, releases active-owner protection, and shortens the
rolling retention window to at most 60 minutes. It deletes nothing immediately.
Use --minutes 15 for a shorter grace period. An earlier deadline stays earlier;
repeating the command never extends it. An already-expired artifact stays expired.
New payload activity restarts the shortened window.
The command refuses held artifacts, another live agent’s artifacts, tracked
content, symlinks, and unreadable state. Use a hold for files awaiting review.
A passing check does not establish whether someone still needs its evidence;
the agent reviewing the result makes that decision. Keep the ordinary three-day
window when unsure. release alone continues to preserve retention.
For work known to need a short window from creation:
harn artifacts create layout-probe --purpose "Temporary layout check" --minutes 90harn artifacts renew <artifact-id> --minutes 180 --reason "Still reviewing"create and renew accept whole minutes from 1 through 5,256,000, or whole
days from 1 through 3,650. The flags are mutually exclusive. renew requires
one of them and can restore a longer window before cleanup deletes the files.
Artifact creation and release, managed qa-run results, and terminal qa-status
reports include after_review guidance with the appropriate discard command.
Release returns this reminder alongside its unchanged manifest fields. The
reminder never shortens retention by itself.
Deliver review files consistently
Section titled “Deliver review files consistently”Create a compact Markdown card with no setup:
harn artifacts delivery-card <artifact-id>The card links the artifact folder and up to five visible root-level directories and regular files. Directories appear first, then files, with each group sorted by name. Harnery skips its own metadata, hidden entries, symlinks, and special files. When more entries exist, the card reports the omitted count and points to the artifact folder instead of expanding the handoff.
Artifact links use the configured Harnery dashboard. When a live tunnel fronts that dashboard port, the card uses its public URL for every artifact file and folder. Otherwise, it uses the local dashboard URL. A stale tunnel or a live tunnel for another service is ignored.
Add a manifest when the card needs a remote URL, a friendly label, or an important file below the root:
harn artifacts delivery-card <artifact-id> \ --title "Video review" \ --url "Video=https://media.example/render.mp4" \ --path "Motion map=finished/motion-map.png" \ --path "Frames=finished/frames" \ --path "Report=finished/report.json"Supplying items validates every local destination, saves
.harnery-delivery.json in the workspace, and writes paste-ready Markdown to
standard output. A saved manifest is an allowlist: the card includes those
destinations and does not append unrelated root entries. The card contains a
linked list followed by a fenced plain-text block. The list uses short labels so
it stays easy to scan. The block contains every complete URL or filesystem path
for copying. The artifact folder is always included automatically.
Run the command again without item options to reproduce the saved selections:
harn artifacts delivery-card <artifact-id>The manifest is optional. --url and --path are repeatable. URL entries must use HTTP or HTTPS. Path
entries must be relative to the artifact workspace, must already exist, and
cannot escape through .. or a symlink. Supplying new items replaces the saved
delivery manifest. Supplying only --title changes the heading and preserves
the existing destinations.
On WSL, the plain-text block renders local destinations as Windows UNC paths so they remain directly copyable. Other systems receive their native absolute paths. A delivery manifest records navigation, not retention; move anything that must outlive the workspace into durable project storage.
New workspaces expire after three inactive days by default. Creating, editing, renaming, or deleting payload files restarts that window. Harnery derives activity from filesystem timestamps when it inventories or cleans the workspace, so agents do not need to run a separate touch command. Internal manifest writes (migration, release, hold, and unhold) preserve the activity clock. Explicit renewal still starts the requested retention window.
The manifest records an activity checkpoint and a fingerprint of root entry names. This separates the parent directory changes caused by manifest replacement from payload deletions and renames. Nested directories and files keep their ordinary timestamps. Invalid checkpoints protect the workspace from cleanup. Manifests without a checkpoint retain their conservative filesystem-based clock until a normal metadata mutation records one or a verified repair applies.
Set a project default in .harnery/config.jsonc:
{ "artifacts": { "default_retention_days": 7 }}HARNERY_ARTIFACT_RETENTION_DAYS overrides the config value for one process.
create --days or create --minutes overrides both. renew starts a new rolling window of
the requested length. The expires_at field returned by list and show is
the effective deadline after accounting for the latest change.
Size budgets
Section titled “Size budgets”Managed artifacts have two byte limits by default:
- The whole repository has a soft 20 GiB budget. When retained managed bytes exceed it, Harnery selects inactive workspaces by earliest effective expiry until the remainder fits.
- One workspace has a 1 GiB ceiling. Pass
create --bigwhen a job is expected to exceed it. An inactive oversized workspace without that acknowledgement becomes a cleanup candidate even before its time window expires.
Holds, live owners, Git-tracked content, unreadable paths, and direct-child symlinks stay protected under pressure. The limits therefore bound eligible managed data, not storage Harnery cannot safely delete. Configure the limits in bytes:
{ "artifacts": { "max_bytes": 21474836480, "max_unit_bytes": 1073741824 }}list and clean expose managed-over-budget and managed-oversize before
anything changes. clean --yes recalculates the whole plan and checks the
candidate’s ID, size, last modification, and expiry immediately before removal.
Adopt loose files
Section titled “Adopt loose files”Older integrations sometimes wrote regular files directly under
.harnery/artifacts/. Preview their adoption with:
harn artifacts adopt-unmanagedharn artifacts adopt-unmanaged --yesThe command moves unchanged, untracked direct-child regular files into one
managed workspace. It adopts an untracked legacy directory in place by adding
its manifest, so existing paths keep working. Symlinks, tracked paths, invalid
manifests, and entries that changed during the preview stay untouched. If the
combined file workspace or one adopted directory exceeds the per-bundle
ceiling, the mutating command also requires --big.
Automatic cleanup
Section titled “Automatic cleanup”Session starts, artifacts create, and new managed QA workspaces offer cleanup
opportunities. A completed sweep suppresses another for one hour. Automatic
sweeps attempt at most ten deletions and yield between candidates after five
seconds. The initial inventory and one filesystem operation can exceed that
time budget; this is not a hard process timeout. Each slice attempts at least
one candidate so a large store can still make progress. Partial or failed
slices can retry on another opportunity after one minute.
The stamp file (.harnery/artifacts-auto-clean.json) distinguishes running,
partial, failed, and completed attempts and records counts, bytes, remaining
candidates, and failures. An interrupted attempt does not count as completion.
The sweep deletes only entries still classified
managed-expired, managed-oversize, or managed-over-budget after the final
recheck, and never touches unmanaged or legacy directories. The mutation lock
serializes the sweep and its timestamp with other artifact mutations. Lock
contention does not consume the cleanup opportunity. Expiry and per-unit size
checks rescan only the candidate; repository-budget eviction still refreshes
the whole store because its decision depends on other entries.
Expiration makes files eligible for the next cleanup opportunity; it is not a background timer. An idle project keeps expired files until another session, new work, or an explicit cleanup. The independent event-ledger sweep stays daily.
Disable it per project or per process:
{ "artifacts": { "auto_clean": false }}HARNERY_ARTIFACT_AUTO_CLEAN=0 disables it for one process.
The separate review_pack.auto_clean setting defaults to false. When enabled,
artifacts clean also previews expired managed review packs inside the artifact
store. --yes removes their payloads and leaves an expiry summary. Holds and
invalid workspace manifests still protect those packs; unexpired and unmanaged
packs remain untouched. This setting also enables the review-pack sweep before
qa-run and review-pack create.
Cleanup classes
Section titled “Cleanup classes”| Classification | Meaning | clean --yes |
|---|---|---|
managed-held |
At least one hold remains, regardless of expiry, owner activity, release, or size. | Keep |
managed-active |
The creating agent has a fresh heartbeat and has not released the unit. | Keep |
managed-current |
Retention has not expired. | Keep |
managed-expired |
Retention expired and no protection remains. | Delete |
managed-oversize |
An inactive unit exceeds the per-bundle ceiling without --big. |
Delete |
managed-over-budget |
The repository exceeds its managed byte budget and this inactive unit expires earliest. | Delete |
managed-tracked |
Git tracks at least one path inside the unit. | Keep |
invalid-manifest |
The manifest is unreadable, invalid, or uses an unknown schema. | Keep |
unmanaged |
The entry has no managed manifest or is not a directory. | Keep |
symlink |
The direct child is a symlink. | Keep without traversing |
unknown |
Harnery could not establish a safe classification. | Keep |
Cleanup previews by default. Even with --yes, Harnery rechecks every deletion
candidate and refuses to delete entries that changed classification.
Hold work that is not ready for cleanup
Section titled “Hold work that is not ready for cleanup”A hold protects files whose next step must finish before cleanup can remove
them, such as an interrupted transfer or an unresolved handoff. Schema v2
requires a holds array. Each hold records id, reason, set_by (an
ArtifactActor), and set_at.
Use create --hold --hold-reason to persist protection in the first manifest.
Adding a hold after writing files leaves a cleanup window before protection
exists. Manual cleanup, session-start cleanup, and review-pack expiry retain
held workspaces and their nested evidence. Holds also override byte budgets.
release and renew preserve every hold.
hold and unhold use the current agent identity by default. A workflow that
outlives an agent session can pass its stable binding identity with --actor.
Keep that identity and the exact hold ID in the binding. A later agent acting
for that binding can remove its hold; another binding’s holds stay intact.
These identities prevent accidental cross-workflow removal. They are local
coordination records, not an authentication boundary against someone who can
edit the files.
Repeating an identical hold is idempotent and preserves its original timestamp.
Reusing its ID with another owner or reason fails. unhold requires the recorded
owner’s instance_id and removes only the named hold. Repeating that removal
after success is harmless. Remove a hold only after the workflow has verified
its completion; artifact release alone does not establish that condition.
Embedding clients can call artifactCapabilities() from harnery/core/artifacts
or run artifacts capabilities --json. Require schema_version: 2, holds: true,
atomic_create_holds: true, and owner_scoped_unhold: true before starting work
that depends on holds. An unknown command or missing capability is a refusal,
not permission to continue without protection.
Migrate existing manifests
Section titled “Migrate existing manifests”The runtime reads schema v2 only. Schema v1 and unknown versions stay on disk
as invalid-manifest; normal operations do not convert them implicitly.
artifacts migrate previews valid v1 manifests. artifacts migrate --yes
converts each eligible unit to v2 with empty holds and saves its exact old
manifest under .harnery/artifact-migrations/<sha256>.v1.json before replacement.
IDs, paths, owner records, release state, and retention fields are preserved.
The migration records activity before replacing the manifest, preserving the
effective deadline as well as the stored retention fields.
Tracked files, symlinks, malformed manifests, unexpected v1 hold fields, and
unsupported schemas remain untouched. Check each returned row: failed units
have action: keep and a reason. A repeated successful migration does nothing.
Version 0.37.0 cleanup rejects schema v2 and therefore retains these workspaces, but it cannot manage their holds. Finish old runtime processes before migration; the new lock cannot coordinate with a cleanup process that predates it.
Repair activity after an older migration
Section titled “Repair activity after an older migration”Earlier v1-to-v2 migrations treated the manifest replacement as fresh activity,
extending the effective deadline by another full retention window.
artifacts repair-activity previews corrections. Pass --yes to apply them;
the command changes metadata only and never deletes payload files.
A correction requires a hash-verified v1 preimage, an exact match to its v2 conversion, and unchanged root and manifest timestamps matching the migration. The command skips artifacts with later metadata changes, holds, tracked files, unverifiable preimages, or later root-directory changes. It derives activity from surviving payload timestamps and the original creation or renewal anchor. Old root-only activity preceding the migration cannot be reconstructed because the old migration did not save that timestamp. This is an explicit recovery operation, not a silent change during inventory.
Every correction saves the original v2 manifest and the before/after deadlines
in .harnery/artifact-migrations/*.activity-repair.json. Repeated repairs leave
checkpointed artifacts unchanged. Review artifacts clean after repair; applying
cleanup remains a separate artifacts clean --yes operation with the existing
hold, owner, tracked-file, and filesystem guards.
Manifest mutation and deletion share .harnery/artifacts-mutation.lock.
Each lock records its hostname, process ID, and process-start token in a unique
owner file. The next writer can recover a dead local owner’s lock. Live owners,
foreign hosts, unreadable owners, symlinks, and empty locks remain protected;
age alone never permits removal. Competing recovery attempts cannot remove a
replacement owner’s lock.
A live PID counts as recycled only when boot-scoped Linux process ticks prove
it belongs to a different process. Wall-clock ps tokens are not enough;
platforms without boot-scoped tokens recover only after the PID is gone.
An empty lock can come from an older runtime or an interruption while publishing or removing its owner file. There is no safe automatic way to distinguish that from a writer initializing the lock. Establish that artifact operations have stopped before explicitly removing that exact empty directory. Do not recursively delete lock contents. Normal exceptions release the lock; interrupted work with a valid owner record recovers automatically.
Journal, artifacts, and durable files
Section titled “Journal, artifacts, and durable files”Use harn journal for short text breadcrumbs that help with context recovery
or peer handoff. Use harn artifacts for file trees that can expire. If a
report, input, or piece of evidence becomes part of the project’s record, copy
it to the appropriate tracked path and document what it contains. Local
artifact retention is not durable storage.