Platform
One feature definition.
One execution model.
Historical and live ML.
Keep feature logic in Python, dependencies in a declarative DAG, and use the same tested Feature Project across historical materialization, point-in-time training, and request-time computation.
Three parts, one mental model
Everything in ClearFeature reduces to a small model: an engine, your code, and the combination of the two.
clearfeature-core
ClearFeature Core
feature-project
Feature Project
runtime
Feature Runtime
Feature Projects: your code stays your code
ClearFeature does not absorb your feature logic into a proprietary format. A Feature Project is a normal, reviewable Python package that lives in your repository, moves through your CI, and is versioned like the software it is.
Feature Project — your repository
snapshot-ratio/
├── feature_project.yaml
└── snapshot_ratio/
├── features.py
├── registry/features_v1.yaml
└── tests/golden.yaml- features.py — feature UDFs in plain Python
- registry — sources, features, versions, dependencies, groups
- golden.yaml — expected values, run as tests
Feature Runtime
ClearFeature Core loads your Feature Project and executes the same registry-defined DAG for historical materialization, point-in-time training datasets, and live request-time computation.
Your code stays your code — Core is the engine, not the owner.
Dependencies are declared, not orchestrated
A feature is a function of sources and upstream features: (sources, deps) → value. Source features read raw report data. Dependent features read upstream feature values. UDFs never call one another — the registry defines the DAG, and the platform resolves execution order.
Source reports
credit_bureau_report
socdem_report
Source features (F1)
active_monthly_payment
monthly_income
Dependent features (F2)
payment_to_income_ratio
reads upstream values via deps
Multiple sources, one entity
Production features rarely read one dataset. A credit application, for example, might need a bureau report and a socio-demographic report that landed separately. Batch workflows consume multiple source manifests and join them by the complete canonical entity key — so the DAG computes over a consistent picture of each entity.
Historical materialization
Materialization executes the DAG over landed source history and persists computed feature values with their metadata. Jobs are durable and deterministic: re-running a materialization does not corrupt or duplicate state, which makes backfills and recovery boring — as they should be.
Feature history and job metadata live in PostgreSQL; raw report payloads stay in S3-compatible object storage.
Point-in-time training datasets
A training row is built from an observation time — the moment a historical prediction or decision was made. A source value is eligible only if it belongs to the past and was available by that observation time. Data that arrived later is excluded, which prevents leakage that silently inflates offline metrics.
Request-time execution
Online computation is request-triggered: send current source reports into the execution path, and the runtime computes the requested feature groups — resolving the same dependencies, with the same versioned code used historically. The latest computed values are kept in Valkey for fast retrieval.
The centerpiece: one implementation, both paths
Everything above exists to make one architectural statement true: the feature values a model was trained on and the feature values it receives in production come from the same tested code and the same dependency graph. Not from two implementations kept in sync by discipline.
Runtime and storage, at a glance
Single-purpose services around one compute core, on infrastructure your team already knows how to operate.
Entry
HTTP API
routes to online, batch, and propagation workers
Compute
Shared compute core
executes your Feature Project DAG
Storage
PostgreSQL
feature history, metadata, jobs
Valkey
latest online values
MinIO / S3-compatible
raw report payloads
Kafka / Redpanda-compatible
events and references
This is what a feature looks like
Real code from the public quickstart — a source feature per report, and a dependent feature that consumes both.
def active_monthly_payment(sources, deps):
loans = sources["credit_bureau_report"]["loans"]
return round(
sum(x["monthly_payment"] for x in loans if x["status"] == "active"),
2,
)
def monthly_income(sources, deps):
return float(sources["socdem_report"]["monthly_income"])
def payment_to_income_ratio(sources, deps):
return round(
deps["active_monthly_payment"] / deps["monthly_income"],
6,
)The registry declares the dependency edge — the platform does the rest:
payment_to_income_ratio:
kind: "udf"
feature_version: 1
udf: "udf.demo.payment_to_income_ratio"
deps:
- feature: active_monthly_payment
version: 1
- feature: monthly_income
version: 1
dtype: "float"
status: "live"Golden tests assert expected values for every feature before deployment. The complete example is in the quickstart.
Adopting it
The path from zero to a running Feature Project is deliberately short.
step 1
Run the Quickstart
step 2
Model your first workflow
See it execute.
The quickstart takes a Feature Project from scaffold to tested features in about five minutes.