Writing a solution
A solution is any shell-invokable program. trap runs it once per case as a subprocess and captures its stdout/stderr/files — it never has to import trap or know it exists.
Minimal trap.yaml
Place it next to your solution code, then run tp run from that directory:
cmd: uv run python solution.py
tasks:
test:
source: ../task # local path or git+ URL to the task dir
Inputs and outputs
trap injects TRAP_MANIFEST, a JSON string with two absolute directory paths:
import json, os
from pathlib import Path
m = json.loads(os.environ["TRAP_MANIFEST"])
cfg = json.loads((Path(m["inputs_dir"]) / "config.json").read_text()) # read case inputs
(Path(m["outputs_dir"]) / "result.json").write_text("...") # write outputs here
inputs_dir— your case's input files (you know their names).outputs_dir— write here; trap owns nothing in it, so it's exactly what you produced.- stdout/stderr/exit code are captured automatically.
To pipe one input file to stdin, declare it:
cmd: uv run python solution.py
stdin: input.json # inputs_dir/input.json → stdin
tasks:
test: { source: ../task }
Full field list: trap.yaml reference. Exact env schema: IO contract.
Cost tracking
If the solution calls an LLM API, trap measures tokens/spend per case with no code
changes — it auto-activates when a key env var is set (ANTHROPIC_API_KEY,
OPENAI_API_KEY, …) and shows up in the report. Disable with tp run --no-cost.
Details: cost tracking.
docs for trap v0.0.14 · source: trapstreet/trap