Entity-centric observability client for scientific instrument pipelines
Project description
helixobs — Python client
Entity-centric observability for scientific instrument pipelines.
Track domain entities (data blocks, beam candidates, astrophysical events) across disjoint asynchronous pipeline stages. The library attaches provenance links between entities, so you can trace any scientific result all the way back to the raw sensor data that produced it.
Built on OpenTelemetry. Signals are standard OTLP — the HelixObs gateway adds entity intelligence on top.
Installation
pip install helixobs
Quick start
from helixobs import Instrument
from helixobs.logging import configure_logging
# Once at startup
configure_logging()
tel = Instrument(
service_name="chime.frb-classifier",
instrument_id="CHIME",
endpoint="interceptor.local:4317",
)
# Track a pipeline stage
with tel.stage("frb_classifier", id=cand_id, parents=[block_id]) as span:
result = classify(candidate)
if result.confidence > 0.95:
span.add_event("helix.event.candidate_promoted", {
"snr": result.snr,
"dm": result.dm,
})
# span ends here — exported asynchronously, pipeline never blocked
Three integration layers
All three layers emit identical OTLP signals. Choose the one that fits your code structure.
Layer 0 — Primitive
For long-running daemons, event loops, or any structure where a context manager is awkward.
token = tel.track("x-engine", id="block-1234", parents=[])
result = process_data_block(block)
tel.complete(token)
# On failure:
tel.error(token, metadata={"type": "BUFFER_OVERFLOW", "rack": "gpu-rack-3"})
Layer 1 — Context manager (recommended)
complete() is called automatically on normal exit. error() is called automatically on any unhandled exception.
with tel.stage("frb_classifier", id=cand_id, parents=[block_id]) as span:
result = classify(candidate)
# Attach scientifically notable events to the span
if result.rfi_probability > 0.9:
span.add_event("helix.event.rfi_flagged", {
"fraction_flagged": result.rfi_probability,
"algorithm": "spectral_kurtosis",
})
Layer 2 — Decorator
For functions that map cleanly to a single pipeline stage. Use callables for id and parents when they depend on the function's arguments.
@tel.stage(
"frb_classifier",
id=lambda cand: cand.id,
parents=lambda cand: [cand.block_id],
)
def classify_candidate(cand):
return run_classifier(cand)
Internal sub-steps with child_span()
Use child_span() for work that belongs inside one entity's trace — RFI mitigation, dedispersion, per-beam clustering — without registering a new entity. Sub-steps appear in Tempo as nested spans.
token = tel.track("l1-search", id=beam_id, parents=[block_id])
with tel.child_span("rfi-mitigation") as span:
n = excise_rfi(data)
span.set_attribute("helix.chime.rfi_flagged_channels", n)
log.info(f"RFI mitigation: {n} channels excised")
with tel.child_span("dedispersion") as span:
dm_trials = run_dedispersion(data)
span.set_attribute("helix.chime.dm_trials", dm_trials)
tel.complete(token)
Post-creation operations with operate()
Some work happens on an entity after its creation trace has ended — often asynchronously in separate processes. These are not new entities; they are operations on an existing entity, each with their own independent OTel trace. The Entity Inspector shows all traces for an entity across its lifetime.
# After the FRB event entity is created and its detection trace is closed:
with tel.operate("hdf5-conversion", entity_id=event_id) as op:
size_mb = write_hdf5(event_id)
op.set_attribute("helix.chime.hdf5_size_mb", size_mb)
if failed:
log.error("HDF5 write failed: disk full")
op.fail("hdf5_write_error") # records helix.error event + sets span ERROR
return
with tel.operate("registration", entity_id=event_id) as op:
register_in_catalog(event_id)
log.info("event registered")
for dest in ["cedar", "niagara"]:
with tel.operate("replication", entity_id=event_id) as op:
op.set_attribute("helix.chime.replication_dest", dest)
replicate_to(dest)
log.info(f"replicated to {dest}")
If the entity doesn't exist when an operation arrives (race condition or late arrival), the gateway creates a minimal placeholder automatically.
N-to-1 provenance
The defining feature of HelixObs: a single entity can have multiple parents from independent processing chains. Standard OTel tracing can't model this.
# Three beam candidates, processed independently
for beam_id in [41, 42, 43]:
with tel.stage("beam-processor", id=f"cand-beam{beam_id}"):
process_beam(beam_id)
# One astrophysical event formed from all three
with tel.stage("clustering", id="frb-20260415-042",
parents=["cand-beam41", "cand-beam42", "cand-beam43"]) as span:
event = cluster_candidates(candidates)
The gateway resolves all three parent IDs into OTel span links, so Tempo shows the full DAG.
CHIME
Pre-configured instrument with semantic convention helpers:
from helixobs.chime import CHIMEInstrument
tel = CHIMEInstrument(
service_name="chime.x-engine",
endpoint="interceptor.local:4317",
)
# data block
token = tel.track("x-engine", id="block-400mhz-001")
tel.data_block_metadata(token, fpga_rack="gpu-rack-3", duration_s=8.0)
tel.complete(token)
# beam candidate
token = tel.track("frb-classifier", id="cand-beam42-001", parents=["block-400mhz-001"])
tel.candidate_metadata(token, beam_id=42, dm=341.2, snr=18.3)
tel.complete(token)
# astrophysical event (N-to-1)
token = tel.track("clustering", id="frb-20260415-042",
parents=["cand-beam41-001", "cand-beam42-001", "cand-beam43-001"])
tel.event_metadata(token, ra=12.4, dec=33.2, classification="FRB")
tel.complete(token)
Log context injection
One call at startup injects trace_id, span_id, entity_id, and instrument_id into every log record emitted anywhere in the process — including from third-party libraries — while a span is active.
from helixobs.logging import configure_logging
configure_logging()
import logging
log = logging.getLogger("chime.pipeline")
with tel.stage("frb_classifier", id=cand_id, parents=[block_id]):
log.info("classifying candidate")
# Record now carries: otelTraceID, otelSpanID, helixEntityID, helixInstrumentID
These fields are indexed by Loki and enable the Grafana cross-signal navigation path: metric anomaly → Loki log line → Tempo trace → parent entity trace.
Span events
Use token.add_event() or span.add_event() (the context manager yields a Token) to attach named events to an entity's span:
with tel.stage("classifier", id=cand_id) as span:
span.add_event("helix.event.rfi_flagged", {"fraction_flagged": 0.92})
span.add_event("helix.event.candidate_promoted", {"snr": 23.4, "confidence": 0.97})
span.add_event("helix.event.voevent_issued", {"ivorn": "ivo://chime/frb/2026a"})
span.add_event("helix.event.archived", {"path": "/data/chime/2026/frb042"})
The gateway extracts every helix.* event and writes it to TimescaleDB using the event's own timestamp, producing a complete queryable timeline for each entity.
Non-blocking guarantee
track(), complete(), error(), and the context manager enter/exit never perform network I/O on the calling thread. All OTLP exports happen in the OTel SDK's BatchSpanProcessor background thread. If the gateway is unreachable, spans queue locally and are retried silently. The instrument pipeline is never aware of any observability infrastructure failure.
Development
pip install -e ".[dev]"
pytest
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file helixobs-0.1.1.tar.gz.
File metadata
- Download URL: helixobs-0.1.1.tar.gz
- Upload date:
- Size: 30.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
410b748d7e5fc71a62005b29aa0be9074ee0e247bd2687faf3bb16387bdc6f8d
|
|
| MD5 |
8a12bc5e12ddb314d71ba756e47d0618
|
|
| BLAKE2b-256 |
7280cdad6dd4be7753662d8b56f4b144b48f71f7f6540879873f99478b637146
|
Provenance
The following attestation bundles were made for helixobs-0.1.1.tar.gz:
Publisher:
publish.yml on HelixObs/client-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
helixobs-0.1.1.tar.gz -
Subject digest:
410b748d7e5fc71a62005b29aa0be9074ee0e247bd2687faf3bb16387bdc6f8d - Sigstore transparency entry: 1344047942
- Sigstore integration time:
-
Permalink:
HelixObs/client-python@2c736ccac9bc48324b025548c2657067b99336c0 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/HelixObs
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@2c736ccac9bc48324b025548c2657067b99336c0 -
Trigger Event:
release
-
Statement type:
File details
Details for the file helixobs-0.1.1-py3-none-any.whl.
File metadata
- Download URL: helixobs-0.1.1-py3-none-any.whl
- Upload date:
- Size: 24.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9f304ba40f699bad8bb1b2c9d1f3541474e72940d3e99c15369d62af7a6eff0d
|
|
| MD5 |
94381275c6b9cc4172f999f2d4d5eb6b
|
|
| BLAKE2b-256 |
0878b7bb946cffc0c9f121e77e84a954b02a4334c428b6217d21a3a13b3a05fc
|
Provenance
The following attestation bundles were made for helixobs-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on HelixObs/client-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
helixobs-0.1.1-py3-none-any.whl -
Subject digest:
9f304ba40f699bad8bb1b2c9d1f3541474e72940d3e99c15369d62af7a6eff0d - Sigstore transparency entry: 1344047989
- Sigstore integration time:
-
Permalink:
HelixObs/client-python@2c736ccac9bc48324b025548c2657067b99336c0 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/HelixObs
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@2c736ccac9bc48324b025548c2657067b99336c0 -
Trigger Event:
release
-
Statement type: