STAC Scout
Find the data. Prove it exists. Know what you're getting.
intent → task semantics → catalog federation → live item evidence → asset plan → manifest
A deterministic decision layer for the STAC ecosystem.
STAC is syntax. Dataset choice is semantics.
A model can generate a valid STAC query.
That does not mean it chose the right dataset.
A Collection can advertise the right spatial and temporal extent.
That does not mean an Item actually exists for your AOI and date.
An asset can be called B08.
That does not mean a caller should silently assume what it contains.
STAC Scout lives in that gap.
"find imagery for this task"
│
▼
what does the task need?
│
▼
which datasets fit?
│
▼
do matching items exist?
│
▼
which assets are actually required?
│
▼
how much data will this read?
│
▼
can the decision be replayed?
The operating rule is simple:
Never infer what can be inspected.
What Scout actually does
natural language / structured request
│
▼
IntentDraft
│
task_type + facts
▼
┌───────────────────┐
│ TaskAdvisor │◄──── versioned task rules
└────────┬─────────┘
│
▼
ScoutRequest
│
┌───────┴────────┐
│ │
▼ ▼
ScoutEngine FederatedScout
│ │
│ provider isolation
│ dataset identity
│ duplicate evidence
│ explicit failures
│ │
└───────┬────────┘
▼
live Item probe
│
AOI coverage
│
asset semantics
│
access planning
│
▼
manifest + replay
Scout does not replace pystac-client, odc-stac, or a STAC API.
It decides what should be asked, checks what came back, and records why.
The invariants
These are not style preferences. They are the contract.
- Collection metadata is not availability evidence. Item Search is.
intersectsis not AOI coverage.- Unknown is not false.
- Required and preferred measurements are different things.
- Task rules do not invent dates.
- Explicit user constraints beat defaults.
- Probable duplicates stay visible.
- Provider health is not scientific quality.
- A language model may classify intent; it does not own geospatial truth.
- Every useful decision should be reproducible.
Or shorter:
metadata ≠ evidence
similarity ≠ identity
availability ≠ suitability
confidence ≠ proof
Install
python -m pip install -e ".[dev]"
Python 3.12 or newer is required.
For Planetary Computer access recipes:
python -m pip install -e ".[planetary-computer]"
Geo Task Intelligence
The user often knows the problem, not the bands.
"assess wildfire impact"
↓
optical
↓
NIR + SWIR2 required
↓
surface reflectance preferred
↓
before/after strategy
↓
comparison_windows still required from the user
Scout keeps that knowledge in a versioned, inspectable registry instead of hiding it in prompts.
List the built-in task archetypes:
stac-scout tasks
stac-scout task-profile wildfire_impact
Derive task-aware data requirements:
stac-scout advise request.json --task wildfire_impact
The output includes:
TaskProfile
├── rule_id
├── rationale
├── sources
├── required measurements
├── preferred measurements
├── temporal strategy
├── processing preferences
└── mask preferences
TaskAdvice
├── enriched ScoutRequest
├── derivations[]
│ ├── rule_id
│ ├── field
│ ├── value
│ ├── required | preferred
│ └── rationale
├── follow_up_requirements[]
└── notes[]
Task rules live in:
src/stac_scout/data/tasks.toml
The scientific basis and rule boundaries are documented in docs/TASK_RULES.md.
Rules are allowed to say "I don't know"
For example, a before/after wildfire task does not cause Scout to hallucinate a pre-fire window.
It returns:
{
"follow_up_requirements": ["comparison_windows"]
}
Likewise, if the user explicitly asks for SAR while a task's default modality is optical, Scout preserves SAR and skips incompatible optical band defaults.
No spectral fan fiction.
Intent boundary
STAC Scout deliberately does not ship an LLM SDK in the core package.
Any structured-output model can produce an IntentDraft.
Get the contract:
stac-scout intent-contract
stac-scout schema intent
The model may identify a supported task_type.
It is explicitly told not to invent:
- coordinates;
- dates;
- dataset names;
- measurement names;
- hidden constraints.
Those belong to deterministic code, explicit user input, or live metadata.
Resolve an intent directly:
stac-scout resolve-intent intent.json
Resolve it and apply task intelligence:
stac-scout advise-intent intent.json
If required information is still ambiguous, the draft stays unresolved.
That is a feature.
Providers are adapters, not assumptions
List the built-in provider registry:
stac-scout providers
stac-scout providers --all
Check operational health:
stac-scout health
stac-scout health --provider earth-search
The default enabled providers are:
- Element 84 Earth Search
- Microsoft Planetary Computer
Provider quirks stay behind adapters.
Planetary Computer signing is recorded explicitly and generated recipes use the official planetary_computer.sign_inplace path instead of reimplementing SAS handling.
Provider latency and uptime are reported as operational evidence only.
Network behavior is bounded: adapters use explicit connect/read timeouts, a finite transient-retry budget, capped backoff/Retry-After delays, and typed provider errors. A reachable provider with invalid metadata is reported as degraded rather than falsely labeled unreachable.
A slow endpoint does not make a scientifically suitable dataset worse.
Federation without pretending everything is the same
Search a single provider:
stac-scout discover request.json --provider earth-search
Or a raw STAC endpoint:
stac-scout discover request.json \
--catalog https://earth-search.aws.element84.com/v1
Search across enabled providers:
stac-scout federate request.json
stac-scout federate request.json --max-workers 4 --overall-timeout 30
Federation isolates expected provider failures such as timeouts, rate limits, authentication errors, malformed metadata, and unsupported capabilities. Each failure reports its type, HTTP status when known, and whether retry is reasonable. Unexpected internal exceptions are not converted into provider failures.
Dataset identity is conservative:
sci:doi
→ exact
collection id + platform / constellation / instrument evidence
→ probable
everything else
→ local to that catalog
Only exact groups are considered safe to collapse.
Probable matches stay visible.
Because two catalogs agreeing on a name is not the same thing as two catalogs describing the same scientific product.
The catalog says maybe. Items say yes or no.
Verification is item-level.
stac-scout verify request.json \
--provider earth-search \
--collection sentinel-2-l2a
Scout records:
- matching Item count;
- Item IDs;
- timestamps;
- asset keys;
- cloud metadata when available;
- AOI coverage ratio;
- fraction of each Item intersected by the AOI;
- warnings when geometry or metadata cannot be evaluated.
AOI coverage normalizes antimeridian-crossing geometry and measures overlap in an AOI-centered WGS84 equal-area projection rather than treating raw longitude/latitude as a flat Cartesian plane.
Because this is geospatial software.
Asset planning
A scientifically correct Collection can still produce a bad access plan.
Scout resolves requested measurements against declared asset and band metadata rather than guessing asset names.
stac-scout plan request.json \
--provider earth-search \
--collection sentinel-2-l2a \
--manifest scout.manifest.json \
--recipe load.py
Planning can produce:
measurement → asset + selection evidence
resampling strategy + evidence
windowed-read estimate
explicit target output resolution
provider signing requirements
warnings
odc-stac recipe
max_source_resolution_m answers “is this source data fine enough?” while
target_resolution_m answers “what output grid should I request?”. It requires an explicit
target_crs whose units are meters (or odc-stac's explicit "utm" selector), because odc-stac
interprets resolution in output-CRS units. Scout never reuses the source threshold as an output
resampling instruction. The legacy input name
max_spatial_resolution_m is accepted as a source-resolution alias for compatibility.
If multiple assets remain equally supported after semantic, role, coverage, media-type, and GSD evidence, Scout reports the selection as ambiguous instead of choosing the alphabetically first key. Unknown resampling semantics likewise remain unresolved rather than defaulting to bilinear.
When file:size exists, Scout estimates transfer volume from the AOI/item intersection fraction.
It is an estimate, not a bandwidth prophecy.
Manifests: because catalogs move
A successful query today is not a frozen scientific record.
Scout writes a versioned provenance manifest containing the canonical request/query, provider/catalog identity, search limit and completeness, Collection fingerprint, full access plan, selected-asset metadata, and the observed decision-matching Item set. Ephemeral asset URLs are not copied into the provenance snapshot.
Replay it later:
stac-scout replay scout.manifest.json
By default replay reuses the manifest's recorded max_items. You can override it explicitly,
but Scout records that the limits differ.
Replay reports:
comparison status: complete / partial / inconclusive
retained Item IDs
confirmed missing Item IDs
confirmed new Item IDs
unresolved missing Item IDs
unresolved new Item IDs
Collection metadata changed: true / false / unknown
A search that returns exactly max_items is marked limit_reached, not assumed complete or
definitely truncated. If either historical or current completeness cannot be proven, Scout will
not promote ordering/sample differences into confirmed drift.
The manifest is evidence of the decision.
It is not a copy of the remote data.
CLI map
stac-scout
├── version
├── validate-request
├── schema
│
├── intent-contract
├── resolve-intent
├── advise-intent
│
├── tasks
├── task-profile
├── advise
│
├── providers
├── health
├── inspect-catalog
│
├── discover
├── federate
├── verify
├── plan
└── replay
Repository map
stac-scout/
├── src/stac_scout/
│ ├── catalogs/ # STAC access boundary
│ ├── data/
│ │ ├── providers.toml
│ │ └── tasks.toml
│ ├── discovery/ # candidate retrieval
│ ├── models/ # strict contracts
│ ├── normalize/ # provider metadata → stable models
│ ├── planning/ # assets, raster semantics, volume
│ ├── provenance/ # manifest + replay
│ ├── verify/ # live evidence + dateline-safe coverage
│ ├── federation.py
│ ├── health.py
│ ├── identity.py
│ ├── reasoning.py
│ ├── scout.py
│ ├── tasking.py
│ └── tasks.py
├── evals/
│ ├── cases/
│ ├── federation_cases/
│ ├── task_cases/
│ ├── runner.py
│ └── live.py
├── docs/
│ ├── ARCHITECTURE.md
│ └── TASK_RULES.md
├── skill/
│ └── SKILL.md
└── tests/
Evals over vibes
The deterministic evaluation corpus checks contracts that affect dataset decisions:
python evals/runner.py
It covers:
- request invariants;
- cross-provider identity;
- task-derived measurements;
- task modality;
- temporal strategy;
- follow-up requirements.
Remote providers are intentionally kept out of ordinary CI.
Live checks run separately:
python evals/live.py
That separation is deliberate:
deterministic behavior
→ CI gate
remote catalog state
→ live observation
Development
ruff check .
ruff format --check .
mypy
pytest --cov=stac_scout --cov-report=term-missing
python evals/runner.py
python -m build
CI runs on Python 3.12 and 3.13.
Coverage must stay at or above 90%.
The wheel is also checked to ensure the provider and task registries are actually packaged.
What Scout refuses to fake
"the collection covers 2024, so data must exist"
"cloud_cover=8 means my AOI is clear"
"B08 probably means NIR"
"these two collections have similar names, merge them"
"the provider is fast, therefore the dataset is better"
"wildfire task means I'll invent a pre-fire date"
"the model sounded confident"
Those are shortcuts.
Scout's job is to turn them into inspectable claims.
Philosophy
STAC gives us a language.
Scout adds skepticism.
Or, in GeoGeek form:
The map can look right while the geography is wrong.
The query can run while the dataset choice is wrong.
Check the semantics. Check the evidence.
License
Find it. Verify it. Plan it. Replay it.
catalog metadata ≠ ground truth
Release files for stac-scout 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| stac_scout-0.5.0.tar.gz | 91.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| stac_scout-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 157.6 kB
Release files / stac_scout-0.5.0.tar.gz
| Download URL | stac_scout-0.5.0.tar.gz |
|---|---|
| Size | 91.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ce547f315f750af509e090da61456a0d01144214fc2ca0ae61392dfd9a654ccc
|
|
BLAKE2b-256 checksum How to use checksums |
586a6b25fd7dad61c9b0ad1bae3156519342f5d44a463332bd8ed72faee64332
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.
Transparency logRelease files / stac_scout-0.5.0-py3-none-any.whl
| Download URL | stac_scout-0.5.0-py3-none-any.whl |
|---|---|
| Size | 66.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f41677311eca87bf27826648944d7ae3d49e5c7e22c8bcc698dd77a5053b1ee1
|
|
BLAKE2b-256 checksum How to use checksums |
e7f0f1a6a5f0d92322345070ba0cff23d66f208682267d750df4b268250783be
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.
Transparency log