{"slug": "show-hn-morletdb-is-a-postgres-extension-for-waveforms", "title": "Show HN: MorletDB is a Postgres extension for waveforms", "summary": "MorletDB, a PostgreSQL extension released by Merkhet, stores waveform recordings as intact channels with sample rate, unit and calibration metadata rather than scattering samples across rows, and answers similarity queries over a bounded range of time shifts. The extension ships a Grizzbit codec that codes raw samples bit-exactly and is often several times smaller than general-purpose time-series compression on the same data, though it is weaker on floats without a grid. MorletDB exposes plain SQL functions such as morlet_search_envelope_probes and morlet_read_interval so AI agents can retrieve matching intervals and original samples, and a demo loading four days of public seismic recordings through a read-only MCP server found that four of the five closest matches were other catalogued earthquakes.", "body_md": "*ἁρμονίη ἀφανὴς φανερῆς κρείττων.*\n\n<sub>— Heraclitus, fragment B54</sub>\n\n**MorletDB is a PostgreSQL extension for waveform archives.**\n\nSelect an interval and ask *“Have we seen a signal like this before?”* — every answer is a recording\n\nand a sample interval you can read back, for an engineer or for an AI agent.\n\n[AI agents](#for-ai-agents) · [Install](#install) · [Use](#use) · [Research](#research) · [Explore](#explore) · [Evidence](#what-has-been-measured) · [Website](https://merkhet-io.github.io/morletdb/)\n\n**Recordings, not rows.** A recording is one channel with a sample rate, a unit, a\ncalibration and its acquisition metadata. MorletDB keeps that unit intact inside\nPostgreSQL, instead of scattering samples over rows or vectors.\n\n- **Lossless and compact.** Raw samples are coded bit-exactly, often several times\nsmaller than general-purpose time-series compression on the same data. The codec,[Grizzbit](https://github.com/merkhet-io/morletdb/blob/main/docs/grizzbit.md) , stores each chunk on the value grid it detects, with no\nprecision parameter, and is weaker on floats without a grid.\n- **Search that tolerates time shifts.** Two occurrences of the same pattern rarely\nline up with a window grid. MorletDB scores similarity over a bounded range of\nshifts, exactly or through a standard vector index.\n- **Every hit traces back.** Results are intervals of recordings, with their\nmetadata, that read back the original samples.\n- **Safe to keep ingesting.** Appends, search representations and index rows commit\nin one transaction; retries are verified, gaps and overlaps are rejected.\n- **Plain PostgreSQL.** SQL functions, your roles and row-level security, your\nbackups. pgvector is optional.\n\n**Give your agents signal evidence to reason with.** An investigation agent can search\nyour recording archive before it answers: *have we seen a signal like this before?*\nMorletDB supplies the matching intervals and their original samples; your application\nconnects them to analysis tools, maintenance notes or experiment records.\n\nThe tool is plain SQL, so it runs with the agent's database role, row-level security and limits:\n\n```\n-- a bounded tool an agent can call: similar intervals for a source interval and an asset\nCREATE FUNCTION agent_similar_intervals(recording text, from_sample bigint, to_sample bigint,\n                                        asset text, k integer DEFAULT 5)\nRETURNS TABLE (recording_id text, start_sample bigint, end_sample bigint, distance float8)\nLANGUAGE sql STABLE AS $$\n  SELECT recording_id, start_sample, end_sample, distance\n  FROM morlet_search_envelope_probes('signals', recording, from_sample, to_sample,\n                                     jsonb_build_object('asset', asset), least(k, 20))\n$$;\n-- and to bring evidence into analysis: morlet_read_interval('signals', recording_id, start, end)\n```\n\n| Use case | What the agent does | \n|---|---|\n| **Anomaly investigation** | an external detector flags an interval; the agent checks whether it resembles earlier events and under which operating conditions | \n| **Autonomous factory workflows** | gathers signal evidence across machine runs before proposing maintenance, a quality check or an escalation; your application decides which actions need approval | \n| **Engineering investigation** | retrieves earlier test runs to explain a changed transient, with the experiment metadata | \n| **Research and labelling assistants** | collects similar intervals for human review, annotation or model evaluation | \n\n**Try it with Claude:** the [agent demo](https://github.com/merkhet-io/morletdb/blob/main/examples/agent-demo/README.md) loads four days of public seismic\nrecordings with Docker and lets Claude Code or Claude Desktop investigate an earthquake through the\nread-only [MCP server](https://github.com/merkhet-io/morletdb/blob/main/docs/mcp.md). Four of the five closest matches it finds are other catalogued earthquakes.\nThe [machine demo](https://github.com/merkhet-io/morletdb/blob/main/examples/machine-demo/README.md) does the same for a bearing run to failure (NASA/IMS):\nClaude finds when the failing bearing first changed and whether the others show it.\n\nImportant\n\nThe SQL function above is an integration pattern; the bundled MCP server only reads. Detection models,\nagent orchestration and machine controls come from your application. On seismic data, this search\nreturns catalogued earthquakes about twice as often as zero-lag similarity\n([E5](https://github.com/merkhet-io/morletdb/blob/main/evaluation/task_relevance/RESULTS.md), [E5-H](https://github.com/merkhet-io/morletdb/blob/main/evaluation/task_relevance/RESULTS_HELDOUT.md));\nanomaly workflows have not been evaluated, and similarity alone does not establish normality, a fault\nor its cause.\n\n1. **Append** chunks of a recording at their global sample offset.\n2. **Build** search representations, such as amplitude envelopes of 4096-sample\nwindows, in the same transaction, so new windows are searchable at commit.\n3. **Search** with a query interval: exact scoring of every eligible window, or\napproximate candidates from a vector index followed by exact re-ranking.\n4. **Read back** each match as the original samples, with its recording metadata.\n\n**Grizzbit** is MorletDB's lossless chunk codec: what goes in comes back **bit for bit**. Each chunk of up to\n32,768 float64 samples is stored in the smallest of three exact forms: integer steps on the value grid the\nencoder detects (predicted and Rice-coded as in FLAC, with last-place corrections where needed), the float64 bit\npatterns coded the same way, or plain float64. Every chunk carries a CRC-32C checksum.\n\n- **No precision parameter.** Nothing can be configured to drop detail. Finite values up to ±10⁹ are stored\nexactly; anything else is refused at write time, never altered.\n- **Checked when written.** With`morlet.verify_appends` on (the default), every append decodes each chunk again\nand compares it bit for bit before storing it.\n- **Strong on gridded signals, weaker without a grid.** On ADC counts, fixed decimals and calibrated waveforms it\nis among the smallest codecs measured (B4); on floats without a clean grid it is 1.6–4× larger than the best.\nGrizzbit 2, an attempt to close that gap, failed study B5 and was not shipped (#326).\n- **Float32 codings, on by default** (chunk format version 6): samples that are float32 values, such as PLC and\nCNC controller signals, are coded in float32 terms where that is smaller. It passed study B8: on IU4 force and\ncontroller channels 3.02 and 2.29 bytes per sample instead of 6.60 and 6.52, never a larger chunk\n([results](https://github.com/merkhet-io/morletdb/blob/main/evaluation/storage_b8/RESULTS.md) ;`SET morlet.float32_chunks = off` turns it off).\n- **Grizzbit 2.1, opt-in** (`morlet.chunk_format = grizzbit21` ): a per-chunk linear predictor and corrections per\ngrid index, kept only where smaller. It passed study B6: waveforms 12.6% smaller in total, never larger\n([how to use it](https://github.com/merkhet-io/morletdb/blob/main/docs/grizzbit.md#using-grizzbit-21) ).\n- **Nothing here is novel** , and the docs say so: Grizzbit is a named, specified format built from established\nlossless techniques (FLAC's fixed predictors and Rice coding, SEED and EDF style gain and offset).\n\nMore: [docs/grizzbit.md](https://github.com/merkhet-io/morletdb/blob/main/docs/grizzbit.md) · [codecs at a glance](https://github.com/merkhet-io/morletdb/blob/main/docs/grizzbit.md#codecs-at-a-glance) · [format specification](https://github.com/merkhet-io/morletdb/blob/main/docs/chunk-format.md) · [crates/grizzbit](https://github.com/merkhet-io/morletdb/blob/main/crates/grizzbit)\n\nFull setup and query examples: [installation and query guide](https://github.com/merkhet-io/morletdb/blob/main/docs/installation-and-queries.md).\nSource builds use Rust 1.99.0 and pgrx 0.19.3.\n\n**Run the release image** (prebuilt: PostgreSQL 17 with pg_morlet, Linux amd64 and arm64, with SBOM and\nbuild provenance; no pgvector, so no probe search; choose your own password):\n\n```\ndocker run -d --name morletdb -e POSTGRES_USER=morlet -e POSTGRES_PASSWORD=change-me \\\n  -e POSTGRES_DB=morlet -p 127.0.0.1:54329:5432 -v morletdb-data:/var/lib/postgresql/data \\\n  ghcr.io/merkhet-io/morletdb:0.3.14-alpha.1\ndocker exec morletdb psql -U morlet -d morlet -c \"CREATE EXTENSION pg_morlet\"\n```\n\n**Try the walkthrough** (builds a PostgreSQL 17 preview for your machine, amd64 or arm64; imports\nsynthetic data, finds a known interval and reconstructs its matches):\n\n```\ndocker compose run --rm walkthrough\n```\n\n**Build from source** with Rust and [pgrx](https://github.com/pgcentralfoundation/pgrx)\n(PostgreSQL 17, 18 or 19; on Debian/Ubuntu install `build-essential bison flex libreadline-dev zlib1g-dev libclang-dev clang pkg-config libssl-dev` first):\n\n```\ncargo install cargo-pgrx --version 0.19.3 --locked\ncargo pgrx init --pg17 download          # or --pg18, --pg19 /path/to/pg_config, or --pg17 /path/to/pg_config\ncd extensions/pg_morlet\ncargo pgrx install --release --pg-config \"$(sed -n 's/^pg17 = \"\\(.*\\)\"/\\1/p' ~/.pgrx/config.toml)\"\n```\n\nThen, in your database:\n\n```\nCREATE EXTENSION pg_morlet;\nCREATE EXTENSION vector;   -- optional: only for probe search (pgvector 0.8 or later)\n```\n\n**Use it from Python**: ingest a NumPy waveform, read exact intervals and search similar ones with the\n[Python SDK](https://github.com/merkhet-io/morletdb/blob/main/docs/python-sdk.md) (`sdk/python`, alpha), starting with a [runnable quickstart](https://github.com/merkhet-io/morletdb/blob/main/examples/python/quickstart.py).\n\n**Let an AI agent investigate your recordings** through a read-only MCP server (Claude Code,\nClaude Desktop and other MCP clients): [MorletDB for AI agents](https://github.com/merkhet-io/morletdb/blob/main/docs/mcp.md).\n\n**What MorletDB works with** (PostgreSQL, pgvector, Docker, MCP, Grafana, Python and Jupyter, file formats),\nwith how each is verified: [integrations](https://github.com/merkhet-io/morletdb/blob/main/docs/integrations.md).\n\n**Import your own files** (WAV, EDF, miniSEED, CSV) and get a first search query:\n[from files to a first search](https://github.com/merkhet-io/morletdb/blob/main/docs/import.md).\n\nSupported versions, CI coverage and local test commands:\n[PostgreSQL compatibility](https://github.com/merkhet-io/morletdb/blob/main/docs/compatibility.md). Upgrading an existing\ninstallation: [existing installations](https://github.com/merkhet-io/morletdb/blob/main/extensions/pg_morlet/README.md#existing-installations).\n\n```\n-- a collection holds recordings, raw chunks and search representations\nSELECT morlet_create_stream_collection('public', 'signals');\nSELECT morlet_enable_envelopes('signals');\n\n-- one recording = one channel: id, sample rate, channel, unit, metadata\nSELECT morlet_start_recording('signals', 'pump-7', 25600, 'accel_x', 'g', '{\"asset\": \"P7\"}');\n\n-- append samples at their global offset; repeating an identical append is a no-op\nSELECT morlet_append_chunk('signals', 'pump-7', 0,\n  ARRAY(SELECT sin(i / 40.0) * (1 + (i / 3000) % 3) FROM generate_series(0, 32767) i));\n\n-- build envelopes (4096-sample windows, stride 2048); call it in the append\n-- transaction to make new windows searchable at commit\nSELECT morlet_build_envelopes('signals', 'pump-7');\n\n-- where else does samples 8192..12288 occur? shift-tolerant search\nSELECT recording_id, start_sample, end_sample, distance\nFROM morlet_search_similar('signals', 'pump-7', 8192, 12288, '{}', 5);\n\n-- read any match back as original samples\nSELECT samples FROM morlet_read_interval('signals', 'pump-7', 26624, 30720);\n\n-- progress and backlog per window size\nSELECT * FROM morlet_collection_status('signals');\n```\n\nFor large archives, enable **probe search** (needs pgvector): it finds candidates\nthrough an HNSW index and re-ranks them with the same exact score. It is\napproximate in which windows it considers, never in the distances it returns.\n\n```\nSELECT morlet_enable_envelope_probes('signals', 4096);\nSELECT * FROM morlet_search_similar('signals', 'pump-7', 8192, 12288);   -- now uses probes\n```\n\n`morlet_search_similar` uses probe search when it is enabled for the window size and exact search\notherwise; `morlet_search_envelope_interval` and `morlet_search_envelope_probes` choose a path\nexplicitly.\n\nThe fifth argument of the interval searches is a metadata filter (`'{\"asset\": \"P7\"}'`).\nSmall filtered galleries are routed to exact search automatically. Searches run with\nthe caller's permissions, including row-level security.\n\nMore: [SQL reference](https://github.com/merkhet-io/morletdb/blob/main/extensions/pg_morlet/README.md) · [search contract](https://github.com/merkhet-io/morletdb/blob/main/docs/search-contract.md) ·\n[recording workflow](https://github.com/merkhet-io/morletdb/blob/main/docs/recording-workflow.md) · [operations](https://github.com/merkhet-io/morletdb/blob/main/docs/operations.md) ·\n[access control](https://github.com/merkhet-io/morletdb/blob/main/docs/access-control.md) · [Parquet import](https://github.com/merkhet-io/morletdb/blob/main/tools/ingest/README.md)\n\n| Task | Main functions | \n|---|---|\n| Create a collection and recording | `morlet_create_stream_collection` ,`morlet_start_recording` | \n| Append and inspect progress | `morlet_append_chunk` ,`morlet_stream_status` ,`morlet_collection_status` | \n| Read original samples | `morlet_read_interval` | \n| Search Morlet/statistical features | `morlet_search_streams` ,`morlet_search_interval` | \n| Materialize envelopes | `morlet_enable_envelopes` ,`morlet_build_envelopes` | \n| Search similar intervals (probes if enabled, else exact) | `morlet_search_similar` | \n| Search bounded-lag envelopes exactly | `morlet_search_envelopes` ,`morlet_search_envelope_interval` | \n| Enable and query optional indexed probes | `morlet_enable_envelope_probes` ,`morlet_search_envelope_probes` | \n| Inspect storage | `morlet_recording_storage` ,`morlet_collection_storage` | \n\nThe [SQL guide](https://github.com/merkhet-io/morletdb/blob/main/docs/installation-and-queries.md#store-search-read-back) walks\nthrough storing two synthetic recordings, building envelopes, filtering a query,\nand reading its matches. Intervals are zero-based and half-open; feature and\nenvelope search use different scorers. Envelopes require an explicit builder;\nraw ingestion alone does not make a recording searchable through that path.\nProbe candidate selection is approximate above the exact-routing threshold.\nSee the [search contract](https://github.com/merkhet-io/morletdb/blob/main/docs/search-contract.md) for budgets, permissions,\nsource exclusion, overlap suppression and snapshot semantics.\n\n| [MorletDB: Recordings, Not Rows — Lag-Tolerant Waveform Retrieval in PostgreSQL](https://github.com/merkhet-io/morletdb/blob/main/research/paper/morletdb.pdf) Version 0.25 Bounded-lag cross-correlation is the right score when matches may be shifted in time, but standard vector indexes rank by an unshifted metric. Because each lag term is an inner product with a shifted copy of the query, a sparse grid of **shifted probes** into an ordinary pgvector HNSW index, re-ranked with the exact score, recovers the shift-tolerant top ten. | On held-out seismic data (234,622 windows):  | \n\nWhat it costs and where it is not shown: a probe table and index of about the size\nof the raw data, slower search under filters that exclude a query's neighbours, and\nextra write-ahead logging for index maintenance. The paper's retrieval results are seismic. Study\n[N1](https://github.com/merkhet-io/morletdb/blob/main/evaluation/nilm_n1/RESULTS.md) added appliance identification from current transients (PLAID): exact\nenvelope search named the appliance about as well as a current-feature 1-NN (H1 passed), but its distances did\nnot flag unknown appliances (H2 failed). Other signals, diagnostic relevance and long-running operation are not\nshown.\n[Supplement](https://github.com/merkhet-io/morletdb/blob/main/research/paper/supplement.pdf) · [study overview](https://github.com/merkhet-io/morletdb/blob/main/research/paper/STUDIES.md) ·\n[reproduce the numbers](https://github.com/merkhet-io/morletdb/blob/main/research/paper/ARTIFACT.md) · [main results](https://github.com/merkhet-io/morletdb/blob/main/research/paper/README.md#main-numbers)\n\n| **Recording viewer.** A local, read-only page for a recording, its context lanes (program, block, tool, part), segments and labels; compare a segment with the same context on other parts, or find similar windows.[tools/viewer](https://github.com/merkhet-io/morletdb/blob/main/tools/viewer/README.md) \n\n```\npython -m pip install -r tools/viewer/requirements.txt\nDATABASE_URL=postgresql://... python tools/viewer/demo.py\nDATABASE_URL=postgresql://... python tools/viewer/viewer.py\n```\n\n |  | \n| **Sleep staging by example.** 153 Sleep-EDF nights stored as raw recordings; click an epoch of a held-out night and see the 40 most similar expert-scored epochs from other people, each read back from its interval. A research demo, not a medical device.[examples/sleep](https://github.com/merkhet-io/morletdb/blob/main/examples/sleep/README.md) |  | \n\nAlso: [compare a machine with its own history](https://github.com/merkhet-io/morletdb/blob/main/docs/context-workflow.md) under the\nsame operating context · [visual notebook](https://github.com/merkhet-io/morletdb/blob/main/examples/notebooks/README.md) ·\n[Parquet importer](https://github.com/merkhet-io/morletdb/blob/main/tools/ingest/README.md)\n\nEvery evaluation fixes its protocol before reading the data, runs once, and reports the result, including negative ones. In short:\n\n- **Held up:** lossless storage (1.2–6× smaller than TimescaleDB compression on the\nsame samples), exact and shift-tolerant search, probe search on standard indexes,\nretrieval of labelled examples as explanation, comparison under the same operating\ncontext, access control.\n- **Did not hold up:** Morlet features have not beaten simple FFT band powers on\nbearings, ECG or sleep EEG; no wavelet or filter type has beaten a plain band-pass\nfilter for bearing faults; own-history comparison has not warned earlier than a plain\nRMS alarm on run-to-failure bearings.\n\nEvery evaluation with its data, baselines, confidence intervals and verdict:\n**[docs/evaluations.md](https://github.com/merkhet-io/morletdb/blob/main/docs/evaluations.md)**.\n\nAppend-only recordings; 1–32,768 samples per chunk; explicit channel and unit; no\nresampling, gap interpolation, background extraction worker or cross-channel search.\nExact search scans eligible windows; probe search is approximate and opt-in. Alpha:\nno stable API or schema guarantee yet. Details: [support boundaries](https://github.com/merkhet-io/morletdb/blob/main/SUPPORT.md) ·\n[compatibility](https://github.com/merkhet-io/morletdb/blob/main/docs/compatibility.md) · [numerical contract](https://github.com/merkhet-io/morletdb/blob/main/docs/morlet-extractor.md).\n\n**The name.** MorletDB honours Jean Morlet and the project's starting hypothesis that\nMorlet wavelets would suit search in technical signal archives. The system is not tied\nto that transform, and our evaluations have not shown a general advantage for it.\n\n| Path | What | \n|---|---|\n| [extensions/pg_morlet](https://github.com/merkhet-io/morletdb/blob/main/extensions/pg_morlet) | the PostgreSQL extension: storage, ingestion, interval reads, search | \n| [crates/grizzbit](https://github.com/merkhet-io/morletdb/blob/main/crates/grizzbit) | [Grizzbit](https://github.com/merkhet-io/morletdb/blob/main/docs/grizzbit.md) , the lossless chunk codec ([specification](https://github.com/merkhet-io/morletdb/blob/main/docs/chunk-format.md) ) | \n| [crates/morlet-core](https://github.com/merkhet-io/morletdb/blob/main/crates/morlet-core) | portable Rust core: envelopes, features, checkpoints; re-exports the codec | \n| [tools](https://github.com/merkhet-io/morletdb/blob/main/tools) | Parquet import, Python client, recording viewer, independent numerical reference | \n| [examples](https://github.com/merkhet-io/morletdb/blob/main/examples) | sleep staging, notebooks and other worked examples | \n| [evaluation](https://github.com/merkhet-io/morletdb/blob/main/evaluation) ·[benchmarks](https://github.com/merkhet-io/morletdb/blob/main/benchmarks) | frozen evaluations and workload benchmarks | \n| [research/paper](https://github.com/merkhet-io/morletdb/blob/main/research/paper) | the paper, supplement and reproduction package | \n\n```\nmake test       # Rust core and formatting\nmake pg-test    # needs a pgrx-managed PostgreSQL\n```\n\n[Contributing](https://github.com/merkhet-io/morletdb/blob/main/CONTRIBUTING.md) · [Development](https://github.com/merkhet-io/morletdb/blob/main/docs/development.md) · [Support](https://github.com/merkhet-io/morletdb/blob/main/SUPPORT.md) ·\n[Security](https://github.com/merkhet-io/morletdb/blob/main/SECURITY.md) · [Roadmap](https://github.com/merkhet-io/morletdb/blob/main/docs/product-roadmap.md) · [Brand assets](https://github.com/merkhet-io/morletdb/blob/main/assets/brand/README.md)\n\nCopyright 2026 MorletDB contributors. Code, documentation and original brand artwork\nare licensed under the [Apache License, Version 2.0](https://github.com/merkhet-io/morletdb/blob/main/LICENSE). Every part of MorletDB, integrations included, is open source under\nthat licence; there is no enterprise edition ([licensing](https://github.com/merkhet-io/morletdb/blob/main/docs/licensing.md)). The technical report's text and\nfigures are licensed under [CC BY 4.0](https://github.com/merkhet-io/morletdb/blob/main/research/paper/LICENSE.md). A\n[Merkhet](https://github.com/merkhet-io) project.", "url": "https://wpnews.pro/news/show-hn-morletdb-is-a-postgres-extension-for-waveforms", "canonical_source": "https://github.com/merkhet-io/morletdb", "published_at": "2026-11-14 20:34:17+00:00", "updated_at": "2026-11-14 20:53:33.073726+00:00", "lang": "en", "topics": ["ai-agents", "agent-protocols", "ai-tools", "artificial-intelligence", "developer-tools"], "entities": ["MorletDB", "PostgreSQL", "Merkhet", "Grizzbit", "pgvector", "Claude Code", "Claude Desktop", "MCP"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/show-hn-morletdb-is-a-postgres-extension-for-waveforms", "markdown": "https://wpnews.pro/news/show-hn-morletdb-is-a-postgres-extension-for-waveforms.md", "text": "https://wpnews.pro/news/show-hn-morletdb-is-a-postgres-extension-for-waveforms.txt", "jsonld": "https://wpnews.pro/news/show-hn-morletdb-is-a-postgres-extension-for-waveforms.jsonld"}}