Inspecting Runs from the Registry

Query, view, export, and diff stored configs and josh sources

Introduction

Every run registered in a joshpy registry stores its rendered config (.jshc) and josh source (.josh) content. The joshpy.inspect module lets you view, export, and diff these stored files – even after the original files have been modified or deleted.

This is useful for:

  • Discovering what’s in a registry: List labels, sessions, and run details from the terminal
  • Comparing runs: See exactly what changed between two parameter configurations
  • Debugging failures: Inspect the config and model source for a failed run
  • Auditing: Verify what was actually run, not what’s currently on disk

Setup

from joshpy.registry import RunRegistry
from joshpy.jobs import JobConfig

registry = RunRegistry(":memory:")
session_id = registry.create_session(
    config=JobConfig(simulation="Main"),
    experiment_name="inspect_demo",
)

# Register two runs with different configs and the same josh source
josh_source = """\
start simulation Main
  grid.size = 30 m
  grid.low = 33.9 degrees latitude, -116.05 degrees longitude
  grid.high = 33.91 degrees latitude, -116.04 degrees longitude
  steps.low = 0 count
  steps.high = 86 count
end simulation

start patch Default
  Tree.init = create 10 count of Tree
  export.treeCount.step = count(Tree)
  export.averageHeight.step = mean(Tree.height)
end patch

start organism Tree
  age.init = 0 years
  age.step = prior.age + 1 year
  height.init = 0 meters
  height.step = prior.height + sample uniform from 0 meters to config sweep_config.maxGrowth
end organism
"""

registry.register_run(
    session_id=session_id,
    run_hash="abc123def456",
    josh_path="model.josh",
    josh_content=josh_source,
    config_content="maxGrowth = 5 meters\nfireYear = 75 count",
    file_mappings={
        "soil_quality": {"path": "data/soil_quality.jshd", "hash": "a1b2c3"},
    },
    parameters={"maxGrowth": 5, "fireYear": 75},
)
registry.label_run("abc123def456", "low_growth")

registry.register_run(
    session_id=session_id,
    run_hash="fed987cba654",
    josh_path="model.josh",
    josh_content=josh_source,
    config_content="maxGrowth = 15 meters\nfireYear = 75 count",
    file_mappings={
        "soil_quality": {"path": "data/soil_quality.jshd", "hash": "a1b2c3"},
    },
    parameters={"maxGrowth": 15, "fireYear": 75},
)
registry.label_run("fed987cba654", "high_growth")

# Record completed runs (3 replicates each)
for run_hash in ["abc123def456", "fed987cba654"]:
    for rep in range(3):
        run_id = registry.start_run(
            run_hash=run_hash,
            session_id=session_id,
            replicate=rep,
            output_path=f"output/{run_hash}/rep{rep}.csv",
        )
        registry.complete_run(run_id, exit_code=0)
registry.update_session_status(session_id, "completed")

print(f"Registered 2 runs: low_growth, high_growth")
Registered 2 runs: low_growth, high_growth

Querying the Registry

Before viewing or diffing runs, you often need to know what’s in the registry. The joshpy.inspect module provides four query utilities that work from both Python and the command line – no interactive session required.

Listing Labels

Labels are the human-readable names assigned to run configurations. Use format_labels() to see what’s available:

from joshpy.inspect import format_labels

print(format_labels(registry))
LABEL        RUN_HASH      REPS  CREATED            
high_growth  fed987cba654  3     2026-04-06 17:21:45
low_growth   abc123def456  3     2026-04-06 17:21:45

Listing Sessions

A session groups the configs from a single sweep. Use format_sessions() to see all sessions with their experiment name, status, and run counts:

from joshpy.inspect import format_sessions

print(format_sessions(registry))
SESSION      EXPERIMENT    STATUS     JOBS  RUNS (ok/fail/pend)  CREATED            
140a96dd...  inspect_demo  completed  2     6/0/0                2026-04-06 17:21:44

Run Details

Use format_run_info() to see full details for a specific run – parameters, data files, and replicate results:

from joshpy.inspect import format_run_info

print(format_run_info(registry, "low_growth"))
Run: low_growth (abc123def456)
==============================
Session:  140a96dd-8995-4fb4-b0fe-b95732573163
Josh:     model.josh
Created:  2026-04-06 17:21:45

Parameters:
  fireYear = 75.0
  maxGrowth = 5.0

Data files:
  soil_quality: data/soil_quality.jshd

Runs: 3 succeeded, 0 failed, 0 pending
REP  EXIT  STARTED              OUTPUT                      
0    0     2026-04-06 17:21:45  output/abc123def456/rep0.csv
1    0     2026-04-06 17:21:45  output/abc123def456/rep1.csv
2    0     2026-04-06 17:21:45  output/abc123def456/rep2.csv

Registry Summary

Use format_summary() for a high-level overview of everything in the registry:

from joshpy.inspect import format_summary

print(format_summary(registry))
Registry Data Summary
========================================
Sessions: 1
Configs:  2
Runs:     6
Rows:     0

Variables: (none)
Entity types: (none)
Parameters: fireYear, maxGrowth

From the Command Line

All four queries are available as CLI flags, so you can inspect a registry without writing any Python:

# List all labeled runs
python -m joshpy.inspect experiment.duckdb --labels

# List sessions with status and run counts
python -m joshpy.inspect experiment.duckdb --sessions

# Show full details for a run (by label or hash)
python -m joshpy.inspect experiment.duckdb --info low_growth

# Print a data summary for the whole registry
python -m joshpy.inspect experiment.duckdb --summary
Tip

Use --labels first to discover what labels are available, then --info or --diff to dig deeper.

Note

For debug logs tied to a registered run, use the debug CLI in registry mode:

python -m joshpy.debug --registry experiment.duckdb --label low_growth --summary

This avoids manual debug file path lookup.

Viewing Configs

Python API

Use registry.export_config() to export a run’s stored config to a file:

import tempfile

tmpdir = tempfile.mkdtemp()
path = registry.export_config("low_growth", tmpdir)
print(path.read_text())
# READ-ONLY snapshot exported from registry
# Run: abc123def456
# Editing this file has no effect. To change parameters,
# edit your source .jshc file and re-run.

maxGrowth = 5 meters
fireYear = 75 count

Or use view_config() to get the content as a string without writing a file:

from joshpy.inspect import view_config

content = view_config(registry, "high_growth")
print(content)
maxGrowth = 15 meters
fireYear = 75 count

Diffing Two Configs

Export both configs and compare them side by side:

from joshpy.inspect import export_pair

path1, path2 = export_pair(registry, "low_growth", "high_growth", tmpdir)
print(f"--- {path1.name} ---")
--- low_growth.jshc ---
print(path1.read_text())
# READ-ONLY snapshot exported from registry
# Run: abc123def456
# Editing this file has no effect. To change parameters,
# edit your source .jshc file and re-run.

maxGrowth = 5 meters
fireYear = 75 count
print(f"--- {path2.name} ---")
--- high_growth.jshc ---
print(path2.read_text())
# READ-ONLY snapshot exported from registry
# Run: fed987cba654
# Editing this file has no effect. To change parameters,
# edit your source .jshc file and re-run.

maxGrowth = 15 meters
fireYear = 75 count

To open an interactive diff in VS Code or Cursor:

# Opens side-by-side diff in VS Code
registry.compare_configs("low_growth", "high_growth")

# Or with Cursor
registry.compare_configs("low_growth", "high_growth", ide="cursor")

Viewing Josh Sources

The same workflow applies to the stored josh simulation source. This is especially useful when the original .josh file was rendered from a .josh.j2 template – the temp file is deleted after the sweep, but the content lives on in the registry.

from joshpy.inspect import view_josh

source = view_josh(registry, "low_growth")
print(source[:200], "...")
start simulation Main
  grid.size = 30 m
  grid.low = 33.9 degrees latitude, -116.05 degrees longitude
  grid.high = 33.91 degrees latitude, -116.04 degrees longitude
  steps.low = 0 count
  steps.hig ...

Diffing Josh Sources

# Opens side-by-side diff of josh sources in VS Code
registry.compare_josh("low_growth", "high_growth")
Note

If both runs used the same josh source (common when only config parameters vary), the diff will show identical files. This confirms that only the config changed between runs.

Command-Line Interface

The python -m joshpy.inspect CLI provides the same functionality from the terminal, without writing Python.

View

# Print stored config to stdout
python -m joshpy.inspect experiment.duckdb --view low_growth

# Print stored josh source
python -m joshpy.inspect experiment.duckdb --view low_growth --type josh

Diff

# Open config diff in VS Code
python -m joshpy.inspect experiment.duckdb --diff low_growth high_growth

# Open josh source diff in Cursor
python -m joshpy.inspect experiment.duckdb --diff low_growth high_growth --type josh --ide cursor

Export Only

Use --export-only to write files without opening an IDE:

# Export configs to a directory
python -m joshpy.inspect experiment.duckdb --diff low_growth high_growth --export-only --output-dir ./exports

# Export josh sources
python -m joshpy.inspect experiment.duckdb --diff low_growth high_growth --type josh --export-only

CLI Reference

Flag Description
--labels List all labeled runs with hash and creation time
--sessions List sessions with experiment name, status, and run counts
--info RUN Show detailed info for a run (parameters, data files, results)
--summary Print a data summary for the entire registry
--view RUN View a single run’s stored content (label or hash)
--diff RUN1 RUN2 Diff two runs
--type {config,josh} What to view/diff (default: config)
--ide {vscode,cursor} IDE for viewing/diffing (default: vscode)
--export-only Write files without opening IDE
--output-dir DIR Directory for exported files (default: system temp)

Smart File Resolution

When you open a view or diff, the inspect module checks whether the original file still exists on disk:

Situation Behavior
Original file exists, content matches Opens the real file (editable)
Original file exists, content has drifted Opens a read-only snapshot with a note
Original file is gone Opens a read-only snapshot from registry

This means open_view() and compare_configs() do the right thing automatically – you get the real file when possible, a faithful snapshot when not.

File Mappings

The registry also stores which .jshd data files were used for each run. Use get_file_mappings() to recall them:

# Register a run with file mappings
from pathlib import Path

registry.register_run(
    session_id=session_id,
    run_hash="with_data_hash",
    josh_path="model.josh",
    josh_content=josh_source,
    config_content="maxGrowth = 10 meters",
    file_mappings={
        "soil_quality": {"path": "/data/soil_quality_gradient.jshd", "hash": "abc123"},
        "climate": {"path": "/data/climate_ssp245.jshd", "hash": "def456"},
    },
    parameters={"maxGrowth": 10},
)
registry.label_run("with_data_hash", "with_data")

mappings = registry.get_file_mappings("with_data")
for name, path in mappings.items():
    print(f"  {name}: {path}")
  soil_quality: /data/soil_quality_gradient.jshd
  climate: /data/climate_ssp245.jshd

Cleanup

registry.close()

Learn More