Archives¶
Every plan execution writes a JSON archive: aat run plan, each plan in aat run batch, aat prompt when it executes the plan, and the MCP server's execution tool. An archive records each request and response, how every input got its value, every assertion result, cleanup, and the outcome, so you can inspect a run long after it finished. The web UI is the viewer; the files are plain JSON for scripts too.
Where Archives Go¶
Archives are written to the directory given by --output, else the manifest's archives: entry, else _output/runs in the working directory. aat web, aat web view, aat run show, aat import, aat run clean, and aat run rebuild-summaries look for them the same way.
Layout¶
A single run gets its own directory:
_output/runs/
run-20260910-225958-d819f460/
archive.json
attempt-01.json
summary.json
A batch gets a directory holding batch.json and one run directory per executed plan (duplicate permutations skipped by dedup appear in batch.json but get no directory):
_output/runs/
batch-20260910-225919-0754c0ea/
batch.json
run-20260910-225919-5a04ca45/
archive.json
summary.json
run-20260910-225921-80a38128/
archive.json
summary.json
...
Directory names are run- or batch-, the local date and time (YYYYMMDD-HHMMSS), and eight random hex characters.
| File | Contents |
|---|---|
archive.json |
The full record of the run. With --retries, the final attempt |
attempt-NN.json |
An earlier attempt that --retries retried (attempt-01.json, attempt-02.json, …), in the same format as archive.json. Present only when the run was retried |
summary.json |
A small summary the web UI reads for its lists: run ID, timestamp, outcome, step counts, duration, plan name, attempt numbers, layers, issues counts, and oas: the OpenAPI validation mode, validatedRequests and validatedResponses, and violations, recorded whenever the graph references a spec, a clean run included. Derived from archive.json; see Rebuilding Summaries |
batch.json |
Batch metadata (batch ID, source directory, layers, layer groups), one entry per run including skipped duplicates (plan name, run ID, outcome, counts, layers, permutation, duplicateOf), and the aggregate result |
What an Archive Contains¶
archive.json has these top-level keys:
| Key | Contents |
|---|---|
metadata |
Run ID, timestamp, environment name, graph version, AAT version, the plan as loaded (plan), the plan after graph defaults and layers were merged in (instantiatedPlan), the layers applied, attempt/totalAttempts for retried runs, and oasValidation, the OpenAPI validation mode, when the graph references a spec |
steps |
One record per main and verification step (see below) |
cleanup |
Cleanup step records in the same format; absent when no cleanup ran |
cleanupSkipped |
Registered cleanups that did not run because they were no longer needed: node, cleanupFor, reason (released or when), and releasedBy or when. See API Graphs: Cleanup. Absent when none were skipped |
knownIssues |
The knownIssue entries that kept a failure out of result.outcome: stepId, node, until, reason, url. When present, a passed run still holds a failed step. Absent when none applied |
knownIssuesResolved |
Entries whose step passed anyway, so the entry has outlived the defect and should be deleted. Absent when none |
result |
outcome (passed, failed, error, aborted, or stopped), error, durationMs, the run's wall-clock time (archives written before it was recorded omit it, and the web UI then sums the step durations), knownIssues, how many failures an entry covered, and endedEarly when a covered failure stopped the run short |
Each step record holds:
| Field | Contents |
|---|---|
stepId, node, startTime, durationMs |
Which step ran, and when: a retried step's start and duration cover every attempt and the waits between them. Archives written before 0.1.0 name the duration duration_ms; AAT reads both |
inputs |
The resolved input values |
request |
Method, full URL, headers, and body. A body that isn't JSON, such as a form-encoded one, is archived as the text it was sent as, and its Content-Type header says which it is. When an override routed the step elsewhere, originalUrl holds the URL it would have used |
response |
Status, headers, and body |
outputs, displayOutputs |
Extracted outputs (after any Lua transform, whose script is in transformScript) and the plan's display outputs |
resolutions |
How each input got its value: source (such as plan_default, graph_default, layer, expression, plan_from, select_edge, or fallback_pool), the layer that set the value, the raw and final value, the step and output it came from, and whether a constraint passed |
selections |
For inputs picked from an array: source step and field, array size, filter and how many elements passed it, strategy, and the selected index |
validation |
Each assertion's type, pass or fail, and message |
expectFailure |
For negative steps: expected statuses, actual status, pass or fail |
oasValidation |
Request and response checks against the OpenAPI spec |
errorClassification, error, retryCount, retriedOn |
Error category and detail for a failed step, and the category of each step-level retry |
iterations, repeatStop |
On a step with a repeat block: each request it sent, with its request, response, outputs, OpenAPI validation, and whether untilMet, plus the inputs it sent on a step that pages with next. repeatStop says why the step stopped: until, exhausted (the listing's last page), loop (a cursor came back), max, timeout, or error. The step's own request and response are its last request's, and OpenAPI counts in summary.json cover every request |
cleanupFor |
On a cleanup step: the step whose resource it releases, or the cleanup step before it in a cleanup chain. Cleanup step IDs are unique within a run (deleteCart, then deleteCart_2) |
whenError |
On a cleanup step: why its pairing's when condition couldn't be evaluated, such as an output the step didn't return. The cleanup ran anyway |
A gRPC step¶
A step that made a gRPC call records what the server sent as well as the HTTP status AAT maps it to, so a reader comparing numbers and a reader looking for a code both find what they need:
{
"request": {"method": "GRPC", "protocol": "grpc",
"url": "grpc://localhost:8767/shop.v1.Payments/Charge"},
"response": {"status": 400,
"grpcCode": "INVALID_ARGUMENT",
"grpcMessage": "CARD_DECLINED: card ending in 0002 was declined by the issuer",
"trailers": {"content-type": "application/grpc"},
"body": {"code": "INVALID_ARGUMENT", "message": "..."}}
}
trailers is kept apart from headers because a gRPC server chooses which to
send a value in, and both are redacted the same way. An HTTP step carries none
of these fields. See gRPC.
What Is Redacted, and What Is Not¶
Archives redact the credentials AAT knows about, not every piece of sensitive data, so an archive, an exported .aar/.aab, or a CI artifact is not automatically safe to commit or share. Before you attach one to a ticket, check what it holds.
Redacted:
- Credential headers, by name. In requests and responses, the values of
Authorization,Proxy-Authorization,X-API-Key,X-Auth-Token,Cookie, andSet-Cookie(any capitalization) are replaced with[REDACTED]. The header name stays. - Known secrets, everywhere. AAT collects the resolved secret credentials of every
authblock that can apply to the run — the environment's, its host overrides' (such as the shop's payments key), the plan's, and those of overlays and their overrides — plus the LLM API key and, inaat run planandaat run batch, the access token the run authenticated with. A secret credential is any credential except the oauth2usernameandclientId, which identify an account rather than prove it. Each secret is redacted from every string in the archive: URLs and query parameters, headers under any name, request and response bodies, inputs and resolved values, outputs and display outputs, error and assertion messages, and the archived plan. In a JSON body only string values change; keys, numbers, and layout are kept.batch.jsonis redacted the same way. - How a secret matches. A string that equals a secret is always replaced. A secret of at least eight characters is also replaced inside longer strings, including its URL-escaped forms. A shorter secret is not, because it is an ordinary word in data too: with the shop sandbox's
demopassword,"password": "demo"is redacted butdemo@example.comis kept. - Plan credentials. In
metadata.planandmetadata.instantiatedPlan, the value of everysource: literalcredential in the plan'sauthblock and the plan's credential headers are replaced with[REDACTED], whatever their length.source: envreferences keep their variable names.
Stored as-is:
- Tokens AAT does not know as secrets anywhere other than a credential header, such as a session token a login step returns, or the access token of a run started by
aat promptor the MCP server'sexecute_plan, echoed in a body. - Other sensitive data an API returns or a plan sends, such as personal data, and a secret written as a literal step value, which is not a credential AAT knows about.
- A short secret inside a longer string, as described above.
Terminal output and the --json summary are not redacted: they are the live view of the run, like the requests themselves.
summary.json holds no request data. The live-state dump written by --dump-state redacts nothing at all; see Checkpoints: Security.
Viewing Archives¶
aat web serves the archive directory; aat web view opens one run or batch in the browser:
aat web view # the run list
aat web view latest # the newest run or batch
aat web view run-20260910-225958-d819f460 # a run
aat web view batch-20260910-225919-0754c0ea # a batch
A reference is a directory name in the archive directory, saved names included; AAT treats it as a batch when the directory holds batch.json. latest opens whichever run or batch in the archive directory has the newest timestamp. If a server is already listening on the port (--port, default 9119), aat web view opens the page there; otherwise it starts a temporary server that runs until you press Ctrl+C.
Viewing a File¶
The reference can also be a file: an exported .aar or .aab, or a bare archive.json or batch.json, for example from a downloaded CI artifact. AAT loads it into memory and serves it from a temporary server, with no project, manifest, or archive directory needed, and writes nothing to disk:
aat web view exported-run.aar
aat web view nightly.aab
aat web view downloaded-artifacts/run-20260910-225958-d819f460/archive.json
Next to an archive.json, sibling attempt-NN.json files are loaded so the attempt selector works; next to a batch.json, the member run directories are loaded. A file is read-only: save, rename, export, and import are unavailable.
Known issue: aat web view <file> always starts its own server, so it fails with bind: address already in use while aat web (or another aat web view) holds the port. Pass a different --port.
Inspecting a Run from the CLI¶
aat run show prints what an archive recorded, for a terminal, a script, or an AI coding assistant that cannot open the web UI. It only reads: it writes nothing, not even summary.json.
aat run show latest # the steps
aat run show latest --step checkout # one step
aat run show latest --step checkout --response --shape # the structure of its response
aat run show latest --step checkout --response --path orderId
The run is one of:
latest: the newest run, runs inside batches included, even those of a batch that is still running- a run ID, such as
run-20260910-225958-d819f460, looked up at the top of the archive directory and then inside each batch - a batch ID and a run ID joined by a slash, such as
batch-20260910-225919-0754c0ea/run-20260910-225919-5a04ca45 - a batch ID and a plan name joined by a slash, as the batch view's PLAN column shows the plan, such as
batch-20260910-225919-0754c0ea/negative/state-machine: the plan's run in that batch. A run ID of the batch wins over a plan of the same name, and a permutation skipped as a duplicate has no run. A plan that ran as several runs, such as its layer permutations, exits with code2and lists them (see A Batch) - a path to a run directory, an
archive.jsonorattempt-NN.json, or an exported.aarfile - a batch ID, such as
batch-20260910-225919-0754c0ea, or a path to a batch directory or itsbatch.json, which shows the batch instead of a run (see A Batch)
IDs are looked up in the archive directory, as Where Archives Go describes.
Without --step, it lists the steps: ID, node, HTTP status, pass or fail, duration, and output names, then the verification steps and the cleanup steps, each with the step it releases. Above them, the header names the plan and the archive, and, when the graph references an OpenAPI spec, what validation checked. After the shop's smoke recipe:
run-20260914-112509-419e1d5f PASSED 2ms
plan: Buy one in-stock product and pay by card
archive: /path/to/shop/_output/runs/run-20260914-112509-419e1d5f/archive.json
oas: auto, 4 requests and 7 responses validated, 0 violations
# STEP NODE STATUS RESULT TIME OUTPUTS
1 listProducts listProducts 200 pass 0ms currency, products
2 createCart createCart 201 pass 0ms cartId, status
3 addItem addItem 201 pass 0ms cartId, lineCount, subtotal
4 checkout checkoutCart 201 pass 0ms currency, discount, orderId, receiptNumber, shipping, status, subtotal, tax, ta…
5 paymentCharge paymentCharge 201 pass 0ms amountDisplay, orderStatus, paymentId, status
cleanup:
# STEP NODE STATUS RESULT TIME FOR
1 deleteOrder deleteOrder 204 pass 0ms checkout
2 deleteCart deleteCart 204 pass 0ms createCart
A run that a known issue kept green prints a known issues: table after the steps, naming each covered step, the date its cover runs out, and why — which is what explains a FAIL row inside a PASSED run. Entries whose step has started passing again are listed separately under known issues no longer needed:.
--step takes a step ID, or a node name when that node ran only once. On its own, it prints:
- the step's method and URL, status, result, duration and retries, and error
- the inputs, each with where its value came from
- the outputs, with arrays and objects by their size
- assertion counts, with each failure
- its knownIssue, when it has one: the reason, the date it lapses, and where it is tracked
- warnings, such as a selection tie
- a repeated step's requests, one line each: status, time, and whether until held, the inputs each sent that differ from the step's when it pages with next, and its outputs
- the sizes of the request and response bodies, which on a repeated step are its last request's
A step that failed before its request was sent still shows the inputs resolved up to the failure, and the one that failed.
step checkout (node checkoutCart)
POST http://localhost:8765/us/v1/carts/cart_0001/checkout
status 201 pass 0ms
inputs:
cartId "cart_0001" plan_from createCart.cartId
customerEmail - optional_skip
deliveryDate - optional_skip
notes - optional_skip
postalCode "78701" expression {{env.postalCode}}
shippingTier "standard" graph_default
outputs:
currency "USD"
orderId "ord_0001"
status "created"
total 10340
...
assertions: 1 passed, 0 failed
request body: 48 bytes
response body: 518 bytes
A repeated step lists its requests after its assertions, and the body sizes it prints are its last request's. The shop's state-machine plan reads its customer's orders a page at a time in a verification step:
$ aat run show latest --step verify_listOrders
step verify_listOrders (node listOrders)
GET http://localhost:8765/us/v1/orders?limit=1&customerEmail=audit-j6ojn7id%40example.com&after=ord_0001
status 200 pass 0ms 2 requests (stopped: exhausted)
...
requests:
# STATUS TIME UNTIL SENT OUTPUTS
1 200 0ms - liveOrderCount 0, nextCursor "ord_0001", orderCount 1, orders [1 item]
2 200 0ms after "ord_0001" liveOrderCount 0, nextCursor "", orderCount 0, orders [0 items]
request body: none
response body: 45 bytes
These flags print one part of the step instead:
| Flag | Prints |
|---|---|
--request, --response |
The request or response body, as indented JSON |
--inputs, --outputs |
The resolved inputs or the extracted outputs, as JSON |
--resolutions |
How each input got its value, as JSON. Each entry is the input's resolution record (source, fromStep and fromOutput, expression, the pool pick, a constraint) with the selection that picked the value, and an error for an input that couldn't be resolved |
--path PATH |
Only what a gjson path selects: lines.0.sku, or lines.#.sku for every element. The $.lines[0].sku form works too. Without a part flag, it reads the response body |
--shape |
The part's structure instead of its values. Without a part flag, the response body's |
--max-bytes N |
Cut a printed part after N bytes, 65536 by default, with a note on stderr; 0 prints everything |
--json |
The step list or the step as JSON with snake_case keys, or the shape as a JSON array. A step's JSON includes resolutions, warnings, and validation, its assertion results as archive.json names them (archive.json itself uses camelCase keys). A repeated step's also includes iterations: each request's status, duration_ms, until_met, retries, error, the sent inputs that differ from the step's, and its scalar outputs |
--compact |
JSON on one line, for a script or jq: a part, with or without --path, and with --json the step list, the step, or the shape. With --shape, or without a part, it needs --json |
--iteration N, with --step, reads one request of a repeated step instead, counting from 1. On its own it prints that request's URL, status, and time, whether until held, why the step stopped when it is the last, the inputs it sent when the step pages, its outputs, its OpenAPI validation, and the sizes of its bodies. With --request, --response, --inputs, --outputs, --path, or --shape, it prints that request's part; --json works as for a step. --resolutions needs the step alone, since a repeated step resolves its inputs once for every request.
A form-encoded body prints as its decoded fields, one name=value per line, since a single escaped string is not what the step sent. --compact prints the body as it went over the wire, and --path and --shape read the value its bracketed keys describe, so --path metadata.source selects from metadata[source]=aat-stripe and a key ending in [], or one sent more than once, reads as an array.
Without --step, a part flag or --path prints that part of every step that has it, cleanup steps included, one line each. One command then answers a question about the whole run:
$ aat run show latest --response --path error.code
STEP NODE VALUE
unknownCoupon applyCoupon "COUPON_INVALID"
addAfterCheckout addItem "CART_NOT_OPEN"
shipBeforePay shipOrder "INVALID_TRANSITION"
returnBeforeDelivery createReturn "INVALID_TRANSITION"
refundBeforePay paymentRefund "PAYMENT_NOT_REFUNDABLE"
declinedCard paymentCharge "CARD_DECLINED"
payTwice paymentCharge "INVALID_TRANSITION"
shipCancelled shipOrder "INVALID_TRANSITION"
refundTwice paymentRefund "PAYMENT_NOT_REFUNDABLE"
With --json or --compact, it prints a JSON array of step_id, node, and value. --shape still needs --step.
--shape is the way to learn a large response. It prints one line per path: the path's type, an array's item count, how many objects hold a key when not all of them do, and a sample value. The elements of an array are merged, so a key that only some elements hold, or a value that is sometimes null (string|null), shows up. Each path works as an extract rule in a template and as --path:
$ aat run show latest --step checkout --response --shape
orderId string "ord_0001"
receiptNumber string "RCPT-US-0001"
status string "created"
...
total number 10340
totalDisplay string "$103.40"
lines array 1 item
lines.# object
lines.#.sku string "SKU-1001"
lines.#.quantity number 1
lines.#.lineTotal number 8999
createdAt string "2026-09-13T03:42:35Z"
Archives several hundred megabytes in size still list their steps in under a second, and so does the shape of a response tens of megabytes long.
A path that matches nothing, an unknown step, or an unknown run exits with code 2, and the message says what does exist: the top-level keys, the step IDs, or the forms a run reference takes. Archives are redacted when they are written, so aat run show prints only what the archive holds; see What Is Redacted, and What Is Not. The MCP server's get_sample_response takes the same path and shape; see MCP Server.
A Batch¶
A batch ID, or a path to a batch directory or its batch.json, shows the batch: its totals from batch.json, OpenAPI validation totals across its runs, one row per permutation, and what cleanup did across its runs.
$ aat run show batch-20260914-112520-9e406b57
batch-20260914-112520-9e406b57 PASSED 27.4s
batch: _output/runs/batch-20260914-112520-9e406b57/batch.json
runs: 63, 27 passed, 0 failed, 0 errors, 36 skipped as duplicates
oas: auto, 161 requests and 330 responses validated, 2 violations
# RUN PLAN LAYERS RESULT STEPS TIME
1 run-20260914-112520-74bbc499 full-lifecycle - pass 15/15 2.5s
2 run-20260914-112523-9369a809 full-lifecycle basket-apparel pass 15/15 2.2s
...
14 run-20260914-112546-ff31eb17 negative/state-machine - pass 16/16 3ms
...
28 - full-lifecycle shipping-standard,basket-apparel skip - - duplicate of full-lifecycle [basket-apparel]
...
cleanup:
NODE RAN FAILED RELEASED WHEN
deleteCart 27 0 0 0
deleteOrder 26 0 0 0
- RAN and FAILED count cleanup steps across the runs.
- RELEASED and WHEN count the pairings skipped: because a step released the resource, or because the pairing's
whenwas false. - Cleanup failures: a
cleanup failures:list names each failed cleanup step by run, with its status or error. --jsonprints the same as a document withsnake_casekeys.- OAS: the
oas:line sums theoascounts in the runs'summary.jsonfiles: the mode, the request and response bodies validated, and the violations. It appears when a run recorded them, and endsrecorded by N of M runswhen not every run did, such as a run from before AAT recorded them.--jsonprints it asoas, withruns. - Violations include those of steps expected to fail, which never fail a step. The 2 above are the shop's add-item mutations, which send a zero quantity and a malformed body on purpose.
- Steps: a batch has no steps of its own, so
--stepand the part flags need a run:batch-ID/run-ID, orbatch-ID/PLANfor a plan with one run in the batch.
A plan name from the PLAN column names the plan's run:
$ aat run show batch-20260914-112520-9e406b57/negative/state-machine
batch-20260914-112520-9e406b57/run-20260914-112546-ff31eb17 PASSED 3ms
plan: The order state machine refuses out-of-order transitions
archive: /path/to/shop/_output/runs/batch-20260914-112520-9e406b57/run-20260914-112546-ff31eb17/archive.json
oas: auto, 12 requests and 18 responses validated, 0 violations
...
A plan that ran as several runs, such as the permutations of a layered batch, exits with code 2 and lists them:
$ aat run show batch-20260914-112520-9e406b57/full-lifecycle --step checkout
aat: plan "full-lifecycle" ran as 6 runs in batch batch-20260914-112520-9e406b57; name one of them:
batch-20260914-112520-9e406b57/run-20260914-112520-74bbc499 no layers
batch-20260914-112520-9e406b57/run-20260914-112523-9369a809 layers basket-apparel
batch-20260914-112520-9e406b57/run-20260914-112525-46a3dd76 layers shipping-express,basket-apparel
batch-20260914-112520-9e406b57/run-20260914-112527-60ccacd7 layers basket-gear
batch-20260914-112520-9e406b57/run-20260914-112529-32fa0acc layers shipping-express,basket-gear
batch-20260914-112520-9e406b57/run-20260914-112531-f9449320 layers shipping-express
Exporting and Importing¶
A run or batch can travel as a single zip file.
Export¶
Export is a web UI feature; there is no CLI command for it. In the run list, each row has a download arrow (Export run or Export batch), and run and batch detail pages have an Export button.
- A run exports as
.aar, a zip of its directory's JSON files:archive.json,summary.json, and anyattempt-NN.json. - A batch exports as
.aab:batch.jsonplus every member run's directory.
The file is named after the run or batch ID, or its saved name (run-20260910-225958-d819f460.aar, checkout-baseline.aar). The content is the archive as written, so the redaction limits above apply to exports too. The same downloads are available at GET /api/runs/{id}/export and GET /api/batches/{id}/export.
Import¶
- Web UI: the Import button on the run list accepts
.aarand.aabfiles, adds them to the served archive directory, and opens the imported run or batch. - CLI:
aat import FILEextracts into the archive directory. - API:
POST /api/importwith a multipart form whosefilefield holds the archive (100 MB limit). The response is{"ref": …, "name": …, "type": "run"|"batch"}; a name that already exists returns409.
$ aat import "nightly run #3.aar"
Imported run "nightly-run-3" → /path/to/shop/_output/runs/nightly-run-3
| Flag | Type | Default | Description |
|---|---|---|---|
--name |
string | derived from the file name | Directory name for the imported archive |
--output |
path | manifest archives, else _output/runs |
Archive directory to import into |
--manifest |
path | auto-discovered | Explicit path to aat-project.yaml |
Without --name, the web UI, the API, and the CLI derive the directory name from the file name: the .aar/.aab extension is dropped, every character other than letters, digits, ., _, and - becomes -, repeated dashes collapse, and leading and trailing dashes go. A result that starts with run- or batch- gets a ! prefix, so run-20260910-225958-d819f460.aar imports as !run-20260910-225958-d819f460. An archive imported without --name is therefore always a named run that aat run clean never deletes.
--name must be a single directory name, without path separators, and a name that starts with run- or batch- gets the same ! prefix.
An import never overwrites or merges. If the target directory exists, it fails (exit code 2 from the CLI):
aat: import: directory "nightly-run-3" already exists
The file must be a zip with archive.json (a run) or batch.json (a batch) at its root. Symlinks, absolute paths, and .. entries are rejected, as are zips with more than 10,000 entries or more than 500 MB uncompressed.
Naming and Saving Runs¶
A directory whose name starts with run- or batch- is auto-generated and a candidate for aat run clean. Any other name, or a name starting with !, marks a named (saved) run or batch: it is never deleted by aat run clean and shows under the Saved filter in the web UI's run list.
On a run or batch detail page in the web UI:
- Save opens a name field, prefilled with the plan name (for a batch, its source). Save in that form renames the directory to the name you typed; Save as-is keeps the ID and adds the
!prefix (!run-20260910-225829-a4c05ee5). A typed name that starts withrun-orbatch-also gets the!prefix. - A saved run shows its name, a pencil (Edit name) to rename it again, and Unsave, which restores the original ID from the archive's metadata.
A name cannot contain / or \ or be . or .., and renaming fails if a directory with the new name exists. Naming works only when serving a directory, not a file.
The same operations are PUT /api/runs/{id}/name with a body such as {"name": "run-keep"} (an empty name saves as-is) and DELETE /api/runs/{id}/name to unsave; batches use /api/batches/{id}/name. The response carries the new directory name to use in URLs:
{"ref":"!run-keep","name":"run-keep"}
Because only the directory name matters, renaming a directory by hand works too. Quote a ! name in the shell, for example '!run-20260910-225829-a4c05ee5'.
Pruning Old Runs (aat run clean)¶
The archive directory grows by one directory per run or batch. aat run clean deletes auto-generated run and batch directories older than a cutoff:
aat run clean # delete runs older than 7 days
aat run clean --days 30 # keep a month
aat run clean --dry-run # list what would be deleted, remove nothing
| Flag | Type | Default | Description |
|---|---|---|---|
--days |
int | 7 |
Delete runs older than this many days |
--dry-run |
bool | false |
Preview without deleting |
--output |
path | manifest archives, else _output/runs |
Archive directory to clean |
--manifest |
path | auto-discovered | Explicit path to aat-project.yaml |
Only top-level directories whose names start with run- or batch- are candidates, and their age comes from the timestamp in the name, not from file times. Deleting a batch directory deletes its member runs with it. Named directories are skipped and counted:
$ aat run clean --dry-run --days 0
aat: dry run — previewing cleanup in _output/runs (older than 0 days)...
would delete: batch-20260910-225858-2e1dcfc5 (age: < 1 day)
would delete: batch-20260910-225919-0754c0ea (age: < 1 day)
would delete: run-20260910-225742-06f1a24d (age: < 1 day)
...
would delete: run-20260910-230834-1a340655 (age: < 1 day)
warning: run-garbage: cannot parse timestamp: unrecognized format: run-garbage
aat: would delete 16 run(s), skipped 2 saved, 1 errors
Known issue: a run- or batch- directory whose name carries no parseable timestamp, like run-garbage above, is reported as an error and left alone, but the command still exits 0. In scripts, check the summary line for errors.
Rebuilding Summaries (aat run rebuild-summaries)¶
The web UI builds its lists from each run's summary.json. After an AAT upgrade adds summary fields, older summaries lack them. Rebuild every summary from the full archives without re-running anything:
$ aat run rebuild-summaries
aat: rebuilding summaries in /path/to/shop/_output/runs...
aat: rebuilt 3 summaries
It rewrites summary.json for every top-level run directory with an archive.json and for every run inside a batch directory (auto-generated or named), using --output or the manifest's archives like the other commands. The next web UI listing picks the new summaries up. A run with no summary.json at all does not need this: the web UI computes and writes the summary the first time it lists the run.
Known issue: batch.json is not recomputed, so batch-level counts stay as they were written.