Workspace (.trap/)

Every tp run writes artifacts under .trap/runs/{solution-key}/{alias}/{timestamp}/. {alias} is the tasks: key from trap.yaml; {solution-key} identifies the solution the run was launched for, so runs of different solutions sharing one workspace never mix — latest is always scoped to a single (solution, task) pair.

Directory layout

The workspace root holds one directory per data type: runs/ for run artifacts and repos/ for the task clone cache. Inside a run, each actor (solution, judge, grader) owns its own subdirectory; its captures live there unprefixed, which keeps the solution's outputs/ clean.

.trap/
├── repos/                                    # task clone cache (remote task sources)
│   └── {basename}-{hash8}/                   # keyed on repo URL + rev, so revs don't collide
└── runs/
    └── {solution-key}/                       # e.g. claude-haiku-4-5-3f2a1b0c
        └── {alias}/                          # the tasks: key from trap.yaml
            └── 2026-05-09T14:30:00/          # one run, named by its start time
                ├── {case_id}/
                │   ├── solution/
                │   │   ├── stdout            # solution stdout (always captured)
                │   │   ├── stderr            # solution stderr
                │   │   ├── meta.json         # {"exit_code": 0, "duration": 0.12}
                │   │   └── outputs/          # files the solution wrote (it owns this dir)
                │   └── judge/                # only present if a judge ran
                │       ├── stdout            # the judge's metrics JSON
                │       ├── stderr
                │       └── meta.json         # {"exit_code": 0, "duration": 0.05}
                ├── grader/                   # only present if a grader ran
                │   ├── stdout                # the grader's metrics JSON
                │   ├── stderr
                │   └── meta.json
                └── report.json               # full serialised run report

The solution key

{solution-key} is <basename>-<hash8>: the solution directory's basename plus a short hash of its full identity — the resolved absolute path for a local solution, or the normalised URL + subdirectory for a git+ one. Aliases of the same solution (./x, x, an absolute path) map to the same key; same-named solutions at different paths get different keys. You never type the key: tp report / tp submit derive it from the same SOLUTION argument you ran with.

latest is derived

The CLI resolves latest at every use by picking the newest timestamp-named run directory that contains a report.json — a crashed, half-written run can never be latest. Nothing on disk records which run is latest, so there is no pointer to go stale.

Key files

{case_id}/solution/meta.json — written by the runner after each case:

{"exit_code": 0, "duration": 0.12}

{case_id}/solution/outputs/ — holds only files the solution wrote; trap never writes here, so a judge can list it to see exactly what the solution produced.

{case_id}/judge/stdout and grader/stdout — the JSON each actor emits, stored in the report as the case's metrics and the run's grader_metrics.

report.json — the full run report in JSON format. Use tp report --output json to print it to stdout instead of reading the file directly.

Re-displaying a run

Use tp report to re-render any stored run without re-executing the solution:

tp report                                       # latest run
tp report --task test --run 2026-05-09T14:30:00 # specific run by timestamp

tp run prints the run id and report path when it finishes; the id doubles as the --run argument for tp report and tp submit.

Ignoring the workspace

Add .trap/ to your .gitignore:

.trap/

docs for trap v0.0.14 · source: trapstreet/trap