2026-07-20 Capability Taxonomy - python.tools Decomposed by Purpose

What I set out to do

A continuation of the same conversation as 2026-07-20 lib.uv Refactor and athena 26.05 Pin, moving from framing (AOP / FOP / dendritic) to acting on the capability model. Question that started it: does local.python.tools make sense as a top-level group? It does not, and unwinding that produced two commits.

The diagnosis

local.python.tools gated seven uv-installed tools: jupyterlab + ipython (data science), comfy-cli (image gen), pyzotero (Zotero API), scalene + memray (profilers), claude-code-transcripts. They share exactly one thing: installed via uv tool install. That is a mechanism, not a purpose. A capability flag is supposed to express host intent (“this host does data science”); python.tools instead expressed “this host installs some Python CLIs the uv way.” It is the tyranny of the dominant decomposition leaking into the capability surface: python.nix is organized by install mechanism, and the flag inherited that axis, scattering purpose across it. A profiler and a notebook kernel were siblings only because they install alike.

The module-count audit (the framing made concrete)

Before touching it, quantified where axis 1 (curated import list) actually does work. 42 top-level module entries (37 files + 5 dirs), 46 auto-loaded on the desktop, 19 curated on the server. Of the 23 desktop-only modules:

  • 14 are redundantly gated (would be inert on a server even if imported): 6 capability-flagged (desktopApps/python.tools), 8 default-off service enables.
  • 9 are axis-1 load-bearing — nothing but non-import keeps them off: copier, docker, js-package-managers, media-stack, prek, sqlite-seed, worktrunk, telemetry, patches. Verified they self-gate only on policy/identity, not a whole-module off-switch, so they would activate if auto-discovered on the server (media-stack and docker being the genuinely dangerous two).

So the curated import list is doing necessary work for ~9 of 42 modules; for 14 it is belt-and-suspenders. That is the concrete measure of what collapsing axis 1 (the dendritic direction) would cost — those 9 are exactly the modules that would need an explicit enable guard first.

What I did

Commit d0ae738 — split by purpose, keep the mechanism shared

  • local.dataScience.enable — top-level capability (jupyter.nix, ipython.nix, the jupyterlab/ipython kernels). Requires python.enable (assertion).
  • local.devTools.profilers.enable — scalene/memray, a 4th devTools subgroup (dev tooling, not data science), rides devTools.enable off on the server.
  • python.tools shrank to the honest residual (comfy-cli, pyzotero, claude-code-transcripts), described as the mechanism-bag it was.

The key move: flags split by purpose, install mechanism stays DRY. python.nix partitions the tool set into purpose groups; the activation installs the union of the enabled ones. mapAttrsToList iterates key-sorted, so all-groups-on reassembles the exact former script — atlas and athena drvPaths byte-identical, no rebuild.

Commit 8194f26 — dissolve the residual

Removed claude-code-transcripts outright, and pulled the last two tools into individual opt-in program flags: programs.comfy-cli.enable and programs.pyzotero.enable (declared in python.nix, default off, enabled in the desktop profile). With the bag empty, local.python.tools was deleted entirely — option, assertion, server-profile line. comfy-cli earned its own gate for being the heaviest uv tool (torch/av/kornia).

Asked (AskUserQuestion) which hosts keep comfy/pyzotero — chose desktop+corp unchanged, so they enable in home.nix. atlas drvPath changed by exactly the cc-transcripts delta: the install script lost that one block, the other six tools unchanged (verified by hasInfix over the rendered script). athena untouched.

What was striking

  • The whole python.tools mess was one instance of a general rule: when a module is decomposed by how (install mechanism), any flag that rides that structure inherits the wrong axis. The fix was not “add a flag” but “stop letting the mechanism define the capability.”
  • drvPath discipline paid off twice in one session at two different bars: byte-identical for the pure split (proving no behaviour change), and exactly-one-delta for the removal (proving the change was only what was intended). The second is the more useful check — it catches an over-broad edit that a green test would not.
  • Each new flag installs via the uv stack, so each needed a -> local.python.enable assertion. That is the no-dependency-resolution rule biting again: a cross-capability requirement has no native satisfaction, only a failing assertion.

Still open

  • The dataScience/profilers/comfy/pyzotero installs are activation content, not a checkable surface, so only dataScience has a contract-test entry (documented inline). If a future host takes a partial slice, that gap is where a silent break could hide.
  • Same athena hm switch validation carried over from the sibling journal.