From 332067a516ff53d3a1c5b61c15667d63c2df8fc4 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 18 Oct 2026 19:52:41 -0700 Subject: [PATCH] live: heartbeat sync 2026-10-18T19:52:41-07:00 (Refs #11) --- agent/config.yml | 6 +- .../corpus-voice-skill-rewrite/SKILL.md | 30 ++++++++ .../gitea-api-pr-operations/SKILL.md | 77 +++++++++++++------ .../gitea-pr-api-cycle/SKILL.md | 29 +++++++ .../gitea-review-comment-html-scrape/SKILL.md | 42 ++++++++++ .../SKILL.md | 51 +++++++++++- .../sharpshooter/tmp-1tpw73408x3e/state.json | 2 +- .../architecture.md | 2 +- .../00mveljelz-vh49.json | 1 - .../state.json | 4 +- .../state.json | 2 +- agent/models.yml | 21 +++-- .../.local/state/noctalia/settings.toml | 8 +- 13 files changed, 231 insertions(+), 44 deletions(-) create mode 100644 agent/managed-skills/corpus-voice-skill-rewrite/SKILL.md create mode 100644 agent/managed-skills/gitea-pr-api-cycle/SKILL.md create mode 100644 agent/managed-skills/gitea-review-comment-html-scrape/SKILL.md delete mode 100644 agent/memories/sharpshooter/wt-20261017-181502-6d1fc1f-1oq2osmoyw03k/queue/01a14c93-8870-75d9-9900-9a731559feef/00mveljelz-vh49.json diff --git a/agent/config.yml b/agent/config.yml index 6fcf6e8..f11cd8d 100644 --- a/agent/config.yml +++ b/agent/config.yml @@ -13,7 +13,7 @@ modelRoles: default: llamacpp (dankserver)/frank-archon:medium smol: llamacpp (dankclaw-3090)/frank-zealot:medium web: web/searxng - speech: local/kokoro + speech: frank-tts (dankclaw)/frank-tts judge: openrouter/~typesafe/jev-latest:auto dictation: local/parakeet-tdt-0.6b-v3 memory: llamacpp (dankclaw-3090)/frank-zealot:medium @@ -206,6 +206,10 @@ speech: enabled: true mode: yield enhanced: true + # Kokoro voice for SPEAK-ALOUD only — the enum is Kokoro's, and this block never + # reaches an HTTP request. The tts tool forwards `voice_id` and nothing else, so + # a frank-tts pack name here would break the vocalizer and still leave a + # synthesis call voice-less. Say `voice_id: "narrator1"` in the call. voice: am_fenrir secrets: enabled: true diff --git a/agent/managed-skills/corpus-voice-skill-rewrite/SKILL.md b/agent/managed-skills/corpus-voice-skill-rewrite/SKILL.md new file mode 100644 index 0000000..c259569 --- /dev/null +++ b/agent/managed-skills/corpus-voice-skill-rewrite/SKILL.md @@ -0,0 +1,30 @@ +--- +name: corpus-voice-skill-rewrite +description: "Rewrite a skill to emulate an author corpus's voice: full read, style-DNA distillation from writing-for-agents levers, positive-phrasing pass, sharpened completion criteria" +--- + +# Corpus-voice skill rewrite + +Rewrite a skill so it reads as if a chosen author wrote it. Done for `grilling` against the `mattpocock/skills` corpus (2026-10-18, command-center PR #20); behaviour preserved, voice swapped. + +## Where the corpus lives + +Installed third-party skills: `~/.agents/skills//SKILL.md` (the skills-CLI global store; `~/.agents/.skill-lock.json` names sources). Whole mattpocock corpus ≈ 2,800 lines — small enough to read inline in ~4 `cat` batches via bash; do NOT delegate the reading, style lives in the exact prose. + +Read the author's own meta-skill FIRST if one exists (`writing-for-agents` for mattpocock — it states his levers explicitly), then the corpus practicing them. + +## Style DNA checklist (mattpocock; generalise per author) + +- **Leading words**: compact pretrained concepts repeated as tokens, never sentences (_red_, _seam_, _fog of war_, _brief_, _frontier_). Define each once, reuse everywhere. Prefer words already in the author's other skills so the corpus shares one language. +- **Vocabulary-first**: bolded glossary terms, then used exactly; explicit "_Avoid_: component, service". +- **Positive phrasing**: prohibitions rewritten as target behaviours; keep a prohibition only when no positive form exists (hard guardrails). +- **Sharpened completion criteria**: fuzzy bounds ("understanding reached") → checkable + exhaustive ("frontier empty and nothing sits blocked", "one artefact per question, counted before sending"). +- **Zero history**: no provenance, attribution, or absorbed-from lineage in the body. +- **No-op hunting**: delete harness defaults and anything the environment already confesses (formats, rendering, caps). Gotchas the environment CANNOT confess (exclusive dialogs, Esc-abort semantics) stay inline — they're the cache worth keeping. + +## Procedure + +1. Read entire corpus; tabulate levers with per-skill evidence before writing. +2. Rewrite preserving settled behaviour 1:1 — voice only; diff the two versions for semantic drift. +3. Audit pass: grep the new text for history words (extracted/absorbed/from/upstream), machine-specific constants, repo-specific paths. +4. Run repo checks, commit as `style()`, note the lever→instance mapping in the PR comment so the reviewer can grade the emulation. diff --git a/agent/managed-skills/gitea-api-pr-operations/SKILL.md b/agent/managed-skills/gitea-api-pr-operations/SKILL.md index 754bbfa..a2a5c32 100644 --- a/agent/managed-skills/gitea-api-pr-operations/SKILL.md +++ b/agent/managed-skills/gitea-api-pr-operations/SKILL.md @@ -1,31 +1,64 @@ --- name: gitea-api-pr-operations -description: "Open, list, merge, and diagnose issues/PRs on the Dankclaw Gitea API (192.168.0.143:3000, dank/command-center) with curl + token — merge payload quirk included" +description: "Open, list, merge, and diagnose issues/PRs on the Dankclaw Gitea API (192.168.0.143:3000, repo dank/command-center) with the token stored in ~/.config/secrets/gitea — incl. the required merge payload and where review comments actually live on 1.27.3." --- -# Gitea API PR operations on Dankclaw +# Gitea API PR operations (Dankclaw `dank/command-center`) -Verified 2026-10-18 on Gitea at `http://192.168.0.143:3000`, repo `dank/command-center` (owner `dank`). +## Where +- Token: `~/.config/secrets/gitea` (`http://user:TOKEN@192.168.0.143` single line). + Extract WITHOUT echoing: + `tok=$(sed -nE 's#^http://[^:/@]+:([^@]+)@192\.168\.0\.143.*#\1#p' ~/.config/secrets/gitea | head -1)` +- Build a 600-perm temp curl config and use `curl -K`: + `cfg=$(mktemp) && chmod 600 "$cfg" && printf 'header = "Authorization: token %s"\n' "$tok" > "$cfg"` + Delete `$cfg` after use. NEVER pass `-H "Authorization: token $tok"` (the literal + header lands in transcript + logs). NEVER echo the token. +- Base URL: `http://192.168.0.143:3000/api/v1/repos/dank/command-center`. -## Token handling (never echo) -```bash -tok=$(sed -nE 's#^http://[^:/@]+:([^@]+)@192\.168\.0\.143.*#\1#p' ~/.config/secrets/gitea | head -1) -cfg=$(mktemp) && chmod 600 "$cfg" && printf 'header = "Authorization: token %s"\n' "$tok" > "$cfg" -# use: curl -sS -K "$cfg" ... then rm -f "$cfg" -``` +## Recipes (all verified live 2026-10-18) +- **Open PR** (token only needed for private repos; this one reads publicly but + POST needs auth): `POST /pulls` JSON `{title, head, base:"main", body}`. +- **PATCH PR title/body**: `PATCH /pulls/` with JSON body `{title}` and/or + `{body}`. Returns 201 with the full PR. Use `jq -n --arg` to build JSON; for + multi-line bodies write the text to a temp file and `--arg b "$(cat b.txt)"`. + (jq CLI: a bare `---` argument after the program is parsed as a flag → error; + fold footers into the --arg string instead.) +- **List open PRs**: `GET /pulls?state=open` → `jq -r '.[] | "#\(.number) \(.head.ref) \(.title)"'`. +- **Read PR**: `GET /pulls/` (no token; public repo) → `jq -c '{state,merged,mergeable,head:.head.ref}'`. +- **MERGE**: `POST /pulls//merge` with `-H 'Content-Type: application/json'` + and body **`{"Do":"merge"}`** — this build REQUIRES the `Do` field: + `{}` or `{"DoNotMerge":false,...}` → 422; `{"Do":"merge"}` → 200 + + `{"state":"closed","merged":true,"merge_commit_sha":"..."}`. Verify with a + plain GET /pulls/ afterwards (merge endpoint's own stdout may not parse). + Merge is the `--no-ff` merge commit on main; then `git -C ~/.omp fetch && git + merge --ff-only origin/main` propagates the live deployment. +- **Issues**: same shape at `/issues` (`POST {title, body}`), `/issues//comments` + (`POST {body}`, `PATCH .../comments/`) — both work with the temp-cfg auth. -## Endpoints that work -- Open PR: `POST /api/v1/repos/dank/command-center/pulls` with JSON `{title, head, base, body}` (build with `jq -n --arg`; `head` = branch name as-is, slashes included). -- Patch PR (title/body): `PATCH .../pulls/` — works, returns 201 with full PR JSON (`mergeable` field handy). -- **Merge PR: `POST .../pulls//merge` with body `{"Do":"merge"}`** — capital-D `Do` is this Gitea's field (mirrors web UI form). `{}` or `{"DoNotMerge":false,...}` → 422 `[Do]: Required`. Success may return EMPTY body — verify with a separate `GET .../pulls/` for `{state:"closed", merged:true, merge_commit_sha}`. -- Issues: `POST .../issues` `{title, body}`; patch `PATCH .../issues/`. -- Issue/PR bodies MUST end with the AGENTS.md footer: `---` line, then `cwd: · session: `. Embed the footer inside the body string (a bare `---` as a separate shell arg breaks `jq` arg parsing). -- `xdg-open ` lands the PR/issue in Frank's Firefox — do it the moment a PR opens. +## Review comments: the API lies on 1.27.3 (learned 2026-10-18) +- `GET /pulls//comments` (inline review comments) → **404 `not found` on every + route** (token or not, `?pull_request_id=` or not) even when a review with + comments exists. +- `GET /pulls//reviews` works (shows `state`, `comments_count`) but bodies are empty. +- **The comments are readable from the PR HTML**: + `curl -sS http://192.168.0.143:3000/dank/command-center/pulls/ -o /tmp/pr.html` + then extract `
` blocks + (python: `re.finditer(r'comment-content', src)` → widen ±800 chars, strip tags). + Duplicates appear (suggested-change UI shows text twice); dedupe mentally. +- Per-conversation resolve API also 404s — flag resolutions in an issue comment instead. + +## Body-footer law (AGENTS.md) +Every issue/PR body ends with `---` line then `cwd: · session: `. Keep +it inside the --arg string (not a `---` CLI arg). ## Gotchas -- POST/PATCH need `-H 'Content-Type: application/json'` on the curl command line; `-d @file` alone with a wrong content type yields 422 `"model.assignUsers: Bind is required"`. -- Merge-with-empty-response is normal; never conclude failure from blank stdout — GET the PR. -- No `tea` CLI anywhere; API + curl is the only path. GITEA_TOKEN env exists in `~/.omp/.env` (gitignored) as alternative source. - -## Post-merge propagation -The `~/.omp` checkout IS the deployment: `git -C ~/.omp fetch && git merge --ff-only origin/main` (dirty runtime files don't block ff). Never merge a PR at all without Frank's explicit OK. +- Content-Type: POSTs with `-d @file.json` MUST carry + `-H 'Content-Type: application/json'` on the command line. `-H` entries put in + the curl config file are ignored for this (header ordering quirk). +- `xdg-open ` lands the PR in Frank's Firefox the moment it is opened — + law, not courtesy. +- `jq: cannot index "not found" with "user"` = endpoint 404'd into an error + object; check `-w '%{http_code}'` before blaming jq. +- The token in `.git/config`'s remote URL is the same secret; the credential + file (`~/.config/secrets/gitea`) is the one git actually consults (global + credential.helper) and the one to reuse for API calls. diff --git a/agent/managed-skills/gitea-pr-api-cycle/SKILL.md b/agent/managed-skills/gitea-pr-api-cycle/SKILL.md new file mode 100644 index 0000000..1226922 --- /dev/null +++ b/agent/managed-skills/gitea-pr-api-cycle/SKILL.md @@ -0,0 +1,29 @@ +--- +name: gitea-pr-api-cycle +description: "Open/update/merge PRs and issues on the Dankclaw Gitea API (192.168.0.143:3000, dank/command-center) with the token-safe curl+jq sequence, including the {\"Do\":\"merge\"} merge payload and post-merge live-checkout fast-forward." +--- + +# Gitea PR/issue cycle — dank/command-center + +Friction-earned 2026-10-18 across PRs #15–#20 and issue #18: three separate fumbles (jq trailer flag, 422 merge payload, silent -o/-w swallowing) codified once. + +## Where +- API base: `http://192.168.0.143:3000/api/v1/repos/dank/command-center/{pulls,issues}` +- Token: never inline, never echoed. Extract from the git credential file into a 600-perm temp curl config: + `tok=$(sed -nE 's#^http://[^:/@]+:([^@]+)@192\.168\.0\.143.*#\1#p' ~/.config/secrets/gitea | head -1)` + `cfg=$(mktemp); chmod 600 "$cfg"; printf 'header = "Authorization: token %s"\n' "$tok" > "$cfg"` → `curl -K "$cfg"`; `rm -f "$cfg"` when done. +- Unauthenticated GET works for state reads (`GET /pulls/N` → `jq -c '{state,merged,merge_commit_sha}'`). + +## Body construction (the two traps) +- Build JSON with `jq -n --arg t --arg b "$(cat b.txt)" '{title:$t, head:<branch>, base:"main", body:$b}'`; write the body to a temp file first (`printf '%s' … > b.txt`). NEVER pass `---` as a bare jq argument — jq parses it as a flag (`unknown flag: ---`). The AGENTS.md attribution trailer (`---` + `cwd: … · session: …`) goes INSIDE the body string. +- Always capture status explicitly: `-o r.json -w '%{http_code}'` and print it; a bare `-o` hides failures (a 422 looked like success-with-no-output once). + +## Merge (the 422 trap) +`POST /pulls/N/merge` requires body `{"Do":"merge"}` — `Do` is the Required field on this Gitea. `{}`, `{"DoNotMerge":false}`, `{"merge_style":…}` all return 422 (`"[Do]: Required"`). Success may return an EMPTY body — verify by GET afterwards (`state=closed, merged=true`), not by the POST response. +- Merge only with Frank's explicit per-PR OK (repo law). +- Post-merge the live deployment must follow: `git -C ~/.omp fetch -q origin && git -C ~/.omp merge --ff-only origin/main` (the checkout IS the deployment; branch is `main`). + +## PR etiquette per AGENTS.md +- Immediately after opening: `xdg-open <PR-URL>` lands it in Frank's Firefox. +- Redesign mid-review: amend + `git push -f` the branch, then PATCH the PR body (`/pulls/N`, `{"body":…}`) so title/body match the net diff — verify `{number,state,mergeable}` in the response. +- Issue creation identical shape: `POST /issues` with `{title, body}`, expect 201; PATCH same endpoint `/issues/N` to fix bodies retroactively, then GET-readback verify. diff --git a/agent/managed-skills/gitea-review-comment-html-scrape/SKILL.md b/agent/managed-skills/gitea-review-comment-html-scrape/SKILL.md new file mode 100644 index 0000000..512a526 --- /dev/null +++ b/agent/managed-skills/gitea-review-comment-html-scrape/SKILL.md @@ -0,0 +1,42 @@ +--- +name: gitea-review-comment-html-scrape +description: "Read inline review comments on the Dankclaw Gitea when pulls/N/comments 404s (Gitea 1.27.3): scrape comment-content blocks and diff anchors from the PR HTML page" +--- + +# Gitea review-comment extraction when the API 404s + +On the Dankclaw Gitea (192.168.0.143:3000, Gitea 1.27.3) the review-comment API is dead for inline comments: +`/api/v1/repos/{owner}/{repo}/pulls/{N}/comments` returns 404 `not found` **even with a valid token**, and `pulls/comments?pull_id=` / `?pull_request_id=` variants 404 too. `/pulls/{N}/reviews` works but its bodies are empty for inline reviews (read `comments_count` for how many exist); `/issues/{N}/comments` shows only conversation comments, never inline ones. + +## Procedure + +1. Confirm the review exists and how many inline comments it holds: + `curl -sS http://192.168.0.143:3000/api/v1/repos/dank/command-center/pulls/{N}/reviews | jq -c '.[] | {id, state, comments_count}'` +2. Scrape the PR page instead (no token needed for a public repo): + `curl -sS http://192.168.0.143:3000/dank/command-center/pulls/{N} -o /tmp/pr.html` +3. Extract each comment's text — every inline comment renders a `comment-content` block; dedupe (each comment appears twice in the HTML): + +```python +import re, html +src = open('/tmp/pr.html').read() +seen = set() +for m in re.finditer(r'comment-content', src): + t = html.unescape(re.sub(r'<[^>]+>', ' ', src[m.start()-100:m.start()+800])) + t = re.sub(r'\s+', ' ', t).strip()[:400] + if t not in seen: + seen.add(t); print('>>', t) +``` + +The comment text appears twice back-to-back inside each block ("text text") — read it once. Blocks start after `Copy Link|Copy Source`; ignore those chrome words. + +4. **Locate the file/line anchor** (comment-content blocks carry no `data-path`/`data-line` attrs on this build): search the raw HTML for the comment string, walk *backward* ~4–7 KB, strip tags; the nearby `agent/...` path, `@@ -a,b +c,d @@` hunk header, and surrounding diff context lines identify the anchored file and the quoted source line. + +## Replying / housekeeping + +- Per-conversation resolve API does not exist on this build (404s) — reply to the round as a normal issue comment (`POST /repos/{o}/{r}/issues/{N}/comments`, token via `curl -K` temp cfg, `header = "Authorization: token …"`, 600 perms, `jq -n --arg b "$(cat b.txt)" '{body:$b}'`) listing which note got which fix commit. +- Edit a posted comment: `PATCH /repos/{o}/{r}/issues/{N}/comments/{id}` with `{"body": ...}` — use to fix your own typos rather than double-posting. +- Token source on this box: `~/.config/secrets/gitea` (grep the remote-URL pattern); never echo it. + +## Why this works + +The PR page is server-rendered with full inline comment bodies; Gitea's "Copy Source" UI element guarantees the raw text is present pre-markdown-render. diff --git a/agent/managed-skills/omp-models-yml-runner-api-placement/SKILL.md b/agent/managed-skills/omp-models-yml-runner-api-placement/SKILL.md index 777e267..3eed00b 100644 --- a/agent/managed-skills/omp-models-yml-runner-api-placement/SKILL.md +++ b/agent/managed-skills/omp-models-yml-runner-api-placement/SKILL.md @@ -1,12 +1,14 @@ --- name: omp-models-yml-runner-api-placement -description: Add or edit a non-chat (tts/image/embedding) model row in ~/.omp/agent/models.yml without disabling every custom provider +description: "Add or edit a non-chat (tts/image/embedding) model row in ~/.omp/agent/models.yml, wire the speech role, and diagnose a failing speech device without blaming a healthy endpoint" --- # Scope `~/.omp/agent/models.yml` (or `models.yaml`) on a workstation: adding a **runner** -model row (TTS / image / embedding / rerank / video) alongside chat providers. +model row (TTS / image / embedding / rerank / video) alongside chat providers, +flipping `config.yml` `modelRoles` at it, and diagnosing a speech device that +refuses to speak. # The landmine @@ -63,9 +65,52 @@ yielding. # Speech transport contract (`openai-speech`) Body is `{model, input, response_format, voice?, speed?}`; `voice` is forwarded -only when explicitly set; `sample_rate`/`bit_rate` **throw** +**only** when the caller passed `voice_id`; `sample_rate`/`bit_rate` **throw** (`openai-speech does not support sampleRate`). Codec comes from the `output_path` suffix; 15 000-character cap. An unknown `voice` name is a server error, not a fallback — and `auth: none` makes a dead endpoint read as *available*, so point `modelRoles.speech` at such a provider only after it has answered a real synthesis. + +# Three speech-role laws (bundle-read + measured 2026-10-18) + +1. **No setting carries a voice for `openai-speech`.** The transport builds the + body with `voice` from the call's `voice_id` and nothing else. + `tts.localVoice` is the *local Kokoro backend's* key; `speech.voice` is the + TUI's **speak-aloud** voice, an enum of Kokoro names that never reaches an + HTTP request (setting it to a pack name breaks the vocalizer and does nothing + for synthesis — read `cfgSpeechVoice` in `dist/cli.js` before believing + otherwise). So a voice-less call is the harness's **normal** shape: a + voice-required endpoint needs a server-side default (fleet answer: + `DEFAULT_VOICE` in `scripts/frank-tts.env`, journal marks the line + `(default)`, unknown names still 400 with the list). +2. **An explicit role has no fallback candidate.** `modelRoles.speech` set to one + model yields exactly one candidate, so a dead endpoint or an unresolvable + selector fails with *"No available speech model matches the speech role"* + instead of degrading to Kokoro. Loud-by-design; do not "fix" it by adding a + fallback in the client. +3. **`models.yml` loads at process start; `config.yml` is read live.** A session + older than the new row fails exactly like a dead endpoint while a fresh + process succeeds in the same minute. + +# Diagnosing "speech device won't speak" — in this order + +1. `curl` the endpoint directly with a real voice name. 200 + audio ⇒ the service + is fine and the fault is harness-side; stop touching the server. +2. Control test in the *same* session: point `modelRoles.speech` at + `local/kokoro` and call the device. It works ⇒ `config.yml` is live-read and + the runner selector or its voice argument is the problem, not the endpoint. + Revert the role immediately after. +3. Distinguish stale registry from dead endpoint with a fresh process: + `omp -p --no-session --no-extensions --no-skills --no-rules --tools tts --cwd /tmp + 'Call the tts tool exactly once with text="…" output_path=/tmp/x.wav + voice_id=<name> — then reply with the tool result line.'` + Succeeds ⇒ the long-lived session predates the `models.yml` row; restart it. +4. Cross-check server-side, never trust the client's echo: the façade/access log + line and the engine's own ledger must carry the same byte count the tool + printed. (`model=…` in a tool result is what the client *requested*; a server + line is the fact.) +5. For harness-behaviour claims, read the bundle rather than reasoning from the + tool description: `/home/blank/.bun/install/global/node_modules/@oh-my-pi/pi-coding-agent/dist/cli.js`, + e.g. `grep -o -E '.{300}response_format:[a-zA-Z$]+.{250}'` for the speech body + builder, `grep -o -E 'cfgSpeechVoice:.{0,400}'` for what a settings key feeds. diff --git a/agent/memories/sharpshooter/tmp-1tpw73408x3e/state.json b/agent/memories/sharpshooter/tmp-1tpw73408x3e/state.json index 5ba4fe8..7601deb 100644 --- a/agent/memories/sharpshooter/tmp-1tpw73408x3e/state.json +++ b/agent/memories/sharpshooter/tmp-1tpw73408x3e/state.json @@ -1,4 +1,4 @@ { "v": 1, - "lastConsolidatedAt": 1792359739617 + "lastConsolidatedAt": 1792378334862 } diff --git a/agent/memories/sharpshooter/wt-20261017-181502-6d1fc1f-1oq2osmoyw03k/architecture.md b/agent/memories/sharpshooter/wt-20261017-181502-6d1fc1f-1oq2osmoyw03k/architecture.md index 3fbe13a..f6d141e 100644 --- a/agent/memories/sharpshooter/wt-20261017-181502-6d1fc1f-1oq2osmoyw03k/architecture.md +++ b/agent/memories/sharpshooter/wt-20261017-181502-6d1fc1f-1oq2osmoyw03k/architecture.md @@ -115,4 +115,4 @@ This rule exists because subagent hangs were observed in early deployments (2026 Before declaring a subagent deployed: verify the heartbeat endpoint returns within the timeout window, and confirm the recovery path is wired (retry/fallback/escalation logic exists in the codepath). -A subagent without a heartbeat or recovery path is a gap: wire the heartbeat, add the recovery path, then deploy. +A subagent without a heartbeat or recovery path is a gap: wire the heartbeat, add the recovery path, then deploy. \ No newline at end of file diff --git a/agent/memories/sharpshooter/wt-20261017-181502-6d1fc1f-1oq2osmoyw03k/queue/01a14c93-8870-75d9-9900-9a731559feef/00mveljelz-vh49.json b/agent/memories/sharpshooter/wt-20261017-181502-6d1fc1f-1oq2osmoyw03k/queue/01a14c93-8870-75d9-9900-9a731559feef/00mveljelz-vh49.json deleted file mode 100644 index d78e538..0000000 --- a/agent/memories/sharpshooter/wt-20261017-181502-6d1fc1f-1oq2osmoyw03k/queue/01a14c93-8870-75d9-9900-9a731559feef/00mveljelz-vh49.json +++ /dev/null @@ -1 +0,0 @@ -{"v":1,"kind":"product_decision","statement":"merge 15 and deploy","source":"contextual_resolution","evidence":"you can merge 15 and deploy","friction":{"corrective":false,"regression":false,"subtle":false},"sessionId":"01a14c93-8870-75d9-9900-9a731559feef","ts":1792374566327} \ No newline at end of file diff --git a/agent/memories/sharpshooter/wt-20261017-181502-6d1fc1f-1oq2osmoyw03k/state.json b/agent/memories/sharpshooter/wt-20261017-181502-6d1fc1f-1oq2osmoyw03k/state.json index f2e9791..3dd740f 100644 --- a/agent/memories/sharpshooter/wt-20261017-181502-6d1fc1f-1oq2osmoyw03k/state.json +++ b/agent/memories/sharpshooter/wt-20261017-181502-6d1fc1f-1oq2osmoyw03k/state.json @@ -1,8 +1,8 @@ { "v": 1, - "lastConsolidatedAt": 1792374446411, + "lastConsolidatedAt": 1792378149820, "lastResult": { - "at": 1792373460132, + "at": 1792374778678, "sessions": 1, "deltas": 1, "model": "frank-zealot" diff --git a/agent/memories/sharpshooter/wt-20261018-170616-7c8a99d-3bf6g32a51amg/state.json b/agent/memories/sharpshooter/wt-20261018-170616-7c8a99d-3bf6g32a51amg/state.json index e3995b7..8ad8823 100644 --- a/agent/memories/sharpshooter/wt-20261018-170616-7c8a99d-3bf6g32a51amg/state.json +++ b/agent/memories/sharpshooter/wt-20261018-170616-7c8a99d-3bf6g32a51amg/state.json @@ -1,6 +1,6 @@ { "v": 1, - "lastConsolidatedAt": 1792374676416, + "lastConsolidatedAt": 1792378276529, "lastResult": { "at": 1792374324899, "sessions": 1, diff --git a/agent/models.yml b/agent/models.yml index c06e53d..2f2eb02 100644 --- a/agent/models.yml +++ b/agent/models.yml @@ -269,14 +269,19 @@ providers: # when explicitly set, and sampleRate/bitRate are REJECTED outright (the # transport throws "openai-speech does not support sampleRate"), so the # codec comes from the output_path suffix and the only tunable that matters - # is the voice name — which must be a real pack name (narrator1/2/3); an - # unknown name is an engine error, not a fallback. - # DELIBERATELY NOT WIRED YET: config.yml `modelRoles.speech` still points at - # local/kokoro. Flip it to "frank-tts (dankclaw)/frank-tts" only after the - # service is deployed and has answered a real synthesis, because `auth: none` - # makes the provider read as AVAILABLE even while the container is down — the - # role would then fail instead of falling back to Kokoro. The same flip must - # re-key `speech.voice` (now am_fenrir, a Kokoro voice) to a pack name. + # is the voice name — and it comes from ONE place: the tool's `voice_id` + # argument. No setting feeds it (`speech.voice` is the speak-aloud Kokoro enum, + # `tts.localVoice` is the local backend's), so a call that omits voice_id arrives + # at 7772 with no `voice` key and the endpoint answers 400 "unknown voice None; + # available voices: narrator1, narrator2, narrator3". Say the voice in the call. + # WIRED 2026-10-18: `config.yml` `modelRoles.speech` is this row, flipped only + # after the service was deployed and answered real synthesis (two packs, + # different bytes, 24 kHz PCM16, RTF 0.57). Why the gate existed: `auth: none` + # makes this provider read as AVAILABLE even while the container is down, and an + # explicit speech role has NO fallback candidate, so a dead 7772 fails the device + # loudly instead of quietly narrating in Kokoro. Reverting is ONE key: + # `modelRoles.speech: local/kokoro`. `speech.voice` stays a Kokoro name either + # way — it was never this endpoint's voice. # SCHEMA LAW, learned the hard way (2026-10-18): provider-level `api` must be # a CHAT transport — the models.yml schema accepts only the chat/judge APIs # there, and naming a runner API (`openai-speech`) at provider level fails the diff --git a/dotfiles/noctalia/.local/state/noctalia/settings.toml b/dotfiles/noctalia/.local/state/noctalia/settings.toml index 9e9181c..65fe783 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_1kdgpg.jpg" + path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_odl3r9.jpg" [wallpaper.last] - path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_1kdgpg.jpg" + path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_odl3r9.jpg" [wallpaper.monitors.DP-1] - path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_1kdgpg.jpg" + path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_odl3r9.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_1kdgpg.jpg" + path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_odl3r9.jpg" [weather] unit = "imperial"