Recording VCR Cassettes for Package Verification
Step-by-step guide to recording HTTP cassettes for API-based tool packs. Record once locally, replay forever in the sandbox.
If your tool pack calls an external API, the verification sandbox will block those calls — network access is disabled by default. To prove your tool works anyway, you record the API responses once and the pipeline replays them on every verification run.
This guide walks you through recording a VCR cassette from scratch.
Prerequisites
- Your tool pack uses
httpxorrequestsfor HTTP calls - You have working API credentials locally
- Python 3.10+ with
vcrpyinstalled
pip install vcrpyStep 1: Create the Recording Script
Create a file called record_fixtures.py in your pack root:
"""Record VCR cassettes for verification fixtures."""
import vcr
import os
# Import your tool's run function
from my_tool_pack.tool import run
# Configure VCR
my_vcr = vcr.VCR(
cassette_library_dir="fixtures/cassettes",
record_mode="new_episodes",
match_on=["method", "uri"], # Don't match on body (multipart varies)
)
os.makedirs("fixtures/cassettes", exist_ok=True)
# Record each fixture case
with my_vcr.use_cassette("search_python.yaml"):
result = run(query="Python programming", max_results=3)
print(f"Recorded: {len(result.get('results', []))} results")
print("Done. Cassettes saved to fixtures/cassettes/")Step 2: Run the Recording
cd my-tool-pack
python record_fixtures.pyThis executes your tool with real API credentials and saves the HTTP interaction (request + response) to a YAML file.
Step 3: Inspect the Cassette
Open fixtures/cassettes/search_python.yaml — it looks like this:
interactions:
- request:
body: q=Python+programming
headers:
User-Agent:
- MyTool/1.0
method: POST
uri: https://api.example.com/search
response:
body:
string: '{"results": [{"title": "Python.org", "url": "..."}]}'
headers:
Content-Type:
- application/json
status:
code: 200
message: OK
version: 1Important: Check the cassette for leaked credentials. VCR records headers by default. If your API key was in a header, you'll see it in the YAML. Either:
- Use
filter_headers=['Authorization']in your VCR config, or - Manually redact the cassette after recording
Step 4: Add to Your Manifest
verification:
cases:
- name: "search_python_programming"
tool: "search_web"
input:
query: "Python programming"
max_results: 3
cassette: "fixtures/cassettes/search_python.yaml"
expected:
return_type: "dict"
required_keys: ["results"]The input must match exactly what you passed during recording. The pipeline will call your tool with these arguments while VCR intercepts and replays the saved response.
Step 5: Include Fixtures in Your Artifact
Create a MANIFEST.in in your pack root:
recursive-include fixtures *
include agentnode.yamlThis ensures python -m build --sdist includes your cassettes and manifest in the tarball.
Step 6: Verify Locally (Optional)
You can test that the cassette replays correctly without network access:
"""Test cassette replay."""
import vcr
from my_tool_pack.tool import run
with vcr.VCR(record_mode="none").use_cassette("fixtures/cassettes/search_python.yaml"):
result = run(query="Python programming", max_results=3)
assert "results" in result
print(f"Replay OK: {len(result['results'])} results")record_mode='none' means VCR will raise an error if any unmatched HTTP request is attempted — exactly what the sandbox does.
Step 7: Publish
agentnode publishThe pipeline will:
- Extract your artifact into
/workspace/ - Install your package
- Import your tool
- For each case with a
cassette: activate VCR withrecord_mode='none', call your tool with the declaredinput, validate againstexpected - Run stability checks (3x same input, measure determinism)
VCR Matching Rules
The pipeline uses match_on=['method', 'uri'] by default. This means:
| Matches on | Doesn't match on |
|---|---|
| HTTP method (GET, POST) | Request body |
| Full URI (scheme + host + path + query) | Headers |
This is important for multipart uploads (like file-based APIs) where the body contains boundary strings that change every time. As long as the method and URI match, the cassette replays.
Multiple Cassettes
You can have multiple cases, each with their own cassette:
verification:
cases:
- name: "search_python"
input: {query: "Python"}
cassette: "fixtures/cassettes/search_python.yaml"
- name: "search_rust"
input: {query: "Rust programming"}
cassette: "fixtures/cassettes/search_rust.yaml"Each cassette is independent. Record them separately in your script.
Common Pitfalls
Cassette path format
The validator enforces: fixtures/cassettes/<name>.yaml (or .yml or .json). No subdirectories, no absolute paths, no ...
Input mismatch
If the input in your manifest doesn't match what your tool sends as an HTTP request, VCR won't find a matching cassette entry and the test fails. Make sure the manifest input produces the exact same HTTP call as during recording.
API key routing
If your tool has an api_key parameter that switches between local and API mode, include it in the case input:
input:
audio_path: "/workspace/fixtures/test.wav"
api_key: "sk-fixture-test-key"The key doesn't need to be real — VCR replays the response regardless. It just needs to trigger the API code path in your tool.
Summary
- Install
vcrpy - Write a recording script that calls your tool with real credentials
- Run it once to generate cassette files
- Check for leaked secrets, redact if needed
- Add
verification.caseswithcassettepaths to your manifest - Add
MANIFEST.into include fixtures - Publish — the pipeline replays your cassettes in the sandbox
That's it. One recording session, permanent Gold eligibility.