
Python's package ecosystem is extraordinary. Python's dependency story is still, for many of us, a recurring injury.
Two libraries want different versions of the same dependency. A script needs a package you forgot to declare. An agent wants to import something mid-task. You solve it the usual way: another virtualenv, another lockfile, another "works in CI, breaks on the laptop" afternoon.
Today we are open-sourcing Depfix — Python dependencies, solved. It is also up on Hacker News.
The pain is not "installing packages"
pip install is fine. The pain starts after that.
1. Dependency hell is still real
The classic case is not theoretical. Some popular packages still cannot share one environment:
awscli==1.32.0 needs botocore==1.34.0
boto3==1.36.0 needs botocore>=1.36,<1.37
Those ranges do not overlap. Conventionally you split processes, split services, pin one tool and abandon the other, or maintain two environments and glue them together with shell scripts.
That is not packaging. That is infrastructure tax for an import.
2. One process, one version — forever
Normal Python has a hard rule: a package name maps to one loaded module graph. If you need requests==2.31 and requests==2.32 in the same process — migration, comparison, plugin host, agent runtime — you lose. Aliasing does not help. sys.path hacks break the moment transitive imports collide.
3. The environment becomes the product
Teams burn hours on the ceremony around code instead of the code:
- virtualenv / conda / container matrix sprawl
requirements.txtdrift againstpyproject.toml- lockfiles that exist mainly to freeze yesterday's compromise
- "just recreate the env" as a debugging strategy
The app is fifty lines. The environment is a second app.
4. Dynamic work hates static manifests
This one hit us building Agent Zero. Agents do not always know their imports at deploy time. They discover libraries while solving a task. Forcing every possible dependency into a prebuilt image is either incomplete or enormous. Installing into the global env at runtime is how you recreate dependency hell live, on a running system.
5. Reproducibility arrives too late
You either start loose and regret it in production, or start locked and drown in pins before the prototype works. Python tooling has many ways to freeze an environment. It has fewer ways to keep moving fast and graduate to a frozen deploy without rewriting how imports work.
What Depfix changes
Depfix makes the import the package manager.
Install once:
python -m pip install depfix
Then put the version next to the import:
import depfix
with depfix.using("requests==2.31.0"):
import requests as requests_old
with depfix.using("requests==2.32.3"):
import requests as requests_new
assert requests_old is not requests_new
No requirements.txt required. No dependency list in pyproject.toml. No second environment because two libraries disagree about a transitive pin. Packages download on first use, cache locally, and keep the dependency versions they were installed with.
Ordinary imports, versioned on purpose
Default versions for the rest of the app
import depfix
depfix.default(
"requests==2.32.3",
"PyYAML==6.0.2",
)
import requests
import yaml
After default(), normal import lines keep working. The first call prepares packages; later runs reuse the cache.
Temporary versions where you need them
import depfix
with depfix.using("openai==0.7.0"):
import openai as openai_0_7
with depfix.using("openai==0.28.1"):
import openai as openai_0_28
using() also works as a decorator, including on async functions. Imported objects keep working after the block ends — the version selection bound the module graph; it did not rent it for one indentation level and vanish.
Direct and dynamic loads
import depfix
requests = depfix.import_module("requests==2.32.3")
version = "2.32.3"
requests = depfix.import_module(f"requests=={version}")
That last form matters for agents, plugin hosts, notebooks, and any code that learns the version at runtime.
The conflict that used to end the conversation
With Depfix, the AWS CLI / Boto3 trap becomes a normal import:
import depfix
with depfix.using("awscli==1.32.0", "boto3==1.36.0"):
import awscli.clidriver
import boto3
Each package receives the Botocore version it needs. You do not pin the shared dependency into a false compromise. You do not split the application. You do not open a second environment just to keep both tools.
There is a runnable proof in the repo: conflicting_botocore_versions.
Under the hood, Depfix builds parent-specific import realms. Artifacts stay content-addressed and immutable. Each root package keeps its own dependency edges. Logical imports resolve through a narrow dispatcher instead of dumping everything into one flat site-packages battlefield.
Why this is especially useful for AI agents
Agent runtimes are dependency chaos amplifiers:
- tasks pull in unexpected libraries
- tools ship with pinned transitive stacks that fight each other
- long-lived processes cannot afford "recreate the venv" as a recovery plan
- side-by-side version checks are common during migrations and evals
Depfix was born from that pressure inside Agent Zero. When an agent needs a package, the version can travel with the import. The cache makes repeat work cheap. Conflicting tool stacks stop being a process-architecture problem.
If you are not building agents, you still get the same win: libraries, CLIs, notebooks, and monoliths that had to be carved into multiple environments can share one Python process again.
Start loose. Lock when production asks.
Local development can stay stupid-simple: run the file. Depfix installs on demand and does not litter the repo.
When you want repeatability:
depfix export . -o .depfix/imports.lock
depfix install .depfix/imports.lock --frozen
python application.py
Need air-gap or container-friendly workflows? Depfix can vendor packages, build offline bundles, and prepare IDE aliases. The point is not "never lock." The point is do not force lockfile theater before the code exists.
Sources are broader than a bare PyPI name too — version ranges, extras, Git, local paths, URLs, and standard PEP 508 direct references are all valid inputs.
Honest limits
Depfix is not magic, and the README is explicit about the edges:
- current focus is pure-Python packages on CPython 3.11–3.13
- native-extension-heavy packages may need a separate worker process
- dependency isolation is not a security sandbox for untrusted code
If your world is only pure wheels and contested pure-Python graphs, the pain relief is immediate. If you live in a native-extension thicket, treat Depfix as a powerful new tool — not a universal solvent.
Try it
- GitHub: github.com/agent0ai/depfix
- PyPI: pypi.org/project/depfix
- Hacker News: news.ycombinator.com/item?id=49133095
- Docs live in the repo: getting started, API, CLI, deployment, troubleshooting
python -m pip install depfix
Python's worst dependency problems were never about downloading a wheel. They were about identity: which version am I talking to, and can two answers coexist?
Depfix makes that identity explicit at the import — and finally lets the answers coexist.
Depfix is MIT-licensed open source from agent0ai, the team behind Agent Zero, Space Agent, and DOX. Issues and PRs welcome.