frank-dragoon grows an XDNA2 NPU lane. halogen-flash-server 0.16.2 — the pin we already run — carries an NPU path: for each model named in HALOGEN_NPU_MODELS it loads the vendor's precompiled AIE programs (XDNA2 executes only AIE-compiled graphs; nothing you can quantize yourself) and answers them on /v1/embeddingsinside the same container behind 7770. First lane model: qwen3-embedding-0.6b, which is what open-notebook needs for retrieval and what its search-with-notes rerank wants later.
The lane is one line wide: HALOGEN_NPU_MODELS blank is the pre-lane endpoint byte for byte — no device, no mounts, no engine env. That is the whole rollback.
The three host preconditions, gated on both sides
Precondition
Why it cannot be skipped
Staged model files
/models is read-only and HALOGEN_DOWNLOAD stays unset, so the container opens no outbound connection at boot; scripts/download-frank-dragoon-npu.sh stages them from the pinned revision with the catalog's own byte counts + sha256s (the engine re-checks both at boot)
Each library mounted twice — where the image looks for XRT, and at its own path — because the libraries find each other where they were installed; an /opt/xilinx/xrt of symlinks into the system dir dangles inside the container (vendor calls this out by name)
GPU fabric clock held
On Strix Halo iGPU+NPU co-running hangs the machine — and corrupts NPU results — while FCLK changes speed; halogen refuses to start in that state but cannot read host sysfs from inside, so the proof lives host-side. dankclaw/host/halogen-fabric-clock{,.service} is the vendor's own hold, verbatim at tag v0.16.2 (sha256 a3a4f7f4e0142e71…, 5f9f1cc4c85a1c02…); the deployer installs/enables/gates it only while the lane is named, and releases the clock + drops the boot hook when it is not
Cost: ~1.5 GiB extra pinned host RAM per named model, ~1 W idle for the clock hold, and every workload on the iGPU runs with FCLK pinned at 2000 MHz.
Verification
./tests/run.sh — 466 passed (11 new: the off arm renders nothing lane-specific, the on arm renders device/mounts/env, each missing precondition refuses by name, deployer twins + host-unit shape + planner scoping).
Deploy path after merge: stage files on the host → deploy.sh dankclaw --service frank-dragoon → health gate → lane gate is a real embedding through /v1/embeddings, because a 200 on /health proves nothing about the NPU programs.
Not in this PR: the vault pages move with the enablement (frank-dragoon's NPU section flips to enabled once the lane is live), and the open-notebook embedding row.
## What
frank-dragoon grows an **XDNA2 NPU lane**. halogen-flash-server 0.16.2 — the pin we already run — carries an NPU path: for each model named in `HALOGEN_NPU_MODELS` it loads the vendor's **precompiled AIE programs** (XDNA2 executes only AIE-compiled graphs; nothing you can quantize yourself) and answers them on `/v1/embeddings` **inside the same container behind 7770**. First lane model: `qwen3-embedding-0.6b`, which is what open-notebook needs for retrieval and what its search-with-notes rerank wants later.
The lane is one line wide: `HALOGEN_NPU_MODELS` blank is the pre-lane endpoint byte for byte — no device, no mounts, no engine env. That is the whole rollback.
## The three host preconditions, gated on both sides
|Precondition|Why it cannot be skipped|
|---|---|
|Staged model files|`/models` is read-only and `HALOGEN_DOWNLOAD` stays unset, so the container opens **no outbound connection at boot**; `scripts/download-frank-dragoon-npu.sh` stages them from the pinned revision with the catalog's own byte counts + sha256s (the engine re-checks both at boot)|
|Distro XRT (`libxrt_coreutil`, `libxrt_core`, `libxrt_driver_xdna`)|Each library mounted **twice** — where the image looks for XRT, and at its own path — because the libraries find each other where they were installed; an `/opt/xilinx/xrt` of symlinks into the system dir dangles inside the container (vendor calls this out by name)|
|GPU fabric clock **held**|On Strix Halo iGPU+NPU co-running hangs the machine — and corrupts NPU results — while FCLK changes speed; halogen refuses to start in that state but cannot read host sysfs from inside, so the proof lives host-side. `dankclaw/host/halogen-fabric-clock{,.service}` is the vendor's own hold, verbatim at tag `v0.16.2` (`sha256 a3a4f7f4e0142e71…`, `5f9f1cc4c85a1c02…`); the deployer installs/enables/gates it **only while the lane is named**, and releases the clock + drops the boot hook when it is not|
Cost: ~1.5 GiB extra pinned host RAM per named model, ~1 W idle for the clock hold, and every workload on the iGPU runs with FCLK pinned at 2000 MHz.
## Verification
- `./tests/run.sh` — **466 passed** (11 new: the off arm renders nothing lane-specific, the on arm renders device/mounts/env, each missing precondition refuses by name, deployer twins + host-unit shape + planner scoping).
- Deploy path after merge: stage files on the host → `deploy.sh dankclaw --service frank-dragoon` → health gate → **lane gate is a real embedding through `/v1/embeddings`**, because a 200 on `/health` proves nothing about the NPU programs.
Not in this PR: the vault pages move with the enablement (frank-dragoon's NPU section flips to enabled once the lane is live), and the open-notebook embedding row.
---
cwd: /home/blank/.omp/wt/wt-20261019-020309-6d1fc1f · session: 01a15366-7601-7175-bb69-c62186c3f8b5
(build worktree: /home/blank/.omp/wt/wt-dragoon-npu)
halogen-flash-server already ships an NPU path on the pin we run: for each
model named in HALOGEN_NPU_MODELS it loads the vendor's precompiled AIE
programs (XDNA2 executes only AIE-compiled graphs) and answers them on
/v1/embeddings inside the SAME container behind port 7770. That gives the
fleet an in-house embedding endpoint without touching the iGPU trunk.
The lane is opt-in and one line wide: HALOGEN_NPU_MODELS blank is the
pre-lane endpoint byte for byte (no device, no mounts, no engine env).
The three host preconditions the vendor names are gated in the serve script
and mirrored in the deployer (the 2026-10-01 twin law), each refused by name:
- the staged model files. /models is read-only and HALOGEN_DOWNLOAD stays
unset so the container opens no outbound connection at boot, so
scripts/download-frank-dragoon-npu.sh stages them from the pinned
revision with the catalog's own byte counts and sha256s (the engine
re-checks both at boot);
- the distro XRT's libxrt_coreutil/libxrt_core/libxrt_driver_xdna, each
mounted twice — where the image looks for XRT and at its own path —
because those libraries find each other where they were installed and
an /opt/xilinx/xrt of symlinks dangles inside the container;
- the GPU fabric clock HELD. On Strix Halo the iGPU and the NPU co-running
hang the machine (and corrupt NPU results) while FCLK changes speed, and
halogen refuses to start in that state; it cannot read host sysfs from
inside, so the hold is proven host-side. dankclaw/host/ carries the
vendor's halogen-fabric-clock{,.service} verbatim at tag v0.16.2 (sha256
a3a4f7f4e0142e71… / 5f9f1cc4c85a1c02…); the deployer installs, enables
and gates it only while the lane is named, and releases the clock plus
drops the boot hook when it is not.
After health, the deployer's lane gate is a real embedding through
/v1/embeddings — a 200 on /health proves nothing about the NPU programs.
Planner: dankclaw/host/halogen-fabric-clock* re-dispatches frank-dragoon
(the deployer installs it); dankclaw/host/grub and the fetch script plan
nothing.
Seam: 11 new tests — the off arm really renders nothing lane-specific, the
on arm renders the device/mounts/env, every missing precondition refuses by
name, the deployer twins and the host unit shape are pinned.
Co-Authored-By: omp <omp@dankclaw>
An endpoint on podman's rootless default (pasta) has no address another
container can use, measured on 2026-10-19 from inside open-notebook:
- 192.168.0.143:7770 connection refused (pasta forwards the
published port in the host netns; no netavark
DNAT exists for traffic arriving from a
rootless bridge)
- host.containers.internal:7770 200, but the alias resolves to 169.254.1.2 —
link-local, which open-notebook's URL guard
refuses to store
- frank-dragoon-halogen:8731 name unknown: different netns
- --network pasta --network dankclaw_default
"cannot set multiple networks without bridge
network mode" — pasta cannot be added to
So reaching the NPU endpoints from an app container means moving the container
onto dankclaw_default (FRANK_DRAGOON_NETWORK; empty keeps today's shape). This
is not an exposure change: the engine's unauthenticated token port binds the
container's own loopback (/proc/net/tcp: 127.0.0.1:8730 against 0.0.0.0:8731),
so only the API port becomes name-resolvable — the same trust the published
7770 already carries on the LAN. The deployer refuses a network that does not
exist rather than starting a unit that restart-loops on it.
Co-Authored-By: omp <omp@dankclaw>
The 2026-10-19 legs table blamed the frank-dragoon refusal on a root/rootless
netavark rule-set split. It is not that: the container is rootless (the root
unit's User=dank runs podman run), it is on pasta, and pasta installs no DNAT
for rootless-bridge traffic while refusing a second network outright. The
gateway alias does reach it (200) and is link-local, which is the same dead end
the Open Notebook URL guard hit. Measured with the move onto the bridge now in
the deployer, including the /proc/net/tcp line that shows 8730 is loopback-bound
and so stays private after the move.
Co-Authored-By: omp <omp@dankclaw>
dank
merged commit b1ca4e02fa into main2026-10-19 21:28:42 -07:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What
frank-dragoon grows an XDNA2 NPU lane. halogen-flash-server 0.16.2 — the pin we already run — carries an NPU path: for each model named in
HALOGEN_NPU_MODELSit loads the vendor's precompiled AIE programs (XDNA2 executes only AIE-compiled graphs; nothing you can quantize yourself) and answers them on/v1/embeddingsinside the same container behind 7770. First lane model:qwen3-embedding-0.6b, which is what open-notebook needs for retrieval and what its search-with-notes rerank wants later.The lane is one line wide:
HALOGEN_NPU_MODELSblank is the pre-lane endpoint byte for byte — no device, no mounts, no engine env. That is the whole rollback.The three host preconditions, gated on both sides
/modelsis read-only andHALOGEN_DOWNLOADstays unset, so the container opens no outbound connection at boot;scripts/download-frank-dragoon-npu.shstages them from the pinned revision with the catalog's own byte counts + sha256s (the engine re-checks both at boot)libxrt_coreutil,libxrt_core,libxrt_driver_xdna)/opt/xilinx/xrtof symlinks into the system dir dangles inside the container (vendor calls this out by name)dankclaw/host/halogen-fabric-clock{,.service}is the vendor's own hold, verbatim at tagv0.16.2(sha256 a3a4f7f4e0142e71…,5f9f1cc4c85a1c02…); the deployer installs/enables/gates it only while the lane is named, and releases the clock + drops the boot hook when it is notCost: ~1.5 GiB extra pinned host RAM per named model, ~1 W idle for the clock hold, and every workload on the iGPU runs with FCLK pinned at 2000 MHz.
Verification
./tests/run.sh— 466 passed (11 new: the off arm renders nothing lane-specific, the on arm renders device/mounts/env, each missing precondition refuses by name, deployer twins + host-unit shape + planner scoping).deploy.sh dankclaw --service frank-dragoon→ health gate → lane gate is a real embedding through/v1/embeddings, because a 200 on/healthproves nothing about the NPU programs.Not in this PR: the vault pages move with the enablement (frank-dragoon's NPU section flips to enabled once the lane is live), and the open-notebook embedding row.
cwd: /home/blank/.omp/wt/wt-20261019-020309-6d1fc1f · session: 01a15366-7601-7175-bb69-c62186c3f8b5
(build worktree: /home/blank/.omp/wt/wt-dragoon-npu)
halogen-flash-server already ships an NPU path on the pin we run: for each model named in HALOGEN_NPU_MODELS it loads the vendor's precompiled AIE programs (XDNA2 executes only AIE-compiled graphs) and answers them on /v1/embeddings inside the SAME container behind port 7770. That gives the fleet an in-house embedding endpoint without touching the iGPU trunk. The lane is opt-in and one line wide: HALOGEN_NPU_MODELS blank is the pre-lane endpoint byte for byte (no device, no mounts, no engine env). The three host preconditions the vendor names are gated in the serve script and mirrored in the deployer (the 2026-10-01 twin law), each refused by name: - the staged model files. /models is read-only and HALOGEN_DOWNLOAD stays unset so the container opens no outbound connection at boot, so scripts/download-frank-dragoon-npu.sh stages them from the pinned revision with the catalog's own byte counts and sha256s (the engine re-checks both at boot); - the distro XRT's libxrt_coreutil/libxrt_core/libxrt_driver_xdna, each mounted twice — where the image looks for XRT and at its own path — because those libraries find each other where they were installed and an /opt/xilinx/xrt of symlinks dangles inside the container; - the GPU fabric clock HELD. On Strix Halo the iGPU and the NPU co-running hang the machine (and corrupt NPU results) while FCLK changes speed, and halogen refuses to start in that state; it cannot read host sysfs from inside, so the hold is proven host-side. dankclaw/host/ carries the vendor's halogen-fabric-clock{,.service} verbatim at tag v0.16.2 (sha256 a3a4f7f4e0142e71… / 5f9f1cc4c85a1c02…); the deployer installs, enables and gates it only while the lane is named, and releases the clock plus drops the boot hook when it is not. After health, the deployer's lane gate is a real embedding through /v1/embeddings — a 200 on /health proves nothing about the NPU programs. Planner: dankclaw/host/halogen-fabric-clock* re-dispatches frank-dragoon (the deployer installs it); dankclaw/host/grub and the fetch script plan nothing. Seam: 11 new tests — the off arm really renders nothing lane-specific, the on arm renders the device/mounts/env, every missing precondition refuses by name, the deployer twins and the host unit shape are pinned. Co-Authored-By: omp <omp@dankclaw>An endpoint on podman's rootless default (pasta) has no address another container can use, measured on 2026-10-19 from inside open-notebook: - 192.168.0.143:7770 connection refused (pasta forwards the published port in the host netns; no netavark DNAT exists for traffic arriving from a rootless bridge) - host.containers.internal:7770 200, but the alias resolves to 169.254.1.2 — link-local, which open-notebook's URL guard refuses to store - frank-dragoon-halogen:8731 name unknown: different netns - --network pasta --network dankclaw_default "cannot set multiple networks without bridge network mode" — pasta cannot be added to So reaching the NPU endpoints from an app container means moving the container onto dankclaw_default (FRANK_DRAGOON_NETWORK; empty keeps today's shape). This is not an exposure change: the engine's unauthenticated token port binds the container's own loopback (/proc/net/tcp: 127.0.0.1:8730 against 0.0.0.0:8731), so only the API port becomes name-resolvable — the same trust the published 7770 already carries on the LAN. The deployer refuses a network that does not exist rather than starting a unit that restart-loops on it. Co-Authored-By: omp <omp@dankclaw>