A Claude-powered triage pipeline for time-domain astronomy. AstroAgent fetches transient alerts from ZTF brokers (Fink, Lasair, ANTARES), enriches them with catalog crossmatches (SIMBAD, NED, TNS), applies physics-motivated filters, and stores candidates in a local SQLite database. Claude Code drives the pipeline end to end — you describe what you're looking for in plain language, and Claude decides what to query, how to filter, and how to interpret the results.
A web dashboard is included for browsing candidates, viewing light curves, and inspecting filters.
- CLI (
astroagent) — query brokers, enrich, filter, list candidates - Web dashboard (
astroagent-web) — Dash-based UI with filter cards, sky maps, magnitude histograms, light curve viewer, and per-filter source code inspection - Plotting (
astroagent-plot) — static matplotlib dashboards - Filter system — physics-motivated Python filters that can be hand-written or
Claude-generated from natural language; auto-loaded from
astroagent/filters/generated/ - CLAUDE.md — the agent's operating manual; loaded automatically when you run Claude Code in this directory
- Python ≥ 3.10
- A working
gitandpip - (Recommended) Claude Code installed and authenticated
git clone https://github.com/<your-org>/astroagent.git
cd astroagent
# Create a virtualenv (recommended)
python3 -m venv .venv
source .venv/bin/activate # bash/zsh
# source .venv/bin/activate.fish # fish
pip install -e .This installs three command-line entry points:
astroagent— main CLIastroagent-web— Dash web dashboardastroagent-plot— static matplotlib plots
cp .env.example .envThen edit .env and fill in:
| Variable | Required for | Where to get it |
|---|---|---|
LASAIR_TOKEN |
Lasair broker | https://lasair-ztf.lsst.ac.uk → register, then profile |
ANTARES_API_KEY |
ANTARES broker | https://antares.noirlab.edu → register |
TNS_API_KEY, TNS_BOT_ID, TNS_BOT_NAME |
TNS crossmatch (optional) | https://www.wis-tns.org/bots |
Fink requires no authentication. SIMBAD and NED also require no keys.
astroagent healthYou should see something like:
✓ fink Reachable. Got 0 test result(s).
✓ lasair Reachable. Got 1 test result(s).
✗ antares No ANTARES_API_KEY set — skipping.
ANTARES will be skipped if you didn't set its key — that's fine, the other two brokers provide enough coverage for most workflows.
The intended workflow is to launch Claude Code inside this repository and ask it questions in natural language:
cd astroagent
claudeThen prompt it like:
> Find tidal disruption event candidates from the last 90 days, apply all filters,
and tell me which ones survived.
> My coworker is interested in extragalactic transients in galaxies — anything still
active in the last 3 months. Can you build a filter for this?
> Open the web dashboard so I can browse what we found.
Claude reads CLAUDE.md automatically and understands the full CLI surface and the
filter system. It will check the API budget first, query the brokers, run the filters,
and report back.
# Check API budget — always run this first
astroagent usage --json
# Query a broker by class (auto-saved to the store)
astroagent query fink class --class-name TDE --days-back 90 --max-results 30 --json
# Apply all filters to everything in the store
astroagent filter --all-stored --json
# List candidates that passed a filter
astroagent candidates list --filter-passed tde --json
# Show full details for one candidate
astroagent candidates show ZTF25acgtfmh --json
# Enrich with catalog crossmatches (SIMBAD, NED, optionally TNS)
astroagent enrich ZTF25acgtfmh --no-tns --json
# Register a custom filter from a .py file
astroagent filters load /path/to/my_filter.pySee CLAUDE.md for the full command reference.
astroagent-web # http://localhost:8050
astroagent-web --port 8080 # custom portThe landing page shows a card per filter with the natural-language description that motivated the filter. Click a card to drill into that filter's candidates: sky map, magnitude histogram, sortable table, and light curves on demand. Each dashboard page also has a "View filter source code" panel and a "↻ Refresh Data" button to re-query brokers and re-apply filters.
If the most recent data is more than a day old, the dashboard auto-refreshes when you click into a filter.
astroagent-plot # all stored candidates
astroagent-plot --filter-passed extragalactic -o out.png # only passed candidatesA filter is a Python class subclassing BaseFilter. Drop a file in
astroagent/filters/generated/ and it gets auto-loaded at startup. Or load it ad hoc
with astroagent filters load <path>.
# astroagent/filters/generated/my_kilonova.py
from astroagent.filters.base import BaseFilter, FilterResult
from astroagent.schema import Alert
class KilonovaFilter(BaseFilter):
name = "kilonova"
description = "Fast-rising, rapidly-reddening transients consistent with kilonovae."
project_text = (
"Find kilonova candidates: fast (< 2 day) rise time, rapidly reddening colour, "
"and faint (peak r > 18). Based on Andreoni+2020 selection criteria."
)
def run(self, alert: Alert) -> FilterResult:
if alert.duration_days is not None and alert.duration_days > 2.0:
return FilterResult(passed=False, reason=f"Too slow: {alert.duration_days:.1f}d")
# ... your selection logic here ...
return FilterResult(passed=True, reason="Consistent with KN timescale")The project_text field is what gets shown on the web dashboard's filter card and the
"science goal" box on the dashboard page. Use it to capture the plain-language reasoning
behind the filter so future-you (or your coworkers) understand what the page is for.
See astroagent/filters/builtin/tde.py for a richer example.
astroagent/
├── astroagent/
│ ├── cli.py # Click CLI entry point
│ ├── webapp.py # Dash web dashboard
│ ├── plotting.py # Static matplotlib plots
│ ├── schema.py # Alert + PhotoPoint dataclasses
│ ├── brokers/ # FinkBroker, LasairBroker, AntaresBroker
│ ├── filters/
│ │ ├── base.py # BaseFilter, FilterRegistry
│ │ ├── builtin/ # quality, galactic_plane, tde
│ │ └── generated/ # auto-loaded user/Claude filters
│ ├── enrichment/ # SIMBAD, NED, TNS crossmatch
│ ├── memory/ # SQLite store + API usage tracker
│ └── config/
├── CLAUDE.md # Agent operating manual (read by Claude Code)
├── pyproject.toml
├── .env.example
└── README.md
The candidate store and API usage history live in ~/.astroagent/candidates.db by
default. Override with ASTROAGENT_DB=/path/to/db in .env.
- TDE filter is conservative: it requires multiple detections and rejects anything
broker-tagged as AGN. Run
astroagent enrichon candidates first to give it crossmatch data — without that, the host-galaxy and TNS checks are skipped. - Webapp loading is slow for large stores (a few seconds per dashboard page) because it currently shells out to the CLI per candidate. A direct-store loader is on the to-do list.
- No automated tests yet — verify changes by running
astroagent healthand spot-checking with the webapp. - Fink/Lasair APIs change without notice. If queries start failing, check
astroagent/brokers/fink.py::FINK_CLASS_MAPandastroagent/brokers/lasair.py::LASAIR_BASEfirst.
This is a research prototype. If you build a useful filter, drop it in
astroagent/filters/generated/ and open a PR — we'll move the good ones to
astroagent/filters/builtin/ over time.