Skip to main content

ADR 0009: Put the pose model behind a backend switch so the licence is a choice

  • Status: โœ… Accepted
  • Date: 2026-08-20
  • Deciders: @akshay
  • Tags: gpu-backend, licensing, ml

Contextโ€‹

ultralytics==8.4.104 sits in the inference path of both products. It is AGPL-3.0. Serving analysis over a network from a derived work triggers the source-disclosure obligation unless an Ultralytics Enterprise licence is held.

This is not a hypothetical the team had missed. The R&D track already treats it as settled: racketcv/CLAUDE.md lists "anything AGPL (Ultralytics YOLO*, boxmot)" under hard bans for a commercial product. The diagnosis existed; what did not exist was a way to act on it short of a rewrite.

Two things use Ultralytics, and they are not equally stuck:

UseModelStatus before this ADR
Person detectionyolo11mAlready had an escape hatch. FasterRCNNPersonDetector (torchvision, BSD-3) has shipped all along behind --player-detector faster-rcnn. It is simply not the default.
Poseyolo11x-poseWelded in. Three call sites constructed YOLO(...) directly.

So the real exposure was one stage, and the blocker was that swapping it meant editing the pipeline rather than setting a variable.

Options Consideredโ€‹

Option A โ€” buy the Enterprise licence, change nothingโ€‹

  • Pros: zero engineering, zero accuracy risk.
  • Cons: recurring cost, and it leaves a single vendor able to reprice a dependency that sits on the critical path. Does not remove the need to know whether an alternative works.

Option B โ€” rip Ultralytics out now and replace itโ€‹

  • Pros: ends the exposure outright.
  • Cons: swaps the model that feeds contacts, stroke windows and STGCN++ on the strength of an untested assumption. Accuracy would move and nobody would know by how much until users noticed.

Option C โ€” put the model behind an interface, ship both (chosen)โ€‹

  • Pros: the licence becomes a runtime choice. Production keeps running what it runs today; the alternative is one variable away and can be measured on the corpus before anyone commits to it.
  • Cons: one more seam, and a second code path to keep honest.

Decisionโ€‹

Pose runs through src/shot_detection/pose_backend.py, which defines a PoseBackend returning PoseResult(xy, conf, boxes_xywh) โ€” COCO-17 keypoints, per-keypoint confidence, and the person boxes the slot assignment needs.

Two implementations:

BackendModelLicenceDefault
ultralyticsyolo11x-poseAGPL-3.0โœ… yes
torchvisionkeypointrcnn_resnet50_fpnBSD-3-Clauseno

Selected with ACESENSE_POSE_BACKEND (UNFORCE_POSE_BACKEND in the sibling).

Three details are load-bearing:

  • Boxes are in the contract, not bolted on. The consumer uses box height to size the player and box position to assign near/far court slots. A backend returning keypoints alone would import cleanly and then mis-assign every player.
  • An unknown backend name raises. It does not fall back to the default. Falling back would mean somebody who asked for the clean path, and misspelled it, ships the licensed one believing otherwise โ€” the single failure this switch exists to prevent.
  • torchvision does its own resizing. Keypoint R-CNN carries a GeneralizedRCNNTransform and maps keypoints back to input pixels itself, so imgsz is accepted for interface parity and deliberately ignored. Forcing a letterbox would put the output in the wrong coordinate space.

Consequencesโ€‹

What this buys. The commercial decision is now open. Buy the licence, or migrate โ€” either is a config change plus an evaluation, not a project.

What it explicitly does not buy: measured accuracy. The two backends are different architectures on different training schedules. The keypoint schema matches, which is what makes the swap mechanically safe. Whether the numbers hold is a question for regression_panel.py over the reference corpus on a GPU, and that run has not happened. Until it does, torchvision is the licence-clean option whose accuracy is unknown โ€” not a drop-in equal, and this ADR is not permission to treat it as one.

Person detection needs no work, only a decision: faster-rcnn is already there and already BSD-3. Making it the default is a one-line change gated on the same evaluation.

Still on AGPL regardless of this switch: yolo11m remains the default person detector, and ultralytics stays in requirements/base.txt because the default pose backend needs it. This ADR makes the exposure optional; it does not remove it. Removing it means flipping both defaults and dropping the dependency, after the corpus run says what that costs.

  • Current State โ€” what ships today
  • racketcv/CLAUDE.md โ€” the R&D track's licence tiers and hard bans