From 2faeab665ace8420611ea09b6396e938deeaeac3 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 19 Oct 2026 12:19:36 -0700 Subject: [PATCH] live: heartbeat sync 2026-10-19T12:19:36-07:00 (Refs #11) --- .../gitea-merge-form-encoded/SKILL.md | 27 ++++++++++++ .../ryzenai-npu-oga-linux-spike/SKILL.md | 43 ++++++++++++++++++ .../sigterm-kill-test-stub/SKILL.md | 44 +++++++++++++++++++ .../state.json | 2 +- .../state.json | 2 +- .../state.json | 2 +- .../.local/state/noctalia/settings.toml | 8 ++-- 7 files changed, 121 insertions(+), 7 deletions(-) create mode 100644 agent/managed-skills/gitea-merge-form-encoded/SKILL.md create mode 100644 agent/managed-skills/ryzenai-npu-oga-linux-spike/SKILL.md create mode 100644 agent/managed-skills/sigterm-kill-test-stub/SKILL.md diff --git a/agent/managed-skills/gitea-merge-form-encoded/SKILL.md b/agent/managed-skills/gitea-merge-form-encoded/SKILL.md new file mode 100644 index 0000000..630c9a1 --- /dev/null +++ b/agent/managed-skills/gitea-merge-form-encoded/SKILL.md @@ -0,0 +1,27 @@ +--- +name: gitea-merge-form-encoded +description: "Use when a Gitea API merge on Dankclaw (192.168.0.143:3000) returns HTTP 422 \"[Do]: Required\" — the merge endpoint needs form-encoded fields, not the JSON body that PR creation accepts." +--- + +Gitea on Dankclaw (192.168.0.143:3000, `dank/serverAppConfigs`) — merge quirk, measured 2026-10-19. + +Creating a PR accepts a JSON body. **Merging does not**: `POST /api/v1/repos/{owner}/{repo}/pulls/{n}/merge` with a JSON body (`{"merge_style":"no-ff", "Do-Not-Reply-From":…, "Reply-To":…}`) or with those values as request headers returns `422 {"message":"[Do]: Required"}` — four variants of that all fail identically, so do not "fix" it by changing the style name or moving the headers around. + +Working call (form-encoded): + +```bash +tok=$(sed -n 's|http://[^:]*:\([^@]*\)@.*|\1|p' ~/.config/secrets/gitea | head -1) # git credential.helper store file +curl -sS -X POST -K <(printf 'header = "Authorization: token %s"\n' "$tok") \ + --data-urlencode 'do=merge' \ + --data-urlencode 'merge_style=no-ff' \ + --data-urlencode 'Do-Not-Reply-From=chief@franksdank.cloud' \ + --data-urlencode 'Reply-To=chief@franksdank.cloud' \ + http://192.168.0.143:3000/api/v1/repos/dank/serverAppConfigs/pulls//merge +# 200 with an empty body == merged +``` + +- `do=merge` is the field the 422 was demanding (the web-UI form field name, not an API doc field). +- `merge_style` values are the dashed names: `no-ff`, `merge`, `rebase`, `squash`. +- Keep the token out of argv: `-K <(printf …)` feeds the `Authorization` header through stdin (a `-H "Authorization: token …"` lands in `/proc//cmdline`, world-readable — the 2026-09-16 HF-token leak class). +- Verify by fast-forwarding a checkout (`git fetch && git merge --ff-only origin/main`) rather than re-reading the PR. +- Fleet law still applies: never merge without an explicit current OK from Frank, and open the PR URL in his browser (`xdg-open`) the moment it exists. diff --git a/agent/managed-skills/ryzenai-npu-oga-linux-spike/SKILL.md b/agent/managed-skills/ryzenai-npu-oga-linux-spike/SKILL.md new file mode 100644 index 0000000..6d1f803 --- /dev/null +++ b/agent/managed-skills/ryzenai-npu-oga-linux-spike/SKILL.md @@ -0,0 +1,43 @@ +--- +name: ryzenai-npu-oga-linux-spike +description: "Run a vendor-compiled AMD Ryzen AI (XDNA2) ONNX-GenAI model on a Linux Strix Halo host from public artifacts: staging, rootless NPU device-access wall, container recipe, and the NuTTS-air speech-token contract." +--- + +Run an AMD-precompiled XDNA2 model (OGA / onnxruntime-genai) on a Linux Strix Halo host (Dankclaw-class) without installing the vendor stack on the host. + +## Where the bits actually live (both public, no AMD account) + +- Runtime tarball: `https://download.amd.com/opendownload/RyzenAI/1.8.0/ryzen_ai-1.8.0.tgz` (~7.28 GiB). Verify with a ranged GET — first bytes `1f 8b 08`. +- NPU driver/XRT zip: `https://download.amd.com/opendownload/RyzenAI/Driver/RAI_1.8_Linux_NPU_XRT.zip` (90 MB). +- Dead ends (404, do not re-probe): `download.amd.com/driver/linux/RAI_1.8_Linux_NPU_XRT.zip`, `.../opendownload/RyzenAI/ryzen_ai-1.8.0.tgz`, `www.xilinx.com/bin/public/openDownload?filename=...` (301 → 404). The login-walled EULA *form* is not the gate; the `opendownload` tree is the real file host. + +Tarball has 30 top-level entries: cp312/linux_x86_64 wheels (`onnxruntime_genai_ryzenai-0.14.0`, `onnxruntime_vitisai-1.27.0`, `vitis_mllib`, `flexml*`, `device_essentials_strx`, `model_generate`, `voe-1.8.0`), plus `deployment.tar.gz` (contains `lib/libonnxruntime-genai.so`, `lib/libonnxruntime_providers_ryzenai.so`, AIE compiler libs), `onnxruntime.tar.gz`, `quicktest.tar.gz` (VitisAI-EP install validator), `LLM.tar.gz` (text-only OGA examples: `run_model.py`, `tokenizer_factory.py`), `install_ryzen_ai.sh`. `voe-4.0-linux_x86_64.tar.gz` is only `vaip_config.json` — no TTS xclbins. **AMD ships no TTS runner and no TTS example.** + +## Host rules + +- **Never `dpkg -i` the vendor XRT.** `dpkg-deb -x ` and bind-mount `/opt/xilinx/xrt` at the same path in the container. The plugin deb would install a DKMS `amdxdna` that replaces the distro in-tree driver; the in-tree driver on Ubuntu 26.04 is `amdxdna 0.7.0` and already loads `amdnpu/17f0_11/npu_7.sbin` at boot (`dmesg | grep amdxdna`). +- **Rootless containers cannot read `/dev/accel/accel0`** (`root:render 0660`): the host gid never enters the userns gid map, so `--device` yields `nobody:nogroup`. Verified failing 2026-10-19: `--group-add keep-groups`, `--userns=keep-id`, `--userns=keep-id:gid=990` (process gets gid 990, node still unreadable), `--group-add render`, `--group-add gid:990` (both error). Options: run rootful (`sudo podman`, separate storage → rebuild the image rootful, ~1 min), or add a world-writable udev rule for `accel0` (the fleet already does exactly this for `kfd`/`renderD*` in `99-comfyui-gpu-access.rules`, whose header explains why: systemd user-unit spawns carry no supplementary groups). +- **Rootless image build trap:** the vendor installer's `tar -xf` preserves recorded uids (95777 etc.) and aborts under `set -e` with `Cannot change ownership ... Invalid argument`, silently skipping the `deployment`/`onnxruntime` extraction. Patch it in the Containerfile: `sed -i 's/tar -xf/tar --no-same-owner -xf/g' install_ryzen_ai.sh`. +- Installer usage: `./install_ryzen_ai.sh -a yes -p /opt/rai -c /opt/rai` (python **3.12** required; base `python:3.12-slim`; needs `linux-libc-dev zip pciutils patch libgomp1`). It builds a venv from the local wheels, reinstalls `onnxruntime-vitisai` then `--no-deps onnxruntime-genai-ryzenai`, installs `requirements_extras.txt` (transformers 4.57.6), patches `bin/activate` (sets `RYZEN_AI_INSTALLATION_PATH=$VIRTUAL_ENV` + `LD_LIBRARY_PATH` into site-packages), and untars `LLM/deployment/onnxruntime/voe/quicktest` into the cpp path. + +## HF artifact staging + +- Gated (401 on API, 403 on git) for a plain account: `neuphonic/neucodec` (the **only** NeuCodec encoder), `neuphonic/neucodec-onnx-decoder`, `neuphonic/neucodec-onnx-decoder-int8`, `neuphonic/neutts-air`. `amd/neutts-air_rai_1.8.0_npu_4K` is public. Public mirrors of the codec exist (`eugenehp/neucodec` has the full 1.16 GB `pytorch_model.bin`) — treat a mirror of a gated release as a licensing decision for the operator, not a workaround to take silently. +- `github.com/neuphonic/neutts` is public and ships `samples/{dave,emily,greta,jo}.{pt,txt,wav}` = **pre-encoded reference codes + transcripts** → a spike needs zero gated bytes. +- Auth without leaking the token into `ps`: build a 600-perm credential file with a shell builtin and point git at it — + `umask 077; printf 'https://oauth2:%s@huggingface.co\n' "$(cat ~/.cache/huggingface/token)" > ~/.git-credentials-hf` + then `git -c credential.helper="store --file ~/.git-credentials-hf" clone ...`. Never `git clone https://oauth2:$TOKEN@...`. + +## NuTTS-air on XDNA2 (the model-side contract) + +- The package is an OGA model dir: `genai_config.json` (`type: qwen2`, 24 layers, hidden 896, 14 heads / 2 KV, ctx 32768, `provider_options: [{"RyzenAI": {"hybrid_opt_token_backend": "npu"}}]`), plus `dd_metastate_*` AIE artifacts and `model.onnx` + `model.onnx.data`. +- **Speech tokens live in the text vocab**: `<|speech_0|>` = id 151671 (10 001 codes), with `<|TEXT_PROMPT_START|>` 151666, `<|TEXT_PROMPT_END|>` 151667, `<|TEXT_REPLACE|>` 151665, `<|SPEECH_REPLACE|>` 151668, `<|SPEECH_GENERATION_START|>` 151669, `<|SPEECH_GENERATION_END|>` 151670. Generation is a plain `og.Generator` loop; the graph's own `eos_token_id` is `[151645, 151643]`, so pass 151670 as an extra eos or stop on it manually. +- **Real-time line = 50 speech tokens/s** (`sample_rate=24000`, `hop_length=480`). Judge any NPU/iGPU TTS number against 50. +- Build the prompt by *importing* the vendor function, never by copying the template: + `sys.path.insert(0, ); from neutts.neutts import NeuTTS`, then + `NeuTTS._apply_chat_template(SimpleNamespace(tokenizer=hf_tok, input_format="text"), ref_codes, ref_text, text, None)` — works on a duck-typed `self`, so no backbone/codec load. Template: `user\n<|TEXT_REPLACE|>\nassistant\n<|SPEECH_REPLACE|>`; splice `TEXT_PROMPT_START + encode(f"{ref_text} {text}") + TEXT_PROMPT_END` at TEXT_REPLACE, then truncate at SPEECH_REPLACE and append `[SPEECH_GENERATION_START] + encode("".join(f"<|speech_{i}|>"))`. +- Voice identity = reference codes + transcript (request-time conditioning). No trained artifact exists to preserve; packs holding only `reference.wav` require the gated encoder once, offline. + +## Status caveat + +Measured on 2026-10-19 up to and including: staged artifacts, image build, device-access matrix, prompt contract. The token-rate measurement (does the vendor userspace drive the in-tree 0.7.0 driver, and does it clear 50 tok/s) is **not** captured here — re-measure, never trust a remembered rate. diff --git a/agent/managed-skills/sigterm-kill-test-stub/SKILL.md b/agent/managed-skills/sigterm-kill-test-stub/SKILL.md new file mode 100644 index 0000000..e55ab03 --- /dev/null +++ b/agent/managed-skills/sigterm-kill-test-stub/SKILL.md @@ -0,0 +1,44 @@ +--- +name: sigterm-kill-test-stub +description: "Pin \"code kills its child process\" behavior with a bash stub player: make the stub SIGTERM-reactive via sleep&wait, log KILLED via trap; diagnose deferred-trap failures." +--- + +Use when a test must prove production code sends SIGTERM to a spawned child (player, worker, server stub) and the test asserts the kill happened — or when such a test mysteriously fails with `KILLED` missing and wall time equal to the stub's sleep duration. + +## The trap-during-sleep gotcha + +A bash stub that sleeps in the **foreground** does NOT run its TERM trap when signalled: bash defers traps until the current foreground command completes. The stub dies (or logs) only after the sleep finishes — long after the test read the evidence file. + +Symptoms: +- Assertion on a `KILLED` marker line fails although production code demonstrably calls `child.kill("SIGTERM")`. +- Test wall time equals the stub's full sleep (the kill "had no effect"). + +## Fix: reactive stub skeleton + +```bash +#!/usr/bin/env bash +printf '%s\n' "$*" >> "$STUB_LOG" # call record +trap 'printf "KILLED\n" >> "$STUB_LOG"; exit 143' TERM # BEFORE the sleep +for arg in "$@"; do case "$arg" in *.wav) cp "$arg" "$COPY" ;; esac; done +sleep "${STUB_HOLD:-0.1}" & # background sleep… +wait $! # …wait is interruptible +printf 'DONE\n' >> "$STUB_LOG" +``` + +- `sleep N & wait $!` is the idiom: a signal makes builtin `wait` return immediately, the trap runs at once, `exit` inside the trap prevents fallthrough to `DONE`. +- Tune duration per test via the env knob (`STUB_HOLD=5`) so the child is provably still alive when the kill is triggered. +- Put the trap BEFORE any long command; a trap registered after the sleep never guards it. + +## Harness wiring (black-box driver pattern) + +- Scenario: start the long work → settle (>spawn latency, ` per line) so tests grep output, not files: black-box seam stays output-only. +- Assert `KILLED` present; for interrupt-semantics also count (`kills -eq 1`) and total call lines (`PLAYS 3` = call, KILLED, call). + +## Orphan hygiene + +Second child still sleeping when the driver exits: spawn with `stdio: "ignore"` so the orphan holds no capture pipe — it SIGPIPEs/quiets at teardown; do not lengthen tests waiting for it. + +## Verify before trusting + +Run the pair separately: kill-tests green while a byte-equality test (`cp` of the payload before sleep) stays green — the reactive rewrite must not drop the copy step (it sits between trap and sleep). diff --git a/agent/memories/sharpshooter/wt-20261019-011132-7c8a99d-y69mcckuzbqu/state.json b/agent/memories/sharpshooter/wt-20261019-011132-7c8a99d-y69mcckuzbqu/state.json index dbc798d..ca00f3a 100644 --- a/agent/memories/sharpshooter/wt-20261019-011132-7c8a99d-y69mcckuzbqu/state.json +++ b/agent/memories/sharpshooter/wt-20261019-011132-7c8a99d-y69mcckuzbqu/state.json @@ -1,6 +1,6 @@ { "v": 1, - "lastConsolidatedAt": 1792433858713, + "lastConsolidatedAt": 1792437458615, "lastResult": { "at": 1792433135862, "sessions": 1, diff --git a/agent/memories/sharpshooter/wt-20261019-020309-6d1fc1f-8knxmfcrfa1k/state.json b/agent/memories/sharpshooter/wt-20261019-020309-6d1fc1f-8knxmfcrfa1k/state.json index 6fbbdc1..c6e68d0 100644 --- a/agent/memories/sharpshooter/wt-20261019-020309-6d1fc1f-8knxmfcrfa1k/state.json +++ b/agent/memories/sharpshooter/wt-20261019-020309-6d1fc1f-8knxmfcrfa1k/state.json @@ -1,6 +1,6 @@ { "v": 1, - "lastConsolidatedAt": 1792433709913, + "lastConsolidatedAt": 1792437542473, "lastResult": { "at": 1792433353188, "sessions": 1, diff --git a/agent/memories/sharpshooter/wt-20261019-021028-6d1fc1f-3p69q8c3d94kw/state.json b/agent/memories/sharpshooter/wt-20261019-021028-6d1fc1f-3p69q8c3d94kw/state.json index 2a7f596..1c70302 100644 --- a/agent/memories/sharpshooter/wt-20261019-021028-6d1fc1f-3p69q8c3d94kw/state.json +++ b/agent/memories/sharpshooter/wt-20261019-021028-6d1fc1f-3p69q8c3d94kw/state.json @@ -1,6 +1,6 @@ { "v": 1, - "lastConsolidatedAt": 1792433789051, + "lastConsolidatedAt": 1792434572397, "lastResult": { "at": 1792432178813, "sessions": 1, diff --git a/dotfiles/noctalia/.local/state/noctalia/settings.toml b/dotfiles/noctalia/.local/state/noctalia/settings.toml index b93317f..93e7c5c 100644 --- a/dotfiles/noctalia/.local/state/noctalia/settings.toml +++ b/dotfiles/noctalia/.local/state/noctalia/settings.toml @@ -275,13 +275,13 @@ transition = [ "fade" ] interval_seconds = 1200 [wallpaper.default] - path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_nzz3gy.jpg" + path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_oxex69.jpg" [wallpaper.last] - path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_nzz3gy.jpg" + path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_oxex69.jpg" [wallpaper.monitors.DP-1] - path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_nzz3gy.jpg" + path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_oxex69.jpg" [wallpaper.monitors.DP-2] path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_oxex69.jpg" @@ -290,7 +290,7 @@ transition = [ "fade" ] path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_v9e2w3.jpg" [wallpaper.monitors.eDP-1] - path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_nzz3gy.jpg" + path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_oxex69.jpg" [weather] unit = "imperial"