Ghost Features is an ML observability agent that detects orphaned feature-store entries — ML features whose upstream warehouse columns have been renamed, dropped, or changed type without the feature store being updated. It works by cross-referencing DataHub's live warehouse schema metadata against the declared source columns in each feature's mlFeatureProperties, using DataHub's MCP Server as the primary interface for reads and writes.
ML models that depend on a feature store have a silent failure mode that standard health checks miss entirely. The model runs, the pipeline completes, no exceptions are raised — but the feature values being consumed are stale, wrong, or absent, because the upstream warehouse column the feature was computed from has quietly changed.
This happens because feature-store metadata (which column a feature maps to, what type that column had at registration time) is written once at feature creation and rarely audited again. Meanwhile, warehouse schemas evolve continuously: columns get renamed during refactors, dropped when tables are restructured, or silently change type when upstream ETL logic changes. There is no automatic feedback loop between the warehouse and the feature store. The result is a "ghost feature" — an entry that looks valid in the catalog but is disconnected from reality in production.
┌─────────────────────────────────────┐
│ DataHub GMS │
│ • schemaMetadata (warehouse cols) │
│ • mlFeatureProperties (sources) │
│ • mlModel catalog │
│ • Documents (remediation notes) │
└────────────────┬────────────────────┘
│ MCP protocol (stdio)
▼
┌────────────────┐
│ MCP Server │ acryldata/mcp-server-datahub
└───────┬────────┘
│ get_entities / list_schema_fields / save_document
▼
┌─────────────────────────┐
│ detect_ghost_features │──► writes remediation Documents back to DataHub
└─────────────────────────┘
The detector's reads and writes go through the MCP Server. There are two documented exceptions where GMS v1.5.0.6's MCP tools do not yet expose ML entity metadata:
mlFeatureProperties(thecustomPropertiesandsourcesfields on each feature) — fetched via the GMS REST/aspectsendpoint directly.mlModelcatalog enumeration — fetched via the GMS OpenAPI v3 endpoint, becauseML_MODELis absent from the MCP search tool's GraphQL entity-type enum on this GMS version.
Both gaps are documented in docs/mcp-server-ml-entity-gap.md and filed upstream as acryldata/mcp-server-datahub#205.
The seeding and simulation scripts (scripts/seed_ml_features.py, scripts/simulate_*.py) use the DataHub Python SDK directly. These are one-time environment setup tools, not part of the agent runtime, so direct SDK use is appropriate and consistent with how DataHub's own ingestion tooling works.
# Install Docker Desktop from https://www.docker.com/products/docker-desktop
# Then install the DataHub CLI
pip install acryl-datahub
# Start a local DataHub instance (pulls ~4 GB of images on first run)
datahub docker quickstartDataHub UI will be available at http://localhost:9002. GMS runs at http://localhost:8080.
datahub docker ingest-sample-dataThis seeds the Snowflake order_entry schema (customers, order_items, etc.) that the feature store references.
git clone https://github.com/himanshunikam/ghost-features.git
cd ghost-features
# Install uv (fast Python package manager)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Install dependencies
uv pip install -r requirements.txt# In a separate terminal — keep this running while the detector runs
uvx mcp-server-datahub --transport stdioThe detector launches this automatically via scripts/datahub_mcp_client.py; you do not need to start it manually unless you want to inspect MCP traffic directly.
python3 scripts/seed_ml_features.pyThis creates the customer_risk_features feature table, three ML features (avg_order_value, customer_lifetime_orders, customer_email_domain), and the fraud_risk_model_v1 ML model in DataHub, with lineage wired to the Snowflake source columns.
python3 scripts/detect_ghost_features.pyThe detector scans every feature in the customer_risk_features feature table, compares each feature's declared source column and type against the live warehouse schema, and prints a status for each:
| Status | Meaning |
|---|---|
OK |
Source column exists and type matches what was recorded at feature registration. |
GHOSTED |
Source column no longer exists in the warehouse schema (renamed or dropped). |
TYPE_CHANGED |
Source column exists but its nativeDataType differs from the type recorded in mlFeatureProperties.customProperties.sourceFieldType. |
For any GHOSTED or TYPE_CHANGED finding, the detector also scans the ML model catalog for any production model that lists the affected feature, prints an impact warning, and writes a remediation Document back into DataHub via the MCP Server.
Scenario 1 — renamed column (causes customer_email_domain → GHOSTED):
python3 scripts/simulate_schema_change.pyRenames cust_email to email_address on the customers dataset.
Scenario 2 — type change (causes avg_order_value → TYPE_CHANGED):
python3 scripts/simulate_type_change.pyChanges unit_price from FLOAT to VARCHAR(16777216) on the order_items dataset.
Scenario 3 — dropped column (causes customer_lifetime_orders → GHOSTED):
python3 scripts/simulate_dropped_column.pyRemoves customer_id entirely from the customers dataset schema.
Run all three simulations and then the detector to see all findings at once:
python3 scripts/simulate_schema_change.py
python3 scripts/simulate_type_change.py
python3 scripts/simulate_dropped_column.py
python3 scripts/detect_ghost_features.pyThe following is a real run after all three simulations have been applied (full output):
Checking feature table: urn:li:mlFeatureTable:(urn:li:dataPlatform:feast,customer_risk_features)
STATUS FEATURE
----------------------------------------------------------------------
TYPE_CHANGED urn:li:mlFeature:(customer_risk_features,avg_order_value)
→ Column 'unit_price' changed type from 'FLOAT' to 'VARCHAR(16777216)' in urn:li:dataset:(urn:li:dataPlatform:snowflake,b2fd91.order_entry_db.order_entry.order_items,PROD)
GHOSTED urn:li:mlFeature:(customer_risk_features,customer_lifetime_orders)
→ Column 'customer_id' NOT FOUND in current schema of urn:li:dataset:(urn:li:dataPlatform:snowflake,b2fd91.order_entry_db.order_entry.customers,PROD)
GHOSTED urn:li:mlFeature:(customer_risk_features,customer_email_domain)
→ Column 'cust_email' NOT FOUND in current schema of urn:li:dataset:(urn:li:dataPlatform:snowflake,b2fd91.order_entry_db.order_entry.customers,PROD)
⚠️ 3 problematic feature(s) detected!
⚠️ IMPACT: This feature is used by production model
'fraud_risk_model_v1' (urn:li:mlModel:(urn:li:dataPlatform:mlflow,fraud_risk_model_v1,PROD))
This model may be silently consuming stale/missing data.
Document created: urn:li:document:shared-246c8e5e-cd3e-405b-af82-34fe3b0488da
⚠️ IMPACT: This feature is used by production model
'fraud_risk_model_v1' (urn:li:mlModel:(urn:li:dataPlatform:mlflow,fraud_risk_model_v1,PROD))
This model may be silently consuming stale/missing data.
Document created: urn:li:document:shared-a1178024-7209-4d14-9f05-96fe4ef510a9
⚠️ IMPACT: This feature is used by production model
'fraud_risk_model_v1' (urn:li:mlModel:(urn:li:dataPlatform:mlflow,fraud_risk_model_v1,PROD))
This model may be silently consuming stale/missing data.
Document created: urn:li:document:shared-601fbeed-62c2-4e89-9984-dcc137d8825c
Each Document created line is a live DataHub Document entity, linked to the affected feature and impacted model, visible in the DataHub UI. See examples/sample_remediation_document.md for the content of one such document.
While building this agent, two gaps were found in the DataHub MCP server's support for ML entity types. A detailed bug report with minimal reproductions was filed upstream:
- acryldata/mcp-server-datahub#205 — filed issue: missing
mlFeaturePropertiesinget_entitiesGraphQL fragments and aValidationErrorwhen searching forML_MODELentities via the MCP search tool on GMS v1.5.0.6. docs/mcp-server-ml-entity-gap.md— local reproduction notes and suggested fix.
- Attach DataHub owners and tags to generated Documents so findings are routed to the right team automatically.
- Slack and email alerting when new ghost features are detected, without requiring a manual detector run.
- Unit tests and CI so schema-change simulations and detection logic are regression-tested on each commit.
- Full catalog-wide ML entity scan (all feature tables, not just one) once the MCP gap for
mlFeaturePropertiesandML_MODELsearch is fixed upstream.
Apache 2.0 — see LICENSE.