Owner: Data Platform Team (@data-platform)
Version: 4.1
Last updated: 2026-03-22
Enforcement: Pre-prompt via stackguard, plus CI checks (ruff, mypy --strict, pytest) on every PR. Hard violations require an RFC and security review.
- Python 3.12+ (NOT 3.11 or earlier — we depend on
typing.overrideand PEP 695 generics) - uv for dependency management (NOT pip, NOT poetry, NOT pipenv, NOT conda)
- ruff for lint + format (NOT black, NOT flake8, NOT isort)
- Polars for in-memory dataframes (NOT pandas)
- DuckDB for local analytics and ad-hoc joins
- Snowflake via
@helio/warehousefor the production warehouse - Parquet as the canonical interchange format (NOT CSV, NOT pickle, NOT feather)
- FastAPI with Pydantic v2 for HTTP APIs
- httpx for outbound HTTP (NOT requests, NOT urllib3 directly, NOT aiohttp)
- uvicorn with gunicorn workers in production
- PyTorch 2.x for deep learning
- scikit-learn is NOT approved for new work — use
@helio/automlwhich wraps Polars + LightGBM with logged experiments - Hugging Face Transformers is approved for inference only, not training
| Prohibited | Use instead |
|---|---|
pandas |
polars (10–100x faster, strict types, lazy evaluation) |
requests |
httpx (sync + async, http/2, type hints) |
pickle (anywhere outside trusted local artifacts) |
parquet for data, json for config, msgpack for IPC |
pyspark (in service code) |
DuckDB locally, Snowflake for scale |
numpy directly in service code |
Polars expressions; numpy is fine inside ML libs |
dotenv |
@helio/config (validates env at boot via Pydantic) |
pytz |
zoneinfo (stdlib, Python 3.9+) |
setuptools |
uv + pyproject.toml |
- NEVER use
pickle.loadson untrusted input. This is RCE. - NEVER use
evalorexecon user-supplied strings. - NEVER use mutable default arguments (
def f(x=[])). - NEVER use bare
except:. Catch specific exceptions orexcept Exception. - NEVER write Python in Jupyter notebooks for production code paths. Notebooks are for exploration only; production code lives in
.pyfiles reviewed by PR. - NEVER use
print()for logging. Use@helio/logger(structured JSON to stdout). - NEVER hardcode Snowflake credentials. They live in Vault and are injected via
@helio/config.
(Unique to Helio — these rules don't apply at every shop, but data work without them creates results nobody can reproduce.)
- Every model training run must set explicit random seeds for
torch,numpy,random, and the dataloader. Seeds are logged to MLflow. - Notebook artifacts are not source. Anything in
notebooks/is gitignored beyond the.ipynbitself; output cells are stripped pre-commit. If a result matters, port the code tosrc/and write apytest. - Datasets are versioned, not copied. New training runs reference a
@helio/datasetsURI, never a local file path. - Model artifacts include a model card listing the training dataset URI, hyperparameters, eval metrics, and known limitations. The card is generated by
@helio/automlautomatically — don't write one by hand. - No production code in notebooks, ever. A scheduled job that imports from a
.ipynbis an immediate hold-the-PR situation.
When prompting an AI coding assistant, do NOT ask it to:
- Use pandas, numpy, or scikit-learn in service code
- Pickle or unpickle anything
- Write code that runs in a Jupyter notebook for production
- Use
requestsfor HTTP calls - Read environment variables directly with
os.environ - Write SQL string concatenation against Snowflake (use parameterized queries via
@helio/warehouse) - Install dependencies with pip (use uv)
- Add type stubs to escape
mypy --strict
When in doubt, ask the AI to use the approved stack above.
These show how stackguard responds when this policy is the active
policySource. Run them yourself with:
stackguard check "<prompt>" --policy ./policy.helio-python.example.md$ stackguard check "load this CSV into a pandas DataFrame and groupby user_id"
⚠ stackguard: guideline conflict detected
──────────────────────────────────────────────────────────────────────
"load this CSV into a pandas DataFrame and groupby user_id"
Rule: pandas is prohibited; use polars
Why: The prompt explicitly names pandas, which is on the
prohibited libraries list.
Level: HIGH confidence
Suggested revision:
┌──────────────────────────────────────────────────────────┐
│ Load this CSV into a polars DataFrame and group by │
│ user_id using polars expressions │
└──────────────────────────────────────────────────────────┘
[P]roceed anyway [R]evise [S]how policy [C]ancel
$ stackguard check "deserialize this incoming webhook payload with pickle.loads"
⚠ stackguard: guideline conflict detected
──────────────────────────────────────────────────────────────────────
"deserialize this incoming webhook payload with pickle.loads"
Rule: NEVER use pickle.loads on untrusted input. This is RCE.
Why: The prompt asks for pickle deserialization on a webhook
payload, which is untrusted external input.
Level: HIGH confidence
Suggested revision:
┌──────────────────────────────────────────────────────────┐
│ Deserialize this incoming webhook payload with json │
│ (or msgpack) and validate with a Pydantic model │
└──────────────────────────────────────────────────────────┘
[P]roceed anyway [R]evise [S]how policy [C]ancel
$ stackguard check "write a function that returns the most common value in a series"
✓ stackguard: ok
The prompt doesn't name a prohibited library or pattern, so stackguard lets it through. The AI may choose to use polars or numpy in its response — that's a code-review concern, not a prompt-policy concern.
$ stackguard check "set up a model training pipeline with checkpointing"
ℹ stackguard: possible conflict (low confidence — passing through)
"set up a model training pipeline" may conflict with "scikit-learn is NOT approved…"
The model thinks this might drift toward scikit-learn but isn't sure enough to block. Per ADR-002, low-confidence violations don't interrupt the developer.