Skip to content

feat(sandbox): add geospatial Python packages - #33

Open
danny-avila wants to merge 3 commits into
mainfrom
feat/geospatial-python-packages
Open

feat(sandbox): add geospatial Python packages#33
danny-avila wants to merge 3 commits into
mainfrom
feat/geospatial-python-packages

Conversation

@danny-avila

Copy link
Copy Markdown
Collaborator

Adds a geospatial stack to the sandbox Python image so agents can do DEM/terrain and routing work — read raster tiles, reproject, run least-cost paths, and export results.

Package Role
rasterio windowed DEM tile reads
rioxarray CRS-aware arrays, clip, reproject
geopandas boundaries, land status, vector attributes
pyogrio fast vector IO (hard dependency of geopandas 1.x)
pyproj reprojection
osmnx OSM trail/road extracts
folium map output for inspecting results
gpxpy GPX export

numpy, scipy and scikit-image are already installed — scikit-image supplies MCP_Geometric for least-cost paths, so no addition was needed there.

Why bake them in

The sandbox runs under clone_newnet: true (api/config/sandbox.cfg:25), so there is no network at execution time and nothing is pip install-able by the user. A package is either in the image or unavailable.

Wheel availability

Every package resolves to a cp314 manylinux wheel on both x86_64 and aarch64, or is pure Python — no source builds are introduced, so the pip install in install_python_packages cannot start compiling GDAL and fail the image build.

richdem was considered and excluded: it publishes an sdist only, with no wheels for any Python version. It would compile from source against GDAL headers and set INSTALL_FAILED=true.

Size

Marginal installed cost is ~263 MB, concentrated in three packages that each vendor their own native stack:

Package Installed Notes
rasterio 110 MB bundles libgdal 27.5 MB + sqlite + crypto
pyogrio 99 MB bundles a second, separate libgdal 76.8 MB
pyproj 32 MB bundles libproj + libcurl + datum grids
all others ~22 MB geopandas, osmnx, folium, rioxarray, gpxpy, shapely, xarray, branca, affine, cligj

The two independent GDAL copies are unavoidable with manylinux wheels, which cannot share system libraries. numpy, pandas, networkx, requests and jinja2 are already in the image and add nothing.

Changes

  • build-packages.sh and docker/package-init.sh — the two copies of the package list, kept in sync
  • test-sandbox.sh — adds test_geospatial, which reprojects a point to EPSG:26918. This deliberately exercises pyproj's bundled PROJ data and GDAL loading under NsJail, which are the parts most likely to break in the sandbox rather than on a build host
  • api/README.md — mentions the geospatial packages in the runtime list

Testing

  • bash -n clean on all three shell scripts
  • The new test's JSON payload parses and its embedded Python compiles
  • Wheel availability verified against the PyPI API for cp314 on x86_64 and aarch64; sizes measured by unpacking the resolved wheels
  • The image build itself has not been run here — no Docker in this environment — so test_geospatial has not executed against a live sandbox. That is the one thing worth confirming in CI.

Adds a geospatial stack to the sandbox Python image: rasterio and
rioxarray for raster IO, geopandas/pyogrio/pyproj/shapely for vector
data and reprojection, osmnx for OSM extracts, folium for map output,
and gpxpy for GPX export.

The sandbox runs with clone_newnet, so nothing is installable at
runtime -- these have to be baked into the image to be usable at all.

All packages resolve to cp314 manylinux wheels on x86_64 and aarch64,
so no source builds are introduced. Marginal installed size is ~263 MB,
of which rasterio (110 MB), pyogrio (99 MB) and pyproj (32 MB) are
vendored GDAL/PROJ copies.

Adds test_geospatial to test-sandbox.sh, which reprojects a point to
EPSG:26918 -- this exercises pyproj's bundled PROJ data and GDAL
loading under NsJail, the parts most likely to break in the sandbox.
Comment thread docker/package-init.sh
@danny-avila

Copy link
Copy Markdown
Collaborator Author

Validation summary

Recording the review rounds and the out-of-band testing, since neither is visible from the diff.

Review rounds

round tool finding resolution
1 Codex (local) test regex ^[0-9.-]+ was too loose — -74.006 matches it, so a to_crs that silently returned unprojected lon/lat would have passed 901b629 — asserts exit 0, unsigned numeric, plausible UTM range
2 Macroscope packages_ready() had no geospatial sentinel; stale /pkgs volumes would skip the install entirely 137e29drasterio added as sentinel
3 Codex (local) no new defects

Codex rounds were codex exec runs against the branch diff, not PR reviews — no Codex GitHub app on this repo.

Testing performed

The image build was not run (no Docker in the authoring environment initially), so the packages were validated directly in python:3.14-bookworm containers.

cp314 wheels — all 8 install as genuine cp314 binaries, no source builds:
rasterio/_io.cpython-314-x86_64-linux-gnu.so, GDAL 3.12.1, PROJ resolved from the wheel-bundled pyproj/proj_dir/share/proj.

Dependency resolution — no conflict with the existing 46 packages, on both installer paths. Note package-init.sh:21 defaults to uv (pinned 0.11.26), which is the path that matters:

installer baseline (46) with PR (54)
uv 0.11.26 (default) OK — 123 pkgs OK — 139 pkgs
pip (fallback) OK — 125 pkgs OK — 141 pkgs

Seccomp — a representative geospatial workload (rasterio + pyproj + geopandas/pyogrio + shapely + skimage.graph.MCP_Geometric + matplotlib PNG + gpxpy) ran clean under seccomp/nsjail.json applied via --security-opt, plus --network none, a 512 MiB cgroup, read-only rootfs, no-new-privileges and --cap-drop ALL.

Filter enforcement was verified rather than assumed — against seccomp=unconfined, nfsservctl / perf_event_open / kcmp return ENOSYS 38 / EFAULT 14 / ESRCH 3, and all three collapse to EPERM 1 under the profile, matching defaultErrnoRet. (An earlier control using sched_setaffinity was invalid — it is in the allowlist, so it discriminated nothing.)

This is the onnxruntime concern noted at build-packages.sh:184. GDAL loads and runs under the filter.

Resource envelope — 2.46 s against the 30 s run_timeout; needs ~320 MiB against cgroup_mem_max of 512 MiB (passes at 320, fails at 256). Roughly 1.6x headroom, so large rasters will OOM before they time out. All geospatial imports together are 0.31 s.

test_geospatial prints 583959.37 — inside the asserted band, and confirming pyproj's bundled datum grids resolve with no network.

Not covered

  • NsJail itself — seccomp is proven, but namespace layering is only approximated by Docker
  • aarch64 — cp314 wheels confirmed to exist for every package via the PyPI API, but not executed
  • A real /pkgs upgrade over a populated volume — verified by simulation only
  • test-sandbox.sh end-to-end against a live endpoint

Excluded from this PR

richdem was considered and dropped: sdist only, no wheels for any Python version. It would compile against GDAL headers and set INSTALL_FAILED=true. pystac-client, dask[array] and duckdb were deferred — and pystac-client in particular would be inert given the sandbox has no network.

@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

The first version guarded on ^[0-9.-]+, copied from test_statsmodels.
That regex matches -74.006, so a to_crs that silently returned
unprojected lon/lat would have passed the test.

Assert exit code 0, a strictly numeric unsigned easting, and a
plausible UTM range instead.
packages_ready() gates package-init.sh on sentinel site-packages
directories. On an upgrade over an existing /pkgs volume, PIL,
markitdown, chdb and statsmodels are all already present, so the
check passed and the script exited before reaching the pip install.
The geospatial packages would never have landed on existing
deployments without a manual FORCE_REBUILD=true.

Add rasterio as a sentinel so stale volumes rebuild.

build-packages.sh needs no equivalent change: it clears
.package-installed and reinstalls unconditionally.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 137e29de52

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .omx/logs/turns-2026-07-26.jsonl Outdated
@@ -0,0 +1 @@
{"timestamp":"2026-07-26T15:20:44.917Z","type":"agent-turn-complete","thread_id":"019f9f01-a0f6-7cf1-b1d0-10892fb4290f","turn_id":"019f9f01-a155-7fe3-8a91-7b2462764325","input_preview":"Review the single commit on this branch (git show HEAD, or diff against origin/main). It adds geospa","output_preview":"**Findings**\n\n1. Medium: [test-sandbox.sh](/home/danny/ch-code-interpreter-worktrees/geospatial-packages/test-sandbox.sh:182) does not actually validate “numeric UTM 18N easting” the way the message c"}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove committed OMX session artifacts

Remove the entire .omx runtime-state directory from this commit and ignore it: these files contain session-specific thread/turn IDs, timestamps, prior prompt/output previews, and a developer workstation path unrelated to the geospatial feature. On subsequent OMX-assisted work, the metrics, logs, and state files will be rewritten and leave contributors with a dirty worktree while continuing to expose local session data in repository history.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, and fixed — thank you, this was a genuine mistake on my part (a git add -A swept the directory into the second commit).

Because the files were already in published history, deleting them at the tip would not have removed them. The branch has been rewritten instead and force-pushed: .omx/ is gone from all three commits, not just the tip.

Verification:

  • git ls-tree -r on the branch head: 0 .omx paths
  • across every commit in origin/main..HEAD: 0 .omx paths
  • PR file list is now exactly the 4 intended files (api/README.md, build-packages.sh, docker/package-init.sh, test-sandbox.sh)
  • the rewritten tree is byte-identical to the reviewed version outside .omx, so no review feedback was lost in the rewrite
  • all three checks green on the new head c626725

On the dirty-worktree point: .omx/ has been added to the repository's local info/exclude rather than .gitignore, since it is contributor-side tooling and not something this repo should carry an ignore rule for. Working tree is clean.

@danny-avila
danny-avila force-pushed the feat/geospatial-python-packages branch from 137e29d to c626725 Compare July 27, 2026 03:21
@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. What shall we delve into next?

Reviewed commit: c626725130

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant