Compare commits
43
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
986081bf1d | ||
|
|
a9b3c495da | ||
|
|
c99eb8e11b | ||
|
|
82dd0c7d09 | ||
|
|
99b9a6a17c | ||
|
|
39385636be | ||
|
|
651924a4bc | ||
|
|
e86e60af39 | ||
|
|
01d9d3fbd5 | ||
|
|
570b5509d0 | ||
|
|
3579c7ebc5 | ||
|
|
0075ca3bbf | ||
|
|
bcd52bb0f4 | ||
|
|
edd7e0cf68 | ||
|
|
f033639b54 | ||
|
|
6fffd83957 | ||
|
|
dd8f3974c1 | ||
|
|
acf67182e9 | ||
|
|
38bf9c4897 | ||
|
|
54f15e2a58 | ||
|
|
1e7e212e5c | ||
|
|
475211d2e7 | ||
|
|
0258ef70f8 | ||
|
|
0c7fe8b4bf | ||
|
|
765b686859 | ||
|
|
332067a516 | ||
|
|
66e3f220e7 | ||
|
|
6134e1168e | ||
|
|
c379d16858 | ||
|
|
ca8b46241e | ||
|
|
3fa0242f56 | ||
|
|
fe039a0b73 | ||
|
|
d4092f2f65 | ||
|
|
cedef97c99 | ||
|
|
0ee08bc368 | ||
|
|
6664f9090a | ||
|
|
7de07ee35c | ||
|
|
fcc7226c3b | ||
|
|
a8011c3d4d | ||
|
|
08f90c6df3 | ||
|
|
57e255f930 | ||
|
|
b3d3df4bdb | ||
|
|
5d523f6e9f |
No files matched your search
@@ -100,15 +100,18 @@ secret in every agent's context window on every machine that pulls this repo.
|
|||||||
|
|
||||||
| box | role | GPU | notes |
|
| box | role | GPU | notes |
|
||||||
|-----|------|-----|-------|
|
|-----|------|-----|-------|
|
||||||
| Dev workstation | client box, never runs inference | laptop iGPU + dGPU (non-unified) | mainline llama.cpp at `~/GitPackages/llama.cpp/build/bin/`; this repo lives here at `~/.omp` |
|
| Dev workstation (`blankpad`) | client box, never runs inference | Lenovo laptop, AMD Ryzen 5 PRO 5650U / Cezanne APU (**unified memory**, s2idle sleep only, no dGPU) | this repo lives here at `~/.omp`; the earlier "iGPU + dGPU / `~/GitPackages/llama.cpp`" description belonged to the sold `dankware` box |
|
||||||
| Dankclaw | bench target, secondary models + service stack | Strix Halo APU (**unified memory**) + RTX 3090 (moved over from Dankserver 2026-09) | prod services `frank-dragoon` (halogen-flash-server, :7770, iGPU) and `frank-zealot` (llama.cpp PrismML fork, :7771, 3090); runbooks in the serverAppConfigs vault (`~/Projects/serverAppConfig/serverAppConfigs-docs/LLM/`); ssh: `ssh dankclaw` |
|
| Dankclaw | bench target, secondary models + service stack | Strix Halo APU (**unified memory**) + RTX 3090 (moved over from Dankserver 2026-09) | prod services `frank-dragoon` (halogen-flash-server, :7770, iGPU) and `frank-zealot` (llama.cpp PrismML fork, :7771, 3090); runbooks in the serverAppConfigs vault (`~/Projects/serverAppConfig/serverAppConfigs-docs/LLM/`); ssh: `ssh dankclaw` |
|
||||||
| Dankserver | main-model box | 2× Radeon AI PRO R9700 — non-unified (was RTX 3090 until 2026-09) | runs `frank-archon` (radiance, TP=2, :7770) — **this repo's operator session runs on it**; ssh: `ssh dankserver` |
|
| Dankserver | main-model box | 2× Radeon AI PRO R9700 — non-unified (was RTX 3090 until 2026-09) | runs `frank-archon` (radiance, TP=2, :7770) — **this repo's operator session runs on it**; ssh: `ssh dankserver` |
|
||||||
|
|
||||||
## Environment gotchas
|
## Environment gotchas
|
||||||
|
|
||||||
- Gitea API needs a token: `GITEA_TOKEN` env (gitignored `.env`). No `tea` CLI
|
- Gitea API needs a token: `GITEA_TOKEN` env (gitignored `.env`). No `tea` CLI
|
||||||
installed anywhere. Remote URLs embed the token locally in `.git/config` — that
|
installed anywhere. Remote URLs carry NO embedded token (2026-10-09
|
||||||
file is never pushed; it is not a substitute for the secret gate.
|
credential-store cutover) — git and the API both read
|
||||||
|
`~/.config/secrets/gitea` (`store --file`, URL-line format; see
|
||||||
|
`omp-docs/tools/gitea/client-access.md`). That file is never pushed; it is
|
||||||
|
not a substitute for the secret gate.
|
||||||
- Passwordless sudo (`sudo -n`) works on Dankclaw and Dankserver for systemctl
|
- Passwordless sudo (`sudo -n`) works on Dankclaw and Dankserver for systemctl
|
||||||
verbs only; user units need no sudo at all.
|
verbs only; user units need no sudo at all.
|
||||||
- SSH into both servers is BatchMode-capable from here:
|
- SSH into both servers is BatchMode-capable from here:
|
||||||
|
|||||||
@@ -96,6 +96,17 @@ Never assume the user reports correct information. He is human, and humans make
|
|||||||
- Always practice Test Driven Development practices; do UNIT TESTS exist for the functionality about to be edited? Is there a LINTER? Is there a FORMATTER? Verify that they exist, and that they are configured as a pre-commit hook. If they do not exist, they MUST be written PRIOR to the code being written. Invoke `/skill:tdd` for preferred testing style. If tests do exist, ensure that they still match the planned changes. If you believe a PRE-EXISTING test must be changed prior to commencing work, ASK THE USER ABOUT IT.
|
- Always practice Test Driven Development practices; do UNIT TESTS exist for the functionality about to be edited? Is there a LINTER? Is there a FORMATTER? Verify that they exist, and that they are configured as a pre-commit hook. If they do not exist, they MUST be written PRIOR to the code being written. Invoke `/skill:tdd` for preferred testing style. If tests do exist, ensure that they still match the planned changes. If you believe a PRE-EXISTING test must be changed prior to commencing work, ASK THE USER ABOUT IT.
|
||||||
- Always ensure tests run on code changes after every commit, and all project tests run before every push using git hooks, EXCEPT in the event that the individual test in question takes an excessive amount of time. Always ask the user before disabling an individual due to running time or performance concerns.
|
- Always ensure tests run on code changes after every commit, and all project tests run before every push using git hooks, EXCEPT in the event that the individual test in question takes an excessive amount of time. Always ask the user before disabling an individual due to running time or performance concerns.
|
||||||
|
|
||||||
|
|
||||||
|
### 6. Keep the Todo List LIVE, Not Retrospective
|
||||||
|
|
||||||
|
The todo list is the user's cockpit view of your work. A list that only flips at the end is a history dump, not tracking - the user sees nothing while you work.
|
||||||
|
|
||||||
|
- Multi-step work: `todo init` BEFORE the first substantive step - the list must exist while the work is unseen, not after.
|
||||||
|
- Batch the todo transition with the step it describes: `done` + `start` ride along in the same message as the first tool calls of the next step. NEVER defer flips until the whole bill of tasks is finished.
|
||||||
|
- A task is `done` the moment its work is verified - not when the surrounding phase ends.
|
||||||
|
- Blocked on external input (user answer, subagent, service)? `block` it with a reason so the stall is visible; `unblock` the instant it is actionable.
|
||||||
|
- NEVER make a todo call the only call in a message - that is ceremony, not progress; pair every flip with real work.
|
||||||
|
|
||||||
## FORBIDDEN ACTS AND MISTAKES TO NOT REPEAT (DO NOT DO THESE):
|
## FORBIDDEN ACTS AND MISTAKES TO NOT REPEAT (DO NOT DO THESE):
|
||||||
- Editing node_modules or similar import module directories. This is third party code that will be overwritten next time `npm update` or a similar command is run.
|
- Editing node_modules or similar import module directories. This is third party code that will be overwritten next time `npm update` or a similar command is run.
|
||||||
- `git push` - unless directed, except if it's our local gitea instance on Dankclaw.
|
- `git push` - unless directed, except if it's our local gitea instance on Dankclaw.
|
||||||
|
|||||||
+18
-5
@@ -1,6 +1,10 @@
|
|||||||
providers:
|
providers:
|
||||||
openrouterVariant: online
|
openrouterVariant: online
|
||||||
fetch: auto
|
fetch: auto
|
||||||
|
maxInFlightRequests:
|
||||||
|
llamacpp (dankserver): 3
|
||||||
|
llamacpp (dankclaw): 2
|
||||||
|
llamacpp (dankclaw-3090): 3
|
||||||
modelRoles:
|
modelRoles:
|
||||||
slow: llamacpp (dankclaw)/frank-dragoon:medium
|
slow: llamacpp (dankclaw)/frank-dragoon:medium
|
||||||
vision: llamacpp (dankclaw-3090)/frank-zealot:xhigh
|
vision: llamacpp (dankclaw-3090)/frank-zealot:xhigh
|
||||||
@@ -13,7 +17,7 @@ modelRoles:
|
|||||||
default: llamacpp (dankserver)/frank-archon:medium
|
default: llamacpp (dankserver)/frank-archon:medium
|
||||||
smol: llamacpp (dankclaw-3090)/frank-zealot:medium
|
smol: llamacpp (dankclaw-3090)/frank-zealot:medium
|
||||||
web: web/searxng
|
web: web/searxng
|
||||||
speech: local/kokoro
|
speech: frank-tts (dankclaw)/frank-tts
|
||||||
judge: openrouter/~typesafe/jev-latest:auto
|
judge: openrouter/~typesafe/jev-latest:auto
|
||||||
dictation: local/parakeet-tdt-0.6b-v3
|
dictation: local/parakeet-tdt-0.6b-v3
|
||||||
memory: llamacpp (dankclaw-3090)/frank-zealot:medium
|
memory: llamacpp (dankclaw-3090)/frank-zealot:medium
|
||||||
@@ -145,17 +149,25 @@ display:
|
|||||||
subagentLivePreview: true
|
subagentLivePreview: true
|
||||||
task:
|
task:
|
||||||
showResolvedModelBadge: true
|
showResolvedModelBadge: true
|
||||||
maxConcurrency: 3
|
maxConcurrency: 2
|
||||||
agentModelOverrides:
|
agentModelOverrides:
|
||||||
designer: llamacpp (dankclaw-3090)/frank-zealot
|
designer: llamacpp (dankclaw-3090)/frank-zealot
|
||||||
librarian: llamacpp (dankclaw-3090)/frank-zealot
|
librarian: llamacpp (dankclaw-3090)/frank-zealot
|
||||||
reviewer: llamacpp (dankserver)/frank-archon
|
reviewer: llamacpp (dankserver)/frank-archon
|
||||||
scout: llamacpp (dankserver)/frank-archon
|
scout: llamacpp (dankclaw-3090)/frank-zealot
|
||||||
security-reviewer: llamacpp (dankclaw)/frank-dragoon
|
security-reviewer: llamacpp (dankclaw)/frank-dragoon
|
||||||
sonic: llamacpp (dankclaw-3090)/frank-zealot
|
sonic: llamacpp (dankclaw-3090)/frank-zealot
|
||||||
task: llamacpp (dankserver)/frank-archon
|
task: llamacpp (dankserver)/frank-archon
|
||||||
|
seance: llamacpp (dankclaw-3090)/frank-zealot
|
||||||
eager: default
|
eager: default
|
||||||
enableLsp: true
|
enableLsp: true
|
||||||
|
maxRuntimeMs: 1800000
|
||||||
|
softRequestBudget: 120
|
||||||
|
maxRecursionDepth: 1
|
||||||
|
disabledAgents: []
|
||||||
|
isolation:
|
||||||
|
enabled: true
|
||||||
|
completionProbe: false
|
||||||
snapcompact:
|
snapcompact:
|
||||||
systemPrompt: none
|
systemPrompt: none
|
||||||
toolResults: false
|
toolResults: false
|
||||||
@@ -203,8 +215,8 @@ vault:
|
|||||||
tts:
|
tts:
|
||||||
localVoice: bm_george
|
localVoice: bm_george
|
||||||
speech:
|
speech:
|
||||||
enabled: true
|
enabled: false
|
||||||
mode: yield
|
mode: all
|
||||||
enhanced: true
|
enhanced: true
|
||||||
voice: am_fenrir
|
voice: am_fenrir
|
||||||
secrets:
|
secrets:
|
||||||
@@ -232,6 +244,7 @@ followUpMode: one-at-a-time
|
|||||||
tools:
|
tools:
|
||||||
approvalMode: yolo
|
approvalMode: yolo
|
||||||
format: auto
|
format: auto
|
||||||
|
maxTimeout: 600
|
||||||
marketplace:
|
marketplace:
|
||||||
autoUpdate: notify
|
autoUpdate: notify
|
||||||
bash:
|
bash:
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,77 @@
|
|||||||
|
---
|
||||||
|
name: audit-never-run-container-deploy
|
||||||
|
description: "Audit a locally built podman/quadlet deploy path that has never actually run: prove base-image assumptions, env-file reader divergence, and image-hash coverage before the first deploy."
|
||||||
|
---
|
||||||
|
|
||||||
|
# Audit a never-run container deploy path
|
||||||
|
|
||||||
|
A deployable that has never been built or started has never been *executed*: every
|
||||||
|
fault in it is invisible to tests that stub the runtime. Audit it as static code
|
||||||
|
against the tools it calls, then prove each suspicion cheaply against the real
|
||||||
|
tool. The faults worth hunting are the ones that **report success while doing
|
||||||
|
nothing** — those are the ones that survive a green `/health` and a green e2e run.
|
||||||
|
|
||||||
|
## The three fault classes (all measured 2026-10-18, frank-tts-engine)
|
||||||
|
|
||||||
|
1. **A command the base image does not ship.** e.g. `adduser` in a `RUN` line
|
||||||
|
against a trimmed `ubuntu:24.04` — absent, and not pulled in by
|
||||||
|
`ca-certificates curl libgomp1`. Dies at build time (loud), but only the first
|
||||||
|
time anyone builds it.
|
||||||
|
Prove it in the base, never from memory:
|
||||||
|
`podman run --rm <base> sh -c "command -v adduser || echo absent; useradd --system --uid 10000 --home-dir /app --shell /usr/sbin/nologin tts && id tts"`
|
||||||
|
Note the accepted cosmetic warning (`uid 10000 is greater than SYS_UID_MAX 999`)
|
||||||
|
in a comment, or a future agent "fixes" it by dropping `--system` and the uid
|
||||||
|
stops matching the sibling container.
|
||||||
|
2. **Two readers of one env file disagree.** systemd units that pass
|
||||||
|
`EnvironmentFile=` hand it to podman `--env-file`, which does **no shell
|
||||||
|
parsing** — it takes the literal remainder of the line — while bash `source`
|
||||||
|
strips one quoting layer. So `WARM_TEXT="Warm up, one two three."` reaches the
|
||||||
|
container *with its quotes*, and a script that interpolates it into a JSON body
|
||||||
|
produces invalid JSON: the container boots, `active`, every voice registered,
|
||||||
|
every voice cold. Fix in the consumer: strip exactly one matched pair **only**
|
||||||
|
when both ends are the same quote AND the inner content carries another quote or
|
||||||
|
backslash (never mangle a value that legitimately contains quotes), then escape.
|
||||||
|
3. **A build-skip hash that under-covers the image.** `build_compose_image`-style
|
||||||
|
helpers skip `podman build` when a label hash matches; hashing the `Containerfile`
|
||||||
|
alone means editing a `COPY`'d app/script rebuilds **nothing** — deploy reports
|
||||||
|
success, `/health` answers, container serves old code. Each case arm must name
|
||||||
|
the paths its `Containerfile` copies (`extra=(…)`) and hash Containerfile+extras.
|
||||||
|
Arms that have no extras keep their old hash, so they do not needlessly rebuild.
|
||||||
|
|
||||||
|
## Test it as behaviour, never as a grep of the handler
|
||||||
|
|
||||||
|
- Drive the **real** function: `awk '/^build_compose_image\(\) \{/,/^\}/' deploy-remote.sh > bci.sh`,
|
||||||
|
`source` it, put a stub `podman` first on `PATH`, assert the observable question —
|
||||||
|
*did the deploy ask podman to build, or did it skip?*
|
||||||
|
- Stub traps that cost real time: podman is invoked `podman image inspect <image> --format …`
|
||||||
|
so the tag is **`$3`**, not `$2`; tags contain `/` so a state file named after one
|
||||||
|
needs `${tag//\//_}` or the write silently lands in a nonexistent directory and
|
||||||
|
every image looks permanently unbuilt; `--label cfhash=…` arrives **before** `-t <tag>`,
|
||||||
|
so collect the label and write it after the arg loop.
|
||||||
|
- For an env-file bug, write one test that reads the file **both** ways — `source`
|
||||||
|
and the `--env-file` way (parse `KEY=literal remainder`) — and assert the program
|
||||||
|
receives the same value in both shapes, including a value that carries its own
|
||||||
|
quotes and a backslash. A suite that only sources the file is how the bug shipped.
|
||||||
|
- Prove red: `git show HEAD:scripts/deploy-remote.sh > scripts/deploy-remote.sh`,
|
||||||
|
run the new test (must fail at the exact intended step), restore. Keep each fix in
|
||||||
|
its own commit and run the seam at each one so the branch bisects.
|
||||||
|
|
||||||
|
## Base-image and host facts to check while auditing
|
||||||
|
|
||||||
|
- `useradd` lives in `login(1)` (base carries it); `adduser` does not.
|
||||||
|
- Rootless podman **cannot raise rlimits** — `--ulimit nofile=…` above the host hard
|
||||||
|
limit dies at container create, not at runtime.
|
||||||
|
- A referenced `Network=` `.network` file the generator cannot find means the
|
||||||
|
container unit is **not produced at all**.
|
||||||
|
- Two containers talk by DNS name only if they attach to a **common** network;
|
||||||
|
check both unit files rather than trusting a comment that says they share one.
|
||||||
|
- Container DNS name + the network that owns it, never `host.containers.internal`
|
||||||
|
or a published loopback port, for service-to-service traffic.
|
||||||
|
|
||||||
|
## Do not
|
||||||
|
|
||||||
|
- Do not deploy the audited thing because the audit is finished: first build of a
|
||||||
|
heavy image (recursive source clone ~10 min) plus a new unit on a host serving
|
||||||
|
people is the operator's call.
|
||||||
|
- Do not add a Prometheus target for a container with no `/metrics` — say the probe
|
||||||
|
is a decision (blackbox HTTP), do not fake a scrape.
|
||||||
@@ -1,82 +1,32 @@
|
|||||||
---
|
---
|
||||||
name: bounding-subagent-runtime
|
name: bounding-subagent-runtime
|
||||||
description: "Cap and diagnose runaway or hung omp subagents: task.maxRuntimeMs, softRequestBudget, agentIdleTtlMs, per-agent model overrides, the cfg save-approval quirk, and the finished-work-but-never-exits nested-fanout deadlock."
|
description: "Cap and diagnose runaway or hung omp subagents: task.maxRuntimeMs, softRequestBudget, maxConcurrency, maxRecursionDepth (the 0-trap), per-provider in-flight ceilings."
|
||||||
---
|
---
|
||||||
|
|
||||||
# Bounding subagent runtime (omp harness)
|
## When
|
||||||
|
Subagent runs long, fan out too wide, or you must (re)declare the fleet's subagent limit policy. Also when asked "what limits can subagents have".
|
||||||
|
|
||||||
Use when a `task`/`eval` subagent runs absurdly long (tens of minutes on a small job),
|
## Read first
|
||||||
when it **finishes its deliverable and still never exits**, when you want a hard cap
|
- Repo KB page `omp-docs/pi/subagent-limits.md` — the live policy of record (values + rationale + deliberately-not-set list). Do not re-derive; read it.
|
||||||
before spawning, or when you must tell a busy agent to stop.
|
- `cfg://task` and `cfg://providers/maxInFlightRequests` — effective values, not the stock defaults.
|
||||||
|
|
||||||
## The knobs (`cfg://task/...`)
|
## The knobs (cfg:// writes; `/save` suffix persists)
|
||||||
|
| Knob | Effect | Gotcha |
|
||||||
| Key | Default | Meaning |
|
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `maxRuntimeMs` | `0` (unlimited) | **Hard wall-clock limit per subagent (ms).** The only true time cap. |
|
| `task.maxConcurrency` | session semaphore; excess queues | stock default 32 — "2" is not a default |
|
||||||
| `softRequestBudget` | `200` | Soft cap on assistant **requests** per run; force-stop at **1.5×**. |
|
| `task.maxRecursionDepth` | nesting cap | predicate is `depth < n`; **1 = children cannot spawn (correct for "no nesting"); 0 disables spawning for the MAIN session too — never reach for 0**; negative = unlimited |
|
||||||
| `softRequestBudgetNotice` | `true` | Injects one "wrap up" steering message at the soft mark. |
|
| `task.maxRuntimeMs` | hard wall clock, overrun = `aborted` | default 0 = off |
|
||||||
| `agentIdleTtlMs` | `420000` | Parks an **idle** agent after 7 min. Useless against a busy-but-slow agent. |
|
| `task.softRequestBudget` | wrap-up notice at N, force-yield at 1.5N | scout/sonic may impose lower built-ins |
|
||||||
| `completionProbe` | `true` | Asks a working subagent to self-report completeness at 2/5/10 requests. |
|
| `task.agentIdleTtlMs` | park idle finished agents | `<= 0` disables parking |
|
||||||
| `maxConcurrency` | `32` (often lowered) | Concurrent subagents; sizes GPU/endpoint load. |
|
| `providers.maxInFlightRequests` | concurrent LLM requests per provider id | keys are the quoted block labels from `agent/models.yml` (e.g. `"llamacpp (dankclaw-3090)"`), flat map lookup; absent/≤0 = unlimited; scope = this machine's config root only — other workstations and non-omp clients are NOT counted; excess queues |
|
||||||
| `agentModelOverrides` | `{}` | Per-agent model routing, e.g. `{"scout":"provider/model-id"}`. |
|
| `tools.maxTimeout` | ceiling on any agent-requested tool timeout | kills `timeout: 0` runaways |
|
||||||
| `tools.maxTimeout` | `0` | Caps the timeout the agent may set on a **tool**, not on the agent. |
|
| `task.isolation.enabled` | makes per-spawn `isolated` flag available | opt-in per spawn, not a blanket sandbox; needs git repo; off in plan mode |
|
||||||
|
| `task.completionProbe` | periodic completeness side-requests | token sink on shared endpoints |
|
||||||
|
|
||||||
Reasonable fleet defaults: `maxRuntimeMs = 900000` (15 min), leave the request budget
|
## Verify semantics from the bundle, not the label
|
||||||
alone unless the failure mode is a looping search agent (then lower it — request count,
|
Setting descriptions lie by omission. `~/.bun/install/global/node_modules/@oh-my-pi/pi-coding-agent/dist/cli.js`:
|
||||||
not the clock, is what bounds a 200-call research loop).
|
- depth gate: `grep -o '.\{130\}xC\b.\{130\}'` finds `U_(n,s)`; `grep -o 'function U_(e,t){.\{0,60\}'` → `e<0||t<e` (n=cap, s=taskDepth).
|
||||||
|
- in-flight lookup: `grep -o '.\{150\}axInFlightRequests.\{200\}'` → flat `[providerId]` lookup, non-number/≤0 = unlimited, clamp ≥1.
|
||||||
|
|
||||||
**Verify the cap instead of assuming it.** `read cfg://task/maxRuntimeMs` at the start of
|
## Landing a policy change
|
||||||
any session that plans to rely on it. A session-only write is gone next session, and an
|
cfg `/save` writes persist immediately; `agent/config.yml` is machine-live tier (heartbeat commits it). Human-facing policy changes land as an `omp-docs` page update via branch → PR (docs tier), `git worktree add` from `origin/main` if the current checkout is branch-locked by workspace policy.
|
||||||
unanswered `/save` approval means the value never persisted — so the fleet can run hours
|
|
||||||
with the cap at `0` while everyone believes it is 15 minutes. Nothing else bounds a child.
|
|
||||||
|
|
||||||
## Three failure modes, three different fixes
|
|
||||||
|
|
||||||
- **Runaway loop** (hundreds of sequential tool calls): lower `softRequestBudget`; the
|
|
||||||
clock cap only truncates it late.
|
|
||||||
- **Slow model, legitimate work** (dozens of calls on a low-t/s endpoint = an hour of wall
|
|
||||||
clock): fix routing, not the clock — check `cfg://task/agentModelOverrides` and move the
|
|
||||||
agent class onto a faster/shared endpoint. Watch the slot math: subagent endpoints share
|
|
||||||
KV slots with the interactive session, so `maxConcurrency + 1` sequences on an N-slot
|
|
||||||
endpoint degrades everyone's decode.
|
|
||||||
- **Finished work, no exit (nested fan-out deadlock).** A subagent that spawned **its own**
|
|
||||||
children blocks on their barriers. If those children die out-of-band (killed, starved, or
|
|
||||||
waiting on an answer whose subject changed mid-flight), the parent waits forever with its
|
|
||||||
deliverable already written. No instruction text prevents this — not the repo AGENTS.md,
|
|
||||||
not the task prompt; only the clock cap or a kill ends it.
|
|
||||||
|
|
||||||
Signature: parent and children report **the same elapsed wall clock**, and the parent's
|
|
||||||
on-disk output is complete. Census (agent registry + `ps`): group by elapsed time, look
|
|
||||||
for identical `etimes` across a parent/child set, then judge the **artifacts** instead of
|
|
||||||
the process — balanced code fences, no truncated tables, trailing newline, front matter or
|
|
||||||
the page's H1+status convention intact. Complete artifacts + still "running" = deadlock,
|
|
||||||
not work.
|
|
||||||
|
|
||||||
Repair: kill the **subtree** and confirm the children leave (a killed parent does not
|
|
||||||
cancel children that came from a separate `tasks[]` batch). Then **flatten**: the
|
|
||||||
top-level session does the fan-out and children never spawn children. Re-run only the
|
|
||||||
slices whose artifacts are missing — integrate from what is already on disk.
|
|
||||||
|
|
||||||
## cfg write mechanics (bit me once)
|
|
||||||
|
|
||||||
- `write cfg://task/maxRuntimeMs 900000` → applies **session-only**, replies "NOT saved".
|
|
||||||
- Persist = `write cfg://task/maxRuntimeMs/save` with the same content, and that write
|
|
||||||
needs its **own interactive approval**. If the approval prompt is not answered in time
|
|
||||||
the value stays session-only and the tool says *NEVER retry unless the user asks* —
|
|
||||||
report it in the reply instead.
|
|
||||||
- Revert a session value by writing the old value back (e.g. `0`).
|
|
||||||
|
|
||||||
## Order of operations when one is already running late
|
|
||||||
|
|
||||||
1. `read proc://` → registry of running agents; `read proc://<id>` for status/output.
|
|
||||||
2. `read agent://<id>` → live progress ("Latest assistant text") — often shows it is
|
|
||||||
mid-batch, not hung. If the latest text is already a wrap-up/summary, treat it as
|
|
||||||
**done** and collect its output instead of waiting for an exit event.
|
|
||||||
3. `write agent://<id>` an explicit wrap-up order: stop searching, answer what is
|
|
||||||
sourced, mark the rest `NOT VERIFIED — needs <what>`, cap the report length.
|
|
||||||
4. Only then `write proc://<id>/kill` — the cap does **not** retroactively bind agents
|
|
||||||
spawned before the change, so a long runner must be ordered or killed, never just
|
|
||||||
re-configured. After any kill, re-read the registry: unreaped children and orphaned
|
|
||||||
host-side processes (a scratch server holding GPU/RAM) are the expensive residue, and
|
|
||||||
`pkill -f` patterns that also match production services are forbidden — kill by pid.
|
|
||||||
@@ -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/<name>/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(<scope>)`, note the lever→instance mapping in the PR comment so the reviewer can grade the emulation.
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
---
|
||||||
|
name: deployment-checkout-trunk-sync
|
||||||
|
description: Use when the live deployment checkout (~/.omp main) must merge a moved origin/main while dirty tracked files block the merge and workspace policy blocks git checkout.
|
||||||
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
name: deployment-checkout-trunk-sync
|
||||||
|
description: Use when the live deployment checkout (~/.omp main) must merge a moved origin/main while dirty tracked files block the merge and workspace policy blocks git checkout.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Sync a dirty live checkout to a deployment (checkout blocked)
|
||||||
|
|
||||||
|
Situation: the checkout IS the deployment; months of machine/hand writes sit
|
||||||
|
uncommitted; `origin/main` has moved; `git checkout`/discard is BLOCKED by
|
||||||
|
workspace policy ("edit forward"; exemption only during active conflict).
|
||||||
|
|
||||||
|
## Procedure
|
||||||
|
1. `git fetch`; trial `git merge --no-commit --no-ff origin/main` to enumerate
|
||||||
|
exact blockers (dirty files the merge would overwrite + untracked collisions).
|
||||||
|
Untracked blockers: `cp -a` to `.AGENTTRASH/pre-merge-<date>/`, then `mv` aside.
|
||||||
|
2. **Preservation commit:** `git add` ONLY the blocking paths (not the whole
|
||||||
|
tree) and commit them as-is: `chore: adopt pre-existing working-tree state
|
||||||
|
before trunk merge`. Nothing is discarded; the secret gate still runs.
|
||||||
|
Include EVERY dirty deployment-state path (dotfiles/live tiers too) — a
|
||||||
|
dirty file that is neither committed nor trashed is unprotected from step 3.
|
||||||
|
3. `git merge --no-ff origin/main`. Conflicts are now resolvable — the policy
|
||||||
|
exemption is active during a merge.
|
||||||
|
4. Resolution law:
|
||||||
|
- **live machine config** (`agent/config.yml`, `models.yml`, dotfiles live
|
||||||
|
state like noctalia `settings.toml`) → `--ours`: the running box is its
|
||||||
|
own authority; the remote copy is a snapshot.
|
||||||
|
- **prose/law/docs** (`AGENTS.md`, KB) → `--theirs` (upstream canonical;
|
||||||
|
local variants survive in the preservation commit).
|
||||||
|
- **append-mostly logs** → union: cut only the three marker lines
|
||||||
|
(`<<<<<<<`/`=======` range + `>>>>>>>`), keep both sides' entries.
|
||||||
|
5. `git commit --no-edit`, push trunk, verify `main == origin/main`.
|
||||||
|
6. Pre-merge byte-compare every dirty file vs `origin/main`
|
||||||
|
(`git show origin/main:$f | diff -q - $f`): SUPERSEDED (identical → discardable),
|
||||||
|
DIVERGED (resolve), untracked-in-upstream (collision).
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
- **NEVER `git stash` to unblock a deployment sync, NEVER `git stash clear`/
|
||||||
|
`drop` afterward.** Observed 2026-10-18: a sync stashed the dirty tree, the
|
||||||
|
stash entry was later destroyed, and months of GUI-built bar layout
|
||||||
|
(noctalia settings.toml, never committed) vanished with it — recovered only
|
||||||
|
via dangling-object archaeology (below). Step 2's preservation commit exists
|
||||||
|
precisely so stashing is never needed.
|
||||||
|
- **Stash-and-retry loops are a red flag**: three `WIP on main` stash commits
|
||||||
|
within ~60s of each other means the operator was fighting the dirty tree,
|
||||||
|
not resolving it.
|
||||||
|
- **Recovery from destroyed stashes** (objects survive entry deletion until gc):
|
||||||
|
`git fsck --lost-found` → `git log -1 --format='%ci %s' <dangling>` to date
|
||||||
|
them → `git show <c>:<path>` to dump candidates; newest `WIP on` commit is
|
||||||
|
usually the last good state. Validate before deploying (e.g.
|
||||||
|
`noctalia config validate`), back up the clobbered file to `.AGENTTRASH/`,
|
||||||
|
then restore through the symlink target.
|
||||||
|
- Never wrap `git checkout` in `2>/dev/null` here: the policy-block error was
|
||||||
|
swallowed and the loop silently no-op'd; the merge then failed identically.
|
||||||
|
- `git add` a directory pathspec stages its untracked children (fine for
|
||||||
|
allowlist dirs; never for prose dirs).
|
||||||
|
- The live-sync heartbeat (#11) commits machine tiers hourly, so this sync is
|
||||||
|
a rare operation post-deployment — mostly memories/dotfiles stragglers.
|
||||||
|
Remember the heartbeat can lag up to an hour behind GUI edits: the tree
|
||||||
|
between heartbeats is the ONLY copy of fresh GUI work.
|
||||||
|
- **Frank's accepted risk posture (2026-10-18):** hourly sweep + the
|
||||||
|
preservation-commit law + fsck recovery is defense-in-depth ENOUGH. Do not
|
||||||
|
re-propose faster heartbeats or path/event triggers to shrink the
|
||||||
|
GUI-edit-to-commit window; he chose status quo after measuring (heartbeat
|
||||||
|
itself is 57 ms CPU — cost was never the objection; un-debounced
|
||||||
|
DirectoryNotEmpty triggering was, and path-unit proposals are vetoed).
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
---
|
||||||
|
name: docs-refresh-scout-summaries
|
||||||
|
description: "Refresh a stale documentation cluster without delegating the writing: scout subagents produce per-line verdict tables (holder verified by comma-insensitive grep), the parent makes every edit, and a port-before-cut census proves nothing was lost. Use for dedupe-our-docs, fix-the-stale-runbooks, pointers-instead-of-copied-numbers jobs."
|
||||||
|
---
|
||||||
|
|
||||||
|
# Refresh stale docs with scout summaries, parent-side writes
|
||||||
|
|
||||||
|
Use when a cluster of documentation pages is stale and/or restates facts another page owns, and
|
||||||
|
the fix touches many files. **Scouts read and classify; the parent writes.** Sibling skill
|
||||||
|
`vault-one-fact-one-owner-dedupe` covers collapsing era pages into owner pages - its section 3
|
||||||
|
(orphan census), section 7 (edit-tool hazard) and section 9 (verify/PR) apply unchanged.
|
||||||
|
|
||||||
|
## Why not writer subagents (measured cost)
|
||||||
|
|
||||||
|
Writer agents produce plausible output and cannot be trusted on the one claim that matters - the
|
||||||
|
owner page already holds this fact. In one run they asserted every cut was grep-confirmed while
|
||||||
|
FOUR cuts left pointers to pages that did not contain the fact (118->238 GiB, 16,095 tokens,
|
||||||
|
0.739/0.554/0.427/0.340, PR #23897). Each became a broken promise the next reader trusts.
|
||||||
|
Classification is the only part that parallelizes; writing is where taste (ownership, era
|
||||||
|
naming, what counts as a law) lives. So fan out read-only scouts and keep the pen.
|
||||||
|
|
||||||
|
If writer agents were already spawned and are no longer wanted: `write agent://all` to stop
|
||||||
|
them, then `git status` the target tree - `proc://` does not list agent jobs, and a finished
|
||||||
|
worker may already have edited files.
|
||||||
|
|
||||||
|
## Pipeline
|
||||||
|
|
||||||
|
1. **Cluster** targets into groups of 3-4 pages sharing subject matter (one cluster per scout).
|
||||||
|
Know the owner map first (endpoint/service/host pages, plus the repo documentation law).
|
||||||
|
2. **Wave of 2 scouts** (harness cap is `task.maxConcurrency`, usually 2). Scout contract:
|
||||||
|
|
||||||
|
> READ-ONLY, no writes of any kind. Per file, one table row per candidate fact:
|
||||||
|
> `| lines | verbatim figure + 6 words | holder elsewhere (file:line) or none | VERDICT |`
|
||||||
|
> Verdicts: `CUT -> [[Owner]]` (holder verified), `KEEP(owned here)` (no holder, this page is
|
||||||
|
> the record), `PORT(parent)` (rightful owner lacks it - name page and section), `STALE`
|
||||||
|
> (contradicted by current state; quote the contradicting line), `INDEX` (locator page).
|
||||||
|
> Verify every holder claim with a COMMA-INSENSITIVE grep: a holder writing 1,350,182
|
||||||
|
> satisfies a page writing 1350182 and vice versa; 16,095 does NOT satisfy a check for 16095
|
||||||
|
> unless commas are stripped. Never name a holder line you have not read.
|
||||||
|
> End with a per-file tally: kept / cut / port / stale.
|
||||||
|
|
||||||
|
Put the law, the owner map and the verification rule in the batch-level `context`, never per
|
||||||
|
task.
|
||||||
|
3. **Write while the next wave runs**: edit cluster N from wave N, launch wave N+1 in the same
|
||||||
|
turn. Never launch a wave you have nothing to write.
|
||||||
|
4. **Verify each CUT yourself** before cutting - read the holder line. A scout row is a
|
||||||
|
hypothesis, not evidence.
|
||||||
|
5. **PORT items are your edits**: add the fact to the owner page section (Eras row / Laws / spec
|
||||||
|
table), then cut it from the technique page in the same commit.
|
||||||
|
6. **STALE items: check live state**, not another document - `ssh <host> systemctl is-active
|
||||||
|
<svc>`, the endpoint `/health`, the env file. Then rewrite with a date, or drop transient
|
||||||
|
live state an owner page already records.
|
||||||
|
|
||||||
|
## The ownership test that stops wasted ports
|
||||||
|
|
||||||
|
Two pages carrying one figure is the defect - a figure with exactly one holder is fine wherever
|
||||||
|
it lives. A technique page that is the sole record of a campaign owns that campaign's arm
|
||||||
|
tables; do not port such ledgers into an endpoint Eras table. Write the clause into the repo
|
||||||
|
documentation law so the next pass stops porting single-owner facts. Corollary: a worked example
|
||||||
|
may repeat a live number if it declares itself dated and names the owner (a snapshot, not fleet
|
||||||
|
state - recompute from the live files).
|
||||||
|
|
||||||
|
## Census after each cluster (catches your own slips too)
|
||||||
|
|
||||||
|
Run the sibling skill's section 3 census over `git diff -U0` for the cluster (deleted lines x
|
||||||
|
distinctive numeric keys vs the whole vault **plus AGENTS.md**), comma-insensitively; drive the
|
||||||
|
count to zero or list each accepted loss in the commit message.
|
||||||
|
|
||||||
|
```python
|
||||||
|
flat = corpus.replace(',', '')
|
||||||
|
pres = lambda n: n in corpus or n.replace(',', '') in flat
|
||||||
|
toks = lambda l: [n for n in re.findall(r'\d[\d,]*(?:\.\d+)?', l)
|
||||||
|
if len(n.replace(',', '').split('.')[0]) >= 4
|
||||||
|
or (len(n.split('.')[-1]) >= 2 and len(n.replace(',', '')) >= 5)]
|
||||||
|
orphan = [l for l in deleted if toks(l) and not any(pres(n) for n in toks(l))]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verify and land
|
||||||
|
|
||||||
|
- Vault link seam must print `0 unresolved links`; hard-wrapped `[[Two Word Page]]` links split
|
||||||
|
across lines are the usual cause - unwrap them.
|
||||||
|
- Full suite once at the end, not per cluster; a docs seam running while scouts are live may
|
||||||
|
read half-written files, so re-run after the last edit.
|
||||||
|
- Commit per cluster (docs-only means **no deploy**); push, open the PR through the API with a
|
||||||
|
600-perm curl config, `xdg-open` the PR URL immediately.
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
---
|
||||||
|
name: frank-tts-add-cloned-voice
|
||||||
|
description: "Add or repair a cloned voice on the dank fleet: pack builder, the reference.wav contract, the boot warm hook (argv-cap and env-quote laws), and how to prove a voice really answers."
|
||||||
|
---
|
||||||
|
|
||||||
|
# Adding (or repairing) a cloned voice on the dank fleet
|
||||||
|
|
||||||
|
Repo law lives in `serverAppConfigs-docs/Dankclaw/Voice Packs.md` and
|
||||||
|
`Frank TTS Engine.md` — read those for the pack format. This skill is the
|
||||||
|
**operating sequence plus the traps that cost real time on 2026-10-18**.
|
||||||
|
|
||||||
|
## Stack, fast
|
||||||
|
|
||||||
|
- `frank-tts-engine` — arm A `qwentts.cpp tts-server`, container-internal `:8880`,
|
||||||
|
publishes **no port**. Reach it with `podman exec frank-tts-engine curl …`.
|
||||||
|
- `frank-tts` — OpenAI façade on host `:7772`; resolves voice **by name**.
|
||||||
|
- Packs: `/home/dank/voices/<name>/` with `reference.wav` (+ manifest, `.spk`,
|
||||||
|
`.rvq`). Builder: `scripts/build-voice-pack.sh` (crop 19.84–25 s, first 10 s of
|
||||||
|
any source is forbidden).
|
||||||
|
- Weights: `/home/dank/models/Qwen3-TTS-GGUF/` —
|
||||||
|
`scripts/download-frank-tts-engine-qwentts.sh [--verify-only]` (1.4 GiB, ~18 s
|
||||||
|
on the LAN; verifies byte count + Hub LFS sha256).
|
||||||
|
|
||||||
|
## Sequence
|
||||||
|
|
||||||
|
1. Build the pack (dry-run first — sizes are arithmetic and asserted).
|
||||||
|
2. Deploy the engine only if its files changed:
|
||||||
|
`./scripts/deploy.sh dankclaw --service frank-tts-engine`
|
||||||
|
(first run builds the image: ~10 min recursive source clone).
|
||||||
|
3. Register/warm: `podman exec frank-tts-engine /app/warm-voices.sh`
|
||||||
|
— registers **every** pack each run (the engine replaces a name it already
|
||||||
|
holds; `tools/tts-server.cpp:148`), then synthesizes twice per voice.
|
||||||
|
4. Prove from the consumer side, not the config:
|
||||||
|
`curl -s localhost:7772/v1/audio/voices` then a real
|
||||||
|
`POST /v1/audio/speech` and listen to the bytes.
|
||||||
|
|
||||||
|
## Trap 1 — the kernel's argument cap (this broke the first deploy)
|
||||||
|
|
||||||
|
`/usr/bin/curl: Argument list too long` in the warm log ⇒ a request body was
|
||||||
|
passed as **one argv string**. Linux caps a single argument at
|
||||||
|
`MAX_ARG_STRLEN = 131 072 bytes`; a ~1 MB `reference.wav` base64's to ~1.27 MB.
|
||||||
|
curl never runs, and hooks report it as `HTTP no response` while `/health` is
|
||||||
|
green. Fix = write the body to a file and use `--data-binary @file`; feed the
|
||||||
|
payload through a redirection, not `$(base64 …)`.
|
||||||
|
Regression-test shape: **real** curl + real listener + a fixture >128 KB, so the
|
||||||
|
kernel (not a stub) produces the failure. Any pack over ~96 KB trips it.
|
||||||
|
|
||||||
|
## Trap 2 — two readers of the env file disagree about quotes
|
||||||
|
|
||||||
|
`bash set -a; source` strips `"…"`. podman's `--env-file` (how the unit really
|
||||||
|
reads it) does **not**. A value interpolated into a JSON body therefore arrives
|
||||||
|
quoted and invalidates it. Test both readers, not just `source`.
|
||||||
|
|
||||||
|
## Trap 3 — locally built images skip rebuilds on a stale hash
|
||||||
|
|
||||||
|
`build_compose_image` compares a `cfhash` label; it must cover every file the
|
||||||
|
`Containerfile` `COPY`s, or an edited `app.py`/hook deploys "successfully" while
|
||||||
|
the container runs old code.
|
||||||
|
|
||||||
|
## Measured numbers (2026-10-18, deployed build — retire the trial's ladder)
|
||||||
|
|
||||||
|
| thing | value |
|
||||||
|
|---|---|
|
||||||
|
| first synthesis of a freshly registered voice | 1 883 ms |
|
||||||
|
| later short requests | 1 226–1 283 ms |
|
||||||
|
| ~110-word request (repeat) | 9.4 s / 9.8 s — tracks generated audio, not state |
|
||||||
|
| whole boot warm, 3 packs | 15.2 s |
|
||||||
|
| engine resident | ~4.0 GiB |
|
||||||
|
|
||||||
|
The trial's `6.2–8.0 s cold → 0.5 ms warm` claim does **not** reproduce; warming
|
||||||
|
is for registration (registry is memory-only, re-register ≠ warm) plus ~0.6 s.
|
||||||
|
|
||||||
|
## Ops notes
|
||||||
|
|
||||||
|
- `systemctl --user stop` **removes** the quadlet container — `podman start` then
|
||||||
|
finds nothing. To run the engine without its `ExecStartPost` hook, use a
|
||||||
|
separate container, not stop+start.
|
||||||
|
- `podman cp` of a fixed script into a running container is a **trial only**: the
|
||||||
|
layer dies with the container. Merge + redeploy to make it durable.
|
||||||
|
- Nothing on the voice pair exposes `/metrics`; Prometheus can't scrape it (a
|
||||||
|
blackbox HTTP probe on 7772 is the only honest option).
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
---
|
||||||
|
name: frank-tts-consumer-voice-triage
|
||||||
|
description: "Triage a consumer (OpenWebUI, omp, open-notebook) rejecting or ignoring a frank-tts voice name: prove whether the server or the browser-side Kokoro.js engine produced the error."
|
||||||
|
---
|
||||||
|
|
||||||
|
# frank-tts consumer voice triage
|
||||||
|
|
||||||
|
A consumer says a voice name is invalid, speaks in the wrong voice, or is
|
||||||
|
silent. `frank-tts` (`192.168.0.143:7772`, shim
|
||||||
|
`dankclaw/voiceservice/app.py`) resolves `voice=<name>` against
|
||||||
|
`/home/dank/voices/<name>/reference.wav` and answers **400 + the list of
|
||||||
|
voices that exist** for a name it cannot resolve — never a silent fallback.
|
||||||
|
Any error naming a *different* voice list did not come from it.
|
||||||
|
|
||||||
|
**Signature that decides the case immediately:** an enum of ~29 names
|
||||||
|
`af_heart, af_alloy, … am_*, bf_*, bm_* … bm_fable` is **Kokoro's fixed pack**,
|
||||||
|
the voice list of **Kokoro.js running in the browser** (OpenWebUI ships it
|
||||||
|
in its own front end as TTS engine value `browser-kokoro`, with a personal
|
||||||
|
"Kokoro.js dtype" setting; model `onnx-community/Kokoro-82M-v1.0-ONNX`).
|
||||||
|
`narrator1/2/3` are frank-tts cloned voices. Kokoro cannot clone.
|
||||||
|
|
||||||
|
## Instrument ladder (stop at the first that answers)
|
||||||
|
|
||||||
|
1. **What the endpoint actually accepts** (never infer from a consumer's text):
|
||||||
|
`curl -s http://127.0.0.1:7772/v1/audio/voices` → JSON array of pack names.
|
||||||
|
2. **Speak with the consumer's exact body, from inside the consumer's
|
||||||
|
container** — same network, same shape. Write the JSON to a file and
|
||||||
|
`--data-binary @file` (nested `sh -c` quoting silently sends an empty
|
||||||
|
body and yields a bogus `400 malformed request body`):
|
||||||
|
```
|
||||||
|
printf '%s' '{"model":"frank-tts","input":"probe","voice":"narrator1","response_format":"mp3","speed":1.0}' > /tmp/p.json
|
||||||
|
podman cp /tmp/p.json <consumer>:/tmp/p.json
|
||||||
|
podman exec <consumer> curl -sS -o /tmp/o.bin -w 'http=%{http_code} type=%{content_type} bytes=%{size_download}\n' \
|
||||||
|
-X POST http://host.containers.internal:7772/v1/audio/speech \
|
||||||
|
-H 'Content-Type: application/json' --data-binary @/tmp/p.json
|
||||||
|
```
|
||||||
|
`200 audio/mpeg|audio/wav` ⇒ the server path is healthy; the failure is elsewhere.
|
||||||
|
3. **Did the request arrive at all?** `podman logs --tail 400 <consumer> | grep -c audio/speech`.
|
||||||
|
A server-path failure leaves a log line **and** a frank-tts error body; a
|
||||||
|
client-side engine leaves **neither**. This contrast is the discriminator.
|
||||||
|
4. **Where the rejection string lives** (proves the emitter without guessing):
|
||||||
|
```
|
||||||
|
podman exec <consumer> sh -c 'grep -ho ".\{200\}Should be one of.\{80\}" /app/build/_app/immutable/chunks/*.js | head'
|
||||||
|
podman exec <consumer> sh -c 'grep -ho ".\{60\}af_heart.\{200\}" /app/build/_app/immutable/chunks/*.js | head -2'
|
||||||
|
```
|
||||||
|
Matched inside the front-end bundle ⇒ browser-side validator, not the API.
|
||||||
|
5. **Config authority sweep** (OpenWebUI): admin config is the `config`
|
||||||
|
table (one key per row, JSON `value`; expected
|
||||||
|
`audio.tts.engine=openai`, `audio.tts.model`, `audio.tts.voice`,
|
||||||
|
`audio.tts.openai.api_base_url`); per-user overrides are the `settings`
|
||||||
|
JSON column of table `user` (singular, no `is_active`; some rows hold the
|
||||||
|
string `null` → guard it). `audio.stt.*` and `audio.tts.*` are separate.
|
||||||
|
No `sqlite3` CLI in the image or on the host: `podman exec -i <c> python -`
|
||||||
|
with the stdlib module. **`podman exec` needs `-i` to accept a heredoc
|
||||||
|
script** — without it the script is silently dropped and you get no output.
|
||||||
|
|
||||||
|
## Known traps
|
||||||
|
|
||||||
|
- OpenWebUI is a **rootless** quadlet: `sudo podman …` reports *no such
|
||||||
|
container*. Read it rootless.
|
||||||
|
- `podman ps` in this podman build has no `.ImageName` field — use `{{.Image}}`.
|
||||||
|
- The in-browser engine choice is **per-browser storage**: empty
|
||||||
|
`user.settings` + correct admin config + zero backend hits is the normal
|
||||||
|
shape of this bug. Fix is UI: Settings → Audio → TTS Engine → **Default**.
|
||||||
|
- `GET /api/config` publishes no `audio` block, and nonexistent `/api/...`
|
||||||
|
paths return the SPA HTML with `200` — never conclude "config missing" from
|
||||||
|
an HTML body.
|
||||||
|
- TTS is memory-registry sensitive: the engine's voice registry is
|
||||||
|
memory-only, so "every name returns 400" after a restart points at the warm
|
||||||
|
pass, not at the caller (see [[frank-tts-add-cloned-voice]]).
|
||||||
|
|
||||||
|
## Local playback side (omp `/say`, manual probes)
|
||||||
|
|
||||||
|
If synthesis succeeds (`200`, sane WAV bytes) but nothing is audible, the
|
||||||
|
fault is the PipeWire graph, not the endpoint:
|
||||||
|
`wpctl status` (default sink marked `*`, and *Default Configured Devices* —
|
||||||
|
a remembered-but-offline `bluez_output.*` there is the classic silent
|
||||||
|
route), `wpctl get-volume @DEFAULT_AUDIO_SINK@`, `pw-play -v <file>` (expect
|
||||||
|
`stream drained` + exit 0), and target a device explicitly with
|
||||||
|
`pw-play --target <node-id>` to identify which hardware is live. A one-word
|
||||||
|
clip (~0.7 s) is easy to miss — probe with a multi-second sentence.
|
||||||
@@ -1,105 +1,64 @@
|
|||||||
---
|
---
|
||||||
name: gitea-api-pr-operations
|
name: gitea-api-pr-operations
|
||||||
description: "Open, list, merge, and verify pull requests (and diagnose issue-API scope failures) on the Dankclaw Gitea via its REST API from a workstation, keeping the token out of argv (curl -K config file, mode 600) — includes the credential-store extraction one-liner, the merge endpoint's Do-field law and Content-Type-header trap, the suggested-URL-is-not-a-PR gotcha, and the insufficient-token-scope trap."
|
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 (Dankclaw)
|
# Gitea API PR operations (Dankclaw `dank/command-center`)
|
||||||
|
|
||||||
`git push` on this fleet prints `Create a new pull request for ...` — that is a
|
## Where
|
||||||
**suggestion, not a PR**. Verify (`GET /pulls?state=open` → expect `[]`) before
|
- Token: `~/.config/secrets/gitea` (`http://user:TOKEN@192.168.0.143` single line).
|
||||||
claiming a PR "should exist"; create it via the API when asked.
|
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`.
|
||||||
|
|
||||||
## Endpoint + auth
|
## Recipes (all verified live 2026-10-18)
|
||||||
- API base: `http://192.168.0.143:3000/api/v1/repos/dank/<repo>`
|
- **Open PR** (token only needed for private repos; this one reads publicly but
|
||||||
- Token lives in the git credential store configured by
|
POST needs auth): `POST /pulls` JSON `{title, head, base:"main", body}`.
|
||||||
`git config --get credential.helper` → `store --file ~/.config/secrets/gitea`,
|
- **PATCH PR title/body**: `PATCH /pulls/<n>` with JSON body `{title}` and/or
|
||||||
line format `http://user:TOKEN@192.168.0.143:3000`.
|
`{body}`. Returns 201 with the full PR. Use `jq -n --arg` to build JSON; for
|
||||||
- The repo-local `.env` `GITEA_TOKEN` (main checkout, gitignored) carries
|
multi-line bodies write the text to a temp file and `--arg b "$(cat b.txt)"`.
|
||||||
**`read:issue` too** (measured 2026-10-18): filing issues needs THAT token,
|
(jq CLI: a bare `---` argument after the program is parsed as a flag → error;
|
||||||
the credential-store one 403s on issues.
|
fold footers into the --arg string instead.)
|
||||||
|
- **List open PRs**: `GET /pulls?state=open` → `jq -r '.[] | "#\(.number) \(.head.ref) \(.title)"'`.
|
||||||
|
- **Read PR**: `GET /pulls/<n>` (no token; public repo) → `jq -c '{state,merged,mergeable,head:.head.ref}'`.
|
||||||
|
- **MERGE**: `POST /pulls/<n>/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/<n> 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/<n>/comments`
|
||||||
|
(`POST {body}`, `PATCH .../comments/<id>`) — both work with the temp-cfg auth.
|
||||||
|
|
||||||
Extract the token (the `http://` scheme contains a colon — scheme-stripping
|
## Review comments: the API lies on 1.27.3 (learned 2026-10-18)
|
||||||
awk regexes silently grab the port instead; use this anchored sed):
|
- `GET /pulls/<n>/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/<n>/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/<n> -o /tmp/pr.html`
|
||||||
|
then extract `<div class="content comment-content">` 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.
|
||||||
|
|
||||||
```bash
|
## Body-footer law (AGENTS.md)
|
||||||
tok=$(sed -nE 's#^http://[^:/@]+:([^@]+)@192\.168\.0\.143.*#\1#p' ~/.config/secrets/gitea | head -1)
|
Every issue/PR body ends with `---` line then `cwd: <wd> · session: <id>`. Keep
|
||||||
[ -n "$tok" ] && echo "token-extracted(len=${#tok})" # length check, never echo the value
|
it inside the --arg string (not a `---` CLI arg).
|
||||||
```
|
|
||||||
|
|
||||||
## Keep the token out of argv (repo secret law)
|
## Gotchas
|
||||||
`curl -H "Authorization: token $tok"` is world-readable in `/proc/<pid>/cmdline`.
|
- Content-Type: POSTs with `-d @file.json` MUST carry
|
||||||
Use a mode-600 config file:
|
`-H 'Content-Type: application/json'` on the command line. `-H` entries put in
|
||||||
|
the curl config file are ignored for this (header ordering quirk).
|
||||||
```bash
|
- `xdg-open <pr-url>` lands the PR in Frank's Firefox the moment it is opened —
|
||||||
cfg=$(mktemp); chmod 600 "$cfg"
|
law, not courtesy.
|
||||||
printf 'header = "Authorization: token %s"\n' "$tok" > "$cfg"
|
- `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
|
||||||
- POST JSON needs a second header line: `header = "Content-Type: application/json"`.
|
file (`~/.config/secrets/gitea`) is the one git actually consults (global
|
||||||
There IS NO `content-type` config option — curl rejects the file.
|
credential.helper) and the one to reuse for API calls.
|
||||||
- `rm -f "$cfg"` when done (a mode-600 token file left in /tmp is its own leak).
|
|
||||||
|
|
||||||
## Operations
|
|
||||||
```bash
|
|
||||||
# list open PRs
|
|
||||||
curl -sS -K "$cfg" ".../pulls?state=open" | jq -r '.[] | [.number,.head.name,.base.name,.title] | @tsv'
|
|
||||||
# create (build the payload as a FILE, see below)
|
|
||||||
curl -sS -K "$cfg" -o /tmp/pr.json -w '%{http_code}\n' -X POST -d @/tmp/pr-payload.json ".../pulls"
|
|
||||||
# verify: 201 is not enough — check mergeable
|
|
||||||
curl -sS -K "$cfg" ".../pulls/1" | jq '{number,state,merged,mergeable,base:.base.ref,head:.head.ref,url:.html_url}'
|
|
||||||
```
|
|
||||||
|
|
||||||
## Merging: the field is `Do`, and it is a STRING naming the strategy
|
|
||||||
|
|
||||||
Measured 2026-10-18 on Gitea at 192.168.0.143:3000 (`POST .../pulls/<n>/merge`).
|
|
||||||
`MergePullRequestOption` is `{From, To, When, Do}` — **there is no `DoMerge` and
|
|
||||||
no `merge_method`**. Every wrong shape is a 422 with a distinct message, so read it:
|
|
||||||
|
|
||||||
|body sent|http|message|
|
|
||||||
|---|---|---|
|
|
||||||
|`{"DoMerge":true,"merge_method":"merge"}`|422|`[Do]: Required`|
|
|
||||||
|`{"Do":true,"merge_method":"merge"}`|422|`[]: json: cannot unmarshal JSON boolean into Go string within "/Do"`|
|
|
||||||
|`{"Do":"true"}`|422|`[Do]: In…` (validation: not a strategy name)|
|
|
||||||
|form body `Do=true&merge_method=merge`|422|`[Do]: Required` (the value must be a strategy name)|
|
|
||||||
|`{"Do":"merge"}`|**200**|merged|
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -sS -K "$cfg" -H 'Content-Type: application/json' -X POST -d '{"Do":"merge"}' ".../pulls/4/merge"
|
|
||||||
# then prove it: .merged true, .state closed, .merge_commit_sha set, and origin/main moved
|
|
||||||
curl -sS ".../pulls/4" | jq -c '{number,state,merged,merge_commit_sha}'
|
|
||||||
```
|
|
||||||
|
|
||||||
Strategy names: `merge`, `rebase`, `rebase-merge`, `squash`, `manually` — a
|
|
||||||
strategy the repo does not allow is rejected too.
|
|
||||||
|
|
||||||
**Merge trap (measured 2026-10-18):** on `POST /merge` the `Content-Type` header
|
|
||||||
must go on the **command line** (`-H 'Content-Type: application/json'`). Same
|
|
||||||
curl that succeeds on `POST /pulls` with Content-Type supplied only via the `-K`
|
|
||||||
config file returns **405 with an EMPTY body** on `/merge`. A 405 there is this
|
|
||||||
trap (or a base-moved mergeability recheck — GET the PR, see `mergeable:true`,
|
|
||||||
then retry with `-H`), NOT a permission problem.
|
|
||||||
|
|
||||||
## Building issue/PR bodies without quote carnage
|
|
||||||
Hand-written `jq -n --arg t "..." '{title:$t, body:"..."}'` one-liners with long
|
|
||||||
markdown bodies WILL break on a stray quote (and the broken quoting can swallow
|
|
||||||
the following commands). Build bodies as files (heredoc or write tool), then:
|
|
||||||
```bash
|
|
||||||
b=$(jq -Rs . body.md)
|
|
||||||
jq -n --arg t "$title" --argjson b "$b" '{title:$t, body:$b, head:"<branch>", base:"main"}' > payload.json
|
|
||||||
curl ... -d @payload.json
|
|
||||||
```
|
|
||||||
|
|
||||||
## Gotchas measured 2026-10-14 / 2026-10-17 / 2026-10-18
|
|
||||||
- Unauthenticated/bad token → HTTP 401 body `{"message":"not found"}`; `jq '.[]|[.number,..]'` then fails with *cannot index "not found"* — that is an auth failure, not a missing repo.
|
|
||||||
- **Insufficient token scope ≠ missing data.** The credential-store token
|
|
||||||
(measured 2026-10-17) carries only `write:repository,write:user`; the issues
|
|
||||||
API answers HTTP 403 `{"message":"token does not have at least one of required
|
|
||||||
scope(s), required=[read:issue], token scope=write:repository,write:user"}`.
|
|
||||||
jq surfaces that JSON as the *cannot index* error — do NOT conclude the issue
|
|
||||||
list is empty, and note POSTs under this token create NOTHING (verify, don't
|
|
||||||
assume retry safety). Filing issues needs a fresh token minted in the Gitea
|
|
||||||
web UI with `read:issue`+`write:issue` (Frank-class credential action; ask) —
|
|
||||||
OR use the main-checkout `.env` `GITEA_TOKEN`, which already has issue scope.
|
|
||||||
PR endpoints work fine with either token (`pulls` POST/GET/merge all 200/201).
|
|
||||||
- On the created-PR response `.head.name` can be null; verify with the `GET /pulls/<n>` and use `.head.ref`.
|
|
||||||
- **Swagger JSON is disabled on this instance** — `/api/swagger.v1.json`, `/api/swagger.json` and `/api/v1/swagger.json` all 404. Do not go looking for the schema there; the 422 messages name the field and its Go type, which is the faster instrument.
|
|
||||||
@@ -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 <title> --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.
|
||||||
@@ -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.
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
---
|
---
|
||||||
name: niri-config-edit-validate
|
name: niri-config-edit-validate
|
||||||
description: "Locate, edit, validate, and commit niri compositor config on Frank's workstations: desk/display profiles, EDID-keyed outputs, layout, keybind grepping, dotfiles mirror, forced hot-reload."
|
description: "Locate, edit, validate, and commit niri compositor config on Frank's workstations: desk/display profiles, EDID-keyed outputs, layout, keybind grepping, dotfiles mirror, noctalia.kdl last-include color override, forced hot-reload."
|
||||||
---
|
---
|
||||||
|
|
||||||
# Editing niri config on Dank workstations
|
# Editing niri config on Dank workstations
|
||||||
|
|
||||||
## Layout of the config
|
## Layout of the config
|
||||||
- Live config: `~/.config/niri/config.kdl` is a thin file of `include "./cfg/*.kdl"` lines. **Never conclude "not bound" from grepping only `config.kdl`** — keybinds live in `cfg/keybinds.kdl`, rules in `cfg/rules.kdl`, layout in `cfg/layout.kdl`, etc. Grep `~/.config/niri/cfg/` recursively.
|
- Live config: `~/.config/niri/config.kdl` is a thin file of `include "./cfg/*.kdl"` lines. **Never conclude "not bound" from grepping only `config.kdl`** — keybinds live in `cfg/keybinds.kdl`, rules in `cfg/rules.kdl`, layout in `cfg/layout.kdl`, etc. Grep `~/.config/niri/cfg/` recursively.
|
||||||
|
- **Include order wins:** `config.kdl` ends with `include "noctalia.kdl"` — a hand-written accent overlay at `~/.config/niri/noctalia.kdl` that re-declares `layout { focus-ring/border/tab-indicator/insert-hint }` + `recent-windows.highlight` colors and BEATS every `cfg/layout.kdl` color edit (observed 2026-10-18: focus-ring color edits to layout.kdl appeared to "not reload" while this file silently repainted them red). Before changing any accent/outline color, grep the WHOLE `~/.config/niri/` tree (not just `cfg/`) for the property and fix the LAST includer. Tracked in repo at `dotfiles/zz-blankpad/niri/.config/niri/noctalia.kdl`; live file is NOT a symlink — sync by `cp` and `diff` like the rest.
|
||||||
- niri notation: `Mod` = Super/Win key. Chords like `Mod+R { switch-preset-column-width; }`.
|
- niri notation: `Mod` = Super/Win key. Chords like `Mod+R { switch-preset-column-width; }`.
|
||||||
- `~/.config/niri` is a REAL directory, not a symlink; the version-controlled copies are a partial mirror under `~/.omp/dotfiles/zz-blankpad/niri/.config/niri/`. Trees can drift: diff them before/after edits. (Hardlink wiring is FRAGILE — editor/agent atomic-rename writes silently break hardlinks; verify live↔mirror by CONTENT diff, never by link trust.)
|
- `~/.config/niri` is a REAL directory, not a symlink; the version-controlled copies are a partial mirror under `~/.omp/dotfiles/zz-blankpad/niri/.config/niri/`. Trees can drift: diff them before/after edits. (Hardlink wiring is FRAGILE — editor/agent atomic-rename writes silently break hardlinks; verify live↔mirror by CONTENT diff, never by link trust.) On blankpad the `cfg/*.kdl` entries are symlinks into the repo (`dotfiles/niri/...` base + `zz-blankpad` overrides) — edit through them freely; `noctalia.kdl` and `config.kdl` are the plain-file exceptions.
|
||||||
|
|
||||||
## Desk/display profiles (monitor arrangement)
|
## Desk/display profiles (monitor arrangement)
|
||||||
- Profiles: `cfg/desk-<n>-<name>.kdl` (e.g. `desk-1-home.kdl`, `desk-2-portable.kdl`); ACTIVE profile is symlinked as `cfg/display.kdl` — `readlink` it to know which. `scripts/desk.sh` manages switching; `desk.sh capture` prints connected outputs' EDID long names.
|
- Profiles: `cfg/desk-<n>-<name>.kdl` (e.g. `desk-1-home.kdl`, `desk-2-portable.kdl`); ACTIVE profile is symlinked as `cfg/display.kdl` — `readlink` it to know which. `scripts/desk.sh` manages switching; `desk.sh capture` prints connected outputs' EDID long names.
|
||||||
@@ -21,21 +22,22 @@ description: "Locate, edit, validate, and commit niri compositor config on Frank
|
|||||||
- Preset list order = cycle order for `Mod+R` (`switch-preset-column-width`); `Mod+Shift+R` cycles backwards. WITH custom preset lists the cycle contains ONLY the configured presets — no implicit full/maximize tail (verified: [0.25, 0.5] cycles period 2). Maximize = separate `Mod+F` `maximize-column`. Built-in defaults without the node: 1/3, 1/2, 2/3.
|
- Preset list order = cycle order for `Mod+R` (`switch-preset-column-width`); `Mod+Shift+R` cycles backwards. WITH custom preset lists the cycle contains ONLY the configured presets — no implicit full/maximize tail (verified: [0.25, 0.5] cycles period 2). Maximize = separate `Mod+F` `maximize-column`. Built-in defaults without the node: 1/3, 1/2, 2/3.
|
||||||
- WITHOUT `preset-window-heights`, `switch-preset-window-height` FALLS BACK to the width presets — user sees width-fractions as heights; define the node explicitly to stop it.
|
- WITHOUT `preset-window-heights`, `switch-preset-window-height` FALLS BACK to the width presets — user sees width-fractions as heights; define the node explicitly to stop it.
|
||||||
- Ground truth for node placement: the binary's embedded template — `grep -a -B4 -A8 "preset-column-widths {" $(which niri)`.
|
- Ground truth for node placement: the binary's embedded template — `grep -a -B4 -A8 "preset-column-widths {" $(which niri)`.
|
||||||
|
- Window outlines: base geometry (`width`, `off`) lives in `cfg/layout.kdl` `focus-ring`/`border`; COLORS effectively live in `noctalia.kdl` (see include-order law). `border { off }` + focus-ring active = the visible outline. Frank's look since 2026-10-18: amber-orange `#ff8c00`, width 4 (`active`+`urgent` in noctalia.kdl; tab-indicator/insert-hint/recent-windows stay red `#f43841` by choice).
|
||||||
|
|
||||||
## Workflow
|
## Workflow
|
||||||
1. Edit the live file under `~/.config/niri/cfg/`.
|
1. Edit the live file under `~/.config/niri/cfg/` (symlinked = repo file; except `noctalia.kdl`, edit live then `cp` to mirror).
|
||||||
2. `niri validate` (exit 0 + "config is valid") — mandatory before done.
|
2. `niri validate` (exit 0 + "config is valid") — mandatory before done.
|
||||||
3. **Hot-reload is NOT reliable after harness edits** (atomic-rename writes are missed by niri's watcher; user then tests against the OLD config and reports "no change"). ALWAYS follow with `niri msg action load-config-file` (NOT `reload-config`).
|
3. **Hot-reload is NOT reliable after harness edits** (atomic-rename writes are missed by niri's watcher; user then tests against the OLD config and reports "no change"). ALWAYS follow with `niri msg action load-config-file` (NOT `reload-config`).
|
||||||
4. Sync changed file into the mirror: `cp ~/.config/niri/cfg/<f>.kdl ~/.omp/dotfiles/zz-blankpad/niri/.config/niri/cfg/<f>.kdl` (repo root is `~/.omp`, branch `main`, remote = dankclaw Gitea `dank/command-center`; push there is pre-authorized). Verify by `diff`, not by cp success.
|
4. Sync changed file into the mirror: `cp ~/.config/niri/cfg/<f>.kdl ~/.omp/dotfiles/zz-blankpad/niri/.config/niri/cfg/<f>.kdl` (repo root is `~/.omp`, branch `main`, remote = dankclaw Gitea `dank/command-center`; push there is pre-authorized). Verify by `diff`, not by cp success.
|
||||||
5. Untracked mirror files: `config.kdl`, `noctalia.kdl`, `animation.kdl`, `display.kdl`, `input.kdl` — flag drift, don't silently add.
|
5. Untracked mirror files: `config.kdl`, `noctalia.kdl`, `animation.kdl`, `input.kdl`, `display.kdl` (symlink target) — flag drift, don't silently add; when a live-only file turns out to be load-bearing (like `noctalia.kdl` was), commit the first durable copy into `dotfiles/zz-blankpad/niri/.config/niri/`.
|
||||||
6. Mirror files often carry pre-existing uncommitted drift (desk.sh captures); a commit of your edit folds it in — say so in the report/commit body.
|
6. Mirror files often carry pre-existing uncommitted drift (desk.sh captures); a commit of your edit folds it in — say so in the report/commit body.
|
||||||
|
|
||||||
## Live-behavior verification without disturbing Frank's windows
|
## Live-behavior verification without disturbing Frank's windows
|
||||||
- `niri msg --json <subcmd>` — the `--json` flag goes BEFORE the subcommand.
|
- `niri msg --json <subcmd>` — the `--json` flag goes BEFORE the subcmd.
|
||||||
- Targeting differs per action: WINDOW-scoped actions take `--id <window-id>` (NOT `--window`) — e.g. `switch-preset-window-height --id N`. COLUMN-scoped actions (`switch-preset-column-width`, `set-column-width`, `maximize-column`) have NO id — they hit the FOCUSED column; only `set-column-width <px|±N%>` positional CHANGE, no flags.
|
- Targeting differs per action: WINDOW-scoped actions take `--id <window-id>` (NOT `--window`) — e.g. `switch-preset-window-height --id N`. COLUMN-scoped actions (`switch-preset-column-width`, `set-column-width`, `maximize-column`) have NO id — they hit the FOCUSED column; only `set-column-width <px|±N%>` positional CHANGE, no flags.
|
||||||
- Spawned test apps do NOT reliably take focus, so blind `spawn + action` cycles resize whatever Frank is looking at. For column actions you must `focus-window --id N` first — and that STEALS focus; if Frank is actively testing chords, your probes and his keystrokes interleave and corrupt both. STOP probing, deliver config + validate, and have him press the chord himself.
|
- Spawned test apps do NOT reliably take focus, so blind `spawn + action` cycles resize whatever Frank is looking at. For column actions you must `focus-window --id N` first — and that STEALS focus; if Frank is actively testing chords, your probes and his keystrokes interleave and corrupt both. STOP probing, deliver config + validate, and have him press the chord himself.
|
||||||
- `niri msg --json focused-window` can report a different window than assumed (focus moves); for per-window measurement read `niri msg --json windows` and filter by `id`. Present height of proportion p with gaps g over work-area H renders ≈ p*(H+g) − g.
|
- `niri msg --json focused-window` can report a different window than assumed (focus moves); for per-window measurement read `niri msg --json windows` and filter by `id`. Present height of proportion p with gaps g over work-area H renders ≈ p*(H+g) − g. NOTE (measured 2026-10-18): this build's `windows`/`focused-window` JSON returns `x/y/width/height = null` — pixel-sampling the outline color from screenshots is not viable; ask Frank to eyeball after `load-config-file` instead of burning turns on capture forensics.
|
||||||
- If Frank reports "still seeing old values" after a config edit: run `load-config-file` first, then verify empirically before believing anything else.
|
- If Frank reports "still seeing old values" after a config edit: run `load-config-file` first, then verify empirically before believing anything else — and check `noctalia.kdl` include-order override BEFORE suspecting the reload.
|
||||||
|
|
||||||
## "What does chord X do?" triage
|
## "What does chord X do?" triage
|
||||||
For "mod+<key>" questions, check in order: `~/.config/niri/cfg/keybinds.kdl` (compositor wins first), terminal configs (`~/.config/ghostty`, `foot`, `kitty`), tmux, then omp TUI chords (`omp://keybindings.md`; modifiers are ctrl/shift/alt/super only, no `Mod`; `Ctrl+R`=history search, `Alt+R`=app.retry).
|
For "mod+<key>" questions, check in order: `~/.config/niri/cfg/keybinds.kdl` (compositor wins first), terminal configs (`~/.config/ghostty`, `foot`, `kitty`), tmux, then omp TUI chords (`omp://keybindings.md`; modifiers are ctrl/shift/alt/super only, no `Mod`; `Ctrl+R`=history search, `Alt+R`=app.retry).
|
||||||
@@ -1,45 +1,52 @@
|
|||||||
---
|
---
|
||||||
name: noctalia-bar-layout-edit
|
name: noctalia-bar-layout-edit
|
||||||
description: "Edit/verify Noctalia v5 bar layout and clock widgets (multi-timezone instances, capsule groups); GUI→repo propagation chain and settings.toml symlink audit on Frank's dotfiles workstations"
|
description: "Edit/verify Noctalia v5 bar layout and clock widgets (multi-timezone instances, capsule groups); GUI-repo propagation chain, settings.toml symlink audit, and stash-clobber recovery on Frank's dotfiles workstations"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Noctalia v5 bar layout edit
|
|
||||||
|
|
||||||
Bar widget layout on Frank's noctalia v5 workstations is GUI-override state, not config.toml.
|
Bar widget layout on Frank's noctalia v5 workstations is GUI-override state, not config.toml.
|
||||||
|
|
||||||
## Where
|
## Where
|
||||||
- Layout file: `~/.local/state/noctalia/settings.toml` → `[bar.<name>]` keys `start` / `center` / `end` (arrays of widget ids; `[widget.<id>]` subtables hold per-widget settings).
|
- Layout file: `~/.local/state/noctalia/settings.toml` -> `[bar.<name>]` keys `start` / `center` / `end` (arrays of widget ids; `[widget.<id>]` subtables hold per-widget settings).
|
||||||
- On Frank's boxes this file MUST be a **symlink into the command-center repo** (e.g. `~/.omp/dotfiles/noctalia/.local/state/noctalia/settings.toml`) — edit the repo file, not the link path (atomic-rename writes through the link path would replace the symlink).
|
- On Frank's boxes this file MUST be a **symlink into the command-center repo** (e.g. `~/.omp/dotfiles/noctalia/.local/state/noctalia/settings.toml`) - edit the repo file, not the link path (atomic-rename writes through the link path would replace the symlink).
|
||||||
- Layer order: built-in defaults < `~/.config/noctalia/config.toml` (+`[include]`) < `settings.toml` (wins). Noctalia writes through symlinks and keeps them.
|
- Layer order: built-in defaults < `~/.config/noctalia/config.toml` (+`[include]`) < `settings.toml` (wins). Noctalia writes through symlinks and keeps them.
|
||||||
- Defaults when a key is absent: `start = ["launcher","wallpaper","workspaces"]`, `center = ["clock"]`.
|
- Defaults when a key is absent: `start = ["launcher","wallpaper","workspaces"]`, `center = ["clock"]`, `end = ["notifications","clipboard","network","bluetooth","tray","battery","session","control-center","clock"]`.
|
||||||
- **Noctalia rewrites this file itself** (GUI actions + normalizes keys/format, e.g. strips `{:%...}` braces, reorders widget lists). Always re-read the exact lines immediately before editing; stale line anchors WILL drift between reads.
|
|
||||||
|
|
||||||
## GUI→repo propagation (checkout-is-deployment)
|
## Clobber window and recovery (2026-10-18 incident law)
|
||||||
- `~/.config/noctalia/config.toml` is symlinked into the repo → GUI settings writes land in the repo working tree automatically.
|
- GUI-built layout is **working-tree-only until the hourly live-sync heartbeat commits it**. A merge/sync session that "clears the dirty tree" can `git stash` those edits, and later stash operations can destroy the entries - customizations then vanish from the live bar with the repo copy stale.
|
||||||
- `~/.local/state/noctalia/settings.toml` can SILENTLY become a plain file (observed 2026-10-17; hardlink/symlink wiring decays) → GUI bar-layout changes then diverge from the repo with no signal. AUDIT: `readlink ~/.local/state/noctalia/settings.toml` must print the repo path; if it's a plain file and repo copy is content-identical, re-arm: `ln -sfn <repo-path> <live-path>` + `noctalia config validate`.
|
- Never blame the GUI or noctalia first: diff live file vs `HEAD`, check heartbeat commits (`git log -- dotfiles/noctalia/.local/state/noctalia/settings.toml`), then the stash chain.
|
||||||
- Landing in the working tree ≠ propagated: other boxes only see it after commit + push to Gitea main. After GUI changes, check `git -C ~/.omp status` and commit (Noctalia's runtime churn — wallpaper path, nightlight, panel positions — rides along; normal).
|
- Destroyed stashes are recoverable: `git fsck --lost-found` lists dangling commits; `git show <dangling>:dotfiles/noctalia/.local/state/noctalia/settings.toml` dumps each candidate; pick by `[bar.default]` richness (capsule groups, extra clock instances). Validate a candidate in a sandboxed `XDG_*_HOME` before touching the live file.
|
||||||
- niri has NO settings GUI; niri "GUI changes" don't exist, and `niri msg output ...` runtime overrides are not persisted. niri cfg mirror↔live links also decay (all inodes decoupled once) → audit by CONTENT: `diff` every `~/.config/niri/cfg/*.kdl` against its `dotfiles/zz-blankpad/niri/...` mirror, never trust links.
|
- After recovery, commit the file immediately so the window closes.
|
||||||
|
|
||||||
## Apply/verify loop (no restart, no reload IPC)
|
## Widgets (v5 census, verified 2026-10-17)
|
||||||
1. Edit the repo `settings.toml`.
|
- Full roster in `settings.toml` widget_order plus docs: activity, backlinks, battery, bluetooth, brief, bright, cat, cheatsheet, chart, clipby, clipboard, control-center, cpu, controls, cava (now playing), disk, gpu, help, keyboard, language, launcher, mediaplayer, memory, mic, network, night-light, noctalia-palette, notification-history, notifications, niri-colors, power-section, power-profile, screenrecord, scratchpad, screenshot, session, shell (custom shell command), slowouts, slowouts-visible (per-output bar assignment), speedtest, swayosd-*, taskbar, tray, voiceline, volume, wallpaper, weather, workspaces.
|
||||||
2. `noctalia config validate` (checks merged layers; also `noctalia config export [full] | yq -p toml` to inspect effective values).
|
- `clock` widget: `format` (chrono strftime), `schema` (12/24), `multi_line`, `show_seconds`, `popup`, and **timezone** = IANA name. Multiple clock widget instances are supported: ids like `clock`, `clock-utc`, `clock-sea` each get a `[widget.<id>]` subtable -> one capsule per instance. This is how multi-timezone (Seattle work + UTC dev) lands: add `clock` + `clock-utc` to `end` (or `center`), set `timezone = "Etc/UTC"` on the utc one.
|
||||||
3. Both layers are file-watched → hot-reload. Prove visually: `noctalia msg screenshot-fullscreen` saves a PNG to `~/Pictures`; read the image and inspect the bar.
|
- `cat` widget: shows random ASCII art, `rows` setting (4-10).
|
||||||
4. **Screenshot race:** the `ok` return precedes the PNG write. Never chain `noctalia msg screenshot-fullscreen && magick "$(ls -t ~/Pictures/screenshot_*.png | head -1)" ...` in one command — you crop the PREVIOUS capture. List `ls -t` in a separate later call and crop the named file.
|
- `shell` widget: `command`, `run interval` (seconds), `icon`, `terminal`; output lands in its capsule popup - can wrap any CLI (hyprstatus-style).
|
||||||
5. Crop for reading: `magick <png> -crop 100%x8%+0+0 +repage /tmp/strip.png`, then read the strip; cross-check clocks against `date -u` / `date`.
|
- `taskbar` widget: shows window buttons **only on outputs where `niri-bridged` is enabled** (noctalia-launcher enables it). Buttons render **icon-only** (app icon from icon theme; fallback = first letter of title); the window title string is NOT shown in the bar. Hover tooltip carries title + app_name. Per-widget settings: `show_icons`, `show_actions`, `disable_middle_click`, `preferred_app_names` (map app_name -> icon for missing icons).
|
||||||
|
- Widget ids usable in `[bar.*].start|center|end` are restricted to "bar widgets" (`is_bar_widget` allowlist in `bar.rs`); non-bar widget ids are silently dropped at load. `group:<id>` entries render capsule groups.
|
||||||
|
- Capsule groups: `[bar.<name>.capsule_groups]` id = `["widget",...]`.
|
||||||
|
- Niri layout law for bars: window width must stay **odd** (strut = (W-1)/2); bars reserve via layer-shell, niri's reserved area = layer surface size, so bar height changes need no niri edit.
|
||||||
|
|
||||||
## Clock widgets
|
## Bar styles (GUI-visible, settings.toml keys)
|
||||||
- `[widget.clock]` settings: `format`, `vertical_format`, `tooltip_format`, `timezone` (IANA id, e.g. `"UTC"`, `""` = system local). Tokens are strftime-compatible (`%H` 24h, `%I:%M %p` 12h, `%F` ISO date); `{:%...}` brace fields optional; `\n` = second line. Docs: docs.noctalia.dev → bar/widgets/clock + configuration/date-format-tokens.
|
- `[bar.*]`: `thickness`, `font_family`, `font_weight`, `font_size`, `opacity`, `border_width`, `border_offset`, `rounded`, `paddings`, `icon_ratio`, `follow_layout`, `output`, `exclusive`, `layer` (`bottom` = below window layer; `overlay` = above windows), `position` (`top`/`bottom`).
|
||||||
- **One timezone per widget; multiple instances allowed:** any `[widget.<id>]` needs `type = "clock"` for custom ids (bare `clock` id infers it). Each instance converts independently — e.g. local 12h + `timezone = "UTC"` 24h side by side.
|
- Per-monitor bars: add a `[bar.<name>]` with `output = "<monitor>"` and `follow_layout = true` (niri) -> per-workspace strut follows tall windows on that monitor. This is the mechanism for "bar shrinks when a window is tall" (the bar is a layer-shell strut, NOT a niri layer rule).
|
||||||
- Placement: add ids to `[bar.<name>]` start/center/end, or to a capsule group: `[[bar.default.capsule_group]]` with `id = "g1"` + `members = [...]`, referenced from `start` as `"group:g1"` (member order = left-to-right inside the capsule).
|
- The GUI "Bar" page is the reference list; anything absent from settings.toml is a built-in default.
|
||||||
- Local date + local 12h fit in ONE widget (same tz); the UTC 24h clock must be a second widget.
|
|
||||||
- Expect the date widget (local) and UTC clock to disagree on the day after ~5 PM Seattle — that's correct UTC, not a bug; offer `%F` inside the UTC widget if it irks.
|
|
||||||
|
|
||||||
## Taskbar recipes
|
## Slowouts (window-height-aware bars/panels)
|
||||||
- Move window indicator beside workspaces: put `"taskbar"` in `start` after `"workspaces"`.
|
- `slowouts` = hidden engine (not a bar capsule): `[slowouts]` keys `enabled`, `duration`, `easing`, `trigger_offset`; per-monitor `[slowouts.<monitor-name>]` overrides (e.g. `DP-1`) with the same keys plus `edge`.
|
||||||
- Combine workspaces+windows into ONE widget: `[widget.taskbar] group_by_workspace = true` — per-workspace capsules with disc labels (focused/occupied/empty/urgent colors); `workspace_group_content = "icons"|"count"|"dots"` (count/dots: left-click activates the workspace); `hide_empty_workspaces`, `show_workspace_label`, `minimal`, `workspace_label_placement = corner|centered|inside`. Requires compositor per-window workspace assignment (niri: yes; LabWC: no → stays flat).
|
- `position = "top"` + `exclusive = false` + slowouts on the monitor -> panel hides when a window touches its edge, re-reveals otherwise. `slowouts-visible` = toggle capsule.
|
||||||
|
- **Slowouts needs niri workspace-autoresize ON** (niri `workspace-autoresize` = "per-workspace" in cfg/layout.kdl `output ... { workspace-autoresize per-workspace }`): noctalia computes hide from workspace height < panel bottom edge; uniform-strut layouts never trigger it.
|
||||||
|
- Per-output bars: slowouts config keys are monitor names, so one `[bar.<x>]` with `output` + `exclusive=false` + `layer=overlay` per slowout monitor is the supported shape.
|
||||||
|
|
||||||
|
## Layout modes and window title decorations
|
||||||
|
- `[layout]`: `mode` (`tiling`|`floating`|`monocle`), `pseudotiled`, `focus_bar` (window-title bar at top, center, layer-shell; style via `[ui.controls...]`/bar-ish settings), `window_follows_cursor`, `gaps`, `border_width`, `rounded`, `blur`, `opacity`, `borderless_csd`, `show_native_decoration`.
|
||||||
|
- `focus_bar` is the noctalia analog of niri's drawn window title, but ONE global bar (no per-window arrows). True per-window title tags = niri `draw-with-border` on window rules; noctalia adds no per-window decoration (it's a bar app + portal, composites nothing).
|
||||||
|
- Floating mode: right-click desktop / `SUPER+Shift+F` / toggle widget; widgets `floating-directories`, `floating-files`, `pins` (sticky notes), `canvas`, `sketchpad`, `post-its`. Float windows snap to screen quarters (`SUPER+Ctrl+Alt+Arrows`) and tile into current layout (`SUPER+Shift+T`). `[layout.floating.files]` = icon/view/sort/preview size.
|
||||||
|
- `[bar.*].window_display` = `disabled`|`icons`|`text`|`combined` - per-window chips in the bar strip (niri only; this is what niri-taskbar could not do). Values: `disabled`|`icons`|`centered`|`inside`. Requires compositor per-window workspace assignment (niri: yes; LabWC: no -> stays flat).
|
||||||
- The `workspaces` widget itself has no window display; styles are regular/minimal/focus_hint only.
|
- The `workspaces` widget itself has no window display; styles are regular/minimal/focus_hint only.
|
||||||
|
|
||||||
## Law
|
## Law
|
||||||
- Full widget/setting tables: https://docs.noctalia.dev/noctalia/bar/ and .../bar/widgets/<name>/ . Commit bar changes atomically to the command-center repo (checkout is the deployment).
|
- Full widget/setting tables: https://docs.noctalia.dev/noctalia/bar/ and .../bar/widgets/<name>/ . Commit bar changes atomically to the command-center repo (checkout is the deployment).
|
||||||
- **Ask before applying.** Frank asks "how do I X" questions about the live bar; answer with the exact snippet + file, apply ONLY on explicit go. A settings.toml edit is a live-deployment change, not a demo.
|
- **Ask before applying.** Frank asks "how do I X" questions about the live bar; answer with the exact snippet + file, apply ONLY on explicit go. A settings.toml edit is a live-deployment change, not a demo.
|
||||||
- Git `M` on settings.toml usually carries noctalia's own runtime churn (wallpaper path, nightlight, panel positions) alongside your edit — mention it when committing.
|
- Git `M` on settings.toml usually carries noctalia's own runtime churn (wallpaper path, nightlight, panel positions) alongside your edit - mention it when committing.
|
||||||
|
- Right after building layout in the GUI, commit settings.toml (or trigger `scripts/live-sync.sh`) before any branch/merge work: the uncommitted-GUI-edit window is exactly where the 2026-10-18 clobber happened.
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
name: omp-bundle-implementation-port
|
||||||
|
description: "Extract and port a working implementation from omp's minified dist/cli.js bundle (copy-not-invent requests); anchors, python slicing, regex-constant sweep, trace-driven test prediction."
|
||||||
|
---
|
||||||
|
|
||||||
|
# Porting built-in omp behavior out of the minified bundle
|
||||||
|
|
||||||
|
Use when Frank says "copy the omp implementation / work off known-working code" — i.e. port a built-in feature (vocalizer segmentation, retry logic, formatting rules) into our own file instead of hand-rolling a heuristic. For merely *auditing behavior claims*, use `omp-bundle-claim-audit`; this skill is extraction-for-porting.
|
||||||
|
|
||||||
|
Bundle: `~/.bun/install/global/node_modules/@oh-my-pi/pi-coding-agent/dist/cli.js` (minified; single-letter names, private `#x` fields survive).
|
||||||
|
|
||||||
|
## Extraction recipe
|
||||||
|
|
||||||
|
1. **Find the setting/entry strings first.** `grep -o 'ce({id:"<area>[^"]*"[^}]\{0,200\}' cli.js` — setting descriptions state semantics verbatim (e.g. `speech.enhanced` = "Rewrite assistant output into natural spoken prose with the tiny/smol model"). Descriptions are quotable ground truth.
|
||||||
|
2. **Anchor to code with a distinctive token**, not guesses: a method name (`pushDelta`), a state init, or a string. Then slice generously with python (bash heredoc), NOT grep -o which truncates:
|
||||||
|
```python
|
||||||
|
src = open('cli.js').read()
|
||||||
|
i = src.index('pushDelta'); print(src[i-1500:i+3000])
|
||||||
|
```
|
||||||
|
3. **Follow the constructor**: pipeline classes instantiate collaborators there (`this.#t ??= new fHe` → `fHe` is the real worker; `#g()` returned `{speakable:new fHe, pending:[]…}`). Dump `class fHe{` +4000 chars.
|
||||||
|
4. **Regex/helper sweep**: minified code declares shared constants in a trailing `var a,b,c,…` list and initializes them elsewhere (`Xyr=`, `Pwc=function`). Loop the names with `re.search(rf'{name}=', src)` and print ~400 chars each — the actual boundary/cleanup rules live in these one-liners.
|
||||||
|
5. Write slices to `/tmp/*.txt` and `read` them; don't eyeball megabyte greps.
|
||||||
|
|
||||||
|
## Port rules
|
||||||
|
|
||||||
|
- Rename fields/methods to readable names but keep logic **1:1** — the value is behavioral fidelity (Frank's "known-working set of code").
|
||||||
|
- Private `#x` fields port cleanly: TS classes in Bun extensions support `#`.
|
||||||
|
- Global-regex `.lastIndex=0` before each scan loop — minified code relies on it; keep it.
|
||||||
|
|
||||||
|
## Verification shortcut
|
||||||
|
|
||||||
|
Before running tests, **hand-trace** each test input through the ported rules (boundary indices, state transitions) and predict exact segment counts/strings; then run — predicted-vs-actual mismatch localizes port errors faster than test output alone. Worked example (2026-10-19, dankvoice PR #26): traced `"First stop here. Second one goes!!! Really? Third ... done"` → predicted 3 segments incl. `"Third ... done"` merged via flush (10-char boundary < 24-char started-min) → bats green first run.
|
||||||
|
|
||||||
|
## Known anchors (2026-10-19 bundle)
|
||||||
|
|
||||||
|
- Speak-aloud vocalizer pipeline: class `I6o` (gating on `speech.enabled`, suspend counter, enhancer, `#g()` per-message state).
|
||||||
|
- Utterance segmenter: class `fHe` + helpers `Pwc` (line-head classify), `Iwc`/`_wc`/`b6o`/`w6o` (boundary search), `Cwc`+`y6o` (cleanup: links→label, URL→host, path→basename). Regex consts: `Xyr` sentence-end runs, `pHe` weak breaks, `mwc` abbreviations, `hwc` undecided line-heads, `g6o` horizontal rules.
|
||||||
|
- Port already living in `agent/extensions/dankvoice.ts` (`SpeechSegmenter`) — when the bundle updates, re-extract `fHe` and diff against it before assuming our copy still matches.
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
---
|
||||||
|
name: omp-extension-live-tui-verify
|
||||||
|
description: "Verify an omp extension's slash commands and event hooks against a real TUI session: pty submit semantics, observable side-effect instrument, transient-toast trap, agent_end willContinue law, PipeWire output triage."
|
||||||
|
---
|
||||||
|
|
||||||
|
Verify that a `.ts` file in `agent/extensions/` actually works inside the interactive TUI — the black-box bats seam cannot see harness-side wiring (event names, arg passing, mode gating), and it will pass beside a dead hook.
|
||||||
|
|
||||||
|
Distinct from `omp-tui-live-visual-verify` (config/theme rendering proven by screenshot): here the instrument is **observable side effects**, not pixels, because extension notifications are transient toasts that vanish before the pty buffer is read.
|
||||||
|
|
||||||
|
## 1. Launch the exact build under test
|
||||||
|
|
||||||
|
```bash
|
||||||
|
omp --no-extensions --no-session -e /abs/path/agent/extensions/<name>.ts
|
||||||
|
```
|
||||||
|
as a **pty service** (`pty: true`, `ready: {"log": "╭|❯|Tip:|─", "timeout": 60}`).
|
||||||
|
`--no-extensions` + explicit `-e` proves the file loads by path and nothing else interferes. Neutral cwd (`/tmp`) so repo state cannot color the run.
|
||||||
|
|
||||||
|
## 2. Submit input correctly (the costly gotcha)
|
||||||
|
|
||||||
|
`write proc://<id>` with a trailing `\n` does **not** submit — the text just sits in the composer.
|
||||||
|
|
||||||
|
- **Works:** two writes — first `"/cmd args"` (no terminator), then a **separate** write whose whole content is `\r`.
|
||||||
|
- **Broken:** `"/cmd args\r"` in one write. It still submits, but the trailing `\r` arrives inside the command args. Symptom: `unknown voice 'narrator1\r'`, an unexpected extra utterance, or a chat message whose transcript line ends in a literal `\r`. That is a harness artifact — never "fix" it by stripping control characters in the extension (`.trim()` already covers what a keyboard can produce).
|
||||||
|
|
||||||
|
## 3. Instrument with a stub, never with ears or eyes
|
||||||
|
|
||||||
|
Point the extension's player/handler at a script that appends evidence:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
echo "$1" >> /tmp/stub.log; cp "$1" "/tmp/out-$$.wav"; exit 0
|
||||||
|
```
|
||||||
|
Run the TUI with that stub in the env the extension reads (`DANKVOICE_PLAYER=…` style). Then every assertion is a file: `wc -l` of the log (how many times it fired), `ffprobe -show_entries format=duration` (what it produced), `ls -d <tmp-glob>` (that cleanup ran).
|
||||||
|
|
||||||
|
Sequence per behavior: command → CR → short `sleep` → count. **Count before and after** each command; the delta is the only honest proof a subcommand stayed silent.
|
||||||
|
|
||||||
|
## 4. Discriminate "fired twice" from "fired once per turn"
|
||||||
|
|
||||||
|
Stray CR writes can create extra empty turns, each with its own `agent_end` — two utterances for one intended prompt is usually **two turns**, not a duplicate speak. Confirm by counting assistant turns in the transcript (`read proc://<id>`), and by matching each clip's duration to its own answer's length.
|
||||||
|
|
||||||
|
## 5. Event-hook contract traps
|
||||||
|
|
||||||
|
- `agent_end` payload (`dist/types/extensibility/shared-events.d.ts`): `{ type, messages: AgentMessage[], willContinue? }`. **`willContinue: true` means the harness already scheduled a continuation** (auto-retry / empty-stop retry) — a subscriber that narrates or notifies there speaks an answer the model is about to redo. Gate on it.
|
||||||
|
- Read the real event/payload shape from `~/.bun/install/global/node_modules/@oh-my-pi/pi-coding-agent/dist/types/…`; grep `dist/cli.js` for the literal event names to confirm they exist.
|
||||||
|
- A driver test that *calls* the handler by hand proves nothing about emission — that is exactly the gap this live check closes.
|
||||||
|
|
||||||
|
## 6. Audio output goes silent while every command reports success
|
||||||
|
|
||||||
|
`pw-play` exits 0 and prints `stream drained` even when nobody hears anything. Triage order:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wpctl status # Sinks, the `*` default, and live Streams
|
||||||
|
wpctl get-volume @DEFAULT_AUDIO_SINK@
|
||||||
|
```
|
||||||
|
- A **`Default Configured Devices` entry naming a `bluez_output.*` node that is not in the Sinks list** = remembered-but-offline Bluetooth: the classic silent-route cause.
|
||||||
|
- Confirm the stream really links: a short clip can vanish before inspection, so use a multi-second clip and read `wpctl status` mid-play, or target explicitly: `pw-play --target <node-id> <file>` (node ids from `wpctl status`; `wpctl inspect <id>` gives `node.name`).
|
||||||
|
- Prove the synth path independently of the player: `curl -o x.wav …` then `file x.wav` (expect `RIFF … WAVE … PCM`).
|
||||||
|
|
||||||
|
## 7. Land it
|
||||||
|
|
||||||
|
Live result belongs in the commit message, the reference page, and the work log — and the guard the live check uncovered gets its own behavior test (assert observable output only: fired count, payload content, silence).
|
||||||
|
|
||||||
|
## Cleanup
|
||||||
|
|
||||||
|
`/exit` or `proc://<id>/kill` the service; move stub scripts, logs, and clips to `~/.AGENTTRASH/<date>-<topic>/` — never `rm`. Kill background jobs that keep playing to the operator's speakers.
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
---
|
||||||
|
name: omp-models-yml-runner-api-placement
|
||||||
|
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,
|
||||||
|
flipping `config.yml` `modelRoles` at it, and diagnosing a speech device that
|
||||||
|
refuses to speak.
|
||||||
|
|
||||||
|
# The landmine
|
||||||
|
|
||||||
|
Provider-level `api` accepts **only chat/judge transports**:
|
||||||
|
`openai-completions`, `openai-responses`, `openai-codex-responses`,
|
||||||
|
`azure-openai-responses`, `anthropic-messages`, `bedrock-converse-stream`,
|
||||||
|
`google-generative-ai`, `google-gemini-cli`, `google-vertex`,
|
||||||
|
`openrouter-decisions`, `typesafe`.
|
||||||
|
|
||||||
|
Naming a runner API (`openai-speech`, `openai-images`, `openai-embeddings`, …)
|
||||||
|
at **provider** level fails whole-file validation, and the failure mode is not
|
||||||
|
scoped to your provider:
|
||||||
|
|
||||||
|
```
|
||||||
|
Warning: models.yml validation failed — custom providers disabled
|
||||||
|
Failed to load config file models, Schema error: providers.<id>.api: must be "openai-completions", …
|
||||||
|
```
|
||||||
|
|
||||||
|
Every custom provider disappears from the registry (fleet chat endpoints stop
|
||||||
|
resolving) and `omp models` lists only built-in locals. Measured 2026-10-18.
|
||||||
|
|
||||||
|
# Correct shape
|
||||||
|
|
||||||
|
Runner api goes on the **model entry only**; `auth: none` satisfies the
|
||||||
|
credential requirement; omit `contextWindow`/`maxTokens` (the positivity check
|
||||||
|
fires only when they are provided — `omp models` still prints filled defaults,
|
||||||
|
which the runner transport never reads).
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
"frank-tts (dankclaw)":
|
||||||
|
baseUrl: http://192.168.0.143:7772/v1 # no `api:` here
|
||||||
|
auth: none
|
||||||
|
models:
|
||||||
|
- id: frank-tts
|
||||||
|
name: "frank-tts (dankclaw) — cloned voices"
|
||||||
|
api: openai-speech # runner api on the model
|
||||||
|
input: [text]
|
||||||
|
```
|
||||||
|
|
||||||
|
Roles live in `config.yml` (`modelRoles`), not in `models.yml`. Kind roles
|
||||||
|
(`image`, `web`, `speech`, `dictation`, `judge`) take no `:thinking` suffix.
|
||||||
|
|
||||||
|
# Verify after every edit (never assume the file loaded)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
omp models --kind tts --no-extensions # the new row must appear
|
||||||
|
omp models --no-extensions | grep -E '<each fleet chat model>' # and still be listed
|
||||||
|
omp models | grep -c 'validation failed' # must be 0
|
||||||
|
```
|
||||||
|
|
||||||
|
A run that lists only built-ins means your row broke the file — revert before
|
||||||
|
yielding.
|
||||||
|
|
||||||
|
# Speech transport contract (`openai-speech`)
|
||||||
|
|
||||||
|
Body is `{model, input, response_format, voice?, speed?}`; `voice` is forwarded
|
||||||
|
**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.
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
---
|
|
||||||
name: one-decision-per-ask-question
|
|
||||||
description: "Use when putting decisions to Frank via the ask tool or a grilling round — exactly one work item per question, rounds may group many single-item questions."
|
|
||||||
---
|
|
||||||
|
|
||||||
# One Decision, One Work Item, One Question
|
|
||||||
|
|
||||||
Friction-earned rule (corrected twice on 2026-10-18, during the voice/TTS planning).
|
|
||||||
|
|
||||||
## Rules
|
|
||||||
|
|
||||||
- Each question addresses **exactly one work item**. A round may contain many questions covering different work items; a question may not.
|
|
||||||
- Never merge two artefacts into one option because they share an implementation dependency. Sharing an endpoint, a service, or a build step is NOT grounds for one question. (`frank-tts` serving both a podcast and an audiobook generator produced exactly this bug.)
|
|
||||||
- Never offer something Frank did not ask for inside another question's option text. If an idea is mine, it gets its own question or stays out of the plan; it never rides along as "…and also X" in an option label or preview.
|
|
||||||
- Do not derive option wording from my own scaffolding (todo text, my table row labels, my runbook headings). Derive it from his stated goal.
|
|
||||||
- Mark the recommended option; a bare "yes" must accept it.
|
|
||||||
- Facts are mine to fetch (subagent/scout), never a question for him. Only decisions go to him.
|
|
||||||
- A question whose answer depends on an unsettled fact or another open question belongs to a **later round**, not this one.
|
|
||||||
|
|
||||||
## Anti-patterns that triggered this
|
|
||||||
|
|
||||||
- One option: "Audiobook/podcast factory — open-notebook + EPUB→narration builder." Two products, one shared dependency, zero requests for the second one. He answered "I never asked for audiobook generation."
|
|
||||||
- One question: "Is the deliverable the podcast, or the platform (harness voice + OpenWebUI + alerts)?" Four work items in one question. Rejected: "You're lumping multiple asks into one question. Ask me per item."
|
|
||||||
|
|
||||||
## Check before sending
|
|
||||||
|
|
||||||
For each question: can I name the single artefact it decides? If two names come to mind, split it.
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
name: openwebui-tts-chain-audit
|
||||||
|
description: Audit/repair OpenWebUI TTS on Dankclaw when cloned voices get rejected by a 29-name Kokoro enum or synthesis is silent; rootless DB + endpoint probes
|
||||||
|
---
|
||||||
|
|
||||||
|
Audit OpenWebUI's TTS/STT wiring on Dankclaw when a voice is rejected, synthesis is silent, or someone asks "is the cloned voice wired correctly". Not for model-picker/ACL issues (openwebui-picker-acl-repair owns those).
|
||||||
|
|
||||||
|
## Shape of the stack
|
||||||
|
- Container `open-webui` runs **rootless as `dank`** on Dankclaw: `ssh dankclaw 'podman exec open-webui ...'` (NO sudo: `sudo podman` sees an empty world). Host port 3005.
|
||||||
|
- Server-side config = `webui.db` `config` table (admin), per-user overrides = `user.settings` JSON column. Settings changed via UI persist per-user; admin page persists to `config`.
|
||||||
|
- Cloned voices live behind the frank-tts façade `:7772` (`/v1/audio/speech`, `/v1/audio/voices`); voices resolve to `/home/dank/voices/<name>/reference.wav`.
|
||||||
|
|
||||||
|
## The trap (2026-10-18 law)
|
||||||
|
`Error: Voice "narrator1" not found. Should be one of: af_heart, ...` (29 names) is **OpenWebUI's bundled in-browser Kokoro** (`browser-kokoro`) rejecting client-side — frank-tts never sees the request. Signature: container logs show zero `audio/speech` hits. Fix = the personal dropdown Settings -> Audio -> TTS Engine -> Default (empty string), not any deploy.
|
||||||
|
|
||||||
|
## Probe sequence (read-only, safe)
|
||||||
|
1. Admin truth:
|
||||||
|
`podman exec open-webui python -c "import sqlite3; c=sqlite3.connect('/app/backend/data/webui.db'); [print(k,'=',v) for k,v in sorted(dict(c.execute('SELECT key,value FROM config')).items()) if 'audio' in k or 'tts' in k.lower()]"`
|
||||||
|
Expect: `audio.tts.engine=openai`, `audio.tts.openai.api_base_url=http://host.containers.internal:7772/v1`, `audio.tts.model=frank-tts`, `audio.tts.voice=<cloned name>`.
|
||||||
|
2. User overrides — a user pinned to `browser-kokoro` beats admin config. CAUTION: `user.settings` can be SQL NULL or the JSON string `"null"`:
|
||||||
|
`... for uid,s in c.execute('SELECT id,settings FROM user'): d=json.loads(s) if s else None; d=d if isinstance(d,dict) else {}; hits={k:v for k,v in d.items() if 'audio' in k or 'tts' in k.lower()} ...`
|
||||||
|
3. Endpoint truth: `curl -s http://127.0.0.1:7772/v1/audio/voices` (roster), then POST the exact OpenWebUI body `{"model":"frank-tts","input":"probe","voice":"narrator1","response_format":"mp3","speed":1.0}` to `/v1/audio/speech` — expect `200 audio/mpeg`.
|
||||||
|
4. Contrast logic: server-path failure leaves a container log line AND a façade error; browser-kokoro failure leaves neither.
|
||||||
|
|
||||||
|
## Law
|
||||||
|
- Full contract + diagnosis live in serverAppConfigs docs (`Dankclaw/OpenWebUI.md` § Text-to-speech, `Dankclaw/Frank TTS.md` troubleshooting). Docs are the source; this skill is just the probe route.
|
||||||
|
- Rootless gotcha generalizes: any Dankclaw service probe through podman goes WITHOUT sudo unless the unit is a system quadlet.
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
name: skills-cli-retroactive-store-wiring
|
||||||
|
description: "Retroactively register hand-copied skills with the vercel-labs skills CLI (global store + lockfile), wire omp via the agents provider (no repo symlinks), and extract authored overrides without twins"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Skills CLI retroactive store wiring (omp tiers)
|
||||||
|
|
||||||
|
Retrofit provenance for hand-copied skills and wire them for omp on Dank workstations. Executed 2026-10-18 on blankpad; doctrine doc: `omp-docs/tools/vercel-labs-skills/skill-store-wiring.md` (repo).
|
||||||
|
|
||||||
|
## Tier law (final design — do NOT reintroduce per-skill symlinks in the repo)
|
||||||
|
| Tier | Path | Owner |
|
||||||
|
|---|---|---|
|
||||||
|
| Store | `~/.agents/skills/` + lockfile `~/.agents/.skill-lock.json` (v3) | `skills` CLI |
|
||||||
|
| Authored | `<repo>/agent/skills/` real dirs | Frank, branch → PR |
|
||||||
|
| Managed | `~/.omp/agent/managed-skills/` | `manage_skill` only |
|
||||||
|
|
||||||
|
omp has NO agent id in the skills CLI (79-agent registry in `dist/cli.mjs`; `-a pi` writes `~/.agents/skills` too — pi detector keys on `~/.pi/agent`). omp reads the store DIRECTLY via its `agents` provider (`skills.enableAgentsUser`, default true; check with `read cfg://skills`). Precedence: authored (native, 100) > skillshare > ... > agents provider > managed (5). Identical copies collapse by realpath; differing same-named copies survive namespaced `agents/<name>` with a startup warning — so when authoring an override, REMOVE the store twin (`npx -y skills remove <name> -g`; prunes lockfile too) or `skills update -g` resurrects a twin.
|
||||||
|
|
||||||
|
## Procedure (retroactive registration)
|
||||||
|
1. List the repo/live skill dirs to register; identify true sources (per-skill — e.g. `find-skills` is `vercel-labs/skills`, not `mattpocock/skills`).
|
||||||
|
2. Drift check FIRST, per skill, before anything moves: `diff -rq <live>/<name> ~/.agents/skills/<name>` after canonical install; abort-and-report on drift (byte-identical = clean cutover).
|
||||||
|
3. Canonical install (scratch dir with a package.json; global scope): `npx -y skills add <owner>/<repo> --all -g` (+ per extra source `npx -y skills add vercel-labs/skills --skill find-skills --agent universal -g`). Artifacts: store dirs + `~/.agents/.skill-lock.json`; verify `jq '.skills|length'`.
|
||||||
|
4. Authored extraction (only for skills the user will customize): copy dir into repo `agent/skills/<name>/`, add an "Authored override" note after frontmatter, then `npx -y skills remove <name> -g` and confirm `jq '.skills|has("<name>")' == false`.
|
||||||
|
5. Clear repo copies of store-managed skills (repo records authored overrides ONLY). Live cutover after merge: move old dirs to /tmp snapshot, `git merge --ff-only origin/main` in the live checkout.
|
||||||
|
6. `skills ls` from a repo containing `agent/skills` mislabels skills as Eve project skills (path collision) — attribution noise, ignore.
|
||||||
|
7. Fresh-box recipe (three commands, no wiring): add `--all -g`, add find-skills, `remove grilling -g`.
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
- Eve/Codex `agents/openai.yaml` files ride along from upstream; omp ignores them — prune from authored dirs (they get swept off disk elsewhere, leaving phantom ` D` in git status).
|
||||||
|
- `skills remove` prunes only global scope if `-g` given; check lockfile after.
|
||||||
|
- Mid-run doc readers (subagents) may see pre-cutover trees — send them a correction message, don't let stale advice ship in issue bodies.
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
---
|
||||||
|
name: vault-one-fact-one-owner-dedupe
|
||||||
|
description: "Collapse per-payload/per-attempt vault pages into one owner page per endpoint without losing a fact: port-before-cut orphan census, index regeneration, the one-page seam, and the merge-resolution order."
|
||||||
|
---
|
||||||
|
|
||||||
|
# One fact, one owner: dedupe the vault without losing a fact
|
||||||
|
|
||||||
|
Use when a repo has accumulated one page per attempt/era (runbooks, bench logs, swap
|
||||||
|
narratives) and the law is **one page owns one thing**. Applies to
|
||||||
|
`serverAppConfigs-docs/` (Obsidian vault, `AGENTS.md` → *Documentation Policy*), but the
|
||||||
|
mechanics are general.
|
||||||
|
|
||||||
|
## 1. Decide ownership before touching files
|
||||||
|
|
||||||
|
- Endpoint/service page owns: current shape, **Eras** table (window | payload | runtime |
|
||||||
|
shape | replaced because | rollback), **Laws**, wire contract, files.
|
||||||
|
- Technique page owns the mechanism and the arithmetic — **no per-endpoint throughput
|
||||||
|
table or boot ledger**.
|
||||||
|
- `log.md` owns incidents and laws only. A swap is an Eras row, never a log entry.
|
||||||
|
- `AGENTS.md` carries operator-critical numbers + cross-border laws; everything else links.
|
||||||
|
|
||||||
|
## 2. Single-writer rule (the mistake that costs the most)
|
||||||
|
|
||||||
|
Facts get **ported into the owner page before they are cut** anywhere else. So while any
|
||||||
|
subagent may port facts, only ONE writer may touch the owner pages. Spawn children with an
|
||||||
|
explicit `DO NOT EDIT <owner pages>` and require a `needsPort` list in their report instead
|
||||||
|
of letting them append. Send the narrowing message early — a child already editing them has
|
||||||
|
to be reconciled by hand.
|
||||||
|
|
||||||
|
## 3. Port-before-cut census (proves nothing was lost)
|
||||||
|
|
||||||
|
For every deleted line, extract distinctive keys (integers ≥ 4 digits with commas stripped,
|
||||||
|
decimals with ≥ 2 fraction digits) and check them against the whole vault minus the file
|
||||||
|
being rewritten. Any line with **no** surviving key is an orphan → port, or accept
|
||||||
|
deliberately and say so.
|
||||||
|
|
||||||
|
```python
|
||||||
|
num = re.compile(r'\d[\d,]*(?:\.\d+)?')
|
||||||
|
def keys(line):
|
||||||
|
out = set()
|
||||||
|
for n in num.findall(line):
|
||||||
|
i = n.split('.')[0].replace(',', '')
|
||||||
|
if len(i) >= 4: out.add(i)
|
||||||
|
if '.' in n and len(n.split('.')[1]) >= 2: out.add(n.replace(',', '')[:6])
|
||||||
|
return out
|
||||||
|
# orphan = deleted line whose keys are absent from corpus(vault minus target file, minus .AGENTTRASH)
|
||||||
|
```
|
||||||
|
|
||||||
|
Expect ~30 orphans per 1,000 deleted lines, of which ~40 % are process trivia recoverable
|
||||||
|
from git (PR/commit hashes, per-boot log lines). Port the measurement/law ones to the owner
|
||||||
|
page's Laws/Eras, and list the accepted losses in the commit message and PR body. Also grep
|
||||||
|
`AGENTS.md` separately — a fact it carries is not an orphan.
|
||||||
|
|
||||||
|
## 4. Deletion mechanics
|
||||||
|
|
||||||
|
- Trash is **gitignored** (`.gitignore` has `.AGENTTRASH/`). Move with plain `mv` into the
|
||||||
|
repo-root dated dir, e.g. `.AGENTTRASH/<YYYYMMDD>-topic/LLM/`. `git mv` a tracked page into
|
||||||
|
it stages a rename *into an ignored path* (index entry the next agent cannot see); undo with
|
||||||
|
`git restore --staged <src> <dst>` and let the deletion be the change.
|
||||||
|
- Never `rm`, never `git clean -f`.
|
||||||
|
|
||||||
|
## 5. Rewire + indexes mechanically
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep -rnoE "\[\[(page-a|page-b|...)\]\]" --include="*.md" serverAppConfigs-docs | grep -v AGENTTRASH
|
||||||
|
grep -rnoE "\((page-a|page-b|...)\.md\)" --include="*.md" serverAppConfigs-docs | grep -v AGENTTRASH
|
||||||
|
```
|
||||||
|
Both wikilinks and markdown path refs must be repointed (Research `See Also` lists and
|
||||||
|
`ubatch`-style bench pages are where path refs hide). Then **regenerate** index pages with a
|
||||||
|
script that walks the vault (`os.walk`, skip `.obsidian`/`.AGENTTRASH`, one row = `[[basename]]`
|
||||||
|
+ first-sentence description stripped of markdown links) rather than hand-editing rows — the
|
||||||
|
`docs index links every docs page` seam fails on any row left behind.
|
||||||
|
|
||||||
|
## 6. Enforce the law with a seam
|
||||||
|
|
||||||
|
`tests/docs_completeness.bats`: derive the live set instead of hardcoding it — an endpoint is
|
||||||
|
live when `scripts/` holds **both** `<name>.env` and `<name>.service`; fail if any
|
||||||
|
`<name>-*.md` page exists in the vault, and guard against a vacuous derivation
|
||||||
|
(`derived -ge 3`). A re-created era page then cannot pass tests.
|
||||||
|
|
||||||
|
## 7. Edit-tool hazard (costs the most time)
|
||||||
|
|
||||||
|
Body rows beginning with `-` or `+` are op markers. The documented `+-` escape **writes a
|
||||||
|
literal `+`** — do not use it. Safe rewrites inside an existing bullet: keep the `- ` opener
|
||||||
|
line untouched and PUT only the indented continuation lines, or convert the note to a nested
|
||||||
|
bullet whose body lines start with two/four spaces. If a wrapped `[[Two Word Page]]` shows up
|
||||||
|
as an unresolved link, the target was hard-wrapped across lines — unwrap it.
|
||||||
|
|
||||||
|
## 8. Merge order (a long-lived branch under a moving main)
|
||||||
|
|
||||||
|
1. Commit your work in atomic groups **before** merging (a dirty tree cannot merge cleanly).
|
||||||
|
2. Inspect the incoming commits first: `git log --oneline --stat HEAD..origin/main`. If they
|
||||||
|
modify a page you deleted, **port their additions into the owner page**, then merge and
|
||||||
|
resolve the modify/delete as `git rm`.
|
||||||
|
3. Resolve a content block with `write conflict://<id>` (`@ours` / `@theirs` / explicit text),
|
||||||
|
then **`git add` the file** — resolving content does not stage it, and `git commit` refuses
|
||||||
|
with "you have unmerged files".
|
||||||
|
4. `git * --no-verify` is forbidden here; commit hooks are fine.
|
||||||
|
5. Sweep for dead paths left in `AGENTS.md`: `grep -oE "[A-Za-z0-9._-]+\.md" AGENTS.md | sort -u`
|
||||||
|
then `find` each. Expect false positives from names hard-wrapped across two lines — confirm
|
||||||
|
each with `ls` before "fixing".
|
||||||
|
|
||||||
|
## 9. Verify, open, land
|
||||||
|
|
||||||
|
- `./tests/run.sh` (full suite) + `bats tests/docs_completeness.bats` +
|
||||||
|
`python3 scripts/check-vault-links.py serverAppConfigs-docs` → must print `0 unresolved links`.
|
||||||
|
- Env trims: prove value-neutrality mechanically — `git show HEAD:scripts/<svc>.env | grep -E
|
||||||
|
'^[A-Z_]+=' | sort` vs the working copy, diff must be empty, then run that service's seam.
|
||||||
|
**Warn that `scripts/<svc>.env` is a deploy-planner path**: a comment-only trim makes the
|
||||||
|
next service-scoped deploy restart a healthy endpoint for identical values.
|
||||||
|
- Open the PR through the API with a 600-perm curl config (managed skill
|
||||||
|
`gitea-api-pr-operations`), end the body with the `cwd:` / `session:` footer, then
|
||||||
|
`xdg-open <PR-URL>` the instant it exists.
|
||||||
|
- Docs-only change ⇒ **no deploy**. Fast-forward the deploy checkout only
|
||||||
|
(`git -C ~/Projects/serverAppConfig merge --ff-only origin/main`).
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"v": 1,
|
"v": 1,
|
||||||
"lastConsolidatedAt": 1792359756091,
|
"lastConsolidatedAt": 1792398332410,
|
||||||
"lastResult": {
|
"lastResult": {
|
||||||
"at": 1791599865775,
|
"at": 1791599865775,
|
||||||
"sessions": 1,
|
"sessions": 1,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"v": 1,
|
"v": 1,
|
||||||
"lastConsolidatedAt": 1792359704968
|
"lastConsolidatedAt": 1792386375195
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"v": 1,
|
"v": 1,
|
||||||
"lastConsolidatedAt": 1792359739617
|
"lastConsolidatedAt": 1792398289746
|
||||||
}
|
}
|
||||||
+59
-107
@@ -1,118 +1,70 @@
|
|||||||
## Runtime Stack
|
## Subagent routing (harness law, 2026-10-18)
|
||||||
|
|
||||||
**llama.cpp** is the inference runtime for all `frank-zealot`-class endpoints. The `frank-zealot` service runs llama.cpp master (0.5.0-dev, commit `5fc4f3c`), CUDA sm_86 build, under a root systemd unit.
|
**No subagent class may route to `frank-archon`** — the operator session lives there and
|
||||||
|
the endpoint runs 3 sequences (`--max-num-seqs 3`), so `task.maxConcurrency + 1` children
|
||||||
|
eat the parent's own KV slots. Violation cost: three doc children + parent = 4 sequences
|
||||||
|
on 3 slots, children ran 2 h 27 m / 2 h 45 m, the interactive session starved.
|
||||||
|
Routing and bounds live in `~/.omp/agent/config.yml` (`task:` block — the owner; verified
|
||||||
|
with `read cfg://task/agentModelOverrides`): heavy writers (`task`, `scout`) → frank-dragoon
|
||||||
|
(2 slots, idle, slow card ⇒ slice work smaller), bursty readers (`reviewer`, `sonic`,
|
||||||
|
`designer`, `librarian`) → frank-zealot (2 slots, also the compaction hub and every
|
||||||
|
tiny/smol/plan/commit/memory role). Caps: `maxConcurrency: 2`, `maxRuntimeMs: 1800000`,
|
||||||
|
`softRequestBudget: 120`, `maxRecursionDepth: 1` (children never spawn children).
|
||||||
|
`cfg://.../save` approvals auto-deny after a hardcoded 10 s dialog — after Frank has
|
||||||
|
approved a change, edit `config.yml` directly and validate the YAML; a running session
|
||||||
|
picks global-config edits up at once. Full procedure: managed skill
|
||||||
|
`bounding-subagent-runtime`.
|
||||||
|
|
||||||
## Model Inventory
|
## Documentation law (2026-10-18)
|
||||||
|
|
||||||
### frank-zealot (Dankclaw: 192.168.0.143:7771)
|
**One fact, one owner.** The `serverAppConfigs` repo owns every fleet fact;
|
||||||
|
`AGENTS.md` restates only what a deploy gate or the harness needs and otherwise
|
||||||
|
links. `serverAppConfigs-docs/` (Obsidian vault) carries the lore: one page per
|
||||||
|
endpoint (`[[frank-archon]]`, `[[frank-dragoon]]`, `[[frank-zealot]]`) holding
|
||||||
|
Current shape, **Eras** (one row per payload swap: dates, payload, runtime,
|
||||||
|
shape, why replaced, rollback), Laws, wire contract, files. `log.md` records
|
||||||
|
incidents and laws only — no per-swap ledgers, no bench tables. A retired
|
||||||
|
payload is an Eras row, never a page of its own. `scripts/<service>.env` beats
|
||||||
|
any page: where they disagree, the page is the bug.
|
||||||
|
|
||||||
**Ternary-Bonsai-2-27B-PQ2_0.gguf** (prism-ml/Ternary-Bonsai-2-27B-gguf)
|
## Model swaps (2026-10-18)
|
||||||
|
|
||||||
- Runtime: llama.cpp
|
Swap the model, don't instrument the swap: edit the trio
|
||||||
- Format: PQ2_0 quantized GGUF
|
(`scripts/<svc>.env` + serve script + unit) → `./tests/run.sh` → PR →
|
||||||
- 27B parameter class
|
`deploy.sh <host> --service <svc>` → `/health` + one chat / tool-call / vision
|
||||||
- Vision: native BF16 mmproj projector for image input
|
probe → done. **No benchmarks, A/Bs, ladders, or acceptance tables unless
|
||||||
- MTP: OFF (no `nextn` tensors in checkpoint)
|
Frank explicitly asks for data.** If a payload misbehaves, revert the commit and
|
||||||
- Context: 256k per-slot window (`--ctx-size 401408 --parallel 2` = 200,704 tokens effective)
|
redeploy. When a bench IS ordered, the GPU-safety procedure (stop + mask, no
|
||||||
- Batch: 4096 / ubatch 2048 (CUDA optimum)
|
scratch ports, never `systemctl disable`) is `AGENTS.md` → *Model swaps*
|
||||||
- Log verbosity: 4
|
and *Deployment completeness*.
|
||||||
- Ports: 7771 on Dankclaw
|
|
||||||
|
|
||||||
### frank-dragoon (Dankclaw: 192.168.0.143:7770)
|
## Where facts live
|
||||||
|
|
||||||
**Quat3rnion/halogen-swift1.5-qwen3.8-flash-next-v2-abliterated** (`.hgn` format)
|
|Need|Owner|
|
||||||
|
|---|---|
|
||||||
|
|Topology, ports, payload, shape|repo `AGENTS.md` → Fleet topology; `scripts/<svc>.env` is the authority|
|
||||||
|
|Endpoint lore, eras, laws, rollback|vault `serverAppConfigs-docs/LLM/<endpoint>.md`|
|
||||||
|
|Harness model entries (`contextWindow`, `maxTokens`, `reasoningContentField`, `modelRoles`)|`~/.omp/agent/models.yml`|
|
||||||
|
|Incidents and operational laws|vault `serverAppConfigs-docs/log.md`|
|
||||||
|
|Service/infra pages|vault `Dankclaw/`, `Dankserver/`, `Infrastructure/`, `Architecture/`|
|
||||||
|
|
||||||
- Runtime: halogen-flash-server 0.16.2 (digest-pinned `ghcr.io/peonist-ai/halogen-flash-server@sha256:0c61bf84…`)
|
Models.yml resolution rules that are harness-local: a default model resolves as
|
||||||
- Format: v2 `.hgn` (flash-server exclusive)
|
a `defaultProvider` + `defaultModel` pair; a subagent override is a bare model
|
||||||
- MTP head: INSIDE checkpoint (`HALOGEN_MTP_HEAD` unset)
|
`id` and resolves only when unique across providers; two ports on one host need
|
||||||
- Vision: ON (v2 tower loads beside checkpoint)
|
two provider blocks — the file cannot express "same host, different port".
|
||||||
- Base: abliterated (27B-era was censored finetune)
|
|
||||||
- Slots: 2 × 262,144 ctx (KV pool 524,288 positions)
|
|
||||||
- Server sampler: 1.0/0.95/20
|
|
||||||
- Prompt cache: ON by default
|
|
||||||
|
|
||||||
### frank-archon (Dankserver: 192.168.0.235:7770)
|
There is **no audio input anywhere in the fleet** (gone with the Gemma4
|
||||||
|
retirement, 2026-10-01). All three chat endpoints take text + images.
|
||||||
|
|
||||||
**StillDeadcode/qwen3.8-next-flash-fp8-iq4r-moe** (`.rad` format)
|
## Retired laws
|
||||||
|
|
||||||
- Runtime: radiance 1.0.13 (digest-pinned `stilldeadcode/radiance@sha256:4c3f484564…`)
|
- The old "Benchmarking Policy (MANDATORY)" wording demanded a bench harness per
|
||||||
- Format: fp8-KV `.rad` (121,969,901,568 B = 116,322 MiB)
|
payload; superseded 2026-10-18 by the swap law above.
|
||||||
- MTP head: MTP depth 2, MNBT 2048, Froggeric v22.5 template
|
- A "Subagent Lifecycle (MANDATORY)" rule (heartbeat ≤5 s, checkpoints, hard
|
||||||
- Vision: 27-layer Qwen-VL tower native
|
timeout) was recorded here 2026-09 and **never implemented anywhere**; it
|
||||||
- KV: 3 × 200,000 fp8-KV (`--max-num-seqs 3`)
|
described nothing that exists and is deleted 2026-10-18. Do not re-add it
|
||||||
- 30,720 MiB of 30,969 MiB claimable
|
without a real implementation.
|
||||||
- 44 GiB/rank pinned host pool, PLE pinned to RAM
|
- The fleet model inventory that used to live in this memory file drifted out of
|
||||||
- Base-alignment: censored (abliterated family lives on frank-dragoon)
|
date within hours (it still claimed Bonsai-2-**27B** at `--ctx-size 401408`
|
||||||
|
and a dragoon 524,288-position pool). Memory files do not get deploys — the
|
||||||
## Resolution Rules
|
repo does. That is why it is pointers now.
|
||||||
|
|
||||||
Official model ids are StarCraft unit names, on the wire since 2026-10-09, and since 2026-10-10 they are also the SERVICE names: `frank-archon` → `192.168.0.235:7770`, `frank-dragoon` → `192.168.0.143:7770`, `frank-zealot` → `192.168.0.143:7771`.
|
|
||||||
|
|
||||||
The default model resolves as a `defaultProvider` + `defaultModel` pair (provider must serve the model); a subagent override uses a bare model `id`, which resolves only when unique across providers. Two ports on one host need two providers — the file cannot express "same host, different port".
|
|
||||||
|
|
||||||
When renaming a service, retiring an endpoint, or moving a model: update the provider block, model `id`, `contextWindow`, `maxTokens`, `input` in `models.yml`, then fix every `modelRoles` entry that named the old endpoint — a role pointing at a provider the file does not define fails at call time. As of 2026-10-06 **all three endpoints accept images** (`input: [text, image]`) — `frank-dragoon` returned to vision with the flash-server v2 swap, while `frank-archon` takes them natively and `frank-zealot` through its BF16 mmproj.
|
|
||||||
|
|
||||||
Reasoning channel differs by runtime: llama.cpp AND SGLang (qwen3 parser, live-verified) emit `reasoning_content`, vLLM emits `reasoning` — `models.yml` sets `reasoningContentField` per endpoint to match.
|
|
||||||
|
|
||||||
There is **no audio** input anywhere in the fleet: that capability left with the FLM NPU endpoint on 2026-09-11, returned 2026-09-17 with the Gemma4 projector's audio encoder, and left again with the Gemma4 retirement on 2026-10-01.
|
|
||||||
|
|
||||||
## Boot Persistence & Self-Healing (MANDATORY)
|
|
||||||
|
|
||||||
Every deployed, working Podman container MUST have a self-healing, boot-persistent Quadlet unit. A container that runs only from `compose.yaml` or a manual `podman run` dies silently at reboot — rootless Podman does not auto-start bare containers, and `restart: unless-stopped` does not survive the explicit stop that shutdown performs. Never report such a service as "up".
|
|
||||||
|
|
||||||
Unit requirements:
|
|
||||||
|
|
||||||
- **Boot persistence** — `[Install] WantedBy=default.target`, installed to `~/.config/containers/systemd/<name>.container`, enabled in the user manager, with linger on for the deploy user (`loginctl show-user dank -p Linger` → `Linger=yes`).
|
|
||||||
- **Self-healing** — `Restart=always` (or `on-failure`) with a short `RestartSec`; quadlet health check where the service has one.
|
|
||||||
- **Repo provenance** — the `.container` file lives in this repo (`<host>/<svc>/quadlet/`), is installed by the deployer, and is covered by the quadlet test seam. The host-side unit is generated state; the repo file is the source of truth.
|
|
||||||
|
|
||||||
Before declaring a service deployed: `systemctl --user is-enabled <name>` → `enabled`, `is-active` → `active`, and the container answers its health check.
|
|
||||||
|
|
||||||
A running container with no unit is a gap: write the unit, test it, deploy it.
|
|
||||||
|
|
||||||
This rule exists because reboots found gaps twice (2026-08-26: Caddy as a bare compose container never returned; 2026-09-11: five observability services had no units). The observability services now have units under `dankclaw/observability/quadlet/` and their compose definitions were removed — one owner per container name. The stack is **configured and delivering as of 2026-09-17**: a Grafana datasource and the `llm-fleet` dashboard are provisioned from `shared/grafana/provisioning/`, `shared/prometheus/rules/llm-fleet.yml` loads 5 alert rules + 1 recording rule, and firing rules are delivered: Prometheus → Alertmanager (`dankclaw/observability/quadlet/alertmanager.container`, loopback :9093) → host-side Signal relay (`scripts/signal-relay.py`, user unit, 192.168.0.143:18093 — the host LAN IP, the one bind a container can reach as `host.containers.internal`; iptables admits loopback, the host and the podman bridge subnet only) → signal-cli account 318311.
|
|
||||||
|
|
||||||
Prometheus targets read down for structural reasons (HTML-scraped "probes", Gitea metrics off); the remedy is a blackbox exporter. See `serverAppConfigs-docs/Infrastructure/Observability.md`.
|
|
||||||
|
|
||||||
## Benchmarking Policy (MANDATORY)
|
|
||||||
|
|
||||||
**STOP the production service BEFORE benchmarking** — these servers serve real users, and a bench beside a live service starves the GPU and corrupts results. Use the runtime's own harness: `llama-bench` for the llama.cpp endpoints; a live llama.cpp server also reports honest per-request numbers in the `/completion` `timings` object (prefill/decode rates, `draft_n`, `draft_n_accepted`), which is how frank-dragoon and frank-zealot were measured. Rolling your own bench requires asking first.
|
|
||||||
|
|
||||||
Scratch ports are not used on LLM hosts — a test process cannot share the single GPU anyway; validate against the service itself.
|
|
||||||
|
|
||||||
A gufo-era law: **`gufo bench` cannot rank speculative-decode settings, and will invert the ranking.**
|
|
||||||
|
|
||||||
Measured 2026-09-28 on frank-dragoon at depth 32768: its corpus reports `acceptance=0.933 ± 0.000` with an **identical trace sha256** across draft widths, i.e. the same highly-predictable text every run, and 93 % of every proposal accepted. It reported 29–37 t/s decode where real traffic gives ~15 t/s, and it made `--draft-tokens 16` look 6 % faster than 7 when real text made it 14 % *slower*. Use it for shape, not for A/B. For an A/B, replay **real** text through the live server and read the `Server-Timing` header — but note the unit rides in the **name** half (`ttft;dur=402.485`), so splitting on `=` alone keys the dict `ttft;dur` and every lookup silently returns nothing. Generate **at least 128 tokens** for the measurement: at 8 tokens, `inter_token` is first-token noise and the run-to-run spread swamps every real difference.
|
|
||||||
|
|
||||||
This covers **any GPU experiment, not just benchmarks** — including a bare 2-rank RCCL/`torch.distributed` probe. On 2026-09-16 one such probe was run against the live service "because it only needs 200 MB of VRAM"; the host went hard down ~20 minutes later (no ARP reply, 100% ICMP loss from two subnets, no recovery — a power cycle was required). A hung AQL dispatch wedging the GPU driver is the plausible mechanism, and this host has precedent for going unreachable until a power cycle. If an experiment needs GPUs, stop and mask first, or use a host that serves nobody. Telling an off machine from a wedged one: `ip neigh show <ip>` from a peer — FAILED means off/NIC dead, while a merely wedged kernel usually still answers ARP.
|
|
||||||
|
|
||||||
1. Stop and mask (self-healing would restart it):
|
|
||||||
- Dankclaw `frank-dragoon` (root unit, halogen podman on the iGPU):
|
|
||||||
`sudo systemctl stop frank-dragoon && sudo systemctl mask frank-dragoon`
|
|
||||||
- Dankclaw `frank-zealot` (root unit, llama.cpp on the 3090):
|
|
||||||
`sudo systemctl stop frank-zealot && sudo systemctl mask frank-zealot`
|
|
||||||
- Dankserver `frank-archon` (root unit, radiance on both R9700s, TP=2):
|
|
||||||
`sudo systemctl stop frank-archon && sudo systemctl mask frank-archon`
|
|
||||||
2. Bench.
|
|
||||||
NOTE: `systemctl mask` FAILS on a relinked unit (the unit file in `/etc/systemd/system/` is itself a symlink into the checkout — mask refuses to replace it). Stop is enough for the bench window; the deploy restores the unit, so re-deploy after benching instead of unmasking.
|
|
||||||
3. Unmask and start; verify `curl http://192.168.0.<host>:7770/health`.
|
|
||||||
|
|
||||||
**CRITICAL:** never `systemctl disable` a system unit on these hosts — it deletes the unit file from `/etc/systemd/system/`, not just the symlink. `mask` symlinks to `/dev/null` and is safe/reversible.
|
|
||||||
|
|
||||||
## Subagent Lifecycle (MANDATORY)
|
|
||||||
|
|
||||||
A subagent that appears stuck must have an explicit recovery path defined before the subagent is considered deployed. "Stuck" includes: silent completion halt, repeated identical responses, or extended latency without token emission.
|
|
||||||
|
|
||||||
Recovery requirements:
|
|
||||||
|
|
||||||
- **Explicit timeout** — a hard timeout (≤30s) on the subagent's response channel, with a defined action (retry, fallback, or escalate).
|
|
||||||
- **State checkpoint** — the subagent maintains checkpoints at decision boundaries, allowing resumption from a known good state.
|
|
||||||
- **Health signal** — the subagent publishes a heartbeat (≤5s interval) that triggers the recovery path when missed.
|
|
||||||
|
|
||||||
A subagent without these three properties is a gap: write the timeout, add the checkpoint, wire the heartbeat, then deploy.
|
|
||||||
|
|
||||||
This rule exists because subagent hangs were observed in early deployments (2026-09-xx: silent completion halt; 2026-09-xx: repeated identical responses). The deployment script must verify the heartbeat channel is live before reporting "deployed".
|
|
||||||
|
|
||||||
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.
|
|
||||||
@@ -1,6 +1,23 @@
|
|||||||
## Voice Generation
|
## Voice Generation
|
||||||
|
|
||||||
- Custom voice source material must exclude the first 10 seconds of any audio sample file used to generate voices; this is a project-wide invariant across all source files, not a per-file exception.
|
- Custom voice source material must exclude the first 10 seconds of any audio sample file used to generate voices; this is a project-wide invariant across all source files, not a per-file exception.
|
||||||
|
- Voice selector valid options: af_heart, af_alloy, af_aoede, af_bella, af_jessica, af_kore, af_nicole, af_nova, af_river, af_sarah, af_sky, am_adam, am_echo, am_eric, am_fenrir, am_liam, am_michael, am_onyx, am_puck, am_santa, bf_emma, bf_isabella, bm_george, bm_lewis, bf_alice, bf_lily, bm_daniel, bm_fable.
|
||||||
|
|
||||||
|
## Input Capabilities
|
||||||
|
|
||||||
|
All endpoints accept text input with optional image input: `frank-dragoon` returned to vision with the flash-server v2 swap, while `frank-archon` takes them natively and `frank-zealot` through its BF16 mmproj.
|
||||||
|
|
||||||
|
There is no audio input anywhere in the fleet: that capability left with the FLM NPU endpoint on 2026-09-11, returned 2026-09-17 with the Gemma4 projector's audio encoder, and left again with the Gemma4 retirement on 2026-10-01.
|
||||||
|
|
||||||
|
## Voice Usage Notes
|
||||||
|
|
||||||
|
- Voice generator output does not affect token counting or inference costs — all voices are rendered post-inference.
|
||||||
|
- Voice quality varies by source sample: exclude the first 10 seconds of any audio sample file used to generate voices; this is a project-wide invariant across all source files, not a per-file exception.
|
||||||
|
- Voice selection is client-side only; the server does not generate or return audio data.
|
||||||
|
|
||||||
|
## Load Management
|
||||||
|
|
||||||
|
- Subagent settings must prevent frank-archon from being overwhelmed; concurrent request handling prioritizes endpoint stability over throughput.
|
||||||
|
|
||||||
## Podcast
|
## Podcast
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"v": 1,
|
"v": 1,
|
||||||
"lastConsolidatedAt": 1792367186349,
|
"lastConsolidatedAt": 1792400553633,
|
||||||
"lastResult": {
|
"lastResult": {
|
||||||
"at": 1792361576391,
|
"at": 1792393886960,
|
||||||
"sessions": 1,
|
"sessions": 1,
|
||||||
"deltas": 1,
|
"deltas": 1,
|
||||||
"model": "frank-zealot"
|
"model": "frank-zealot"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"v": 1,
|
"v": 1,
|
||||||
"lastConsolidatedAt": 1792367076712,
|
"lastConsolidatedAt": 1792367676725,
|
||||||
"lastResult": {
|
"lastResult": {
|
||||||
"at": 1792366721197,
|
"at": 1792366721197,
|
||||||
"sessions": 1,
|
"sessions": 1,
|
||||||
|
|||||||
Whitespace-only changes.
@@ -0,0 +1,3 @@
|
|||||||
|
## top bar
|
||||||
|
- Preserve top bar customizations; customizations lost or obliterated on subsequent runs
|
||||||
|
- Todo list updates as tasks change, not waiting for entire bill of tasks to finish
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"v": 1,
|
||||||
|
"lastConsolidatedAt": 1792381281460,
|
||||||
|
"lastResult": {
|
||||||
|
"at": 1792381281460,
|
||||||
|
"sessions": 1,
|
||||||
|
"deltas": 1,
|
||||||
|
"model": "frank-zealot"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
## window outlines
|
||||||
|
- Window outline color is amber, and outline thickness is thickened
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"v": 1,
|
||||||
|
"lastConsolidatedAt": 1792381246498
|
||||||
|
}
|
||||||
Whitespace-only changes.
@@ -0,0 +1,3 @@
|
|||||||
|
# Output behavior
|
||||||
|
|
||||||
|
Output must not be shaken off during presentation. Output remains anchored until explicitly confirmed.
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"v": 1,
|
||||||
|
"lastConsolidatedAt": 1792400453482,
|
||||||
|
"lastResult": {
|
||||||
|
"at": 1792398531282,
|
||||||
|
"sessions": 1,
|
||||||
|
"deltas": 1,
|
||||||
|
"model": "frank-zealot"
|
||||||
|
}
|
||||||
|
}
|
||||||
Whitespace-only changes.
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"v": 1,
|
||||||
|
"lastConsolidatedAt": 1792381487662
|
||||||
|
}
|
||||||
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"v": 1,
|
||||||
|
"lastConsolidatedAt": 1792393561866,
|
||||||
|
"lastResult": {
|
||||||
|
"at": 1792381870670,
|
||||||
|
"sessions": 1,
|
||||||
|
"deltas": 1,
|
||||||
|
"model": "frank-zealot"
|
||||||
|
}
|
||||||
|
}
|
||||||
Whitespace-only changes.
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"v": 1,
|
||||||
|
"lastConsolidatedAt": 1792388836682
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
{"v":1,"kind":"product_decision","statement":"Limit on zealot set to 3, dragoon set to 2, archon set to 3","source":"explicit_user","evidence":"make the limit on zealot 3, dragoon 2, archon 3","friction":{"corrective":false,"regression":false,"subtle":false},"sessionId":"01a15302-f12d-77d8-9a33-be31a1a05528","ts":1792394470419}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"v": 1,
|
||||||
|
"lastConsolidatedAt": 1792394427081
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
{"v":1,"kind":"product_decision","statement":"Documentation links to @~/Projects/serverAppConfig/serverAppConfigs-docs/","source":"explicit_user","evidence":"correct our documentation to just link to @~/Projects/serverAppConfig/serverAppConfigs-docs/","friction":{"corrective":false,"regression":false,"subtle":false},"sessionId":"01a1531d-3e48-70b0-8ee0-6d48a197cae3","ts":1792397374946}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"v": 1,
|
||||||
|
"lastConsolidatedAt": 1792397349857
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"v": 1,
|
||||||
|
"lastConsolidatedAt": 1792400552453
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"v": 1,
|
||||||
|
"lastConsolidatedAt": 1792398333838
|
||||||
|
}
|
||||||
+77
-77
@@ -24,27 +24,13 @@ providers:
|
|||||||
# compactionModel candidate keeps the wire model id valid there.
|
# compactionModel candidate keeps the wire model id valid there.
|
||||||
compactionModel: llamacpp (dankclaw-3090)/frank-zealot
|
compactionModel: llamacpp (dankclaw-3090)/frank-zealot
|
||||||
name: "frank-archon"
|
name: "frank-archon"
|
||||||
# Dankserver: BOTH Radeon AI PRO R9700s (gfx1201, TP=2 over the
|
# Dankserver: BOTH Radeon AI PRO R9700s (gfx1201, TP=2, wht6 wire) on
|
||||||
# 2026-09-20 board swap's root ports, wht6 wire). Engine:
|
# the digest-pinned radiance image, plain `podman run` under a root
|
||||||
# radiance (StillDeadcode, Apache-2.0) from the digest-pinned
|
# unit. Payload, shape, eras, laws, rollback:
|
||||||
# stilldeadcode/radiance image (index digest sha256:4c3f4845..., 287
|
# serverAppConfigs-docs/LLM/frank-archon.md — and
|
||||||
# MiB, kernels prebuilt for gfx1201), plain `podman run` through
|
# scripts/frank-archon.env is the authority for every number there.
|
||||||
# scripts/serve-frank-archon.sh, root unit (User=dank, dank's
|
# contextWindow MUST quote the engine's --max-model-len: radiance
|
||||||
# rootless podman storage). Serving the OFFICIAL
|
# refuses a longer prompt and never truncates.
|
||||||
# Qwen/Qwen3.8-Flash-Next-FP8 at NATIVE modelopt block-FP8
|
|
||||||
# precision — a rad-convert no-recipe pure copy into
|
|
||||||
# qwen4exp-fp8-native.rad (172.76 GiB measured by plan-only), built by
|
|
||||||
# scripts/build-frank-archon-rad.sh against a staging dir whose
|
|
||||||
# config.json drops quantization_config so the shipped qwen4exp
|
|
||||||
# plugin's (qwen4exp, "") selection key matches; per-weight
|
|
||||||
# encodings are untouched. Experts stream from a 40 GiB/rank
|
|
||||||
# pinned host pool; the 47.7 GiB PLE ngram table is pinned in RAM
|
|
||||||
# (Frank's call); KV fp8; MTP depth 3 (the checkpoint's own mtp.*
|
|
||||||
# head); prefix cache 4 GiB host + 64 GiB disk. Chat template:
|
|
||||||
# froggeric's fixed qwen3.8 v22.5
|
|
||||||
# (scripts/qwen38-froggeric-v22.5.jinja) via
|
|
||||||
# --override-chat-template. Rollback: git tag
|
|
||||||
# frank-archon-freetoken-pr587-20261005 (FreeToken, one card).
|
|
||||||
contextWindow: 200000
|
contextWindow: 200000
|
||||||
# maxTokens must survive the harness compaction gap AND the chars/4
|
# maxTokens must survive the harness compaction gap AND the chars/4
|
||||||
# token-estimate under-run on code-heavy prompts. The clamp is
|
# token-estimate under-run on code-heavy prompts. The clamp is
|
||||||
@@ -105,40 +91,22 @@ providers:
|
|||||||
# compaction model candidate so the wire model id stays valid
|
# compaction model candidate so the wire model id stays valid
|
||||||
# there even from a frank-dragoon session.
|
# there even from a frank-dragoon session.
|
||||||
compactionModel: llamacpp (dankclaw-3090)/frank-zealot
|
compactionModel: llamacpp (dankclaw-3090)/frank-zealot
|
||||||
# halogen-flash-server 0.16.2 (ghcr.io/peonist-ai/
|
# halogen flash-server on the Strix Halo iGPU. Payload, shape, eras,
|
||||||
# halogen-flash-server@sha256:0c61bf84, digest-pinned) serving the
|
# laws, rollback: serverAppConfigs-docs/LLM/frank-dragoon.md — and
|
||||||
# Quat3rnion NATIVE v2 ABLITERATED conversion of orcarouter's pinned
|
# scripts/frank-dragoon.env is the authority for every number there.
|
||||||
# BF16 Qwen3.8-Flash-Next (qwen38-flash-next-v2-abliterated.hgn,
|
# contextWindow sits deliberately BELOW the engine ceiling
|
||||||
# ~62 GiB resident, + REQUIRED n-gram table + vision tower, both
|
# (HALOGEN_CTX): the clamp below leaves the gap that absorbs the
|
||||||
# hardlinked from the swift15 dir) on the Strix Halo iGPU: TWO slots
|
# harness's chars/4 under-count, so a window equal to the ceiling
|
||||||
# over ONE 401,408-position KV pool (= 2 x ctx, raised 2026-10-18) at
|
# would make every long request a guaranteed 400.
|
||||||
# HALOGEN_CTX=200704 (the ceiling lowered from the card's 262,144 on
|
|
||||||
# 2026-10-17; ~10.8 GiB reserved where 200,704 cost 5.5, 262,144 cost
|
|
||||||
# 7.2 and 524,288 measured 14.6), with the MTP head ALWAYS ON
|
|
||||||
# (HALOGEN_SPEC_ADAPT=0 — the default adaptive policy halves
|
|
||||||
# decode). Measured 2026-10-17: 50 t/s greedy decode, 1,604 t/s
|
|
||||||
# cold prefill at 59k, NIAH 18/18. ABLITERATED base (no refusals:
|
|
||||||
# 0/5 on the hostile census) and VISION ON (OpenAI image_url
|
|
||||||
# parts; >=256 px images or it answers "video"). Runbook:
|
|
||||||
# serverAppConfigs-docs/LLM/frank-dragoon-flash-server-v2-native.md.
|
|
||||||
# Window the harness may fill — deliberately BELOW the engine
|
|
||||||
# ceiling (HALOGEN_CTX=200704, lowered from 262,144 on 2026-10-17):
|
|
||||||
# the clamp below leaves the 45k gap that absorbs the estimate error.
|
|
||||||
# Both slots SHARE one 401,408-position KV pool — exactly two full
|
|
||||||
# windows — so TWO maximum-length conversations can be resident at the
|
|
||||||
# same time (at the old pool == ctx 200,704 only two of <=100k could).
|
|
||||||
# The per-request ceiling is unchanged, so this window is unchanged.
|
|
||||||
contextWindow: 155648
|
contextWindow: 155648
|
||||||
# The clamp is max_tokens = min(maxTokens, contextWindow − est −
|
# The clamp is max_tokens = min(maxTokens, contextWindow − est −
|
||||||
# 4096); the chars/4 est UNDER-counts the rendered prompt by ~3 %
|
# 4096); the chars/4 est UNDER-counts the rendered prompt by ~3 %
|
||||||
# on code/agent traffic (measured 2026-10-07: est 152,938 ->
|
# on code/agent traffic (measured 2026-10-07: est 152,938 ->
|
||||||
# server 157,388). Worst-case request = window − 4096 + 3 % est
|
# server 157,388). Worst-case request = window − 4096 + 3 % est
|
||||||
# error ≈ 156.2k, inside the 200,704 engine ceiling with ~44k
|
# error ≈ 156.2k, inside the engine ceiling with ~44k of margin
|
||||||
# margin (robust to a ~25 % under-count). Compaction fires at 85 %
|
# (robust to a ~25 % under-count). Compaction fires at 85 % of the
|
||||||
# of the window = 132,300 est ≈ 136k measured — the 180k target of
|
# window and rides on frank-zealot: input ≈ threshold −
|
||||||
# the 262,144 era is unreachable now (the window itself is 155,648)
|
# keepRecent(20k), comfortably inside its per-slot window.
|
||||||
# — and rides on frank-zealot: input ≈ threshold − keepRecent(20k)
|
|
||||||
# ≈ 112k, inside its 200,704 slot.
|
|
||||||
# HALOGEN_MAX_TOKENS_CAP in frank-dragoon.env must stay >= this.
|
# HALOGEN_MAX_TOKENS_CAP in frank-dragoon.env must stay >= this.
|
||||||
maxTokens: 45056
|
maxTokens: 45056
|
||||||
reasoning: true
|
reasoning: true
|
||||||
@@ -183,33 +151,14 @@ providers:
|
|||||||
# config.yml (192.168.0.143:7771 — this endpoint); the
|
# config.yml (192.168.0.143:7771 — this endpoint); the
|
||||||
# compactionModel candidate keeps the wire model id valid there.
|
# compactionModel candidate keeps the wire model id valid there.
|
||||||
compactionModel: llamacpp (dankclaw-3090)/frank-zealot
|
compactionModel: llamacpp (dankclaw-3090)/frank-zealot
|
||||||
# Dankclaw's RTX 3090: llama.cpp PrismML fork build (vendor 10777,
|
# Dankclaw's RTX 3090 llama.cpp endpoint (PrismML fork build — the only
|
||||||
# commit e3de71522 — the ONLY loader for this payload's custom tensor
|
# loader for this payload's custom tensor type). Shape, payload, eras,
|
||||||
# type 142 + prism.hadamard.* metadata, and the minimum build that
|
# laws, rollback: serverAppConfigs-docs/LLM/frank-zealot.md, and
|
||||||
# pairs MTP with a quantized V cache) serving
|
# scripts/frank-zealot.env is the authority for every number there.
|
||||||
# prism-ml/Ternary-Bonsai-2-9B-gguf at PQ2_0 (2,675,410,720 B) WITH the
|
# contextWindow is the PER-SLOT window: llama.cpp divides --ctx-size
|
||||||
# MTP head in the file — qwen35 hybrid, 32 layers (8 full-attention +
|
# across slots and answers a longer prompt with a 400, never a
|
||||||
# 24 Gated DeltaNet), 4 KV heads x 256 dim, native 262,144 — in THREE
|
# truncation, so this value must follow every reslice (live now:
|
||||||
# conversations (--ctx-size 786432 / --parallel 3), each slot at the
|
# 2 x 262,144 over exact f16 KV, 68afc6d).
|
||||||
# model's TRAINED window.
|
|
||||||
# KV is q8_0 K + q8_0 V (17 KiB/token over the 8 full layers =
|
|
||||||
# 13,056 MiB pool, ledger-exact); f16 KV would bill 26,112 MiB and
|
|
||||||
# cannot boot this card. Boot ledger 17,876 used / 6,249 free. Vision
|
|
||||||
# through the publisher's Q8_0 mmproj (624,229,664 B — Q8_0 is the
|
|
||||||
# publisher's requirement for concurrent image requests), whose
|
|
||||||
# worst-case lands outside the engine's fit projection. **MTP OFF —
|
|
||||||
# measured, not assumed**: the draft context bills its own f16 KV over
|
|
||||||
# the WHOLE pool (3,072 MiB) and at ubatch 2048 that squeeze halves
|
|
||||||
# decode (167.2 → 109.2 t/s); SPEC_TYPE=draft-mtp + UBATCH_SIZE=1024 is
|
|
||||||
# the 213.5 t/s arm if the card ever has room. 2026-10-18 swap from
|
|
||||||
# Ternary-Bonsai-2-27B PQ2_0 at 2 × 200,704. Measured: 167.2 t/s
|
|
||||||
# single-stream greedy decode, 280.2 t/s across three concurrent
|
|
||||||
# streams, 2,877 t/s prefill at 106,418. Base-alignment: censored base
|
|
||||||
# lineage; unlike the 27B there is NO CRACK re-export matching this
|
|
||||||
# file. Runbook:
|
|
||||||
# serverAppConfigs-docs/LLM/frank-zealot-llama-prism-bonsai9-mtp.md.
|
|
||||||
# contextWindow is the PER-SLOT window (= the native 262,144): a longer
|
|
||||||
# prompt needs compaction — llama.cpp answers a 400, never truncates.
|
|
||||||
contextWindow: 262144
|
contextWindow: 262144
|
||||||
# Prompt + budget must fit inside the slot window; thinking counts
|
# Prompt + budget must fit inside the slot window; thinking counts
|
||||||
# against the budget. Compaction fires at 85 % of the window =
|
# against the budget. Compaction fires at 85 % of the window =
|
||||||
@@ -254,3 +203,54 @@ providers:
|
|||||||
# prior assistants' <think> blocks from history. Keep them —
|
# prior assistants' <think> blocks from history. Keep them —
|
||||||
# multi-turn tool traffic on this endpoint is the majority.
|
# multi-turn tool traffic on this endpoint is the majority.
|
||||||
preserve_thinking: true
|
preserve_thinking: true
|
||||||
|
|
||||||
|
# Speech endpoint — the fleet's TTS, NOT a chat provider. `api:
|
||||||
|
# openai-speech` is a runner API (catalog kind `tts`), so this row never
|
||||||
|
# enters the chat picker, the Ctrl+P cycle, or any chat role: it is
|
||||||
|
# reachable only through the `speech` model role and the tts device.
|
||||||
|
# frank-tts (host port 7772) is the OpenAI /v1/audio/speech façade that
|
||||||
|
# resolves a voice BY NAME; its engine (frank-tts-engine, container-internal
|
||||||
|
# 8880, never published) is arm A qwentts.cpp 5916bf7 — measured 0.55-0.67
|
||||||
|
# RTF warm and 86-451 ms to the first audio byte on Dankclaw's CPU, with
|
||||||
|
# mainline llama-tts (TTS_BACKEND=llama-tts-cli) kept as the rollback.
|
||||||
|
# Transport contract, read out of the harness itself: the request body is
|
||||||
|
# {model, input, response_format, voice?, speed?}; `voice` is forwarded ONLY
|
||||||
|
# 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 — 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
|
||||||
|
# file's validation, which DISABLES EVERY custom provider: all three fleet
|
||||||
|
# endpoints vanish from the registry, not just this one (`omp models` prints
|
||||||
|
# "models.yml validation failed — custom providers disabled" and lists only
|
||||||
|
# local/kokoro). The runner api belongs on the MODEL entry only, which is what
|
||||||
|
# the block below does — `auth: none` covers the credential requirement, and
|
||||||
|
# `contextWindow`/`maxTokens` stay unset because the check only fires when they
|
||||||
|
# are provided (the bundled Kokoro row is null on both). `omp models --kind tts`
|
||||||
|
# still prints 128K/16K for this row — the registry fills display defaults — and
|
||||||
|
# the speech transport never reads them: its whole request body is the five
|
||||||
|
# fields named above. Verified 2026-10-18 with `omp models --kind tts` (row
|
||||||
|
# present) and `omp models` (frank-tts absent from the chat roster, all three
|
||||||
|
# fleet providers still listed).
|
||||||
|
"frank-tts (dankclaw)":
|
||||||
|
baseUrl: http://192.168.0.143:7772/v1
|
||||||
|
auth: none
|
||||||
|
models:
|
||||||
|
- id: frank-tts
|
||||||
|
name: "frank-tts (dankclaw) — cloned voices"
|
||||||
|
api: openai-speech
|
||||||
|
input: [text]
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
---
|
|
||||||
name: code-review
|
|
||||||
description: "Review the changes since a fixed point (commit, branch, tag, or merge-base) along two axes: Standards (does the code follow this repo's documented coding standards?) and Spec (does the code match what the originating issue/spec asked for?). Runs both reviews in parallel sub-agents and reports them side by side. Use when the user wants to review a branch, a PR, work-in-progress changes, or asks to \"review since X\"."
|
|
||||||
---
|
|
||||||
|
|
||||||
Two-axis review of the diff between `HEAD` and a fixed point the user supplies:
|
|
||||||
|
|
||||||
- **Standards**: does the code conform to this repo's documented coding standards?
|
|
||||||
- **Spec**: does the code faithfully implement the originating issue / spec?
|
|
||||||
|
|
||||||
Both axes run as **parallel sub-agents** so they don't pollute each other's context, then this skill aggregates their findings.
|
|
||||||
|
|
||||||
The issue tracker should have been provided to you. If not, tell the user to run `/setup-matt-pocock-skills`.
|
|
||||||
|
|
||||||
## Process
|
|
||||||
|
|
||||||
### 1. Pin the fixed point
|
|
||||||
|
|
||||||
Whatever the user said is the fixed point (a commit SHA, branch name, tag, `main`, `HEAD~5`, etc.). If they didn't specify one, ask for it.
|
|
||||||
|
|
||||||
Capture the diff command once: `git diff <fixed-point>...HEAD` (three-dot, so the comparison is against the merge-base). Also note the list of commits via `git log <fixed-point>..HEAD --oneline`.
|
|
||||||
|
|
||||||
Before going further, confirm the fixed point resolves (`git rev-parse <fixed-point>`) and the diff is non-empty. A bad ref or empty diff should fail here, not inside two parallel sub-agents.
|
|
||||||
|
|
||||||
### 2. Identify the spec source
|
|
||||||
|
|
||||||
Look for the originating spec, in this order:
|
|
||||||
|
|
||||||
1. Issue references in the commit messages (`#123`, `Closes #45`, GitLab `!67`, etc.), fetched via the workflow in the tracker doc.
|
|
||||||
2. A path the user passed as an argument.
|
|
||||||
3. A spec file under `docs/`, `specs/`, or `.scratch/` matching the branch name or feature.
|
|
||||||
4. If nothing is found, ask the user where the spec is. If they say there isn't one, the **Spec** sub-agent will skip and report "no spec available".
|
|
||||||
|
|
||||||
### 3. Identify the standards sources
|
|
||||||
|
|
||||||
Search the repo for every file that documents how code should be written. When `CODING_STANDARDS.md` or `CONTRIBUTING.md` exists, it must be on the list.
|
|
||||||
|
|
||||||
On top of whatever the repo documents, the Standards axis always carries the **smell baseline** below: a fixed set of Fowler code smells (_Refactoring_, ch.3) that applies even when a repo documents nothing. Two rules bind it:
|
|
||||||
|
|
||||||
- **The repo overrides.** A documented repo standard always wins; where it endorses something the baseline would flag, suppress the smell.
|
|
||||||
- **Always a judgement call.** Each smell is a labelled heuristic ("possible Feature Envy"), never a hard violation. Like any standard here, skip anything tooling already enforces.
|
|
||||||
|
|
||||||
Each smell reads *what it is* → *how to fix*; match it against the diff:
|
|
||||||
|
|
||||||
- **Mysterious Name**: a function, variable, or type whose name doesn't reveal what it does or holds. → rename it; if no honest name comes, the design's murky.
|
|
||||||
- **Duplicated Code**: the same logic shape appears in more than one hunk or file in the change. → extract the shared shape, call it from both.
|
|
||||||
- **Feature Envy**: a method that reaches into another object's data more than its own. → move the method onto the data it envies.
|
|
||||||
- **Data Clumps**: the same few fields or params keep travelling together (a type wanting to be born). → bundle them into one type, pass that.
|
|
||||||
- **Primitive Obsession**: a primitive or string standing in for a domain concept that deserves its own type. → give the concept its own small type.
|
|
||||||
- **Repeated Switches**: the same `switch`/`if`-cascade on the same type recurs across the change. → replace with polymorphism, or one map both sites share.
|
|
||||||
- **Shotgun Surgery**: one logical change forces scattered edits across many files in the diff. → gather what changes together into one module.
|
|
||||||
- **Divergent Change**: one file or module is edited for several unrelated reasons. → split so each module changes for one reason.
|
|
||||||
- **Speculative Generality**: abstraction, parameters, or hooks added for needs the spec doesn't have. → delete it; inline back until a real need shows.
|
|
||||||
- **Message Chains**: long `a.b().c().d()` navigation the caller shouldn't depend on. → hide the walk behind one method on the first object.
|
|
||||||
- **Middle Man**: a class or function that mostly just delegates onward. → cut it, call the real target direct.
|
|
||||||
- **Refused Bequest**: a subclass or implementer that ignores or overrides most of what it inherits. → drop the inheritance, use composition.
|
|
||||||
|
|
||||||
### 4. Spawn both sub-agents in parallel
|
|
||||||
|
|
||||||
Issue both sub-agent calls together, in the foreground, and aggregate the reports they return.
|
|
||||||
|
|
||||||
**Standards sub-agent prompt** should include:
|
|
||||||
|
|
||||||
- The full diff command and commit list.
|
|
||||||
- The list of standards-source files you found in step 3, **plus the smell baseline from step 3** pasted in full (the sub-agent has no other access to it).
|
|
||||||
- The brief: "Report, per file/hunk where relevant, (a) every place the diff violates a documented standard: cite the standard (file + the rule); and (b) any baseline smell you spot: name it and quote the hunk. Distinguish hard violations from judgement calls: documented-standard breaches can be hard, but baseline smells are always judgement calls, and a documented repo standard overrides the baseline. Skip anything tooling enforces. Under 400 words."
|
|
||||||
|
|
||||||
**Spec sub-agent prompt** should include:
|
|
||||||
|
|
||||||
- The diff command and commit list.
|
|
||||||
- The path or fetched contents of the spec.
|
|
||||||
- The brief: "Report: (a) requirements the spec asked for that are missing or partial; (b) behaviour in the diff that wasn't asked for (scope creep); (c) requirements that look implemented but where the implementation looks wrong. Quote the spec line for each finding. Under 400 words."
|
|
||||||
|
|
||||||
If the spec is missing, skip the Spec sub-agent and note this in the final report.
|
|
||||||
|
|
||||||
### 5. Aggregate
|
|
||||||
|
|
||||||
Present the two reports under `## Standards` and `## Spec` headings, verbatim or lightly cleaned. Do **not** merge or rerank findings, because the two axes are deliberately separate (see _Why two axes_).
|
|
||||||
|
|
||||||
End with a one-line summary: total findings per axis, and the worst issue _within each axis_ (if any). Don't pick a single winner across axes: that's the reranking the separation exists to prevent.
|
|
||||||
|
|
||||||
## Why two axes
|
|
||||||
|
|
||||||
A change can pass one axis and fail the other:
|
|
||||||
|
|
||||||
- Code that follows every standard but implements the wrong thing → **Standards pass, Spec fail.**
|
|
||||||
- Code that does exactly what the issue asked but breaks the project's conventions → **Spec pass, Standards fail.**
|
|
||||||
|
|
||||||
Reporting them separately stops one axis from masking the other.
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
interface:
|
|
||||||
display_name: "Code Review"
|
|
||||||
short_description: "Review a diff on standards and spec"
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
---
|
|
||||||
name: diagnosing-bugs
|
|
||||||
description: Diagnosis loop for hard bugs and performance regressions. Use when the user says "diagnose"/"debug this", or reports something broken/throwing/failing/slow.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Diagnosing Bugs
|
|
||||||
|
|
||||||
A discipline for hard bugs. Skip phases only when explicitly justified.
|
|
||||||
|
|
||||||
When exploring the codebase, read `GLOSSARY.md` (if it exists) to get a clear mental model of the relevant modules, and check ADRs in the area you're touching.
|
|
||||||
|
|
||||||
## Redact
|
|
||||||
|
|
||||||
This skill has you show commands, outputs and captured artifacts. **Redact every secret first**: write `<REDACTED>` in its place. Build loops against env vars, so the credential stays in the environment rather than in what you show. Captured artifacts carry auth headers: quote only the lines that carry the signal.
|
|
||||||
|
|
||||||
If the redacted output is not enough to diagnose the bug, say so and ask the user.
|
|
||||||
|
|
||||||
## Phase 1: Build a feedback loop
|
|
||||||
|
|
||||||
**This is the skill.** Everything else is mechanical. If you have a **tight** pass/fail signal for the bug (one that goes red on _this_ bug), you will find the cause; bisection, hypothesis-testing, and instrumentation all just consume it. If you don't have one, no amount of staring at code will save you.
|
|
||||||
|
|
||||||
Spend disproportionate effort here. **Be aggressive. Be creative. Refuse to give up.**
|
|
||||||
|
|
||||||
### Ways to construct one, in roughly this order
|
|
||||||
|
|
||||||
1. **Failing test** at whatever seam reaches the bug: unit, integration, e2e.
|
|
||||||
2. **Curl / HTTP script** against a running dev server.
|
|
||||||
3. **CLI invocation** with a fixture input, diffing stdout against a known-good snapshot.
|
|
||||||
4. **Headless browser script** (Playwright / Puppeteer) that drives the UI and asserts on DOM/console/network.
|
|
||||||
5. **Replay a captured trace.** Save a real network request / payload / event log to disk; replay it through the code path in isolation.
|
|
||||||
6. **Throwaway harness.** Spin up a minimal subset of the system (one service, mocked deps) that exercises the bug code path with a single function call.
|
|
||||||
7. **Property / fuzz loop.** If the bug is "sometimes wrong output", run 1000 random inputs and look for the failure mode.
|
|
||||||
8. **Bisection harness.** If the bug appeared between two known states (commit, dataset, version), automate "boot at state X, check, repeat" so you can `git bisect run` it.
|
|
||||||
9. **Differential loop.** Run the same input through old-version vs new-version (or two configs) and diff outputs.
|
|
||||||
10. **HITL bash script.** Last resort. If a human must click, drive _them_ with `scripts/hitl-loop.template.sh` so the loop is still structured. Captured output feeds back to you.
|
|
||||||
|
|
||||||
Build the right feedback loop, and the bug is 90% fixed.
|
|
||||||
|
|
||||||
### Tighten the loop
|
|
||||||
|
|
||||||
Treat the loop as a product. Once you have _a_ loop, **tighten** it:
|
|
||||||
|
|
||||||
- Can I make it faster? (Cache setup, skip unrelated init, narrow the test scope.)
|
|
||||||
- Can I make the signal sharper? (Assert on the specific symptom, not "didn't crash".)
|
|
||||||
- Can I make it more deterministic? (Pin time, seed RNG, isolate filesystem, freeze network.)
|
|
||||||
|
|
||||||
A 30-second flaky loop is barely better than no loop; a 2-second deterministic one is tight, a debugging superpower.
|
|
||||||
|
|
||||||
### Non-deterministic bugs
|
|
||||||
|
|
||||||
The goal is not a clean repro but a **higher reproduction rate**. Loop the trigger 100×, parallelise, add stress, narrow timing windows, inject sleeps. A 50%-flake bug is debuggable; 1% is not, so keep raising the rate until it's debuggable.
|
|
||||||
|
|
||||||
### When you genuinely cannot build a loop
|
|
||||||
|
|
||||||
Stop and say so explicitly. List what you tried. Ask the user for: (a) access to whatever environment reproduces it, (b) a redacted captured artifact (HAR file, log dump, core dump, screen recording with timestamps), or (c) permission to add temporary production instrumentation. Do **not** proceed to hypothesise without a loop.
|
|
||||||
|
|
||||||
### Completion criterion: a tight loop that goes red
|
|
||||||
|
|
||||||
Phase 1 is done when the loop is **tight** and **red-capable**: you can name **one command** (a script path, a test invocation, a curl) that you have **already run at least once** (show the invocation and its output, redacted), and that is:
|
|
||||||
|
|
||||||
- [ ] **Red-capable**: it drives the actual bug code path and asserts the **user's exact symptom**, so it can go red on this bug and green once fixed. Not "runs without erroring"; it must be able to _catch this specific bug_.
|
|
||||||
- [ ] **Deterministic**: same verdict every run (flaky bugs: a pinned, high reproduction rate, per above).
|
|
||||||
- [ ] **Fast**: seconds, not minutes.
|
|
||||||
- [ ] **Agent-runnable**: you can run it unattended; a human in the loop only via `scripts/hitl-loop.template.sh`.
|
|
||||||
|
|
||||||
If you catch yourself reading code to build a theory before this command exists, **stop: jumping straight to a hypothesis is the exact failure this skill prevents.** No red-capable command, no Phase 2.
|
|
||||||
|
|
||||||
## Phase 2: Reproduce + minimise
|
|
||||||
|
|
||||||
Run the loop. Watch it go red as the bug appears.
|
|
||||||
|
|
||||||
Confirm:
|
|
||||||
|
|
||||||
- [ ] The loop produces the failure mode the **user** described, not a different failure that happens to be nearby. Wrong bug = wrong fix.
|
|
||||||
- [ ] The failure is reproducible across multiple runs (or, for non-deterministic bugs, reproducible at a high enough rate to debug against).
|
|
||||||
- [ ] You have captured the exact symptom (error message, wrong output, slow timing) so later phases can verify the fix actually addresses it.
|
|
||||||
|
|
||||||
### Minimise
|
|
||||||
|
|
||||||
Once it's red, shrink the repro to the **smallest scenario that still goes red**. Cut inputs, callers, config, data, and steps **one at a time**, re-running the loop after each cut, and keep only what's load-bearing for the failure.
|
|
||||||
|
|
||||||
Why bother: a minimal repro shrinks the hypothesis space in Phase 3 (fewer moving parts left to suspect) and becomes the clean regression test in Phase 5.
|
|
||||||
|
|
||||||
Done when **every remaining element is load-bearing**: removing any one of them makes the loop go green.
|
|
||||||
|
|
||||||
Do not proceed until you have reproduced **and** minimised.
|
|
||||||
|
|
||||||
## Phase 3: Hypothesise
|
|
||||||
|
|
||||||
Generate **3–5 ranked hypotheses** before testing any of them. Single-hypothesis generation anchors on the first plausible idea.
|
|
||||||
|
|
||||||
Each hypothesis must be **falsifiable**: state the prediction it makes.
|
|
||||||
|
|
||||||
> Format: "If <X> is the cause, then <changing Y> will make the bug disappear / <changing Z> will make it worse."
|
|
||||||
|
|
||||||
If you cannot state the prediction, the hypothesis is a vibe: discard or sharpen it.
|
|
||||||
|
|
||||||
**Show the ranked list to the user before testing.** They often have domain knowledge that re-ranks instantly ("we just deployed a change to #3"), or know hypotheses they've already ruled out. Cheap checkpoint, big time saver. Don't block on it; proceed with your ranking if the user is AFK.
|
|
||||||
|
|
||||||
## Phase 4: Instrument
|
|
||||||
|
|
||||||
Each probe must map to a specific prediction from Phase 3. **Change one variable at a time.**
|
|
||||||
|
|
||||||
Tool preference:
|
|
||||||
|
|
||||||
1. **Debugger / REPL inspection** if the env supports it. One breakpoint beats ten logs.
|
|
||||||
2. **Targeted logs** at the boundaries that distinguish hypotheses.
|
|
||||||
3. Never "log everything and grep".
|
|
||||||
|
|
||||||
**Tag every debug log** with a unique prefix, e.g. `[DEBUG-a4f2]`. Cleanup at the end becomes a single grep. Untagged logs survive; tagged logs die.
|
|
||||||
|
|
||||||
**Perf branch.** For performance regressions, logs are usually wrong. Instead: establish a baseline measurement (timing harness, `performance.now()`, profiler, query plan), then bisect. Measure first, fix second.
|
|
||||||
|
|
||||||
## Phase 5: Fix + regression test
|
|
||||||
|
|
||||||
Write the regression test **before the fix**, but only if there is a **correct seam** for it.
|
|
||||||
|
|
||||||
A correct seam is one where the test exercises the **real bug pattern** as it occurs at the call site. If the only available seam is too shallow (single-caller test when the bug needs multiple callers, unit test that can't replicate the chain that triggered the bug), a regression test there gives false confidence.
|
|
||||||
|
|
||||||
**If no correct seam exists, that itself is the finding.** Note it. The codebase architecture is preventing the bug from being locked down. Flag this for the next phase.
|
|
||||||
|
|
||||||
If a correct seam exists:
|
|
||||||
|
|
||||||
1. Turn the minimised repro into a failing test at that seam.
|
|
||||||
2. Watch it fail. If you forced the red by mutating code or a fixture, `diff` against a pristine copy to prove the mutation landed before you trust it.
|
|
||||||
3. Apply the fix.
|
|
||||||
4. Watch it pass.
|
|
||||||
5. Re-run the Phase 1 feedback loop against the original (un-minimised) scenario.
|
|
||||||
|
|
||||||
## Phase 6: Cleanup
|
|
||||||
|
|
||||||
Required before declaring done:
|
|
||||||
|
|
||||||
- [ ] Original repro no longer reproduces (re-run the Phase 1 loop)
|
|
||||||
- [ ] Regression test passes (or absence of seam is documented)
|
|
||||||
- [ ] All `[DEBUG-...]` instrumentation removed (`grep` the prefix)
|
|
||||||
- [ ] Throwaway prototypes deleted (or moved to a clearly-marked debug location)
|
|
||||||
- [ ] The hypothesis that turned out correct is stated in the commit / PR message, so the next debugger learns
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
interface:
|
|
||||||
display_name: "Diagnosing Bugs"
|
|
||||||
short_description: "Diagnose hard bugs and regressions"
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Human-in-the-loop reproduction loop.
|
|
||||||
# Copy this file, edit the steps below, and run it.
|
|
||||||
# The agent runs the script; the user follows prompts in their terminal.
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# bash hitl-loop.template.sh
|
|
||||||
#
|
|
||||||
# Two helpers:
|
|
||||||
# step "<instruction>" → show instruction, wait for Enter
|
|
||||||
# capture VAR "<question>" → show question, read response into VAR
|
|
||||||
#
|
|
||||||
# At the end, captured values are printed as KEY=VALUE for the agent to parse.
|
|
||||||
#
|
|
||||||
# `capture` prints its value back to the terminal, where the agent reads it,
|
|
||||||
# so capture observations, and leave signing in to the user as a `step`.
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
step() {
|
|
||||||
printf '\n>>> %s\n' "$1"
|
|
||||||
read -r -p " [Enter when done] " _
|
|
||||||
}
|
|
||||||
|
|
||||||
capture() {
|
|
||||||
local var="$1" question="$2" answer
|
|
||||||
printf '\n>>> %s\n' "$question"
|
|
||||||
read -r -p " > " answer
|
|
||||||
printf -v "$var" '%s' "$answer"
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- edit below ---------------------------------------------------------
|
|
||||||
|
|
||||||
step "Open the app at http://localhost:3000 and sign in."
|
|
||||||
|
|
||||||
capture ERRORED "Click the 'Export' button. Did it throw an error? (y/n)"
|
|
||||||
|
|
||||||
capture ERROR_MSG "Paste the error message (or 'none'):"
|
|
||||||
|
|
||||||
# --- edit above ---------------------------------------------------------
|
|
||||||
|
|
||||||
printf '\n--- Captured ---\n'
|
|
||||||
printf 'ERRORED=%s\n' "$ERRORED"
|
|
||||||
printf 'ERROR_MSG=%s\n' "$ERROR_MSG"
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
---
|
|
||||||
name: find-skills
|
|
||||||
description: Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Find Skills
|
|
||||||
|
|
||||||
This skill helps you discover and install skills from the open agent skills ecosystem.
|
|
||||||
|
|
||||||
## When to Use This Skill
|
|
||||||
|
|
||||||
Use this skill when the user:
|
|
||||||
|
|
||||||
- Asks "how do I do X" where X might be a common task with an existing skill
|
|
||||||
- Says "find a skill for X" or "is there a skill for X"
|
|
||||||
- Asks "can you do X" where X is a specialized capability
|
|
||||||
- Expresses interest in extending agent capabilities
|
|
||||||
- Wants to search for tools, templates, or workflows
|
|
||||||
- Mentions they wish they had help with a specific domain (design, testing, deployment, etc.)
|
|
||||||
|
|
||||||
## What is the Skills CLI?
|
|
||||||
|
|
||||||
The Skills CLI (`npx skills`) is the package manager for the open agent skills ecosystem. Skills are modular packages that extend agent capabilities with specialized knowledge, workflows, and tools.
|
|
||||||
|
|
||||||
**Key commands:**
|
|
||||||
|
|
||||||
- `npx skills find [query] [--owner <owner>]` - Search for skills interactively or by keyword, optionally scoped to a GitHub owner
|
|
||||||
- `npx skills add <package>` - Install a skill from GitHub or other sources
|
|
||||||
- `npx skills update` - Update all installed skills
|
|
||||||
|
|
||||||
**Browse skills at:** https://skills.sh/
|
|
||||||
|
|
||||||
## How to Help Users Find Skills
|
|
||||||
|
|
||||||
### Step 1: Understand What They Need
|
|
||||||
|
|
||||||
When a user asks for help with something, identify:
|
|
||||||
|
|
||||||
1. The domain (e.g., React, testing, design, deployment)
|
|
||||||
2. The specific task (e.g., writing tests, creating animations, reviewing PRs)
|
|
||||||
3. Whether this is a common enough task that a skill likely exists
|
|
||||||
|
|
||||||
### Step 2: Check the Leaderboard First
|
|
||||||
|
|
||||||
Before running a CLI search, check the [skills.sh leaderboard](https://skills.sh/) to see if a well-known skill already exists for the domain. The leaderboard ranks skills by total installs, surfacing the most popular and battle-tested options.
|
|
||||||
|
|
||||||
For example, top skills for web development include:
|
|
||||||
- `vercel-labs/agent-skills` — React, Next.js, web design (100K+ installs each)
|
|
||||||
- `anthropics/skills` — Frontend design, document processing (100K+ installs)
|
|
||||||
|
|
||||||
### Step 3: Search for Skills
|
|
||||||
|
|
||||||
If the leaderboard doesn't cover the user's need, run the find command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills find [query] [--owner <owner>]
|
|
||||||
```
|
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
- User asks "how do I make my React app faster?" → `npx skills find react performance`
|
|
||||||
- User asks "can you help me with PR reviews?" → `npx skills find pr review`
|
|
||||||
- User asks "I need to create a changelog" → `npx skills find changelog`
|
|
||||||
|
|
||||||
### Step 4: Verify Quality Before Recommending
|
|
||||||
|
|
||||||
**Do not recommend a skill based solely on search results.** Always verify:
|
|
||||||
|
|
||||||
1. **Install count** — Prefer skills with 1K+ installs. Be cautious with anything under 100.
|
|
||||||
2. **Source reputation** — Official sources (`vercel-labs`, `anthropics`, `microsoft`) are more trustworthy than unknown authors.
|
|
||||||
3. **GitHub stars** — Check the source repository. A skill from a repo with <100 stars should be treated with skepticism.
|
|
||||||
|
|
||||||
### Step 5: Present Options to the User
|
|
||||||
|
|
||||||
When you find relevant skills, present them to the user with:
|
|
||||||
|
|
||||||
1. The skill name and what it does
|
|
||||||
2. The install count and source
|
|
||||||
3. The install command they can run
|
|
||||||
4. A link to learn more at skills.sh
|
|
||||||
|
|
||||||
Example response:
|
|
||||||
|
|
||||||
```
|
|
||||||
I found a skill that might help! The "react-best-practices" skill provides
|
|
||||||
React and Next.js performance optimization guidelines from Vercel Engineering.
|
|
||||||
(185K installs)
|
|
||||||
|
|
||||||
To install it:
|
|
||||||
npx skills add vercel-labs/agent-skills@react-best-practices
|
|
||||||
|
|
||||||
Learn more: https://skills.sh/vercel-labs/agent-skills/react-best-practices
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 6: Offer to Install
|
|
||||||
|
|
||||||
If the user wants to proceed, you can install the skill for them:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add <owner/repo@skill> -g -y
|
|
||||||
```
|
|
||||||
|
|
||||||
The `-g` flag installs globally (user-level) and `-y` skips confirmation prompts.
|
|
||||||
|
|
||||||
## Common Skill Categories
|
|
||||||
|
|
||||||
When searching, consider these common categories:
|
|
||||||
|
|
||||||
| Category | Example Queries |
|
|
||||||
| --------------- | ---------------------------------------- |
|
|
||||||
| Web Development | react, nextjs, typescript, css, tailwind |
|
|
||||||
| Testing | testing, jest, playwright, e2e |
|
|
||||||
| DevOps | deploy, docker, kubernetes, ci-cd |
|
|
||||||
| Documentation | docs, readme, changelog, api-docs |
|
|
||||||
| Code Quality | review, lint, refactor, best-practices |
|
|
||||||
| Design | ui, ux, design-system, accessibility |
|
|
||||||
| Productivity | workflow, automation, git |
|
|
||||||
|
|
||||||
## Tips for Effective Searches
|
|
||||||
|
|
||||||
1. **Use specific keywords**: "react testing" is better than just "testing"
|
|
||||||
2. **Try alternative terms**: If "deploy" doesn't work, try "deployment" or "ci-cd"
|
|
||||||
3. **Check popular sources**: Many skills come from `vercel-labs/agent-skills` or `ComposioHQ/awesome-claude-skills`
|
|
||||||
|
|
||||||
## When No Skills Are Found
|
|
||||||
|
|
||||||
If no relevant skills exist:
|
|
||||||
|
|
||||||
1. Acknowledge that no existing skill was found
|
|
||||||
2. Offer to help with the task directly using your general capabilities
|
|
||||||
3. Suggest the user could create their own skill with `npx skills init`
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```
|
|
||||||
I searched for skills related to "xyz" but didn't find any matches.
|
|
||||||
I can still help you with this task directly! Would you like me to proceed?
|
|
||||||
|
|
||||||
If this is something you do often, you could create your own skill:
|
|
||||||
npx skills init my-xyz-skill
|
|
||||||
```
|
|
||||||
@@ -3,28 +3,44 @@ name: grilling
|
|||||||
description: Grill the user relentlessly about a plan, decision, or idea. Use when the user wants to stress-test their thinking, or uses any 'grill' trigger phrases.
|
description: Grill the user relentlessly about a plan, decision, or idea. Use when the user wants to stress-test their thinking, or uses any 'grill' trigger phrases.
|
||||||
---
|
---
|
||||||
|
|
||||||
Interview the user relentlessly until you reach a shared understanding. Map this as a **design tree**: every decision branches into the decisions that hang off it.
|
# Grilling
|
||||||
|
|
||||||
Work the tree in **rounds**. The **frontier** is every decision whose prerequisites are already settled: the questions you can ask _now_ without guessing at answers you haven't heard yet. Ask the whole frontier in one round: number each question and give your recommended answer. Then wait for the user's answers before the next round.
|
Interview the user relentlessly until nothing is silently assumed and you reach a shared understanding. Map this as a **design tree**: every decision branches into the decisions that hang off it.
|
||||||
|
|
||||||
Format a round like so:
|
A decision is **frontier** when it can be asked now, without an answer you haven't heard yet. The interview is done when the frontier is empty: every branch of the tree visited.
|
||||||
|
|
||||||
```
|
## Rounds
|
||||||
❓ **Q1** - **<question title>**: <question body, might be multiple paragraphs, including multiple choices>
|
|
||||||
|
|
||||||
➡️ <your recommended answer>
|
Work the tree in **rounds**: ask the whole frontier at once, then wait. A decision whose answer hangs on an open question or an unfetched fact joins a later round.
|
||||||
|
|
||||||
---
|
Ask each closed decision - anything expressible as 2–5 options - through the `ask` tool:
|
||||||
|
|
||||||
❓ **Q2** - **<question title>**: <question body, might be multiple paragraphs, including multiple choices>
|
- **One artefact per question**, counted before sending. Name the artefact each question decides; two names means two questions. Shared plumbing (an endpoint, a service, a build step) still leaves two artefacts.
|
||||||
|
- Recommended answer first, marked `recommended: 0`, which labels it "(Recommended)" for one-glance acceptance. Every reply names what was picked, so the record is unambiguous.
|
||||||
|
- Options quote the user's goal. Their words, their stated goal, ground the wording; your todo text and table rows stay out of it.
|
||||||
|
- Your own idea is welcome: it gets its own recommended question. Attach it to its own question, so it rides there on its merits.
|
||||||
|
- `multi: true` for pick-many, `header` for the branch chip, `preview` when the decision needs code or config in front of the user.
|
||||||
|
|
||||||
➡️ <your recommended answer>
|
Open narrative - "walk me through the deploy flow" - has no options to offer; ask it as prose in chat. Without an `ask` tool the whole round degrades to text: `❓ **Q1 — title**: body`, then `➡️ recommendation`, phrased so one bare "yes" accepts every recommendation.
|
||||||
```
|
|
||||||
|
|
||||||
Word each question so "yes" accepts your recommended answer.
|
The dialog is exclusive: dispatch scouts before the `ask` call in the same message, or they wait behind it; their results land between rounds. Esc ends the run outright, with no turn to wrap up, so the ledger is what survives it.
|
||||||
|
|
||||||
Each round the user answers reshapes the tree: settled decisions push the frontier outward and unblock questions that depended on them. Recompute the frontier and ask the next round. A question whose answer depends on another question still open in this round belongs to a _later_ round, not this one.
|
## The ledger
|
||||||
|
|
||||||
Finding _facts_ is your job, never the user's. When a frontier question needs a fact from the environment (filesystem, tools, etc.), dispatch a sub-agent to find it; don't ask the user for anything you could look up yourself. Don't block on it: a running exploration is an unsettled prerequisite, so only the questions downstream of it wait for the sub-agent to report; ask the rest of the frontier now. The _decisions_ are the user's: put each to them and wait.
|
The tree lives in the `todo` list while the interview runs, so the user watches it fall:
|
||||||
|
|
||||||
The session is done when the frontier is empty: every branch of the design tree visited, nothing left silently assumed. Do not act on it until the user confirms you have reached a shared understanding.
|
- One task per decision, filed under the phase for its branch; `via: <parent decision>` in the task text carries the edge.
|
||||||
|
- `pending` is the frontier; `block` with a one-line reason waits on a fact or an open decision; `done` is settled. Unblock the moment the dependency settles.
|
||||||
|
|
||||||
|
Write `local://grilling-state.md` at the end of every round: the tree, each decision's verdict and one-line rationale. It is the durable copy of the interview; keep it current so an Esc costs one round.
|
||||||
|
|
||||||
|
## Facts are yours, decisions are theirs
|
||||||
|
|
||||||
|
- Environment facts (filesystem, repo, live config): dispatch a **scout** per fact-cluster, batched into one `task` call, before the round's `ask`. Cluster tightly; doing so keeps each scout more aware of possible adjacencies.
|
||||||
|
- Outside-world facts: call `web_search` directly, do not delegate to a subagent.
|
||||||
|
- Cheap lookups: `read`/`grep` inline.
|
||||||
|
- The main session is where questions happen: every spawn carries the exact facts to return, and comes back into the next round.
|
||||||
|
|
||||||
|
## Definition of Done
|
||||||
|
|
||||||
|
The frontier is empty and nothing sits blocked. Distil the ledger into a **brief**: one line per decision, verdict plus rationale, grouped by branch. Put it in chat, then one closing `ask`: "Proceed on this brief?" Nothing executes before that yes. After it, fold the brief into the project's knowledge base, or a log markdown file, wherever the project keeps its decisions.
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
interface:
|
|
||||||
display_name: "Grilling"
|
|
||||||
short_description: "Stress-test thinking a round of questions at a time"
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
---
|
|
||||||
name: handoff
|
|
||||||
description: Compact the current conversation into a handoff document for another agent to pick up.
|
|
||||||
argument-hint: "What will the next session be used for?"
|
|
||||||
disable-model-invocation: true
|
|
||||||
---
|
|
||||||
|
|
||||||
Write a handoff document summarising the current conversation so a fresh agent can continue the work. Save to the temporary directory of the user's OS (`$TMPDIR`, else `/tmp`; `%TEMP%` on Windows) - not the current workspace.
|
|
||||||
|
|
||||||
Include a "suggested skills" section in the document, naming which skills the next agent should call the Skill tool for.
|
|
||||||
|
|
||||||
Do not duplicate content already captured in other artifacts (specs, plans, ADRs, issues, commits, diffs). Reference them by path or URL instead.
|
|
||||||
|
|
||||||
Redact any sensitive information, such as API keys, passwords, or personally identifiable information.
|
|
||||||
|
|
||||||
If the user passed arguments, treat them as a description of what the next session will focus on and tailor the doc accordingly.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
interface:
|
|
||||||
display_name: "Handoff"
|
|
||||||
short_description: "Compact a conversation into a handoff"
|
|
||||||
policy:
|
|
||||||
allow_implicit_invocation: false
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
---
|
|
||||||
name: implement
|
|
||||||
description: "Implement a piece of work based on a spec or set of tickets."
|
|
||||||
disable-model-invocation: true
|
|
||||||
---
|
|
||||||
|
|
||||||
Implement the work described by the user in the spec or tickets.
|
|
||||||
|
|
||||||
If the user passes a ticket reference, fetch it from the issue tracker and state its title before starting. If the reference is ambiguous, ask.
|
|
||||||
|
|
||||||
Call the Skill tool with "tdd" where possible, at pre-agreed seams.
|
|
||||||
|
|
||||||
Run typechecking regularly, single test files regularly, and the full test suite once at the end.
|
|
||||||
|
|
||||||
Once done, call the Skill tool with "code-review" to review the work.
|
|
||||||
|
|
||||||
Commit your work to the current branch.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
interface:
|
|
||||||
display_name: "Implement"
|
|
||||||
short_description: "Build work from a spec or tickets"
|
|
||||||
policy:
|
|
||||||
allow_implicit_invocation: false
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
# Logic Prototype
|
|
||||||
|
|
||||||
A single, self-contained HTML file (a **shareable demo**) that lets anyone drive a state model by clicking buttons. Use this when the question is about **business logic, state transitions, or data shape**: the kind of thing that looks reasonable on paper but only feels wrong once you push it through real cases.
|
|
||||||
|
|
||||||
Because it's one file with nothing to install, you can hand it to a non-developer (a designer, a PM, a domain expert) and let them feel the model for themselves. So it speaks their language, not the code's.
|
|
||||||
|
|
||||||
## When this is the right shape
|
|
||||||
|
|
||||||
- "I'm not sure if this state machine handles the edge case where X then Y."
|
|
||||||
- "Does this data model actually let me represent the case where..."
|
|
||||||
- "I want to feel out what the API should look like before writing it."
|
|
||||||
- Anything where someone wants to **press buttons and watch state change**.
|
|
||||||
|
|
||||||
If the question is "what should this look like," this is the wrong branch. Use [UI.md](UI.md).
|
|
||||||
|
|
||||||
## Process
|
|
||||||
|
|
||||||
### 1. State the question
|
|
||||||
|
|
||||||
Before writing code, write down what state model and what question you're prototyping. One paragraph, at the top of the demo (in a visible intro, not just a comment). A logic prototype that answers the wrong question is pure waste, so make the question explicit so it can be checked later, whether the user is watching now or returning to it AFK.
|
|
||||||
|
|
||||||
### 2. Isolate the logic in a portable module
|
|
||||||
|
|
||||||
Put the actual logic (the bit that's answering the question) in a single `<script>` block written as a small, pure module that could be lifted out and dropped into the real codebase later. The page around it is throwaway; this module isn't.
|
|
||||||
|
|
||||||
The right shape depends on the question:
|
|
||||||
|
|
||||||
- **A pure reducer**: `(state, action) => state`. Good when actions are discrete events and state is a single value.
|
|
||||||
- **A state machine**: explicit states and transitions. Good when "which actions are even legal right now" is part of the question.
|
|
||||||
- **A small set of pure functions** over a plain data type. Good when there's no implicit current state, just transformations.
|
|
||||||
- **A class or module with a clear method surface** when the logic genuinely owns ongoing internal state.
|
|
||||||
|
|
||||||
Pick whichever shape best fits the question being asked, *not* whichever is easiest to wire to a page. Keep it pure: no DOM, no `document`, no button handlers reaching inside it. The page calls into it; nothing flows the other direction. This is what makes the prototype useful past its own lifetime: once the question's answered, the validated reducer / machine / function set lifts into the real module on its own.
|
|
||||||
|
|
||||||
### 3. Build the shareable HTML file
|
|
||||||
|
|
||||||
One file, plain HTML/CSS/JS: no framework, no bundler, no server, everything inline so it opens by double-click and survives being emailed around. Anyone should be able to run it by opening it.
|
|
||||||
|
|
||||||
Write it for a non-developer. Every label is in **domain language**, not code: buttons and state read like the business, not the reducer. Explain in plain words what's happening.
|
|
||||||
|
|
||||||
Lay it out with a clean hierarchy, top to bottom:
|
|
||||||
|
|
||||||
1. **Title and one-line explanation** of what this demo lets you explore (the question from step 1).
|
|
||||||
2. **Current state**: the full relevant state, rendered as a readable panel (labelled fields, not a raw JSON dump), re-rendered after every click so the change is visible. Where it helps a non-developer follow, call out what just changed.
|
|
||||||
3. **Free-play buttons**: one button per action, always available, so anyone can poke at the model in any order. Each click dispatches its action and re-renders the state.
|
|
||||||
4. **Guided walkthroughs**: a set of **scenarios**, one per tab. Each tab holds a short plain-language description of the scenario (the situation it sets up and what to watch for) and underneath it, the ordered **buttons to press** for that scenario. Each step is a real button: clicking it performs that action and moves to the next step. Starting a walkthrough resets to a known initial state so the scenario runs the same way every time.
|
|
||||||
|
|
||||||
Choose scenarios that demonstrate the awkward cases, the ones hard to reason about on paper: the happy path, a tricky edge case, an attempt at something that should be illegal.
|
|
||||||
|
|
||||||
Keep it beautiful but restrained: clean typography, generous spacing, one accent colour. No animations, no gimmicks: nothing that competes with the state and the buttons.
|
|
||||||
|
|
||||||
### 4. Hand it over
|
|
||||||
|
|
||||||
Send them the file, or open it for them. They'll click through the walkthroughs and free-play whenever they get to it; the interesting moments are when they say "wait, that shouldn't be possible" or "huh, I assumed X would be different"; those are the bugs in the _idea_, which is the whole point. If they want new actions or a new scenario, add them. Prototypes evolve.
|
|
||||||
|
|
||||||
### 5. Capture the answer and the prototype
|
|
||||||
|
|
||||||
Once the prototype has answered its question, capture the answer, then capture the prototype the way the [SKILL](SKILL.md) describes. The logic-specific mapping: the validated reducer / machine / function set lifts into the real module (the decision, absorbed); the HTML shell rides along to the throwaway branch that keeps the prototype as a primary source, and being one self-contained file, it stays trivially re-runnable there.
|
|
||||||
|
|
||||||
## Anti-patterns
|
|
||||||
|
|
||||||
- **Don't add tests.** A prototype that needs tests is no longer a prototype.
|
|
||||||
- **Don't wire it to the real database.** Use in-memory state unless the question is specifically about persistence.
|
|
||||||
- **Don't generalise.** No "what if we wanted to support X later." The prototype answers one question.
|
|
||||||
- **Don't blur the logic and the page together.** If the pure module references the DOM, `document`, or button handlers, it's no longer liftable. Keep the page as a thin shell over a pure module.
|
|
||||||
- **Don't reach for a framework, bundler, or server.** One file the recipient double-clicks; a React app or a dev server defeats "shareable".
|
|
||||||
- **Don't ship the HTML shell into production.** The page is optimised for being clicked through by hand. The logic module behind it is the bit worth keeping.
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
---
|
|
||||||
name: prototype
|
|
||||||
description: Build a throwaway prototype to answer a design question. Use when the user wants to sanity-check whether a state model or logic feels right, or explore what a UI should look like.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Prototype
|
|
||||||
|
|
||||||
A prototype is **throwaway code that answers a question**. The question decides the shape.
|
|
||||||
|
|
||||||
## Pick a branch
|
|
||||||
|
|
||||||
Identify which question is being answered, using the user's prompt, the surrounding code, or by asking if the user is around:
|
|
||||||
|
|
||||||
- **"Does this logic / state model feel right?"** → [LOGIC.md](LOGIC.md). Build a single shareable HTML file (free-play buttons plus tabbed guided walkthroughs) that pushes the state machine through cases that are hard to reason about on paper, and that a non-developer can drive.
|
|
||||||
- **"What should this look like?"** → [UI.md](UI.md). Generate several radically different UI variations on a single route, switchable via a URL search param and a floating bottom bar.
|
|
||||||
|
|
||||||
The two branches produce very different artifacts, so getting this wrong wastes the whole prototype. If the question is genuinely ambiguous and the user isn't reachable, default to whichever branch better matches the surrounding code (a backend module → logic; a page or component → UI) and state the assumption at the top of the prototype.
|
|
||||||
|
|
||||||
## Rules that apply to both
|
|
||||||
|
|
||||||
1. **Throwaway from day one, and clearly marked as such.** Locate the prototype code close to where it will actually be used (next to the module or page it's prototyping for) so context is obvious, but name it so a casual reader can see it's a prototype, not production. For throwaway UI routes, obey whatever routing convention the project already uses; don't invent a new top-level structure.
|
|
||||||
2. **Trivial to run.** A UI prototype starts from one command in the project's task runner: `pnpm <name>`, `python <path>`, `bun <path>`, etc. A logic demo is a single HTML file the user double-clicks. Either way, no thinking required to start it.
|
|
||||||
3. **No persistence by default.** State lives in memory. Persistence is the thing the prototype is _checking_, not something it should depend on. If the question explicitly involves a database, hit a scratch DB or a local file with a clear "PROTOTYPE, wipe me" name.
|
|
||||||
4. **Skip the polish.** No tests, no error handling beyond what makes the prototype _runnable_, no abstractions. The point is to learn something fast.
|
|
||||||
5. **Surface the state.** After every action (logic) or on every variant switch (UI), print or render the full relevant state so the user can see what changed.
|
|
||||||
6. **Capture it when done.** Fold any validated decision into the real code, then capture the prototype itself as a **primary source**: commit it to a throwaway branch, out of main, and leave a context pointer to that branch on the implementation issue. Capture the answer too (the verdict and the question it settled) in the issue or a commit. The main branch keeps only the validated decision.
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
# UI Prototype
|
|
||||||
|
|
||||||
Generate **several radically different UI variations** on a single route, switchable from a floating bottom bar. The user flips between variants in the browser, picks one (or steals bits from each), then throws the rest away.
|
|
||||||
|
|
||||||
If the question is about logic/state rather than what something looks like, this is the wrong branch. Use [LOGIC.md](LOGIC.md).
|
|
||||||
|
|
||||||
## When this is the right shape
|
|
||||||
|
|
||||||
- "What should this page look like?"
|
|
||||||
- "I want to see a few options for this dashboard before committing."
|
|
||||||
- "Try a different layout for the settings screen."
|
|
||||||
- Any time the user would otherwise spend a day picking between three vague mockups in their head.
|
|
||||||
|
|
||||||
## Two sub-shapes: strongly prefer sub-shape A
|
|
||||||
|
|
||||||
A UI prototype is much easier to judge when it's **butting up against the rest of the app**: real header, real sidebar, real data, real density. A throwaway route on its own is a vacuum: every variant looks fine in isolation. Default to sub-shape A whenever there's a plausible existing page to host the variants. Only reach for sub-shape B if the prototype genuinely has no nearby home.
|
|
||||||
|
|
||||||
### Sub-shape A: adjustment to an existing page (preferred)
|
|
||||||
|
|
||||||
The route already exists. Variants are rendered **on the same route**, gated by a `?variant=` URL search param. The existing data fetching, params, and auth all stay. Only the rendering swaps. This is the default; pick it unless there's a specific reason not to.
|
|
||||||
|
|
||||||
If the prototype is for something that doesn't yet have a page but *would naturally live inside one* (a new section of the dashboard, a new card on the settings screen, a new step in an existing flow), it's still sub-shape A. Mount the variants inside the host page.
|
|
||||||
|
|
||||||
### Sub-shape B: a new page (last resort)
|
|
||||||
|
|
||||||
Only use this when the thing being prototyped genuinely has no existing page to live inside (e.g. an entirely new top-level surface, or a flow that can't be embedded anywhere sensible).
|
|
||||||
|
|
||||||
Create a **throwaway route** following whatever routing convention the project already uses. Don't invent a new top-level structure. Name it so it's obviously a prototype (e.g. include the word `prototype` in the path or filename). Same `?variant=` pattern.
|
|
||||||
|
|
||||||
Before committing to sub-shape B, sanity-check: is there really no existing page this could be embedded in? An empty route hides design problems that a populated one would expose.
|
|
||||||
|
|
||||||
In both sub-shapes the floating bottom bar is identical.
|
|
||||||
|
|
||||||
## Process
|
|
||||||
|
|
||||||
### 1. State the question and pick N
|
|
||||||
|
|
||||||
Default to **3 variants**. More than 5 stops being radically different and starts being noise, so cap there.
|
|
||||||
|
|
||||||
Write down the plan in one line, in the prototype's location or a top-of-file comment:
|
|
||||||
|
|
||||||
> "Three variants of the settings page, switchable via `?variant=`, on the existing `/settings` route."
|
|
||||||
|
|
||||||
This works whether the user is here to push back or not.
|
|
||||||
|
|
||||||
### 2. Generate radically different variants
|
|
||||||
|
|
||||||
Draft each variant. Hold each one to:
|
|
||||||
|
|
||||||
- The page's purpose and the data it has access to.
|
|
||||||
- The project's component library / styling system (TailwindCSS, shadcn, MUI, plain CSS, whatever).
|
|
||||||
- A clear exported component name, e.g. `VariantA`, `VariantB`, `VariantC`.
|
|
||||||
|
|
||||||
Variants must be **structurally different**: different layout, different information hierarchy, different primary affordance, not just different colours. Three slightly-tweaked card grids isn't a UI prototype, it's wallpaper. If two drafts come out too similar, redo one with explicit "do not use a card grid" guidance.
|
|
||||||
|
|
||||||
### 3. Wire them together
|
|
||||||
|
|
||||||
Create a single switcher component on the route:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// pseudo-code, adapt to the project's framework
|
|
||||||
const variant = searchParams.get('variant') ?? 'A';
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{variant === 'A' && <VariantA {...data} />}
|
|
||||||
{variant === 'B' && <VariantB {...data} />}
|
|
||||||
{variant === 'C' && <VariantC {...data} />}
|
|
||||||
<PrototypeSwitcher variants={['A','B','C']} current={variant} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
For sub-shape A (existing page): keep all the existing data fetching above the switcher; only the rendered subtree changes per variant.
|
|
||||||
|
|
||||||
For sub-shape B (new page): the throwaway route under `/prototype/<name>` mounts the same switcher.
|
|
||||||
|
|
||||||
### 4. Build the floating switcher
|
|
||||||
|
|
||||||
A small fixed-position bar at the bottom-centre of the screen with three pieces:
|
|
||||||
|
|
||||||
- **Left arrow**: cycles to the previous variant (wraps around).
|
|
||||||
- **Variant label**: shows the current variant key and, if the variant exports a name, that name too. e.g. `B (Sidebar layout)`.
|
|
||||||
- **Right arrow**: cycles forward (wraps around).
|
|
||||||
|
|
||||||
Behaviour:
|
|
||||||
|
|
||||||
- Clicking an arrow updates the URL search param (use the framework's router, e.g. `router.replace` on Next, `navigate` on React Router, etc) so the variant is shareable and reload-stable.
|
|
||||||
- Keyboard: `←` and `→` arrow keys also cycle. Don't intercept arrow keys when an `<input>`, `<textarea>`, or `[contenteditable]` is focused.
|
|
||||||
- Visually distinct from the page (e.g. high-contrast pill, subtle shadow) so it's obviously not part of the design being evaluated.
|
|
||||||
- Hidden in production builds: gate on `process.env.NODE_ENV !== 'production'` or an equivalent check, so a stray prototype merge can't ship the bar to users.
|
|
||||||
|
|
||||||
Put the switcher in a single shared component so both sub-shapes can reuse it. Locate it wherever shared UI lives in the project.
|
|
||||||
|
|
||||||
### 5. Hand it over
|
|
||||||
|
|
||||||
Surface the URL (and the `?variant=` keys). The user will flip through whenever they get to it. The interesting feedback is usually **"I want the header from B with the sidebar from C"**, which is the actual design they want.
|
|
||||||
|
|
||||||
### 6. Capture the answer and clean up
|
|
||||||
|
|
||||||
Once a variant has won, capture the answer (which variant and why), then capture the prototype the way the [SKILL](SKILL.md) describes. Fold the winner into the real code and move the rest onto the throwaway branch, not into main:
|
|
||||||
|
|
||||||
- **Sub-shape A**: fold the winner into the existing page; drop the losing variants and the switcher from main.
|
|
||||||
- **Sub-shape B**: promote the winning variant to a real route; drop the throwaway route and the switcher from main.
|
|
||||||
|
|
||||||
The full set of variants is the primary source, so it lands on the throwaway branch, not the bin, since variant components and the switcher left in the main branch rot fast and confuse the next reader.
|
|
||||||
|
|
||||||
## Anti-patterns
|
|
||||||
|
|
||||||
- **Variants that differ only in colour or copy.** That's a tweak, not a prototype. Real variants disagree about structure.
|
|
||||||
- **Sharing too much code between variants.** A shared `<Header>` is fine; a shared `<Layout>` defeats the point. Each variant should be free to throw out the layout.
|
|
||||||
- **Wiring variants to real mutations.** Read-only prototypes are fine. If a variant needs to mutate, point it at a stub: the question is "what should this look like", not "does the backend work".
|
|
||||||
- **Promoting the prototype directly to production.** The variant code was written under prototype constraints (no tests, minimal error handling). Rewrite it properly when you fold it in.
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
interface:
|
|
||||||
display_name: "Prototype"
|
|
||||||
short_description: "Prototype to answer a design question"
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
---
|
|
||||||
name: tdd
|
|
||||||
description: Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Test-Driven Development
|
|
||||||
|
|
||||||
TDD is the red → green loop. This skill is the reference that makes that loop produce tests worth keeping: what a good test is, where tests go, the anti-patterns, and the rules of the loop. Every section applies on every cycle: consult them before and during the loop, not after.
|
|
||||||
|
|
||||||
When exploring the codebase, read `GLOSSARY.md` (if it exists) so test names and interface vocabulary match the project's domain language, and respect ADRs in the area you're touching.
|
|
||||||
|
|
||||||
## What a good test is
|
|
||||||
|
|
||||||
Tests verify behavior through public interfaces, not implementation details. Code can change entirely; tests shouldn't. A good test reads like a specification: "user can checkout with valid cart" tells you exactly what capability exists, and it survives refactors because it doesn't care about internal structure.
|
|
||||||
|
|
||||||
See [tests.md](tests.md) for examples and [mocking.md](mocking.md) for mocking guidelines.
|
|
||||||
|
|
||||||
## Seams: where tests go
|
|
||||||
|
|
||||||
A **seam** is the public boundary you test at: the interface where you observe behavior without reaching inside. Tests live at seams, never against internals.
|
|
||||||
|
|
||||||
**Test only at pre-agreed seams.** Before writing any test, write down the seams under test and confirm them with the user. No test is written at an unconfirmed seam. You can't test everything, so agreeing the seams up front is how testing effort lands on the critical paths and complex logic instead of every edge case.
|
|
||||||
|
|
||||||
Ask: "What's the public interface, and which seams should we test?" Give each proposed seam a one-line note on what it catches and what it misses.
|
|
||||||
|
|
||||||
When the shape of that interface is itself in question (how deep the module is, where the seam belongs, what the interface should expose), call the Skill tool with "codebase-design" for the vocabulary. It is the shared source of the module, interface, depth, seam, adapter, leverage and locality terms, and it is a reference to consult, not a session to run.
|
|
||||||
|
|
||||||
## Anti-patterns
|
|
||||||
|
|
||||||
- **Implementation-coupled**: mocks internal collaborators, tests private methods, or verifies through a side channel (querying the database instead of using the interface). The tell: the test breaks when you refactor but behavior hasn't changed.
|
|
||||||
- **Tautological**: the assertion recomputes the expected value the way the code does (`expect(add(a, b)).toBe(a + b)`, a snapshot derived by hand the same way, a constant asserted equal to itself), so it passes by construction and can never disagree with the code. Expected values must come from an independent source of truth: a known-good literal, a worked example, the spec.
|
|
||||||
- **Horizontal slicing**: writing all tests first, then all implementation. Bulk tests verify _imagined_ behavior: you test the _shape_ of things rather than user-facing behavior, the tests go insensitive to real changes, and you commit to test structure before understanding the implementation. Work in **vertical slices** instead: one test → one implementation → repeat, each test a **tracer bullet** that responds to what the last cycle taught you.
|
|
||||||
|
|
||||||
## Rules of the loop
|
|
||||||
|
|
||||||
- **Red before green.** Write the failing test first, then only enough code to pass it. Don't anticipate future tests or add speculative features.
|
|
||||||
- **One slice at a time.** One seam, one test, one minimal implementation per cycle.
|
|
||||||
- **Refactoring is not part of the loop.** It belongs to the review stage (see the `code-review` skill), not the red → green implementation cycle.
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
interface:
|
|
||||||
display_name: "TDD"
|
|
||||||
short_description: "Test-driven red-green-refactor"
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
# When to Mock
|
|
||||||
|
|
||||||
Mock at **system boundaries** only:
|
|
||||||
|
|
||||||
- External APIs (payment, email, etc.)
|
|
||||||
- Databases (sometimes - prefer test DB)
|
|
||||||
- Time/randomness
|
|
||||||
- File system (sometimes)
|
|
||||||
|
|
||||||
Don't mock:
|
|
||||||
|
|
||||||
- Your own classes/modules
|
|
||||||
- Internal collaborators
|
|
||||||
- Anything you control
|
|
||||||
|
|
||||||
## Designing for Mockability
|
|
||||||
|
|
||||||
At system boundaries, design interfaces that are easy to mock:
|
|
||||||
|
|
||||||
**1. Use dependency injection**
|
|
||||||
|
|
||||||
Pass external dependencies in rather than creating them internally:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Easy to mock
|
|
||||||
function processPayment(order, paymentClient) {
|
|
||||||
return paymentClient.charge(order.total);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hard to mock
|
|
||||||
function processPayment(order) {
|
|
||||||
const client = new StripeClient(process.env.STRIPE_KEY);
|
|
||||||
return client.charge(order.total);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**2. Prefer SDK-style interfaces over generic fetchers**
|
|
||||||
|
|
||||||
Create specific functions for each external operation instead of one generic function with conditional logic:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// GOOD: Each function is independently mockable
|
|
||||||
const api = {
|
|
||||||
getUser: (id) => fetch(`/users/${id}`),
|
|
||||||
getOrders: (userId) => fetch(`/users/${userId}/orders`),
|
|
||||||
createOrder: (data) => fetch('/orders', { method: 'POST', body: data }),
|
|
||||||
};
|
|
||||||
|
|
||||||
// BAD: Mocking requires conditional logic inside the mock
|
|
||||||
const api = {
|
|
||||||
fetch: (endpoint, options) => fetch(endpoint, options),
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
The SDK approach means:
|
|
||||||
- Each mock returns one specific shape
|
|
||||||
- No conditional logic in test setup
|
|
||||||
- Easier to see which endpoints a test exercises
|
|
||||||
- Type safety per endpoint
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
# Good and Bad Tests
|
|
||||||
|
|
||||||
## Good Tests
|
|
||||||
|
|
||||||
**Integration-style**: Test through real interfaces, not mocks of internal parts.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// GOOD: Tests observable behavior
|
|
||||||
test("user can checkout with valid cart", async () => {
|
|
||||||
const cart = createCart();
|
|
||||||
cart.add(product);
|
|
||||||
const result = await checkout(cart, paymentMethod);
|
|
||||||
expect(result.status).toBe("confirmed");
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Characteristics:
|
|
||||||
|
|
||||||
- Tests behavior users/callers care about
|
|
||||||
- Uses public API only
|
|
||||||
- Survives internal refactors
|
|
||||||
- Describes WHAT, not HOW
|
|
||||||
- One logical assertion per test
|
|
||||||
|
|
||||||
## Bad Tests
|
|
||||||
|
|
||||||
**Implementation-detail tests**: Coupled to internal structure.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// BAD: Tests implementation details
|
|
||||||
test("checkout calls paymentService.process", async () => {
|
|
||||||
const mockPayment = jest.mock(paymentService);
|
|
||||||
await checkout(cart, payment);
|
|
||||||
expect(mockPayment.process).toHaveBeenCalledWith(cart.total);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Red flags:
|
|
||||||
|
|
||||||
- Mocking internal collaborators
|
|
||||||
- Testing private methods
|
|
||||||
- Asserting on call counts/order
|
|
||||||
- Test breaks when refactoring without behavior change
|
|
||||||
- Test name describes HOW not WHAT
|
|
||||||
- Verifying through external means instead of interface
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// BAD: Bypasses interface to verify
|
|
||||||
test("createUser saves to database", async () => {
|
|
||||||
await createUser({ name: "Alice" });
|
|
||||||
const row = await db.query("SELECT * FROM users WHERE name = ?", ["Alice"]);
|
|
||||||
expect(row).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
// GOOD: Verifies through interface
|
|
||||||
test("createUser makes user retrievable", async () => {
|
|
||||||
const user = await createUser({ name: "Alice" });
|
|
||||||
const retrieved = await getUser(user.id);
|
|
||||||
expect(retrieved.name).toBe("Alice");
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**Tautological tests**: Expected value restates the implementation, so the test passes by construction.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// BAD: Expected value is recomputed the way the code computes it
|
|
||||||
test("calculateTotal sums line items", () => {
|
|
||||||
const items = [{ price: 10 }, { price: 5 }];
|
|
||||||
const expected = items.reduce((sum, i) => sum + i.price, 0);
|
|
||||||
expect(calculateTotal(items)).toBe(expected);
|
|
||||||
});
|
|
||||||
|
|
||||||
// GOOD: Expected value is an independent, known literal
|
|
||||||
test("calculateTotal sums line items", () => {
|
|
||||||
expect(calculateTotal([{ price: 10 }, { price: 5 }])).toBe(15);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
# GLOSSARY.md Format
|
|
||||||
|
|
||||||
`GLOSSARY.md` is the canonical language for this teaching workspace. All explainers, exercises, and learning records should adhere to its terminology. Building it is itself part of learning: compressing a concept into a tight definition is evidence the user understands it.
|
|
||||||
|
|
||||||
## Structure
|
|
||||||
|
|
||||||
```md
|
|
||||||
# {Topic} Glossary
|
|
||||||
|
|
||||||
{One or two sentence description of the topic this glossary covers.}
|
|
||||||
|
|
||||||
## Terms
|
|
||||||
|
|
||||||
**Hypertrophy**:
|
|
||||||
Muscle growth driven by mechanical tension and metabolic stress over repeated training sessions.
|
|
||||||
_Avoid_: Bulking, getting big
|
|
||||||
|
|
||||||
**Progressive overload**:
|
|
||||||
Systematically increasing the demand on a muscle over time, via load, volume, or intensity.
|
|
||||||
_Avoid_: Pushing harder, levelling up
|
|
||||||
|
|
||||||
**RPE (Rate of Perceived Exertion)**:
|
|
||||||
A 1–10 self-rating of how hard a set felt, where 10 is failure and 8 means two reps left in the tank.
|
|
||||||
_Avoid_: Effort score, intensity rating
|
|
||||||
```
|
|
||||||
|
|
||||||
## Rules
|
|
||||||
|
|
||||||
- **Add a term only when the user understands it.** The glossary is a record of compressed knowledge, not a dictionary the user reads to learn. If the user has just been introduced to a concept, wait until they can use it correctly before promoting it here.
|
|
||||||
- **Be opinionated.** When several words exist for the same concept, pick the best one and list the rest as aliases to avoid. This is how language compresses.
|
|
||||||
- **Keep definitions tight.** One or two sentences. Define what the term IS, not what it does or how to do it.
|
|
||||||
- **Use the glossary's own terms inside definitions.** Once a term is in the glossary, prefer it everywhere, including inside other definitions. This is what makes complex terms easier to grasp later.
|
|
||||||
- **Group under subheadings** when natural clusters emerge (e.g. `## Anatomy`, `## Programming`). A flat list is fine when terms cohere.
|
|
||||||
- **Flag ambiguities explicitly.** If a term is used loosely in the wider field, note the resolution: "In this workspace, 'set' always means a working set; warm-ups are tracked separately."
|
|
||||||
- **Revise as understanding deepens.** A definition the user wrote in week one may be wrong by week six. Update in place; do not leave stale entries.
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
# Learning Record Format
|
|
||||||
|
|
||||||
Learning records live in `./learning-records/` and use sequential numbering: `0001-slug.md`, `0002-slug.md`, etc. Create the directory lazily: only when the first record is written.
|
|
||||||
|
|
||||||
They are the teaching equivalent of ADRs: they capture non-obvious lessons, key insights, and stated prior knowledge that will steer future sessions. They are used to calculate the zone of proximal development.
|
|
||||||
|
|
||||||
## Template
|
|
||||||
|
|
||||||
```md
|
|
||||||
# {Short title of what was learned or established}
|
|
||||||
|
|
||||||
{1-3 sentences: what was learned (or what prior knowledge was established), and why it matters for future sessions.}
|
|
||||||
```
|
|
||||||
|
|
||||||
That is the whole format. A learning record can be a single paragraph. The value is recording _that_ this is now known and _why_ it changes what to teach next, not in filling out sections.
|
|
||||||
|
|
||||||
## Optional sections
|
|
||||||
|
|
||||||
Only include these when they add genuine value. Most records won't need them.
|
|
||||||
|
|
||||||
- **Status** frontmatter (`active | superseded by LR-NNNN`): useful when an earlier understanding turns out to be wrong and is replaced.
|
|
||||||
- **Evidence**: how the user demonstrated the understanding (a question answered, an exercise completed, prior experience cited). Useful when the claim might be revisited.
|
|
||||||
- **Implications**: what this unlocks or rules out for future sessions. Worth recording when non-obvious.
|
|
||||||
|
|
||||||
## Numbering
|
|
||||||
|
|
||||||
Scan `./learning-records/` for the highest existing number and increment by one.
|
|
||||||
|
|
||||||
## When to write a learning record
|
|
||||||
|
|
||||||
Write one when any of these is true:
|
|
||||||
|
|
||||||
1. **The user demonstrated genuine understanding of something non-trivial**: not just exposure, but evidence they can use the concept correctly. This sets a new floor for what to teach next.
|
|
||||||
2. **The user disclosed prior knowledge**: "I already know X." Record it so future sessions don't re-teach it. Also record the _depth_ claimed.
|
|
||||||
3. **A misconception was corrected**: the user previously believed something wrong and now sees why. These are high-value: they predict future stumbling blocks for related topics.
|
|
||||||
4. **The mission shifted in response to learning**: the user discovered they cared about something different than they thought. Cross-link to [[MISSION.md]] and update it.
|
|
||||||
|
|
||||||
### What does _not_ qualify
|
|
||||||
|
|
||||||
- Material that was merely covered. Coverage is not learning. Wait for evidence.
|
|
||||||
- Anything already captured tersely in [[GLOSSARY.md]] as a term definition. Don't duplicate.
|
|
||||||
- Session-by-session activity logs. Learning records are not a journal: they are decision-grade insights.
|
|
||||||
|
|
||||||
## Supersession
|
|
||||||
|
|
||||||
When a later record contradicts an earlier one (the user's understanding deepened or corrected), mark the old record `Status: superseded by LR-NNNN` rather than deleting it. The history of how understanding evolved is itself useful signal.
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# MISSION.md Format
|
|
||||||
|
|
||||||
`MISSION.md` lives at the workspace root. It captures the _reason_ the user is learning this topic. Every teaching decision (what to teach next, which resources to surface, which exercises to design) should trace back to this document.
|
|
||||||
|
|
||||||
## Template
|
|
||||||
|
|
||||||
```md
|
|
||||||
# Mission: {Topic}
|
|
||||||
|
|
||||||
## Why
|
|
||||||
{1-3 sentences. The concrete real-world goal the user is chasing. What changes in their life or work when they have this skill? Avoid abstract framings like "to understand X"; push for the underlying outcome.}
|
|
||||||
|
|
||||||
## Success looks like
|
|
||||||
- {A specific, observable thing the user will be able to do}
|
|
||||||
- {Another specific thing}
|
|
||||||
- {…}
|
|
||||||
|
|
||||||
## Constraints
|
|
||||||
- {Time, budget, prior commitments, learning preferences, anything that bounds the approach}
|
|
||||||
|
|
||||||
## Out of scope
|
|
||||||
- {Adjacent topics the user explicitly does not want to chase right now, protecting the zone of proximal development}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Rules
|
|
||||||
|
|
||||||
- **One mission per workspace.** If the user wants to learn two unrelated things, that is two workspaces.
|
|
||||||
- **Concrete over abstract.** "Run a half marathon by October" beats "get fitter." "Ship a Rust CLI to my team" beats "learn Rust."
|
|
||||||
- **Push back on vagueness.** If the user cannot articulate why, interview them before writing anything. A bad mission is worse than no mission.
|
|
||||||
- **Revise when reality shifts.** Missions change. When the user's goal moves, update this file: don't leave a stale mission steering future sessions.
|
|
||||||
- **Keep it short.** If `MISSION.md` runs past a screen, it has stopped being a compass and started being a plan.
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
# RESOURCES.md Format
|
|
||||||
|
|
||||||
`RESOURCES.md` is the curated set of trusted sources for this topic. Knowledge for explainers should be drawn from here, not from parametric guesses. Wisdom comes from the communities listed here.
|
|
||||||
|
|
||||||
## Structure
|
|
||||||
|
|
||||||
```md
|
|
||||||
# {Topic} Resources
|
|
||||||
|
|
||||||
## Knowledge
|
|
||||||
|
|
||||||
- [Book: _The Science and Practice of Strength Training_ by Zatsiorsky & Kraemer](https://example.com)
|
|
||||||
Foundational text on programming and adaptation. Use for: anything to do with periodisation, recovery, intensity zones.
|
|
||||||
- [Article: "How Much Should I Train?" by Greg Nuckols (Stronger By Science)](https://example.com)
|
|
||||||
Evidence-based review of volume landmarks. Use for: weekly set targets per muscle group.
|
|
||||||
|
|
||||||
## Wisdom (Communities)
|
|
||||||
|
|
||||||
- [r/weightroom](https://reddit.com/r/weightroom)
|
|
||||||
High-signal subreddit, moderated against bro-science. Use for: programme critique, plateau troubleshooting.
|
|
||||||
- Local: Tuesday strength class at {gym name}
|
|
||||||
Use for: real-time coaching feedback on lifts.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Rules
|
|
||||||
|
|
||||||
- **High-trust only.** Prefer primary sources, recognised experts, peer-reviewed work, and communities with strong moderation. If a resource is marketing dressed as education, leave it out.
|
|
||||||
- **Annotate every entry.** A bare link is useless in three months. Add one line: what it covers and when to reach for it.
|
|
||||||
- **Group by Knowledge / Wisdom.** Mirrors the philosophy in [SKILL.md](./SKILL.md). It is fine for a resource to appear in only one group.
|
|
||||||
- **Surface gaps explicitly.** If no good resource exists for an area the mission needs, write a `## Gaps` section listing what is missing. This drives future search.
|
|
||||||
- **Prune ruthlessly.** A resource that turned out to be wrong, shallow, or off-mission should be removed, not buried. Better five sharp sources than thirty mediocre ones.
|
|
||||||
- **Record community preferences.** If the user has opted out of joining communities, note it here so future sessions don't keep proposing them.
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
---
|
|
||||||
name: teach
|
|
||||||
description: Teach the user a new skill or concept, within this workspace.
|
|
||||||
disable-model-invocation: true
|
|
||||||
argument-hint: "What would you like to learn about?"
|
|
||||||
---
|
|
||||||
|
|
||||||
The user has asked you to teach them something. This is a stateful request - they intend to learn the topic over multiple sessions.
|
|
||||||
|
|
||||||
## Teaching Workspace
|
|
||||||
|
|
||||||
Treat the current directory as a teaching workspace. Workspace paths (`./lessons/` and the rest) resolve from the directory `/teach` was run in; only the `*-FORMAT.md` links resolve from this skill's folder. The state of their learning is captured in this directory in several files:
|
|
||||||
|
|
||||||
- `MISSION.md`: A document capturing the _reason_ the user is interested in the topic. This should be used to ground all teaching. Use the format in [MISSION-FORMAT.md](./MISSION-FORMAT.md).
|
|
||||||
- `./reference/*.html`: A directory of reference materials. These are the compressed learnings from the lessons - cheat sheets, reference algorithms, syntax, yoga poses, glossaries. They are the raw units of learning. They should be beautiful documents which print out well, and are designed for quick reference.
|
|
||||||
- `RESOURCES.md`: A list of resources which can be explored to ground your teaching in contextual knowledge, or to acquire knowledge and wisdom. Use the format in [RESOURCES-FORMAT.md](./RESOURCES-FORMAT.md).
|
|
||||||
- `./learning-records/*.md`: A directory of learning records, which capture what the user has learned. These are loosely equivalent to architectural decision records in software development - they capture non-obvious lessons and key insights that may need to be revised later, or drive future sessions. These should be used to calculate the zone of proximal development. They are titled `0001-<dash-case-name>.md`, where the number increments each time. Use the format in [LEARNING-RECORD-FORMAT.md](./LEARNING-RECORD-FORMAT.md).
|
|
||||||
- `./lessons/*.html`: A directory of lessons. A **lesson** is a single, self-contained HTML output that teaches one tightly-scoped thing tied to the mission. This is the primary unit of teaching in this workspace.
|
|
||||||
- `./assets/*`: Reusable **components** shared across lessons. See [Assets](#assets).
|
|
||||||
- `NOTES.md`: A scratchpad for you to jot down user preferences, or working notes.
|
|
||||||
|
|
||||||
## Philosophy
|
|
||||||
|
|
||||||
To learn at a deep level, the user needs three things:
|
|
||||||
|
|
||||||
- **Knowledge**, captured from high-quality, high-trust resources
|
|
||||||
- **Skills**, acquired through highly-relevant interactive lessons devised by you, based on the knowledge
|
|
||||||
- **Wisdom**, which comes from interacting with other learners and practitioners
|
|
||||||
|
|
||||||
Before the `RESOURCES.md` is well-populated, your focus should be to find high-quality resources which will help the user acquire knowledge. Never trust your parametric knowledge.
|
|
||||||
|
|
||||||
Some topics may require more skills than knowledge. Learning more about theoretical physics might be more knowledge-based. For yoga, more skills-based.
|
|
||||||
|
|
||||||
### Fluency vs Storage Strength
|
|
||||||
|
|
||||||
You should be careful to split between two types of learning:
|
|
||||||
|
|
||||||
- **Fluency strength**: in-the-moment retrieval of knowledge
|
|
||||||
- **Storage strength**: long-term retention of knowledge
|
|
||||||
|
|
||||||
Fluency can give the user an illusory sense of mastery, but storage strength is the real goal. Try to design lessons which build long-term retention by desirable difficulty:
|
|
||||||
|
|
||||||
- Using retrieval practice (recall from memory)
|
|
||||||
- Spacing (distributing practice over time)
|
|
||||||
- Interleaving (mixing up different but related topics in practice - for skills practice only)
|
|
||||||
|
|
||||||
## Lessons
|
|
||||||
|
|
||||||
A lesson is the main thing you produce: the unit in which knowledge and skills reach the user. Each lesson is one self-contained HTML file, saved to `./lessons/` and titled `0001-<dash-case-name>.html` where the number increments each time.
|
|
||||||
|
|
||||||
A lesson should be **beautiful**, with clean, readable typography and layout, since the user will return to these later to review. Think Tufte.
|
|
||||||
|
|
||||||
The lesson should be short, and completable very quickly. Learners' working memory is very small, and we need to stay within it. But each lesson should give the user a single tangible win that they can build on. It should be directly tied to the mission, and should be in the user's zone of proximal development.
|
|
||||||
|
|
||||||
If possible, open the lesson file for the user by running a CLI command.
|
|
||||||
|
|
||||||
Each lesson should link via HTML anchors to other lessons and reference documents.
|
|
||||||
|
|
||||||
Each lesson should recommend a primary source for the user to read or watch. This should be the most high-quality, high-trust resource you found on the topic.
|
|
||||||
|
|
||||||
Each lesson should contain a reminder to ask followup questions to the agent. The agent is their teacher, and can assist with anything that's unclear.
|
|
||||||
|
|
||||||
## Assets
|
|
||||||
|
|
||||||
Lessons are built from reusable **components**, stored in `./assets/`: stylesheets, quiz widgets, simulators, diagram helpers, and anything else a second lesson could reuse.
|
|
||||||
|
|
||||||
Reuse is the default, not the exception. Before authoring a lesson, read `./assets/` and build from the components already there. When a lesson needs something new and reusable, write it as a component in `./assets/` and link to it; never inline code a future lesson would duplicate.
|
|
||||||
|
|
||||||
A shared stylesheet is the first component every workspace earns: every lesson links it, so the lessons look like one consistent course rather than a pile of one-offs. As the workspace grows, so should the component library.
|
|
||||||
|
|
||||||
## The Mission
|
|
||||||
|
|
||||||
Every lesson should be tied into the mission - the reason that the user is interested in learning about the topic.
|
|
||||||
|
|
||||||
If the user is unclear about the mission, or the `MISSION.md` is not populated, your first job should be to question the user on why they want to learn this.
|
|
||||||
|
|
||||||
Failing to understand the mission will mean knowledge acquisition is not grounded in real-world goals. Lessons will feel too abstract. You will have no way of judging what the user should do next.
|
|
||||||
|
|
||||||
Missions may change as the user develops more skills and knowledge. This is normal - make sure to update the `MISSION.md` and add a learning record to capture the change. Confirm with the user before changing the mission.
|
|
||||||
|
|
||||||
## Zone Of Proximal Development
|
|
||||||
|
|
||||||
Each lesson, the user should always feel as if they are being challenged 'just enough'.
|
|
||||||
|
|
||||||
The user may specify an exact thing they want to learn. If they don't, figure out their zone of proximal development by:
|
|
||||||
|
|
||||||
- Reading their `learning-records`
|
|
||||||
- Figuring out the right thing to teach them based on their mission
|
|
||||||
- Teach the most relevant thing that fits in their zone of proximal development
|
|
||||||
|
|
||||||
## Knowledge
|
|
||||||
|
|
||||||
Lessons should be designed around a skill the user is going to learn. The knowledge in the lesson should be only what's required to acquire that skill. You teach the knowledge first, then get the user to practice the skills via an interactive feedback loop.
|
|
||||||
|
|
||||||
Knowledge should first be gathered from trusted resources. Use `RESOURCES.md` to keep track of them. Lessons should be littered with citations - links to external resources to back up any claim made. This increases the trustworthiness of the lesson.
|
|
||||||
|
|
||||||
For acquiring knowledge, difficulty is the enemy. It eats working memory you need for understanding.
|
|
||||||
|
|
||||||
## Skills
|
|
||||||
|
|
||||||
If knowledge is all about acquisition, skills are about durability and flexibility. Make the knowledge stick.
|
|
||||||
|
|
||||||
For skill acquisition, difficulty is the tool. Effortful retrieval is what builds storage strength. Skills should be taught through interactive lessons. There are several tools at your disposal:
|
|
||||||
|
|
||||||
- Interactive lessons, using quizzes and light in-browser tasks
|
|
||||||
- Lessons which guide the user through a list of real-world steps to take (for instance, yoga poses)
|
|
||||||
|
|
||||||
Each of these should be based on a **feedback loop**, where the user receives feedback on their performance. This feedback loop should be as tight as possible, giving feedback immediately - and ideally automatically.
|
|
||||||
|
|
||||||
For quizzes, each answer should be exactly the same number of words (and characters, if possible). Vary which position holds the correct answer across questions. Don't give the user any clues about the answer through formatting or order.
|
|
||||||
|
|
||||||
## Acquiring Wisdom
|
|
||||||
|
|
||||||
Wisdom comes from true real-world interaction - testing your skills outside the learning environment.
|
|
||||||
|
|
||||||
When the user asks a question that appears to require wisdom, your default posture should be to attempt to answer - but to ultimately delegate to a **community**.
|
|
||||||
|
|
||||||
A community is a place (online or offline) where the user can test their skills in the real world. This might be a forum, a subreddit, a real-world class (budget permitting) or a local interest group.
|
|
||||||
|
|
||||||
You should attempt to find high-reputation communities the user can join. If the user expresses a preference that they don't want to join a community, respect it.
|
|
||||||
|
|
||||||
## Reference Documents
|
|
||||||
|
|
||||||
While creating lessons, you should also create reference documents. Lessons can reference these documents - they are useful for tracking raw units of knowledge useful across lessons.
|
|
||||||
|
|
||||||
Lessons will rarely be revisited later - reference documents will be. They should be the compressed essence of the lesson, in a format designed for quick reference.
|
|
||||||
|
|
||||||
Some learning topics lend themselves to reference:
|
|
||||||
|
|
||||||
- Syntax and code snippets for programming
|
|
||||||
- Algorithms and flowcharts for processes
|
|
||||||
- Yoga poses and sequences for yoga
|
|
||||||
- Exercises and routines for fitness
|
|
||||||
- Glossaries for any topic with its own nomenclature
|
|
||||||
|
|
||||||
Glossaries, in particular, are an essential reference. Once one is created, it should be adhered to in every lesson.
|
|
||||||
|
|
||||||
## `NOTES.md`
|
|
||||||
|
|
||||||
The user will sometimes express preferences of how they want to be taught, or things you should keep in mind. This is the place to record those preferences, so you can refer back to them when designing lessons or working with the user.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
interface:
|
|
||||||
display_name: "Teach"
|
|
||||||
short_description: "Learn a concept in a guided workspace"
|
|
||||||
policy:
|
|
||||||
allow_implicit_invocation: false
|
|
||||||
@@ -19,9 +19,9 @@
|
|||||||
}
|
}
|
||||||
shadow
|
shadow
|
||||||
focus-ring {
|
focus-ring {
|
||||||
width 2
|
width 4
|
||||||
|
|
||||||
active-color "#e01b24"
|
active-color "#ff8c00"
|
||||||
}
|
}
|
||||||
border {
|
border {
|
||||||
off
|
off
|
||||||
|
|||||||
@@ -275,13 +275,13 @@ transition = [ "fade" ]
|
|||||||
interval_seconds = 1200
|
interval_seconds = 1200
|
||||||
|
|
||||||
[wallpaper.default]
|
[wallpaper.default]
|
||||||
path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_oxex69.jpg"
|
path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_g7r3vd.jpg"
|
||||||
|
|
||||||
[wallpaper.last]
|
[wallpaper.last]
|
||||||
path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_oxex69.jpg"
|
path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_g7r3vd.jpg"
|
||||||
|
|
||||||
[wallpaper.monitors.DP-1]
|
[wallpaper.monitors.DP-1]
|
||||||
path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_oxex69.jpg"
|
path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_g7r3vd.jpg"
|
||||||
|
|
||||||
[wallpaper.monitors.DP-2]
|
[wallpaper.monitors.DP-2]
|
||||||
path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_oxex69.jpg"
|
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"
|
path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_v9e2w3.jpg"
|
||||||
|
|
||||||
[wallpaper.monitors.eDP-1]
|
[wallpaper.monitors.eDP-1]
|
||||||
path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_oxex69.jpg"
|
path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_g7r3vd.jpg"
|
||||||
|
|
||||||
[weather]
|
[weather]
|
||||||
unit = "imperial"
|
unit = "imperial"
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
layout {
|
||||||
|
|
||||||
|
focus-ring {
|
||||||
|
active-color "#ff8c00"
|
||||||
|
inactive-color "#181818"
|
||||||
|
urgent-color "#ff8c00"
|
||||||
|
}
|
||||||
|
|
||||||
|
border {
|
||||||
|
active-color "#f43841"
|
||||||
|
inactive-color "#181818"
|
||||||
|
urgent-color "#f43841"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
tab-indicator {
|
||||||
|
active-color "#f43841"
|
||||||
|
inactive-color "#74050b"
|
||||||
|
urgent-color "#f43841"
|
||||||
|
}
|
||||||
|
|
||||||
|
insert-hint {
|
||||||
|
color "#f4384180"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recent-windows {
|
||||||
|
highlight {
|
||||||
|
active-color "#f43841"
|
||||||
|
urgent-color "#f43841"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,12 @@
|
|||||||
|
|
||||||
Agentic web research means that an AI drives the search loop. The agent formulates queries, reads results, decides what to read next, and writes a synthesis. It works best when search, extraction, and reasoning each run in the right place.
|
Agentic web research means that an AI drives the search loop. The agent formulates queries, reads results, decides what to read next, and writes a synthesis. It works best when search, extraction, and reasoning each run in the right place.
|
||||||
|
|
||||||
> **Your setup:** Pi Web Access calls SearXNG at `192.168.0.143:8081` (dankclaw) and serves models through llama.cpp at `192.168.0.235:7770`, `192.168.0.143:7770`, and `localhost:8080`. The pages below assume that topology.
|
> **Fleet topology (2026-10):** SearXNG lives at `192.168.0.143:8081` (UI) /
|
||||||
|
> `:8082` (JSON) on dankclaw; model endpoints are `frank-archon`
|
||||||
|
> (dankserver :7770, radiance), `frank-dragoon` (dankclaw :7770,
|
||||||
|
> halogen-flash-server), `frank-zealot` (dankclaw :7771, llama.cpp PrismML).
|
||||||
|
> The pi-era `localhost:8080` llama.cpp endpoint is retired. See
|
||||||
|
> [System hosts](/system/index.md).
|
||||||
|
|
||||||
This section connects four decisions:
|
This section connects four decisions:
|
||||||
|
|
||||||
|
|||||||
@@ -32,9 +32,15 @@ Qwen3 is the current state of the art for open function calling and tool use. It
|
|||||||
|
|
||||||
The 8B and 14B models run on one consumer GPU. The 30B-A3B MoE runs on a single A100-class card or equivalent.
|
The 8B and 14B models run on one consumer GPU. The 30B-A3B MoE runs on a single A100-class card or equivalent.
|
||||||
|
|
||||||
## Best Vision Model: Qwen2.5-VL-7B
|
## Best Vision Model: Qwen3-VL (Apache 2.0)
|
||||||
|
|
||||||
Many web tasks need a model that can read a page image, not just text. Qwen2.5-VL-7B is the best open vision model for this. It reads text, charts, icons, and layout in screenshots. It supports computer-use and phone-use agents. Apache 2.0 license. The 7B model fits on a single GPU with 8 GB or more.
|
Many web tasks need a model that can read a page image, not just text.
|
||||||
|
**Qwen3-VL** supersedes Qwen2.5-VL as the strongest open-weights vision family
|
||||||
|
(4B/8B dense through 30B-A3B/235B-A22B MoE): text, chart, icon, and layout
|
||||||
|
reading in screenshots, plus computer-use agent support. Small dense sizes
|
||||||
|
ship Apache 2.0; a quantized 4B/8B runs on a single consumer GPU. On this
|
||||||
|
fleet, vision duty is carried by the mmproj vision towers on the endpoints
|
||||||
|
(dragoon, zealot) — see [System hosts](/system/index.md).
|
||||||
|
|
||||||
Llama 4 Scout is the larger multimodal option. Verify the claims before you trust them:
|
Llama 4 Scout is the larger multimodal option. Verify the claims before you trust them:
|
||||||
|
|
||||||
@@ -47,7 +53,7 @@ Llama 4 Scout is the larger multimodal option. Verify the claims before you trus
|
|||||||
```mermaid
|
```mermaid
|
||||||
graph TD
|
graph TD
|
||||||
A[Consumer GPU 16-24GB] --> B[Qwen3-8B / 14B]
|
A[Consumer GPU 16-24GB] --> B[Qwen3-8B / 14B]
|
||||||
A --> C[Qwen2.5-VL-7B]
|
A --> C[Qwen3-VL-4B / 8B]
|
||||||
D[Single server GPU 80GB] --> E[Qwen3-30B-A3B]
|
D[Single server GPU 80GB] --> E[Qwen3-30B-A3B]
|
||||||
D --> F[Llama 4 Scout INT4]
|
D --> F[Llama 4 Scout INT4]
|
||||||
G[Multi-GPU cluster] --> H[Qwen3-235B-A22B]
|
G[Multi-GPU cluster] --> H[Qwen3-235B-A22B]
|
||||||
@@ -71,7 +77,7 @@ llama-server -m qwen3-8b-q4_k_m.gguf -c 131072 --port 7770
|
|||||||
| Need | Pick |
|
| Need | Pick |
|
||||||
|------|------|
|
|------|------|
|
||||||
| Best tool calling, one consumer GPU | Qwen3-8B or 14B |
|
| Best tool calling, one consumer GPU | Qwen3-8B or 14B |
|
||||||
| Visually read a web page | Qwen2.5-VL-7B |
|
| Visually read a web page | Qwen3-VL-4B / 8B |
|
||||||
| Server-scale reliable agent | Qwen3-235B-A22B |
|
| Server-scale reliable agent | Qwen3-235B-A22B |
|
||||||
| Cheapest acceptable agent | Qwen3-8B on a mid GPU |
|
| Cheapest acceptable agent | Qwen3-8B on a mid GPU |
|
||||||
|
|
||||||
@@ -79,7 +85,8 @@ llama-server -m qwen3-8b-q4_k_m.gguf -c 131072 --port 7770
|
|||||||
|
|
||||||
- Qwen3 technical report: <https://qwenlm.github.io/blog/qwen3/>
|
- Qwen3 technical report: <https://qwenlm.github.io/blog/qwen3/>
|
||||||
- Qwen3 repository: <https://github.com/QwenLM/Qwen3>
|
- Qwen3 repository: <https://github.com/QwenLM/Qwen3>
|
||||||
- Qwen2.5-VL-7B model card: <https://huggingface.co/Qwen/Qwen2.5-VL-7B-Instruct>
|
- Qwen3-VL repository: <https://github.com/QwenLM/Qwen3-VL> (tech report: <https://arxiv.org/abs/2511.21631>)
|
||||||
|
- Qwen2.5-VL-7B model card (predecessor): <https://huggingface.co/Qwen/Qwen2.5-VL-7B-Instruct>
|
||||||
- Meta AI Llama 4 announcement: <https://ai.meta.com/blog/llama-4-multimodal-intelligence/>
|
- Meta AI Llama 4 announcement: <https://ai.meta.com/blog/llama-4-multimodal-intelligence/>
|
||||||
- Ollama library: <https://ollama.com/library>
|
- Ollama library: <https://ollama.com/library>
|
||||||
- Browser Use supported models: <https://docs.browser-use.com/open-source/supported-models>
|
- Browser Use supported models: <https://docs.browser-use.com/open-source/supported-models>
|
||||||
|
|||||||
@@ -8,18 +8,41 @@ tags:
|
|||||||
- privacy
|
- privacy
|
||||||
- configuration
|
- configuration
|
||||||
- unverified
|
- unverified
|
||||||
verified: 2026-08-28
|
verified: 2026-10-19
|
||||||
timestamp: 2026-08-11
|
timestamp: 2026-08-11
|
||||||
resource: https://docs.searxng.org
|
resource: https://docs.searxng.org
|
||||||
---
|
---
|
||||||
|
|
||||||
# SearXNG Tuning
|
# SearXNG Tuning
|
||||||
|
|
||||||
SearXNG is a metasearch engine. It aggregates 274 or more upstream search services into one result set. Configuration lives in `settings.yml`. The recommended deployment is Docker Compose with a Valkey container.
|
SearXNG is a metasearch engine. It aggregates hundreds of upstream search
|
||||||
|
services into one result set (upstream `settings.yml` defines 388 engine
|
||||||
|
entries at master, probed 2026-10-19; the live fleet instance loads 262).
|
||||||
|
Configuration lives in `settings.yml`. The recommended deployment is Docker
|
||||||
|
Compose (or Podman Quadlet) with a Valkey container.
|
||||||
|
|
||||||
This page lists the settings that matter for speed, privacy, and useful results.
|
This page lists the settings that matter for speed, privacy, and useful results.
|
||||||
|
|
||||||
> **Your setup:** Pi Web Access calls a remote SearXNG at [`192.168.0.143:8081`](../pi/packages/pi-web-access.md) (dankclaw). Apply these settings to that host, not to this machine.
|
> **Fleet instance:** the harness `web/searxng` provider calls the instance at
|
||||||
|
> `192.168.0.143:8081` (UI) / `:8082` (JSON API) on dankclaw. The pi-era
|
||||||
|
> [Pi Web Access](../pi/packages/pi-web-access.md) called the same host.
|
||||||
|
|
||||||
|
## Live instance (probed 2026-10-19)
|
||||||
|
|
||||||
|
User Quadlet stack on dankclaw: `searxng.container`
|
||||||
|
(`ghcr.io/searxng/searxng:latest`) + `searxng-valkey.container`
|
||||||
|
(`docker.io/valkey/valkey:9-alpine`) + `gluetun.container` (VPN egress; the
|
||||||
|
containers share its network namespace, which is why `podman ps` shows :8081/
|
||||||
|
:8082 on several names) + `marreta`/`marreta_browser`. `limiter.toml`: enabled,
|
||||||
|
`pass_searxng_org: true`.
|
||||||
|
|
||||||
|
**Policy drift flag:** this page's tuned engine policy (google and bing
|
||||||
|
disabled; DuckDuckGo/Wikipedia/Google News enabled — verified via `/config` on
|
||||||
|
2026-08-16) is NOT live: `/config` on 2026-10-19 reports all 262 engines
|
||||||
|
enabled, google and bing included. Either the settings override was lost in
|
||||||
|
the 2026-10 stack reinstatement or the policy was deliberately dropped.
|
||||||
|
Confirm with Frank before re-applying.
|
||||||
|
|
||||||
|
|
||||||
## Plan the Deployment
|
## Plan the Deployment
|
||||||
|
|
||||||
|
|||||||
@@ -29,10 +29,17 @@ Key ideas:
|
|||||||
- Ten watchdogs handle CAPTCHAs, crashes, popups, downloads, and security.
|
- Ten watchdogs handle CAPTCHAs, crashes, popups, downloads, and security.
|
||||||
- Real browser profiles carry saved logins.
|
- Real browser profiles carry saved logins.
|
||||||
|
|
||||||
**On the "87.4 percent" number:** that result refers to Browser Use's managed Cloud agent (the Hybrid type that blends structured browser tools with screenshots). Peer-reviewed results for vision-only (CUA) models are much lower, about 44.5 percent on the same task type. Treat any single accuracy number as one benchmark, not a universal score.
|
**On the "87.4 percent" number:** that result refers to Browser Use's managed
|
||||||
|
Cloud agent (the Hybrid type that blends structured browser tools with
|
||||||
|
screenshots), not the open-source library you self-host. Academic
|
||||||
|
computer-use results on comparable web tasks run far lower, and an earlier
|
||||||
|
"44.5 percent peer-reviewed CUA" figure carried in this page could not be
|
||||||
|
re-attributed to a specific published benchmark (checked 2026-10-19) and was
|
||||||
|
dropped. Treat any single accuracy number as one benchmark, not a universal
|
||||||
|
score.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# Local model via llama.cpp (OpenAI-compatible endpoint)
|
# Local model via any OpenAI-compatible endpoint
|
||||||
from browser_use import Agent
|
from browser_use import Agent
|
||||||
from browser_use.llm.openai import ChatOpenAI
|
from browser_use.llm.openai import ChatOpenAI
|
||||||
|
|
||||||
@@ -40,7 +47,7 @@ agent = Agent(
|
|||||||
task="Search for the latest AI news",
|
task="Search for the latest AI news",
|
||||||
llm=ChatOpenAI(
|
llm=ChatOpenAI(
|
||||||
base_url="http://192.168.0.235:7770/v1",
|
base_url="http://192.168.0.235:7770/v1",
|
||||||
model="frank-default-small",
|
model="frank-archon",
|
||||||
api_key="EMPTY",
|
api_key="EMPTY",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -171,8 +171,14 @@ This pattern places the whole SearXNG process inside the tunnel namespace. The h
|
|||||||
4. Confirm that unrelated host traffic keeps the host IP.
|
4. Confirm that unrelated host traffic keeps the host IP.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# From inside the tunnel with Gluetun
|
# Gluetun ships no curl; use its own endpoints or a throwaway container.
|
||||||
docker exec gluetun curl -s ifconfig.me
|
podman exec gluetun sh -c 'test -e /sys/class/net/tun0 && echo tunnel-up'
|
||||||
|
podman exec gluetun sh -c 'wget -qO- 127.0.0.1:9999/health' # "OK" when up
|
||||||
|
# NOTE: :9999 is Gluetun's HTTP health server (200 "OK" / 500); :8000 is the
|
||||||
|
# CONTROL server (/v1/* API), which 404s on everything else. Do not health-
|
||||||
|
# probe :8000. Egress-IP check needs a real binary — run a companion
|
||||||
|
# container on gluetun's network: docker run --rm --net container:gluetun
|
||||||
|
# curlimages/curl -s ifconfig.me
|
||||||
```
|
```
|
||||||
|
|
||||||
## Citations
|
## Citations
|
||||||
|
|||||||
@@ -2,14 +2,13 @@
|
|||||||
|
|
||||||
Caddy is the reverse proxy and TLS terminator for the homelab. A single caddy
|
Caddy is the reverse proxy and TLS terminator for the homelab. A single caddy
|
||||||
container on dankclaw (192.168.0.143) fronts every service on both hosts. It
|
container on dankclaw (192.168.0.143) fronts every service on both hosts. It
|
||||||
runs Caddy v2.11.4 from the custom `franksdank/caddy-porkbun` image
|
runs Caddy v2.11.7 (verified in-container 2026-10-19) from the custom
|
||||||
(xcaddy-built with the Porkbun DNS-01 module, built locally on dankclaw).
|
`localhost/franksdank/caddy-porkbun:latest` image (xcaddy-built with the
|
||||||
|
Porkbun DNS-01 module, built locally on dankclaw).
|
||||||
Since 2026-08-26 it is managed as a Podman Quadlet unit (`caddy.container`)
|
Since 2026-08-26 it is managed as a Podman Quadlet unit (`caddy.container`)
|
||||||
installed to `~/.config/containers/systemd/`, providing boot-persistent,
|
installed to `~/.config/containers/systemd/`, providing boot-persistent,
|
||||||
auto-restart service management.
|
auto-restart service management.
|
||||||
|
|
||||||
* [[caddyfile|caddyfile]] — directive and matcher syntax, named matchers,
|
|
||||||
directive sorting, and the `handle`/`route` order rule.
|
|
||||||
|
|
||||||
The Caddyfile lives in the serverAppConfigs repo at
|
The Caddyfile lives in the serverAppConfigs repo at
|
||||||
`dankclaw/Caddyfile`. `deploy.sh` renders basicauth hashes from the host `.env`
|
`dankclaw/Caddyfile`. `deploy.sh` renders basicauth hashes from the host `.env`
|
||||||
@@ -27,4 +26,4 @@ resolves `*.franksdank.cloud` names to dankclaw.
|
|||||||
|
|
||||||
The porkbun DNS module source: [caddy-dns/porkbun](https://github.com/caddy-dns/porkbun).
|
The porkbun DNS module source: [caddy-dns/porkbun](https://github.com/caddy-dns/porkbun).
|
||||||
|
|
||||||
* [Caddyfile Syntax](caddyfile.md) — Caddy Caddyfile reference — directive and matcher syntax, named matchers, directive sorting, and the handle/route order rule. Grounded in caddyserver.com (fetched 2026-08-28).
|
* [Caddyfile Syntax](caddyfile.md) — Caddy Caddyfile reference — directive and matcher syntax, named matchers, directive sorting, and the `handle`/`route` order rule. Grounded in caddyserver.com (fetched 2026-08-28).
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
---
|
||||||
|
type: reference
|
||||||
|
title: dankvoice
|
||||||
|
description: omp extension that speaks assistant output with the fleet's cloned voices through frank-tts.
|
||||||
|
tags:
|
||||||
|
- omp
|
||||||
|
- extension
|
||||||
|
- tts
|
||||||
|
- voice
|
||||||
|
- frank-tts
|
||||||
|
- dankclaw
|
||||||
|
verified: 2026-10-19
|
||||||
|
timestamp: 2026-10-19
|
||||||
|
---
|
||||||
|
|
||||||
|
# dankvoice
|
||||||
|
|
||||||
|
`agent/extensions/dankvoice.ts` adds cloned-voice speech to the interactive
|
||||||
|
harness: `/say` opens the options menu, `/say <text>` speaks through the
|
||||||
|
speakers and `/say on` narrates the assistant's thinking and final answer
|
||||||
|
after every turn. The words are first rewritten into natural spoken prose by
|
||||||
|
the **@tiny** role model (exactly what the built-in vocalizer's
|
||||||
|
`speech.enhanced` does), and everything is served by **frank-tts**
|
||||||
|
(Dankclaw :7772, the `speech` model role in [agent/models.yml](/core/index.md)).
|
||||||
|
|
||||||
|
## Why it exists
|
||||||
|
|
||||||
|
Nothing built into the harness can pronounce Frank's cloned voices.
|
||||||
|
|
||||||
|
| Surface | Engine | Verdict |
|
||||||
|
|---|---|---|
|
||||||
|
| TUI speak-aloud (`speech.enabled`, `speech.voice`) | bundled Kokoro ONNX worker, `synthesizeStream(voice, speed, signal)` | Kokoro voice enum only; never consults `models.yml` or the speech role |
|
||||||
|
| `omp say [TEXT]` | same local backend (`--model` = *local* model key) | `omp say --voice narrator1 "test"` → `error: Expected --voice to be one of: af_heart, af_bella, af_nicole, af_kore, af_sarah, am_michael, am_fenrir, am_puck, bf_emma, bm_george, bm_fable` (verified 2026-10-18) |
|
||||||
|
| `tts` tool / `xd://tts` device (`modelRoles.speech`) | frank-tts over OpenAI `/v1/audio/speech` | cloned voices work, but only as a **file** — nothing plays them |
|
||||||
|
|
||||||
|
`dankvoice` is not a patch to that vocalizer (harness code, not ours); it is a
|
||||||
|
second transport that happens to speak, so Kokoro stays available for quick
|
||||||
|
local speech and the cloned voices carry the real narration.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
| Command | Effect |
|
||||||
|
|---|---|
|
||||||
|
| `/say` \| `/say menu` | options menu (`ctx.ui.select`): auto-speak toggle, voice, output device, player, spoken cap, thinking-narration toggle, humanize toggle, test phrase |
|
||||||
|
| `/say <text>` | synthesize once and play; interrupts whatever is playing |
|
||||||
|
| `/say on` \| `/say off` | arm/disarm auto-speak of the thinking blocks and final text at `agent_end`; the footer shows `🔊 <voice>` while armed |
|
||||||
|
| `/say voice <name>` | pick a voice; an empty name defers to the server's own default |
|
||||||
|
| `/say voices` | list what frank-tts reports |
|
||||||
|
| `/say stop` | cut the current utterance |
|
||||||
|
| `/say status` \| `/say help` | endpoint, voice, player, device, cap, thinking, humanize |
|
||||||
|
|
||||||
|
Menu choices persist to `~/.omp/cache/dankvoice.json` (runtime state, never
|
||||||
|
tracked); the environment overrides the file, the file overrides defaults.
|
||||||
|
"Speak a test phrase" synthesizes a short line through the current voice,
|
||||||
|
player and device — the audible proof whenever a session "has say on" but
|
||||||
|
stays silent.
|
||||||
|
|
||||||
|
Auto-speak is skipped in `print`/`json` mode so benches and pipelines stay
|
||||||
|
silent. Each spoken unit — thinking first when narrated, then the answer —
|
||||||
|
is first rewritten into spoken prose by the @tiny model; when the enhancer
|
||||||
|
(below) declines, the text goes to segmentation as-is, where fenced code and
|
||||||
|
tables vanish, links collapse to their label and bullets and table pipes
|
||||||
|
disappear. A long answer is never one monolithic synthesis request; see
|
||||||
|
[Segmentation](#segmentation) for how utterances are cut and played.
|
||||||
|
|
||||||
|
## Segmentation
|
||||||
|
|
||||||
|
Utterances are cut by a state machine ported from the harness's own
|
||||||
|
speak-aloud vocalizer (the minified `fHe` class in the agent bundle's
|
||||||
|
`dist/cli.js`), kept deliberately in lockstep with the stock path:
|
||||||
|
|
||||||
|
* A strong boundary is a run of `[.!?…]` followed by whitespace — so
|
||||||
|
`!!!!!` and `...` are one boundary, never a pile of cuts.
|
||||||
|
* Boundaries after `e.g.`, `etc.`, `Mr.` and friends, or inside inline
|
||||||
|
code, don't count; decimals (`3.5`, `1,5`) survive because a boundary
|
||||||
|
requires trailing whitespace.
|
||||||
|
* Line starts are classified: table rows and fenced code are swallowed
|
||||||
|
whole (their content is never read aloud), heading and bullet markers
|
||||||
|
vanish, ordered-list markers speak their number ("1, …"), quote markers
|
||||||
|
strip.
|
||||||
|
* Per segment, markdown is cleaned: links collapse to their label, URLs
|
||||||
|
to their host, deep paths to their last segment; emphasis markup dies.
|
||||||
|
* Segments aim for one sentence; run-ons break at commas or word wraps
|
||||||
|
before 280 characters.
|
||||||
|
|
||||||
|
Playback is strictly sequential: segment N+1 is synthesized only after
|
||||||
|
segment N's player process exited, so audio never stacks. The chain runs
|
||||||
|
detached from `agent_end` (fire-and-forget) and may legitimately outlive
|
||||||
|
the harness's 30 s handler timeout — the old awaiting design produced a
|
||||||
|
"handler timed out after 30000ms" toast on long answers. A new utterance,
|
||||||
|
`/say stop`, or disarming auto-speak retires the live chain through a run
|
||||||
|
token: the current player is killed and pending segments are skipped.
|
||||||
|
`DANKVOICE_MAX` is a per-answer character budget: the leading whole
|
||||||
|
segments that fit are spoken, the rest are dropped — the thinking unit
|
||||||
|
draws on it before the answer does.
|
||||||
|
|
||||||
|
## Endpoint resolution
|
||||||
|
|
||||||
|
The base URL is **read from `agent/models.yml`**, from the provider block whose
|
||||||
|
model row carries `api: openai-speech` — the same row the `speech` role
|
||||||
|
resolves, so no host address and no credential appears in this file. From
|
||||||
|
`baseUrl: http://192.168.0.143:7772/v1` it derives
|
||||||
|
`POST …/v1/audio/speech`, `GET …/v1/audio/voices` and `GET …/health`.
|
||||||
|
|
||||||
|
| Environment | Default | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| `DANKVOICE_URL` | models.yml | speech base URL (`…/v1`), bypasses the YAML read |
|
||||||
|
| `DANKVOICE_MODEL` | the id in that block (`frank-tts`) | model named in the request body |
|
||||||
|
| `DANKVOICE_CONFIG` | `~/.omp/cache/dankvoice.json` | persisted menu choices (voice, player, sink, cap, auto) |
|
||||||
|
| `DANKVOICE_VOICE` | empty (server default) | default voice name |
|
||||||
|
| `DANKVOICE_AUTO=1` | off | start with auto-speak armed |
|
||||||
|
| `DANKVOICE_MAX` | `700` | spoken-character budget per answer (whole leading segments, shared by the thinking and answer units) |
|
||||||
|
| `DANKVOICE_PLAYER` | detect | player binary, skipping detection |
|
||||||
|
| `DANKVOICE_MODELS` | `~/.omp/agent/models.yml` | YAML to read the endpoint from |
|
||||||
|
| `DANKVOICE_REWRITE_URL` | the @tiny role | chat base URL (`…/v1`) for the enhancer, bypassing role resolution |
|
||||||
|
| `DANKVOICE_REWRITE_MODEL` | the @tiny role's model | chat model named in the rewrite body |
|
||||||
|
| `DANKVOICE_REWRITE=0` | enhancer on when a route resolves | speak mechanical text |
|
||||||
|
| `DANKVOICE_THINKING` | settings file, default on | `0`/`1` force thinking narration off/on |
|
||||||
|
| `DANKVOICE_OMPCONFIG` | `~/.omp/agent/config.yml` | YAML to read the `modelRoles.tiny` role from |
|
||||||
|
|
||||||
|
Player detection order is `pw-play` → `paplay` → `ffplay` (PipeWire first:
|
||||||
|
the workstations run WirePlumber). A saved output device routes the player
|
||||||
|
explicitly — `pw-play --target <sink>` or `paplay --device=<sink>`; ffplay
|
||||||
|
has no device concept and ignores it — while an unset device leaves the
|
||||||
|
player's own default. Sinks are enumerated with `wpctl list audio sinks`;
|
||||||
|
if wireplumber is unreachable the menu offers only "system default". Each
|
||||||
|
utterance is written to `$TMPDIR/dankvoice-*/say-<ms>.wav` and the file plus
|
||||||
|
its directory are removed when the player process exits.
|
||||||
|
|
||||||
|
## Humanized narration (the @tiny role)
|
||||||
|
|
||||||
|
The built-in vocalizer never reads answers aloud verbatim when
|
||||||
|
`speech.enhanced` is on: a small model rewrites each utterance into words a
|
||||||
|
person would say. `dankvoice` ports that enhancer — same system prompt
|
||||||
|
(verbatim from the bundle), same budgets (1536 max tokens, 6 s timeout,
|
||||||
|
4000-char input window), same contract (a failed rewrite falls back to the
|
||||||
|
mechanically cleaned text; an empty rewrite skips the unit as "nothing to
|
||||||
|
say"). The target model is the harness's `@tiny` role, resolved the way the
|
||||||
|
harness resolves it: `modelRoles.tiny` in `agent/config.yml` reads
|
||||||
|
`provider (host)/model:effort`; the provider block in `agent/models.yml`
|
||||||
|
supplies the `baseUrl`; the request is `POST {base}/chat/completions`. On
|
||||||
|
this fleet that is **frank-zealot** (`llamacpp (dankclaw-3090)`,
|
||||||
|
`:7771`) — the 6 s abort keeps a slow tiny model off the critical path.
|
||||||
|
|
||||||
|
Thinking blocks ride the same path: at `agent_end` the last assistant
|
||||||
|
message's true `thinking` parts are narrated (rewritten first, as their own
|
||||||
|
unit, before the answer), while blocks flagged `narration: true` — which
|
||||||
|
providers use to address the user through the thinking channel — count as
|
||||||
|
answer text. The menu, `/say status`, and `DANKVOICE_THINKING` toggle it;
|
||||||
|
the choice persists in the settings file.
|
||||||
|
|
||||||
|
## Verified facts (2026-10-18, extended 2026-10-19)
|
||||||
|
|
||||||
|
* **Extension discovery** — any `.ts` file in `~/.omp/agent/extensions/` loads
|
||||||
|
at startup: a probe file there printed `loaded 2026-10-19T02:36:55Z` from a
|
||||||
|
fresh `omp -p` run. `omp --help` documents `--no-extensions` (discovery off,
|
||||||
|
explicit `-e/--extension` and `--hook` still load) and `plugin install|link`.
|
||||||
|
* **Endpoint** — resolving the live `agent/models.yml` yields
|
||||||
|
`speechUrl http://192.168.0.143:7772/v1/audio/speech`,
|
||||||
|
`voicesUrl …/v1/audio/voices`, `healthUrl …/health`, `model frank-tts`.
|
||||||
|
* **Roster** — `GET /v1/audio/voices` answers the bare JSON array
|
||||||
|
`["narrator1","narrator2","narrator3"]`; `/health` answers
|
||||||
|
`{"status":"ok","backend":"omni-http","voices":[…]}`. `GET /v1/voices` and
|
||||||
|
`GET /v1/models` are **404** on this shim, and an unknown voice answers
|
||||||
|
`400 unknown voice 'zzz'; available voices: …` with `available_voices` in the
|
||||||
|
body — which is the fallback the extension uses if the roster route ever
|
||||||
|
disappears.
|
||||||
|
* **Omitting `voice` is legal** — frank-tts fills its `DEFAULT_VOICE` since the
|
||||||
|
2026-10-18 voice-pack work, so an unset voice synthesizes instead of failing.
|
||||||
|
* **Speech** — `/say "dankvoice live smoke test"` produced a 99,884 B / 2.08 s
|
||||||
|
WAV and `/say voice narrator2` + `/say "cloned voice check"` a 69,164 B /
|
||||||
|
1.44 s WAV (durations from `ffprobe`), each handed to the player; temp
|
||||||
|
directories were gone afterwards.
|
||||||
|
* **Auto-speak** — verified in a real TUI session: `/say on` armed the footer
|
||||||
|
(`🔊 narrator1`), the next finished turn synthesized and played the answer
|
||||||
|
(1.20 s WAV), and an unknown voice name answers with the roster instead of
|
||||||
|
speaking. An `agent_end` carrying `willContinue` (the harness scheduled an
|
||||||
|
auto-retry) is never narrated — only the terminal settle speaks.
|
||||||
|
* **Menu** — verified via driver scenarios 2026-10-18 (Refs #23): the menu
|
||||||
|
lists auto-speak/voice/device/player/cap/test-phrase; a voice chosen in the
|
||||||
|
menu is used by later utterances and survives a fresh load via the settings
|
||||||
|
file; a device chosen in the menu appears in the player invocation as
|
||||||
|
`--target <sink>`.
|
||||||
|
* **Segmented playback** — driver scenarios 2026-10-19: "First stop here.
|
||||||
|
Second one goes!!! Really? Third ... done" produced exactly 3 sequential
|
||||||
|
requests and 3 plays; "Model 3.5 ships today. Version 1,5 too." produced
|
||||||
|
exactly 2 (decimals never split); the markdown fixture's fenced code and
|
||||||
|
URL never reached the fixture endpoint; with `DANKVOICE_MAX=60` only the
|
||||||
|
leading segment was spoken.
|
||||||
|
* **Humanize + thinking (2026-10-19, Refs #27)** — driver scenarios cover the
|
||||||
|
rewrite round-trip (fixture chat route), the 404 → mechanical fallback,
|
||||||
|
thinking narrated as its own unit before the answer, `DANKVOICE_THINKING=0`
|
||||||
|
silencing it, and `narration: true` spoken once as the answer; the endpoint
|
||||||
|
status line shows the resolved humanize model from the @tiny role. Live
|
||||||
|
smoke against the real services: a markdown `/say` became 3 sequential
|
||||||
|
prose segments (mechanical cleanup of that text yields at most 2), and a
|
||||||
|
thinking+answer `agent_end` played thinking first. 22 green in
|
||||||
|
`tests/dankvoice.bats`.
|
||||||
|
|
||||||
|
## Limits
|
||||||
|
|
||||||
|
* The TUI's own speak-aloud feature still narrates in Kokoro: its voice list is
|
||||||
|
a compile-time enum in the harness bundle, and no setting routes it to
|
||||||
|
frank-tts. Cloned voices live only on this file-producing/speaking path and
|
||||||
|
on the `tts` tool. See [Handy](/tools/handy.md) for the STT half.
|
||||||
|
* `agent_end` narrates thinking blocks and the final message only — mid-turn
|
||||||
|
tool chatter is never spoken. Thinking covers the last assistant message;
|
||||||
|
tool-loop gaps in earlier messages are skipped by design.
|
||||||
|
* Humanized narration is serial: audio starts only after the @tiny rewrite
|
||||||
|
returns (≤ 6 s per unit, then frank-tts synthesis). Say `/say off`-grade
|
||||||
|
latency is the price of prose; `DANKVOICE_REWRITE=0` trades it back for
|
||||||
|
instant mechanical speech.
|
||||||
|
* frank-tts is LAN-only: no Caddy vhost fronts :7772, so this extension is
|
||||||
|
usable on the home network.
|
||||||
|
* Deployment, packs and the voice registry are owned by the
|
||||||
|
`serverAppConfigs` repo (`serverAppConfigs-docs/Dankclaw/Frank TTS.md`), not
|
||||||
|
by this one.
|
||||||
|
|
||||||
|
## Citations
|
||||||
|
|
||||||
|
* Live probes on the workstation, 2026-10-18: `omp say --voice narrator1 …`,
|
||||||
|
`curl http://192.168.0.143:7772/health`, `/v1/audio/voices`,
|
||||||
|
`/v1/audio/…/speech` (invalid voice), `ffprobe` on the produced WAVs.
|
||||||
|
* `omp --help` (extension discovery flags, `say`, `plugin`) and
|
||||||
|
`@earendil-works/pi-coding-agent` `dist/core/extensions/types.d.ts`
|
||||||
|
(`ExtensionAPI.on/registerCommand`, `ExtensionMode = "tui" | "rpc" | "json" | "print"`).
|
||||||
|
* The stock speak-aloud implementation — vocalizer pipeline `I6o`, utterance
|
||||||
|
segmenter `fHe` and speech enhancer `Qyr` with its prompt `T6o` — in
|
||||||
|
`@oh-my-pi/pi-coding-agent`'s `dist/cli.js`, read off the minified bundle
|
||||||
|
2026-10-19: dankvoice ports the mechanical and the enhanced halves, so an
|
||||||
|
LLM rewrite does happen before frank-tts.
|
||||||
|
* Gitea issue #21 on `dank/command-center` (the work item this page ships with).
|
||||||
|
* Gitea issue #23 on `dank/command-center` (the options-menu work item).
|
||||||
|
* Gitea issue #27 on `dank/command-center` (the enhanced-narration work item).
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
* [Core Documentation](/core/index.md) — the live harness configuration.
|
||||||
|
* [Handy](/tools/handy.md) — offline speech-to-text, the input half of voice.
|
||||||
|
* `serverAppConfigs-docs/Dankclaw/Frank TTS.md` — the frank-tts service runbook.
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
---
|
|
||||||
type: reference
|
|
||||||
title: AGENTS.md Index
|
|
||||||
description: Overview of the cached tool documentation system — how it works, why the pre-flight check matters, and how to add new entries.
|
|
||||||
tags:
|
|
||||||
- documentation
|
|
||||||
- index
|
|
||||||
- pre-flight-check
|
|
||||||
- meta
|
|
||||||
timestamp: 2026-01-15
|
|
||||||
---
|
|
||||||
|
|
||||||
# pi Documentation Index
|
|
||||||
|
|
||||||
This directory (`/home/dank/.pi/agent/docs/`) contains cached documentation for tools used during system configuration. It is the agent's primary reference before touching any configuration file.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## How to Use
|
|
||||||
|
|
||||||
Before configuring any tool, check whether documentation already exists here. If it does not, a subagent **must** be spawned to fetch documentation before proceeding.
|
|
||||||
|
|
||||||
This is not a suggestion. It is the difference between "I remember how to configure this" and "I will need to re-read the docs for the third time this week."
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Available Tool Documentation
|
|
||||||
|
|
||||||
### Core
|
|
||||||
|
|
||||||
* [AGENTS.md Format](agents-md-format.md) — Structure specification for the system-level instruction file: layer conventions, naming, summary table format, and add/remove procedures.
|
|
||||||
|
|
||||||
### Tools
|
|
||||||
|
|
||||||
* [Ghostty Terminal](../tools/ghostty/index.md) — GPU-accelerated terminal emulator. Complete configuration reference with 100+ options organized by category, CLI actions, keybindings, shaders, and default bindings.
|
|
||||||
* [llama.cpp Server](../tools/llama-cpp/index.md) — LLM inference server. GGUF format, build guides (CPU/CUDA/Metal/Vulkan), server CLI options, API endpoints, Docker setup, and curl examples.
|
|
||||||
* [Linux Power Management](../tools/linux-power-management.md) — Systemd sleep configuration, kernel sleep states, NVIDIA GPU suspend/resume, diagnostic tools, kernel parameters, and automated testing procedures.
|
|
||||||
* [Niri](../tools/niri/index.md) — Scrollable-tiling Wayland compositor. Full keybind cheat sheet, configuration syntax, window management, workspace navigation, and action reference.
|
|
||||||
|
|
||||||
### Pi Packages
|
|
||||||
|
|
||||||
* [pi-browser-use](../packages/pi-browser-use.md) — Browser automation extension wrapping chrome-devtools-mcp with `browser_` prefix, full config reference, session modes, vision model setup, and tool augmentation docs.
|
|
||||||
* [pi-permission-system](../packages/pi-permission-system.md) — 10 docs covering permission enforcement, config reference, session approvals, cross-extension API, subagent integration, troubleshooting, and migration guides.
|
|
||||||
|
|
||||||
### Skills
|
|
||||||
|
|
||||||
* [Npx Skills](../skills/npx-skills.md) — Distribution and installation patterns for agent skills via npm, package structure, SKILL.md frontmatter, discovery rules, and the Agent Skills specification.
|
|
||||||
* [Vercel Labs `skills` CLI](../skills/vercel-skills.md) — CLI meta-installer for cross-agent skill distribution, supporting 68+ agents, with add/use/find/list/update/remove commands.
|
|
||||||
|
|
||||||
### References
|
|
||||||
|
|
||||||
* [OKF v0.1 Specification](../references/okf-spec.md) — Complete specification for OKF knowledge bundles: bundle structure, frontmatter fields, concept documents, index/log files, cross-linking, and conformance rules.
|
|
||||||
* [OKF Bundle Maintenance](../references/okf-maintenance.md) — Bundle maintenance: log.md format, frontmatter discipline, one-concept-per-file, staleness detection, and automation strategies.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Adding New Tool Documentation
|
|
||||||
|
|
||||||
When a subagent fetches documentation for a new tool:
|
|
||||||
|
|
||||||
1. Save it to `tools/<tool-name>/index.md` (multi-file) or `tools/<tool-name>.md` (small, single-concept).
|
|
||||||
2. Add an entry to this index table.
|
|
||||||
3. Include a brief description of what the document covers.
|
|
||||||
4. Add YAML frontmatter with `type` and `description`.
|
|
||||||
5. Update `log.md` with the change.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Index Format
|
|
||||||
|
|
||||||
Give each row this content:
|
|
||||||
|
|
||||||
- **Tool** — the tool or technology name
|
|
||||||
- **File / Path** — relative path within this directory
|
|
||||||
- **Status** — for example, "Complete", "Partial", "Needs Update"
|
|
||||||
@@ -11,8 +11,10 @@ These files define the structural conventions that govern the agent system itsel
|
|||||||
## Concept Files
|
## Concept Files
|
||||||
|
|
||||||
* [AGENTS.md Format Specification](agents-md-format.md) — Canonical structure and conventions for the system-level instruction file that governs agent behavior.
|
* [AGENTS.md Format Specification](agents-md-format.md) — Canonical structure and conventions for the system-level instruction file that governs agent behavior.
|
||||||
* [AGENTS.md Index](index-doc.md) — Overview of the cached tool documentation system and how to add new entries.
|
|
||||||
* [Pi Settings System](settings.md) — pi-era `settings.json` record (archive): providers, models, packages, subagent overrides.
|
* [Pi Settings System](settings.md) — pi-era `settings.json` record (archive): providers, models, packages, subagent overrides.
|
||||||
|
* [dankvoice](dankvoice.md) — Cloned-voice vocalization extension: `/say` and auto-speak over frank-tts, endpoint read from `agent/models.yml`.
|
||||||
|
* [OMP Status Line Customization](statusline.md) — How presets, composer shapes, and segment arrays actually interact; the fleet's tuned bar. (moved from /pi/, 2026-10-19)
|
||||||
|
* [OMP Subagent Limits & Fan-out Policy](subagent-limits.md) — Live caps: concurrency 2, no nested spawns (the `maxRecursionDepth: 0` trap), per-endpoint in-flight ceilings, isolation policy. (moved from /pi/, 2026-10-19)
|
||||||
|
|
||||||
## Change Tracking
|
## Change Tracking
|
||||||
|
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ Currently installed:
|
|||||||
| `@xynogen/pix-ls` | 0.2.1 | Directory listing |
|
| `@xynogen/pix-ls` | 0.2.1 | Directory listing |
|
||||||
| `@xynogen/pix-edit` | 0.2.1 | File editing utilities |
|
| `@xynogen/pix-edit` | 0.2.1 | File editing utilities |
|
||||||
|
|
||||||
See [packages](../packages/index.md) for full package documentation.
|
See [Pi Packages](/pi/packages/index.md) for full package documentation.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -236,5 +236,5 @@ When `lastChangelogVersion` is behind pi's internal version, pi prompts for migr
|
|||||||
|
|
||||||
- [pi](../pi/index.md) — Pi configuration overview
|
- [pi](../pi/index.md) — Pi configuration overview
|
||||||
- [[models|models]] — Provider and model definitions
|
- [[models|models]] — Provider and model definitions
|
||||||
- [packages](../packages/index.md) — Installed package documentation
|
- [Pi Packages](/pi/packages/index.md) — Installed package documentation
|
||||||
- [[skills|skills]] — Installed skill documentation
|
- [[skills|skills]] — Installed skill documentation
|
||||||
File renamed without changes.
@@ -0,0 +1,97 @@
|
|||||||
|
---
|
||||||
|
type: reference
|
||||||
|
title: OMP Subagent Limits & Fan-out Policy
|
||||||
|
description: The live caps on omp subagents — concurrency 2, no nested spawns, per-endpoint in-flight ceilings, 30-min runtime, soft request budget, and the isolated-workspace policy; includes the maxRecursionDepth=0 trap.
|
||||||
|
tags:
|
||||||
|
- omp
|
||||||
|
- subagents
|
||||||
|
- config
|
||||||
|
- fleet
|
||||||
|
timestamp: 2026-10-19
|
||||||
|
---
|
||||||
|
|
||||||
|
# OMP Subagent Limits & Fan-out Policy
|
||||||
|
|
||||||
|
Enforced caps on subagents spawned through the `task` tool / eval `agent()`+`workpool()`
|
||||||
|
from this workstation (config root `agent/config.yml`, machine-live tier — the live-sync
|
||||||
|
heartbeat commits it). Set 2026-10-19; values verified against the live config and the
|
||||||
|
enforcement code in the installed bundle (`@oh-my-pi/pi-coding-agent/dist/cli.js`).
|
||||||
|
|
||||||
|
## The caps in force
|
||||||
|
|
||||||
|
| Key | Value | Default | Effect |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `task.maxConcurrency` | 2 | 32 | Session-scoped semaphore; excess spawns queue, never drop. |
|
||||||
|
| `task.maxRecursionDepth` | 1 | 2 | Subagents cannot spawn their own subagents. See depth semantics. |
|
||||||
|
| `task.maxRuntimeMs` | 1800000 | 0 (off) | Hard 30-min wall clock per subagent; overrun = `aborted`. |
|
||||||
|
| `task.softRequestBudget` | 120 | 200 | Wrap-up notice at 120 assistant requests; force-stopped to yield at 180 (1.5×). |
|
||||||
|
| `task.agentIdleTtlMs` | 420000 | same | Finished subagents parked to disk after 7 min idle (revivable via `agent://`). |
|
||||||
|
| `task.enableEffort` | false | false | Per-spawn `effort` hint hidden from the schema. |
|
||||||
|
| `task.isolation.enabled` | true | false | `isolated: true` spawns run in a copy of the checkout; only successful patches land (merge=patch, apply). Opt-in per spawn. |
|
||||||
|
| `task.completionProbe` | false | true | No periodic completeness side-requests into running subagents. |
|
||||||
|
| `tools.maxTimeout` | 600 | 0 (off) | No agent may request a tool timeout above 10 min; kills `timeout: 0` runaways. |
|
||||||
|
| `providers.maxInFlightRequests` | see below | {} (unlimited) | Per-provider ceiling on concurrent LLM requests, shared across local omp processes. |
|
||||||
|
|
||||||
|
## Depth semantics — never set `maxRecursionDepth: 0`
|
||||||
|
|
||||||
|
Bundle predicate (`U_`): a session at `taskDepth` *s* keeps the `task` tool iff
|
||||||
|
`n < 0 || s < n` where `n = maxRecursionDepth`. The preflight repeats the check
|
||||||
|
(`Cannot spawn another agent at task depth s; maximum depth is n`).
|
||||||
|
|
||||||
|
- `n = 1` → main (depth 0) spawns; children (depth 1) have `task` stripped. **This is
|
||||||
|
"no nested subagents".**
|
||||||
|
- `n = 0` → `0 < 0` fails → **not even the main session can spawn anything.** Don't.
|
||||||
|
- `n < 0` → cap disabled.
|
||||||
|
|
||||||
|
## Per-endpoint in-flight ceilings
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
providers:
|
||||||
|
maxInFlightRequests:
|
||||||
|
"llamacpp (dankserver)": 3 # main session + up to 2 task/reviewer subagents
|
||||||
|
"llamacpp (dankclaw)": 2 # frank-dragoon: security-reviewer + fallbacks
|
||||||
|
"llamacpp (dankclaw-3090)": 3 # frank-zealot: scout/sonic/librarian/designer + smol/tiny/plan/commit/vision roles
|
||||||
|
```
|
||||||
|
|
||||||
|
- Keys are **provider ids** = the quoted block labels in `agent/models.yml`
|
||||||
|
(one provider per host:port). The consumer does a flat map lookup by provider id;
|
||||||
|
absent or ≤0 = unlimited, otherwise clamped to ≥1.
|
||||||
|
- Scope is **per machine, per config root**: it bounds what *this workstation's* omp
|
||||||
|
processes put on each endpoint. Other workstations, OpenWebUI, and any non-omp
|
||||||
|
client are not counted. Requests past the ceiling queue, never fail.
|
||||||
|
- `web/searxng` intentionally uncapped.
|
||||||
|
|
||||||
|
## Isolation note
|
||||||
|
|
||||||
|
`task.isolation.enabled: true` makes the per-spawn `isolated` flag available — it is
|
||||||
|
**opt-in per spawn**, not a blanket sandbox. Requires a git repo; unavailable in plan
|
||||||
|
mode; baseline snapshot capped at 1 GiB; backends resolve via `isolation.backend: auto`
|
||||||
|
(overlayfs/reflink/rcopy chain on this box). Given *the checkout is the deployment*,
|
||||||
|
prefer `isolated: true` for any subagent that edits tracked files.
|
||||||
|
|
||||||
|
## Deliberately not set
|
||||||
|
|
||||||
|
- `bash.gitGuard` — offered 2026-10-19, declined; destructive-git protection stays opt-in.
|
||||||
|
- `task.disabledAgents` — no bundled agent type warrants a ban.
|
||||||
|
- `task.agentAccountPools`, `tier.subagent` — no-op against keyless local endpoints.
|
||||||
|
- `PI_TASK_MAX_OUTPUT_BYTES` / `PI_TASK_MAX_OUTPUT_LINES` — 500 KB / 5000 line defaults are sane.
|
||||||
|
- `before_subagent_spawn` hook — programmable spawn policy; revisit if these caps prove insufficient.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
`cfg://task`, `cfg://providers/maxInFlightRequests` reflect all rows above; persisted
|
||||||
|
copies in `agent/config.yml`. Depth semantics confirmed against the shipped bundle, not
|
||||||
|
just the setting description.
|
||||||
|
|
||||||
|
# Citations
|
||||||
|
|
||||||
|
- `agent/config.yml` (live config, this repo)
|
||||||
|
- `agent/models.yml` — provider id block labels
|
||||||
|
- `@oh-my-pi/pi-coding-agent/dist/cli.js` — `U_` depth predicate, in-flight map lookup
|
||||||
|
- omp docs: `omp://tools/task.md` § Limits & Caps
|
||||||
|
|
||||||
|
# See Also
|
||||||
|
|
||||||
|
- [/core/statusline.md](/core/statusline.md) — the omp-era bundle-audit page pattern this page follows
|
||||||
|
- [/pi/packages/pi-subagents.md](/pi/packages/pi-subagents.md) — retired pi-era subagent system (historical)
|
||||||
|
- [/system/index.md](/system/index.md) — fleet endpoints these ceilings target
|
||||||
@@ -97,4 +97,4 @@ Distribute skills through npm packages with a `pi` manifest, or through the `ski
|
|||||||
## See Also
|
## See Also
|
||||||
|
|
||||||
* [SDLC and Tooling](sdlc.md)
|
* [SDLC and Tooling](sdlc.md)
|
||||||
* [Skills Ecosystem](skills-ecosystem.md)
|
* [Skills Ecosystem](/skills/ecosystem.md)
|
||||||
@@ -11,7 +11,7 @@ This area distills grounded research into an OKF knowledge bundle. Each page cov
|
|||||||
* [Language and Framework Stacks](languages/index.md) - Node/JavaScript, React, Python, Rust, Go.
|
* [Language and Framework Stacks](languages/index.md) - Node/JavaScript, React, Python, Rust, Go.
|
||||||
* [Infrastructure and Cloud](infra/index.md) - Terraform/OpenTofu, containers, Kubernetes, AWS, DigitalOcean, Hetzner.
|
* [Infrastructure and Cloud](infra/index.md) - Terraform/OpenTofu, containers, Kubernetes, AWS, DigitalOcean, Hetzner.
|
||||||
* [Pi Plugins](plugins.md) - The pi extension ecosystem and notable packages.
|
* [Pi Plugins](plugins.md) - The pi extension ecosystem and notable packages.
|
||||||
* [Skills Ecosystem](skills-ecosystem.md) - skills.sh registry and the top skills.
|
* [Skills Ecosystem](/skills/ecosystem.md) - skills.sh registry and the live install leaderboard.
|
||||||
* [Signal Remote Control](signal-remote.md) - Drive a pi instance from Signal messages.
|
* [Signal Remote Control](signal-remote.md) - Drive a pi instance from Signal messages.
|
||||||
|
|
||||||
## Conventions
|
## Conventions
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ Some package names that appear in older documentation, such as `pi-browser-use`
|
|||||||
|
|
||||||
## Related Ecosystem
|
## Related Ecosystem
|
||||||
|
|
||||||
Pi implements the open Agent Skills standard. This makes pi skills compatible with Claude Code, Codex CLI, Amp, Droid, Cursor, and many other agents. See the [Skills Ecosystem](skills-ecosystem.md) page.
|
Pi implements the open Agent Skills standard. This makes pi skills compatible with Claude Code, Codex CLI, Amp, Droid, Cursor, and many other agents. See the [Skills Ecosystem](/skills/ecosystem.md) page.
|
||||||
|
|
||||||
## Citations
|
## Citations
|
||||||
|
|
||||||
|
|||||||
@@ -1,144 +0,0 @@
|
|||||||
---
|
|
||||||
type: reference
|
|
||||||
title: Skills Ecosystem and Top Skills
|
|
||||||
description: The skills.sh registry, the Agent Skills ecosystem, and the verified top 50 skills by installs.
|
|
||||||
tags:
|
|
||||||
- skills
|
|
||||||
- skills-sh
|
|
||||||
- agent-skills
|
|
||||||
- ecosystem
|
|
||||||
- engineering
|
|
||||||
- unverified
|
|
||||||
---
|
|
||||||
verified: 2026-08-28
|
|
||||||
|
|
||||||
# Skills Ecosystem and Top Skills
|
|
||||||
|
|
||||||
This page covers the open agent skills ecosystem. It describes the registries, the install tooling, and the top skills by category.
|
|
||||||
|
|
||||||
## The Ecosystem
|
|
||||||
|
|
||||||
`skills.sh` is the primary registry and leaderboard for agent skills. It runs on the open-source `skills` CLI from Vercel Labs. Use these commands:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add <owner/repo> # Install skills
|
|
||||||
npx skills find [query] # Search skills
|
|
||||||
npx skills list # List installed skills
|
|
||||||
```
|
|
||||||
|
|
||||||
The underlying standard is the Agent Skills specification at [agentskills.io](https://agentskills.io). Over 20 coding agents support it. A single `SKILL.md` works across these agents.
|
|
||||||
|
|
||||||
Related registries:
|
|
||||||
|
|
||||||
| Registry | Focus |
|
|
||||||
|----------|-------|
|
|
||||||
| skills.sh | Primary registry and leaderboard. |
|
|
||||||
| agentskill.sh | Directory and marketplace with many indexed skills. |
|
|
||||||
| officialskills.sh | Verified skills from major organizations. |
|
|
||||||
| qaskills.sh | QA and testing skills. |
|
|
||||||
|
|
||||||
Use the local `npx skills` command to explore what is available. You can query the leaderboard data directly at `GET /api/skills/all-time/{page}` on skills.sh. The page index is zero-based and returns 200 skills per page.
|
|
||||||
|
|
||||||
## Top 50 Skills by All-Time Installs
|
|
||||||
|
|
||||||
The table lists the top 50 skills verified from the live skills.sh API on 2026-08-12. Install counts are exact at query time.
|
|
||||||
|
|
||||||
| # | Skill | Source | Installs |
|
|
||||||
|----|-------|--------|----------|
|
|
||||||
| 1 | `find-skills` | vercel-labs/skills | 2,918,155 |
|
|
||||||
| 2 | `grill-me` | mattpocock/skills | 832,797 |
|
|
||||||
| 3 | `frontend-design` | anthropics/skills | 767,871 |
|
|
||||||
| 4 | `grill-with-docs` | mattpocock/skills | 708,703 |
|
|
||||||
| 5 | `improve-codebase-architecture` | mattpocock/skills | 683,652 |
|
|
||||||
| 6 | `tdd` | mattpocock/skills | 660,660 |
|
|
||||||
| 7 | `agent-browser` | vercel-labs/agent-browser | 659,465 |
|
|
||||||
| 8 | `vercel-react-best-practices` | vercel-labs/agent-skills | 625,739 |
|
|
||||||
| 9 | `setup-matt-pocock-skills` | mattpocock/skills | 607,703 |
|
|
||||||
| 10 | `handoff` | mattpocock/skills | 570,592 |
|
|
||||||
| 11 | `triage` | mattpocock/skills | 568,784 |
|
|
||||||
| 12 | `lark-doc` | open.feishu.cn | 561,794 |
|
|
||||||
| 13 | `lark-approval` | open.feishu.cn | 561,758 |
|
|
||||||
| 14 | `lark-base` | open.feishu.cn | 561,634 |
|
|
||||||
| 15 | `lark-drive` | open.feishu.cn | 561,452 |
|
|
||||||
| 16 | `lark-shared` | open.feishu.cn | 561,337 |
|
|
||||||
| 17 | `lark-calendar` | open.feishu.cn | 561,273 |
|
|
||||||
| 18 | `lark-im` | open.feishu.cn | 561,248 |
|
|
||||||
| 19 | `lark-contact` | open.feishu.cn | 561,157 |
|
|
||||||
| 20 | `lark-sheets` | open.feishu.cn | 561,006 |
|
|
||||||
| 21 | `lark-wiki` | open.feishu.cn | 560,924 |
|
|
||||||
| 22 | `lark-attendance` | open.feishu.cn | 560,891 |
|
|
||||||
| 23 | `lark-event` | open.feishu.cn | 560,888 |
|
|
||||||
| 24 | `lark-minutes` | open.feishu.cn | 560,818 |
|
|
||||||
| 25 | `lark-openapi-explorer` | open.feishu.cn | 560,777 |
|
|
||||||
| 26 | `lark-whiteboard` | open.feishu.cn | 560,768 |
|
|
||||||
| 27 | `lark-mail` | open.feishu.cn | 560,720 |
|
|
||||||
| 28 | `lark-task` | open.feishu.cn | 560,692 |
|
|
||||||
| 29 | `lark-slides` | open.feishu.cn | 560,590 |
|
|
||||||
| 30 | `lark-skill-maker` | open.feishu.cn | 560,554 |
|
|
||||||
| 31 | `lark-vc` | open.feishu.cn | 560,466 |
|
|
||||||
| 32 | `lark-workflow-meeting-summary` | open.feishu.cn | 560,368 |
|
|
||||||
| 33 | `lark-workflow-standup-report` | open.feishu.cn | 560,358 |
|
|
||||||
| 34 | `prototype` | mattpocock/skills | 556,403 |
|
|
||||||
| 35 | `lark-okr` | open.feishu.cn | 541,430 |
|
|
||||||
| 36 | `web-design-guidelines` | vercel-labs/agent-skills | 535,426 |
|
|
||||||
| 37 | `lark-markdown` | open.feishu.cn | 526,555 |
|
|
||||||
| 38 | `microsoft-foundry` | microsoft/azure-skills | 523,287 |
|
|
||||||
| 39 | `azure-diagnostics` | microsoft/azure-skills | 518,052 |
|
|
||||||
| 40 | `azure-ai` | microsoft/azure-skills | 517,867 |
|
|
||||||
| 41 | `azure-deploy` | microsoft/azure-skills | 517,694 |
|
|
||||||
| 42 | `azure-prepare` | microsoft/azure-skills | 517,512 |
|
|
||||||
| 43 | `azure-validate` | microsoft/azure-skills | 517,228 |
|
|
||||||
| 44 | `azure-storage` | microsoft/azure-skills | 516,821 |
|
|
||||||
| 45 | `entra-app-registration` | microsoft/azure-skills | 516,567 |
|
|
||||||
| 46 | `appinsights-instrumentation` | microsoft/azure-skills | 516,335 |
|
|
||||||
| 47 | `azure-resource-lookup` | microsoft/azure-skills | 516,249 |
|
|
||||||
| 48 | `azure-compliance` | microsoft/azure-skills | 516,198 |
|
|
||||||
| 49 | `azure-aigateway` | microsoft/azure-skills | 516,058 |
|
|
||||||
| 50 | `azure-kusto` | microsoft/azure-skills | 515,980 |
|
|
||||||
|
|
||||||
## Notable Categories
|
|
||||||
|
|
||||||
The top 50 breaks down into these groups:
|
|
||||||
|
|
||||||
* Development workflow: find-skills, grill-me, tdd, handoff, triage, prototype.
|
|
||||||
* Design and frontend: frontend-design, vercel-react-best-practices, web-design-guidelines.
|
|
||||||
* Lark office suite: 22 skills from open.feishu.cn, roughly ranks 12-35.
|
|
||||||
* Azure infrastructure: 13 skills from microsoft/azure-skills, ranks 38-50.
|
|
||||||
* Agent orchestration: agent-browser, setup-matt-pocock-skills.
|
|
||||||
|
|
||||||
`find-skills` leads by a wide margin with 2.9 million installs. The Lark suite and Azure suite dominate the middle of the list.
|
|
||||||
|
|
||||||
## SKILL.md Format
|
|
||||||
|
|
||||||
A skill is a directory that contains a `SKILL.md` file:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
---
|
|
||||||
name: my-skill
|
|
||||||
description: What this skill does
|
|
||||||
---
|
|
||||||
```
|
|
||||||
|
|
||||||
The `name` field is required and lowercase. The `description` field is required. Skills load progressively: discovery loads only the metadata, activation loads the full file on match.
|
|
||||||
|
|
||||||
## 2025/2026 Trends
|
|
||||||
|
|
||||||
* The Agent Skills spec is the common standard across 20+ agents.
|
|
||||||
* Skills are write once, use everywhere.
|
|
||||||
* Registries add security audits for skill content.
|
|
||||||
* Specialized registries, such as QA, are emerging.
|
|
||||||
|
|
||||||
## Citations
|
|
||||||
|
|
||||||
* [skills.sh](https://skills.sh)
|
|
||||||
* [skills.sh API `/api/skills/all-time/0`](https://skills.sh/api/skills/all-time/0)
|
|
||||||
* [vercel-labs/skills](https://github.com/vercel-labs/skills)
|
|
||||||
* [vercel-labs/agent-skills](https://github.com/vercel-labs/agent-skills)
|
|
||||||
* [agentskills.io](https://agentskills.io)
|
|
||||||
* [openai/skills](https://github.com/openai/skills)
|
|
||||||
|
|
||||||
## See Also
|
|
||||||
|
|
||||||
* [Agent Instruction Files](agents-files.md)
|
|
||||||
* [Pi Plugins](plugins.md)
|
|
||||||
* [Skills Documentation](../skills/index.md)
|
|
||||||
+15
-3
@@ -23,7 +23,7 @@ Porkbun DNS-01, hot reload).
|
|||||||
|
|
||||||
## [Gitea and tooling](tools/index.md)
|
## [Gitea and tooling](tools/index.md)
|
||||||
|
|
||||||
Installed software: Gitea, llama.cpp/ornith, niri, ghostty, tmux, ComfyUI,
|
Installed software: Gitea, llama.cpp, niri, ghostty, tmux, ComfyUI,
|
||||||
CachyOS, skills tooling — each with its verified operational facts.
|
CachyOS, skills tooling — each with its verified operational facts.
|
||||||
|
|
||||||
## [Pi-era agent setup](pi/index.md)
|
## [Pi-era agent setup](pi/index.md)
|
||||||
@@ -31,9 +31,11 @@ CachyOS, skills tooling — each with its verified operational facts.
|
|||||||
The legacy pi agent configuration (~/.pi era): settings, models, packages,
|
The legacy pi agent configuration (~/.pi era): settings, models, packages,
|
||||||
skills. Kept for reference; the live harness is OMP under `~/.omp`.
|
skills. Kept for reference; the live harness is OMP under `~/.omp`.
|
||||||
|
|
||||||
## [OMP packages and skills](packages/index.md), [skills index](skills/index.md)
|
## [Skills tooling](skills/index.md)
|
||||||
|
|
||||||
Extensions and skills installed for the current OMP harness.
|
The Agent Skills standard, the `skills` CLI, this fleet's store wiring, and
|
||||||
|
the registries — for the live OMP harness. Pi-era packages are archived under
|
||||||
|
[Pi-era agent setup](pi/index.md).
|
||||||
|
|
||||||
## [Core config](core/index.md)
|
## [Core config](core/index.md)
|
||||||
|
|
||||||
@@ -48,7 +50,17 @@ Practice notes: SDLC, languages, infra tooling (Terraform, K8s, containers).
|
|||||||
Decision-support research: agentic web tooling, chezmoi evaluation,
|
Decision-support research: agentic web tooling, chezmoi evaluation,
|
||||||
secrets-for-agents. Research docs inform decisions; they are not facts yet.
|
secrets-for-agents. Research docs inform decisions; they are not facts yet.
|
||||||
|
|
||||||
|
## [Agentic web research](agentic-web-research/index.md)
|
||||||
|
|
||||||
|
Self-hosted web-agent stack research: SearXNG tuning, VPN routing, agentic
|
||||||
|
search practices, open models, MCP.
|
||||||
|
|
||||||
## [Inventory](inventory/index.md)
|
## [Inventory](inventory/index.md)
|
||||||
|
|
||||||
Pre-pruning census of every userspace config on this box — the input for the
|
Pre-pruning census of every userspace config on this box — the input for the
|
||||||
pruning session.
|
pruning session.
|
||||||
|
|
||||||
|
## [Change history](log.md)
|
||||||
|
|
||||||
|
Chronological update history of this bundle — incident lessons land here first,
|
||||||
|
then settle into concept pages.
|
||||||
+198
-262
@@ -6,343 +6,279 @@ tags:
|
|||||||
- okf
|
- okf
|
||||||
- maintenance
|
- maintenance
|
||||||
- changelog
|
- changelog
|
||||||
timestamp: 2026-08-28
|
timestamp: 2026-10-19
|
||||||
---
|
---
|
||||||
|
|
||||||
# Bundle Change Log
|
# Bundle Change Log
|
||||||
|
|
||||||
## 2026-10-18 — Noctalia bar layout clobbered by trunk-sync stash, recovered from dangling objects
|
Entry law (since 2026-10-19): one dated entry = what changed + the durable
|
||||||
|
lesson, a few lines. Full incident narratives are recoverable from git
|
||||||
|
(`git log --follow omp-docs/log.md`); knowledge that outlives an entry lives
|
||||||
|
on its concept page, linked here. Never re-expand an entry in place.
|
||||||
|
|
||||||
Frank's months of GUI-built bar customizations (cat/clock/clock-utc/weather start, taskbar center, capsule groups g1–g4, control-cheat-sheet, thickness/font-scale/opacity styling) vanished from the live bar at 16:46. Root cause: the layout existed **only in the working tree** — the repo copy of `settings.toml` carried a stale plain layout because GUI edits hadn't been committed inside the hourly heartbeat window — and a trunk-sync session clearing the dirty tree for `origin/main` stashed it; the stash entry was subsequently destroyed (three `WIP on main` stashes within 60 s, all later dropped). Recovery: `git fsck --lost-found` surfaced the destroyed stash as dangling commit `7fe3ed8` (16:46:09); candidate validated in a sandboxed `XDG_*_HOME` before restore, clobbered file preserved to `.AGENTTRASH/`, layout restored through the symlink target and proven by screenshot, committed as `c87b378` — the durable copy the layout never had. Lessons codified into managed skills: trunk-sync must preservation-**commit** every dirty deployment path (never stash, never `stash clear`), and GUI layout edits get committed the same session they're made.
|
## 2026-10-19 — dankvoice: humanized narration and thinking aloud (Refs #27)
|
||||||
|
|
||||||
|
Spoken units are rewritten by the `@tiny` role (frank-zealot) under the
|
||||||
|
bundle's speech budgets before synthesis, with mechanical cleanup as fallback;
|
||||||
|
auto-speak narrates the last message's thinking blocks first; all toggles
|
||||||
|
persist. 7 new scenarios, 22 green; live smoke through real zealot + frank-tts.
|
||||||
|
|
||||||
|
## 2026-10-19 — dankvoice: segmented sequential speech
|
||||||
|
|
||||||
|
`agent_end` awaiting one whole-answer synthesis hit the harness's 30 s
|
||||||
|
extension-handler timeout — lesson: extensions must never await work that can
|
||||||
|
exceed 30 s. Ported the built-in vocalizer's utterance-splitting state machine
|
||||||
|
(boundary = `[.!?…]+` + whitespace, abbreviation/code guards, per-answer
|
||||||
|
segment budget); segments synthesize strictly sequentially. `DANKVOICE_MAX` is
|
||||||
|
now a segment budget. 17/17 in `tests/dankvoice.bats`.
|
||||||
|
|
||||||
|
## 2026-10-18 — dankvoice: `/say` options menu with output-device routing (Refs #23)
|
||||||
|
|
||||||
|
`/say menu` (pi-peon-style): auto-speak, voice, PipeWire output device
|
||||||
|
(`wpctl` → `pw-play --target`), player, char cap, test phrase. Precedence
|
||||||
|
env > `~/.omp/cache/dankvoice.json` > defaults — born from the incident where
|
||||||
|
PipeWire's remembered Bluetooth route played answers into nowhere.
|
||||||
|
|
||||||
|
## 2026-10-18 — dankvoice: the cloned voices finally speak in the TUI (Refs #21)
|
||||||
|
|
||||||
|
The harness gates `--voice` against the bundled Kokoro enum, so frank-tts's
|
||||||
|
cloned voices were unreachable in-TUI. Added `agent/extensions/dankvoice.ts`
|
||||||
|
(`/say …`) synthesizing over the OpenAI speech route, endpoint read from the
|
||||||
|
`api: openai-speech` row of `agent/models.yml` (no host or secret in code).
|
||||||
|
|
||||||
|
## 2026-10-18 — Noctalia bar layout clobbered by trunk-sync stash, recovered
|
||||||
|
|
||||||
|
GUI-built bar customizations existed only in the working tree; a trunk-sync
|
||||||
|
stash-then-drop destroyed them. Recovery: `git fsck --lost-found` surfaced the
|
||||||
|
dropped stash as a dangling commit; validated in a sandboxed `XDG_*_HOME`
|
||||||
|
before restore. Lesson: GUI-written config is only durable once committed —
|
||||||
|
the motivation for the live-sync heartbeat below.
|
||||||
|
|
||||||
## 2026-10-18 — Fleet law: issue/PR attribution footers
|
## 2026-10-18 — Fleet law: issue/PR attribution footers
|
||||||
|
|
||||||
Every agent-opened issue and PR body now ends with a `cwd:` + `session:` footer (session id from `/status`), in AGENTS.md of both `dank/command-center` and `dank/serverAppConfigs` (mirrored PRs). Born from the live-sync review: once the machine starts filing and committing on its own, its artifacts must trace back to the box and session that produced them.
|
Every agent-opened issue/PR body ends with `cwd:` + `session:` footer, in both
|
||||||
|
`dank/command-center` and `dank/serverAppConfigs` AGENTS.md: machine-made
|
||||||
|
artifacts must trace to the box and session that produced them.
|
||||||
|
|
||||||
## 2026-10-18 — live-sync heartbeat for machine-written paths (Refs #11)
|
## 2026-10-18 — live-sync heartbeat for machine-written paths (Refs #11)
|
||||||
|
|
||||||
Root-caused the month of silent divergence in the live checkout (~61 auto-learned skills on one disk only, a stranded `unexpectedStopDetection` fix, merge-refusals from dirty trees): the checkout IS the deployment, symlinks make machine writes dirty tracked files by design, and no commit law covered that churn. Added `scripts/live-sync.sh` + systemd user timer — allowlist tiers (memories, managed-skills, live config, dotfiles) auto-commit with hooks armed, merge `origin/main`, push, and reap stale `wt/*` worktrees; everything outside the allowlist is never staged, conflicts abort-and-alert. Black-box suite `tests/live-sync.bats` (9 tests). Tier law in AGENTS.md § Live-sync tiers; reference page `tools/live-sync.md` registered in the tools index.
|
A month of silent divergence in the live checkout proved the checkout-is-
|
||||||
|
deployment model needs a committer: `scripts/live-sync.sh` + systemd user
|
||||||
|
timer auto-commits allowlisted tiers (memories, managed-skills, live config,
|
||||||
|
dotfiles) with hooks armed, merges `origin/main`, pushes, reaps stale `wt/*`.
|
||||||
|
Non-allowlist dirt never staged; conflicts abort-and-alert. See
|
||||||
|
[tools/live-sync.md](tools/live-sync.md); tier law in AGENTS.md.
|
||||||
|
|
||||||
## 2026-10-18 — Fleet law: filed PRs open in Frank's browser immediately
|
## 2026-10-18 — Fleet law: filed PRs open in Frank's browser immediately
|
||||||
|
|
||||||
Both repos' git rules now require it: the moment a PR is opened, `xdg-open <PR-URL>` drops a Firefox tab on the workstation (AGENTS.md § Trunk-based SDLC here; § Git Workflow step 6 in `dank/serverAppConfigs`, riding their PR #6 branch).
|
`xdg-open <PR-URL>` the moment a PR opens — codified in both repos' git rules.
|
||||||
|
|
||||||
## 2026-10-18 — Trunk law: extension route closed unmerged; direct repo alignment instead
|
## 2026-10-18 — Trunk law: extension route closed unmerged; direct repo alignment
|
||||||
|
|
||||||
Built and live-verified a cwd-scoped `before_agent_start` extension (quote probes: fires under `~/Projects`, silent elsewhere; kept as `.AGENTTRASH/trunk-law.js`). **Operator closed PR #9 unmerged** in favor of editing each repo's own AGENTS.md. Durable finding from the exploration: every context-file provider (AGENTS.md walk-up, `.omp/` dirs, CLAUDE.md) resolves at or below the repo root — a `~/Projects/AGENTS.md` for ancestor scope can never load; user-level `APPEND_SYSTEM.md` broadcasts to *all* sessions. Second finding: **serverAppConfigs never had git-flow** — its AGENTS.md mandated no branch regime at all (direct commit+push); a trunk + PR law is being added there by PR in that repo, not corrected.
|
Operator closed PR #9 (cwd-scoped `before_agent_start` extension) in favor of
|
||||||
|
editing each repo's AGENTS.md. Durable findings: every context-file provider
|
||||||
|
(AGENTS.md walk-up, `.omp/` dirs, CLAUDE.md) resolves at or below the repo
|
||||||
|
root — ancestor-scope files can never load; user-level `APPEND_SYSTEM.md`
|
||||||
|
broadcasts to all sessions; serverAppConfigs never had git-flow.
|
||||||
|
|
||||||
## 2026-10-18 — SDLC switched to trunk-based development
|
## 2026-10-18 — SDLC switched to trunk-based development
|
||||||
|
|
||||||
`develop` retired by operator decision (single-dev repo; the branch bought nothing and cost a back-merge every release). New law in AGENTS.md § *Trunk-based SDLC*: branch off the freshest `origin/main` before every work item, PRs target `main` directly, `--no-ff` merges, tags `vX.Y.Z` straight on main. No back-merges, no release branches. The last back-merge (`527596a`) lands with this switch; the managed skill `gitflow-back-merge-plumbing` is now historical.
|
`develop` retired (single-dev repo). Law in AGENTS.md § Trunk-based SDLC:
|
||||||
|
branch off freshest `origin/main`, PRs target `main`, `--no-ff` merges, tags
|
||||||
|
`vX.Y.Z` on main. Managed skill `gitflow-back-merge-plumbing` is historical.
|
||||||
|
|
||||||
## 2026-10-18 — Sibling-vault overlap census; system docs realigned to live ground truth
|
## 2026-10-18 — Sibling-vault overlap census; system docs realigned to live ground truth
|
||||||
|
|
||||||
Census of `omp-docs/` against `~/Projects/serverAppConfig/serverAppConfigs-docs/` (subagent + live-ssh verification) found four contradicting pairs; all OURS-side stale pages corrected, THEIRS side flagged only (their repo, their PR flow). Ground truth from read-only probes on both boxes 2026-10-18.
|
Census of `omp-docs/` vs `serverAppConfigs-docs` (subagents + live-ssh probes)
|
||||||
|
found four contradicting pairs; ours fixed, theirs flagged for their PR flow.
|
||||||
|
Recorded then: RTX 3090 moved Dankserver→Dankclaw 2026-09; dankserver = 2×
|
||||||
|
Radeon AI PRO R9700 (the "board swap" story was false); served models per
|
||||||
|
live units. Current state always: [/system/index.md](/system/index.md)
|
||||||
|
(27B-era zealot figures here are historical; live census 2026-10-19 below).
|
||||||
|
|
||||||
* **Hardware drift corrected** (`system/dankserver.md`, `system/dankclaw.md` rewritten, `AGENTS.md` machine table): RTX 3090 moved Dankserver→Dankclaw 2026-09; dankserver now 2× Radeon AI PRO R9700 (7700X stays — the "board swap" story is false), 246 GiB RAM, no swap; dankclaw 121 GiB RAM + 64 GiB swap. Served models per live units: frank-archon (:7770 dankserver, radiance TP=2 iq4r), frank-dragoon (:7770 dankclaw, halogen NATIVE v2, iGPU), frank-zealot (:7771 dankclaw, llama.cpp PrismML, Ternary-Bonsai-2-27B PQ2_0, 3090). Both kernels 7.0.0-38-generic; dankclaw :7770 needs no auth.
|
## 2026-10-18 — Status line rebuilt on custom segments; full-source audit
|
||||||
* **Gitea pages corrected** (`tools/gitea/` index + 3 subpages): `gitea-caddy` quadlet retired — fleet Caddy fronts the domain; data tree is `/home/dank/Containers/gitea/{data,postgres,env}` (2026-09-01 standard), `/opt/gitea/` is a do-not-use leftover; LAN IP, live domain, no WAN address.
|
|
||||||
* **Pi-era pages demarcated as archive** (`pi/index.md`, `core/index.md` banners; `core/index.md` said settings.md was "Current"): `frank-default-small/large` endpoints, role tables, and 3090/4070 hardware rows now read as pi-era records. `tools/llama-cpp/index.md` fleet note modernized to the one remaining llama.cpp endpoint (zealot :7771).
|
|
||||||
* **Their-side staleness flagged, NOT edited** (for operator clarification): `Agent/OMP Harness.md` still says thresholdPercent 70 — live config is **95** (`agent/config.yml:84`); `index.md:18` says Dankserver Ubuntu 24.04 vs their own specs page 26.04; `Dankclaw/Stack.md` "current mapping (2026-09-03)" predates the ACTIVE dragoon/zealot runbooks it links beside; `index.md:548` links the superseded pi-era KB at `~/.pi/agent/docs/`.
|
|
||||||
* Consistent duplicates (no action): niri/noctalia desktop pages, Caddy reference. Divergence clarified: their "composer" = OpenWebUI chat box; ours = omp composer shape — unrelated subjects.
|
|
||||||
|
|
||||||
## 2026-10-18 — Status line docs truth audit (full-source re-verification)
|
New page [core/statusline.md](core/statusline.md) with an audit log — four
|
||||||
|
first-pass claims were false and corrected against the minified bundle + a
|
||||||
|
live screenshot matrix (context meter is `box`/`band` only; `composer.shape`
|
||||||
|
routes segment arrays; `mode` states = Plan/Prewalk/Goal/Vibe/Loop). Bar
|
||||||
|
rebuilt on `band` + custom segments; frank-archon display name slimmed in
|
||||||
|
models.yml.
|
||||||
|
|
||||||
Exhaustive claim-by-claim re-audit of [OMP Status Line Customization](/pi/statusline.md) against the minified `dist/cli.js` bundle plus a live screenshot matrix (band × {off, percentage, embedded}, claude × off). Page carries an **Audit log** section; four first-pass claims were false and are corrected (see there): claude's rule is *not* the context meter (meter is `box`/`band` only; the "red rule" was the session-accent filler), the "Tern-only" composer-bar claim was bogus (the readout is `composer.tokenRate`, default off), `segmentOptions` are **not** gated to `preset: custom` (only the segment arrays are), and the `mode` segment's states are Plan/Prewalk/Goal/Vibe/Loop (no yolo). Page expanded with: the 7-preset defaults table, per-shape `statusAttachment`/`bottomBar` model (8 built-ins + plugin shape registry), `contextLine` per-value behavior, the 27-segment catalog with triggers/options, `segmentOptions` schema, and the full `statusLine.*`/`composer.*` knob registry. The bundle's own "(box composer only)" knob description for `contextLine` is upstream-wrong — band honors it.
|
## 2026-10-09 — Blankpad DE redesign: desk profiles, keybinds, Handy wiring
|
||||||
|
|
||||||
ServerAppConfigs-docs overlap census: flagged separately for the operator (morning report); no doc merged/changed on either side.
|
Per-desk niri profiles keyed by EDID long name (connector names reshuffle
|
||||||
|
between docks; identity travels) + `scripts/desk.sh`; niri 26.04 has **no
|
||||||
|
hotplug event** (`OutputConfigChanged` is only an ACK per niri-ipc) — the
|
||||||
|
autoswitch daemon diffs output sets on `WorkspacesChanged`. Keybind cheat
|
||||||
|
sheet now generated (`scripts/gen-keybinds.sh`); noctalia docs corrected to
|
||||||
|
the v5 `noctalia msg` API. Handy: niri owns autostart; open breaker —
|
||||||
|
frank-secondary answers in `reasoning_content` with empty `content`, and
|
||||||
|
Handy cannot send `chat_template_kwargs.enable_thinking=false`. 27/27 green.
|
||||||
|
|
||||||
## 2026-10-18 — Status line rebuilt on custom segments; frank-archon display name slimmed
|
## 2026-10-09 — Incident: agent sudo-loop tripped pam_faillock, locked out the operator
|
||||||
|
|
||||||
Interactive tune-up of the omp status bar (grilling session, live loop: config edit → watcher repaint → niri screenshot verify). New knowledge page: [OMP Status Line Customization](/pi/statusline.md).
|
|
||||||
|
|
||||||
* **Mechanics discovered:** `leftSegments`/`rightSegments` are inert unless `preset: custom` (fleet was on `nerd` with dead arrays; `segmentOptions` *do* merge under named presets); `composer.shape` routes them — `band` merges both arrays into one row above the editor (title rides the working row), `claude` splits them into top chip + bottom strip. The context meter exists **only in `box`/`band`** — `contextLine`: `off` plain rule, `embedded` hoists the context segment into the labeled inline meter (`60% ——— × 200K`), `percentage`/`annotated` paint an unlabeled proportional rule meter (all four live-verified); every other shape's rule is a constant session-accent filler. Working row (session title + tok/s readout gated by `composer.tokenRate`, default off) is composer chrome — model/thinking can never join it. Eight composer shapes ship in the bundle; plugins can register more.
|
|
||||||
* **Final bar (`band`, after a `claude` sojourn):** left = `session_name · vim · mode · hostname · path · git(no branch text) · pr`; right cluster in array order = `model · cost · time_spent` (`context_pct` hoists out of the run to paint the labeled inline meter `60% ——— × 200K` as a flexible spacer between the groups). Cut: tok/s duplicate, clock, cache trio, `context_total`, `subagents`, `session` hash, and `token_in`/`token_out` (cumulative totals live in `/session`). `path`: logical `project/worktree` label (`stripWorkPrefix: true`, kept after a live `false` trial of the full cwd); branch text dropped (worktree branches are autogenerated), dirty badges kept.
|
|
||||||
* **models.yml:** frank-archon display `name` slimmed to `"frank-archon"` (endpoint suffix dropped; `id` untouched).
|
|
||||||
* **Out of scope noted:** worktree name generation (`wt-<date>-<hash>`) is omp-internal; possible upstream feature request.
|
|
||||||
|
|
||||||
## 2026-10-09 — Blankpad DE redesign: desk profiles, keybind rework, Handy fleet wiring
|
|
||||||
|
|
||||||
Branch `blankpad-de-redesign` (worktree `wt/de-redesign-20261009`). Full sweep + redesign of the niri/noctalia/Handy stack on blankpad.
|
|
||||||
|
|
||||||
* **Desk profiles.** `cfg/display.kdl` retired to a symlink; per-desk profiles `desk-1-home.kdl` / `desk-2-portable.kdl` declare monitors by **EDID long name** (`// requires:` lines) — connector names reshuffle between docks, identity travels. `scripts/desk.sh` = list/current/switch/cycle/match/capture; apply = relink → `niri validate` (rollback on failure) → `load-config-file` → noctalia notification. `scripts/desk-autoswitch.sh` + `blankpad-desk-autoswitch.service` watch `niri msg --json event-stream`: niri 26.04 has **no hotplug event** (`OutputConfigChanged` is only an ACK that an IPC/config output change was Applied, per niri-ipc); the daemon diffs the sorted output set carried by `WorkspacesChanged` and hot-applies the matching profile (debounced 1.5 s). Proven live with an eDP-1 off/on cycle: `[DP-2 eDP-1] → [DP-2] → [DP-2 eDP-1]`, apply + reload each time. PM1601 EDID still uncaptured (panel disconnected) — connector-keyed placeholder, `desk.sh capture` pending.
|
|
||||||
* **Keybinds.** `swap-window-left/right` on Mod+CTRL+H/L; move-column demoted to Mod+CTRL+Alt+H/L; 7 column-width presets 12.5–87.5 %; Print trio (full / gui / screen) via noctalia screenshot IPC; launcher Mod+D; Alt+Tab → noctalia window-switcher; Mod+V → clipboard panel (id verified live); Mod+Shift+D → desk cycle; Mod+Shift+Space → `handy --toggle-post-process`; lid-close → `noctalia msg session lock-and-suspend` (pairs with logind `HandleLidSwitch=ignore`). Cheat sheet now GENERATED: `scripts/gen-keybinds.sh` → `omp-docs/tools/niri/keybindings.md` (119 bindings).
|
|
||||||
* **Noctalia.** `config.toml` + `settings.toml` (lives in `~/.local/state/`, not `~/.config/`) tracked; `taskbar` widget added to bar center, applied live via `noctalia msg config-reload`. Docs corrected to the v5 `noctalia msg` API.
|
|
||||||
* **Handy.** Self-autostart killed (`~/.config/autostart/Handy.desktop` → `.AGENTTRASH`; `autostart_enabled=false`); niri owns autostart (`spawn-at-startup "handy" "--start-hidden"`). Fleet post-processing wired into the live store: provider `custom` → `http://192.168.0.143:7771/v1`, model `frank-secondary`, prompt `default_improve_transcriptions`. **Open breaker:** frank-secondary answers in `reasoning_content` and leaves `content` empty; per-request cure `chat_template_kwargs.enable_thinking=false` verified, but Handy cannot send it and the server is shared with fleet compaction — dedicated no-think port proposed. 0.9.6→0.9.8 upgrade is operator-owned (`yay -Syu handy-bin`).
|
|
||||||
* **Scripts + tests.** Two subagents died mid-run (one mid-test-write; one crashed **looping `sudo yay` without a TTY** — see next entry). Their scripts/tests were rebuilt from live-format ground truth: `tests/desk.bats` (16), `tests/gen-keybinds.bats` (6), `tests/niri-config.bats` (5, incl. real `niri validate` on the assembled base+zz tree with each desk profile active). Suite: 27/27 green.
|
|
||||||
|
|
||||||
## 2026-10-09 — Incident: agent sudo-loop tripped pam_faillock and locked out the operator's sudo
|
|
||||||
|
|
||||||
While attempting the Handy 0.9.8 install, a subagent ran `sudo yay -Syu handy-bin` in a retry loop for ~15 minutes. Harness sudo has no TTY, so every attempt logged 3 `pam_unix` auth failures; `pam_faillock` (deny=3) locked user `blank` out — the operator's own correct-password `sudo`/`pacman -Syu` began failing with "temporarily locked out", which looked exactly like a changed password.
|
|
||||||
|
|
||||||
* **No password change:** `/etc/shadow` mtime 2026-09-10 23:31, untouched; no `passwd`/`chpasswd`/`usermod` in the journal.
|
|
||||||
* **Recovery:** loop process died with the agent; `faillock --user blank --reset` (unprivileged for own user, dir `/run/faillock/blank` is 0700) cleared the counter; verified empty.
|
|
||||||
* **New law:** agents NEVER run `sudo`/`yay`/`pacman`/`pkexec` on Frank's workstations — root-requiring steps are handed to the operator as exact copy-paste commands. Codified as managed skill `faillock-sudo-lockout-recovery`.
|
|
||||||
|
|
||||||
|
A subagent retried `sudo yay` (no TTY) for ~15 min; `pam_faillock` deny=3
|
||||||
|
locked user `blank` — correct-password sudo failed, mimicking a password
|
||||||
|
change. Recovery: `faillock --user blank --reset`. Law: agents never run
|
||||||
|
`sudo`/`yay`/`pacman`/`pkexec`; root steps go to the operator as exact
|
||||||
|
commands. Skill `faillock-sudo-lockout-recovery`.
|
||||||
|
|
||||||
## 2026-10-09 — Full secret audit of the command-center repo: clean
|
## 2026-10-09 — Full secret audit of the command-center repo: clean
|
||||||
|
|
||||||
Five-layer audit of `dank/command-center` at `79e780f`, driven by the four live values in `~/.config/secrets/` (gitea, hf_token, hf_token_write, kagi_api_key) plus 21 provider shapes (sk-, hf_, gh[pousr]_, glpat-, AKIA/ASIA, xox, npm_, pypi-, AIza, JWT, PEM/OpenSSH keys, `scheme://user:pass@host` basic-auth URLs, `password|secret|api_key = <literal>` assignments). Findings printed redacted (prefix + length + sha256 prefix).
|
Five layers (live-value scan across 77 revs/765 blobs, whole object store
|
||||||
|
incl. dangling, shape triage, entropy ≥4.3, LFS payloads) — no secret in any
|
||||||
* **Live-value scan — clean.** 77 revs (5 branches, 2 tags), 765 reachable blobs, 3.4 MiB of blob text: no known live value appears in any tracked blob at any ref.
|
reachable or dangling object; quarantine list holds. Fixed `hf_token_write`
|
||||||
* **Whole object store, reachable *and* dangling — clean.** `git cat-file --batch-all-objects` finds 767 blobs; 2 are unreachable, never committed on any ref (`git log --all --find-object` empty). One is a 123 KB superseded memory page; the other is a 35-byte test fixture holding a synthetic `sk-` key whose body is the alphabet followed by `1234` — pattern-minted, not issued. No PEM key, no JWT, no basic-auth URL anywhere. (Quoting the literal here tripped the secret gate, which is the gate working as designed.)
|
0644→0600. Open gap: the pre-commit gate lacks `hf_`/`npm_`/`pypi-`/`AIza`/
|
||||||
* **Shape hits, all triaged benign**: `api_key="sk-no-key-required"` (llama.cpp doc placeholder), `Set password:` / `authenticate(user, password)` prose in the Authentik memory skills, and a 40-hex string in the home-config census that is a git **packfile filename** (`pack-ff7cc541….pack`), not a token.
|
Stripe/Telegram shapes and basic-auth URLs — widening it is an open decision.
|
||||||
* **Entropy layer — clean.** Zero tokens ≥ 32 chars with Shannon entropy ≥ 4.3 in HEAD blobs.
|
|
||||||
* **Payloads, not just pointers**: all 23 git-lfs wallpaper objects (81 MiB) scanned as bytes — no secret shape in any payload.
|
|
||||||
* **Quarantine list holds**: no tracked file matches `auth.json`/`trust.json`/`.env`/`*.pem`/`*.key`/`identity*`; the 42 ignored paths on disk carry no secret shape; no credential in any git config scope (local/global/system).
|
|
||||||
* **Fixed on the spot**: `~/.config/secrets/hf_token_write` was mode 0644 (world-readable) → 0600. `~/.cache/huggingface/token` (the standard HF location, holds the same value) verified 0600.
|
|
||||||
* **Known gap, not yet closed**: `.githooks/pre-commit` covers GitHub/GitLab/Slack/AWS/PEM/JWT/OpenAI-shaped `sk-`, but **not** `hf_…`, `npm_…`, `pypi-…`, `AIza…`, Stripe, Telegram, or a `scheme://user:token@host` basic-auth URL — which is exactly the shape that sat in `remote.origin.url` until today. Widening the gate is an open decision.
|
|
||||||
|
|
||||||
## 2026-10-09 — Gitea token moved out of every remote URL
|
## 2026-10-09 — Gitea token moved out of every remote URL
|
||||||
|
|
||||||
Both workstation clones (`~/.omp` → `dank/command-center`, `~/Projects/serverAppConfig` → `dank/serverAppConfigs`) carried the Gitea HTTP token inside `remote.origin.url`, so it sat in `.git/config`, was printed by `git remote -v`, and was echoed verbatim by the git-lfs lock warning on every push.
|
Remotes bare; credential in `~/.config/secrets/gitea` (0600) behind a global
|
||||||
|
`credential.helper store`. Trap (git 2.56.0, measured): `git
|
||||||
|
credential-store` rejects the `protocol=`/`host=` block shape the man page
|
||||||
|
shows — the store file must be one URL per line, port included. Verified with
|
||||||
|
`GIT_TERMINAL_PROMPT=0`. Details: [tools/gitea/client-access.md](tools/gitea/client-access.md).
|
||||||
|
|
||||||
* **Cutover**: remotes are now bare (`http://192.168.0.143:3000/dank/<repo>.git`); the credential lives in `~/.config/secrets/gitea` (0600) behind a **global** helper — `git config --global credential.helper 'store --file ~/.config/secrets/gitea'` — so every repo on the host authenticates from one file. `lfs.…info/lfs.locksverify true` set on `~/.omp` to silence the warning that leaked the URL.
|
## 2026-10-09 — Working tree reconciled: callsign renames, compaction move, gitignore quarantine
|
||||||
* **The trap (git 2.56.0, measured)**: `git credential-store` will not parse the `protocol=`/`host=`/`password=` block shape that `git credential fill` accepts on stdin and that the man page shows for `~/.git-credentials`. It returns an empty `get`, and git falls through to askpass: `could not read Username for …`. The store file must be one URL per line, port included: `http://dank:<token>@192.168.0.143:3000`.
|
|
||||||
* **Verified** with prompts disabled (`GIT_TERMINAL_PROMPT=0`): `git credential-store get` returns the credential, `ls-remote` reads, `push --dry-run` says `Everything up-to-date` on both repos, `git remote get-url origin` contains no `@`, and a grep for the token across `~/.gitconfig`, both `.git/config` files, and the shell rc files returns nothing.
|
|
||||||
* **Doc**: new [[client-access|client-access]] (`tools/gitea/client-access.md`) — credential file layout, the store-format trap, the verification loop, LFS lock warning, rotation procedure. Cross-linked from the gitea index and management pages.
|
|
||||||
|
|
||||||
## 2026-10-09 — Working tree reconciled: callsign renames, Dankclaw compaction, gitignore quarantine
|
200-entry dirty tree committed as seven atomic units. Harness model ids
|
||||||
|
renamed `frank-{medium,large,small}` → `frank-{archon,dragoon,zealot}` —
|
||||||
A 200-entry dirty tree (116 deleted, 73 untracked, 11 modified) sat uncommitted across several sessions. Committed as seven atomic units; nothing discarded.
|
**services keep old names on the wire**; the engines accept any request model
|
||||||
|
id, so renames are harness-side only. Server-side compaction moved to
|
||||||
* **Model ids renamed to the unit-name scheme** (`agent/models.yml`): `frank-medium → frank-archon` (dankserver :7770, 200,000 ctx / 16,384 max), `frank-large → frank-dragoon` (Dankclaw :7770, 217,088 / 45,056, `input: [text, image]`), `frank-small → frank-zealot` (Dankclaw-3090 :7771, 200,704 / 16,384). The **services keep the old names on the wire** — `/v1/models` still serves `frank-primary`, `frank-large`, `frank-secondary`; the engines accept an arbitrary request `model` id, so the rename is harness-side only. Probed live: all three chat completions return 200 with `reasoning_content`.
|
zealot's 3090 (threshold 70→85 %). `.gitignore` quarantine for worktree-keyed
|
||||||
* **Server-side compaction moved** off `192.168.0.235:7770` to `192.168.0.143:7771` (frank-zealot's 3090), threshold 70 % → 85 %, remote streaming v2 + async on; every model's `compactionModel` points at `frank-zealot` so the wire id stays valid at the compaction endpoint. Reference integrity checked programmatically: 22 model references across `modelRoles`, `task.agentModelOverrides`, `compactionModel` — zero unresolved.
|
memory buckets and runtime dirs. Skill renames are not losses: git renders
|
||||||
* **`.gitignore` quarantine** for runtime state that was tracked-adjacent noise: `autoresearch/`, `agent/predict/`, `local/`, `wt/`, and `agent/memories/--home-blank-.omp-wt-*` (a session inside a git worktree mints a memory bucket keyed on the ephemeral worktree path). All had zero tracked files, so the rules are purely additive — 73 untracked → 68, and the remaining 68 are real memory records that got committed.
|
directory renames as mass delete+add; pre-cutover content intact at `12c5e88`.
|
||||||
* **Skill libraries were renamed, not lost.** `agent/managed-skills` consolidated 94 → 25 (git renders directory renames as mass delete + mass add; pre-cutover content is intact at `12c5e88`). Workstation memory skills re-bucketed by topic: `dotfile-git-workflow` → `ghostty-text-aa` + `niri-display-rotation` + `noctalia-theme`, `handy-service-mgmt` → `handy-flm-inference`, `inference-runtime` → `llama-cpp-parallelism`.
|
|
||||||
* **Display layout**: blankpad DP-2 back to landscape `3440x1440@239.999` (`transform "normal"`), HDMI-A-1 portrait bezel flipped `270 → 90`, eDP-1 re-pinned rightmost. Supersedes the 2026-09-15 portrait pin (`2baf0c0`).
|
|
||||||
* **Secrets**: `.zshrc` now reads `KAGI_API_KEY` / `HF_TOKEN` from `~/.config/secrets/*` (600-perm files) instead of carrying literals into a repo published to Gitea. The pre-commit secret gate passed on all seven commits.
|
|
||||||
|
|
||||||
## 2026-09-28 — Retired workstation purged from the knowledge base
|
## 2026-09-28 — Retired workstation purged from the knowledge base
|
||||||
|
|
||||||
Frank sold the client workstation. Every identifier for it is gone from this bundle: hostname, both of its LAN addresses, and its Wi-Fi SSID. The knowledge outlives the box; the machine does not.
|
Sold box: hostname, both LAN addresses, Wi-Fi SSID gone bundle-wide; the
|
||||||
|
knowledge distilled into [system/network.md](system/network.md) (multi-link
|
||||||
|
resolver law: a static resolver must be pinned on each *connection*) and
|
||||||
|
[tools/niri/displays.md](tools/niri/displays.md) (portrait transforms, the
|
||||||
|
three ways a multi-monitor rig breaks). Hostname stays only where it is a
|
||||||
|
lookup key (install.sh `--host`, `packages/<hostname>.pkgs`).
|
||||||
|
|
||||||
* **Distilled, not deleted.** The four per-host system pages carried two pieces of knowledge worth keeping and one that was not:
|
## 2026-09-01 — Retry backoff and thinking default (pi era)
|
||||||
* `/system/network.md` (new) — LAN topology, plus the multi-link resolver law: a client with two `+DefaultRoute` links flaps its DNS, and the fix is a static resolver pinned on each *connection* (a `resolved.conf` `DNS=` line is only a per-link fallback). Keeps the flush-caches verification loop, because one successful `dig` proves nothing on a flapping resolver.
|
|
||||||
* `/tools/niri/displays.md` (new) — the multi-monitor rig: portrait transforms, absolute positions, the auto-placed third panel, the iGPU render-node pin, and the three ways it breaks (duplicate output blocks, positions that do not reflow, a mode the panel cannot drive).
|
|
||||||
* Dropped: the machine's own spec sheet and quick-facts table. The OS and userspace facts they repeated already live in `tools/cachyos.md`.
|
|
||||||
* **Mechanical scrub** across the rest of the bundle: indexes, tool and research pages, the inventory census, the changelog, one generated HTML report, and four managed skills. Facts kept, names dropped — `install.sh`'s `--host` case list and the `packages/<hostname>.pkgs` filename stay, because the hostname is the lookup key there.
|
|
||||||
* `AGENTS.md` and `README.md` keep a nameless "workstation" row so the fleet tables still describe the class of box without advertising a machine that is no longer Frank's.
|
|
||||||
* Out of scope by decision: `agent/memories/**` (past-session records the learn pipeline rewrites) and the retired pages' history — they are recoverable from `.AGENTTRASH/20260928-retired-workstation/` and from git.
|
|
||||||
|
|
||||||
## 2026-09-01 — Retry backoff and thinking default
|
|
||||||
|
|
||||||
* **Retry backoff**: configured Pi's automatic agent-level retries in `settings.json` with a 10-second base delay, producing 10s, 20s, and 40s delays across three retries.
|
|
||||||
* **Thinking default**: set `settings.json` `defaultThinkingLevel` to `medium`. `models.json` has no supported per-model default-thinking field; Pi applies this global default to the configured models.
|
|
||||||
|
|
||||||
|
Pi `settings.json`: 10 s base retry backoff (10/20/40 s), `defaultThinkingLevel:
|
||||||
|
medium` (no per-model thinking field exists in `models.json`).
|
||||||
|
|
||||||
## 2026-08-28 — KB rebuilt to OKF v0.1; live-verified fleet facts
|
## 2026-08-28 — KB rebuilt to OKF v0.1; live-verified fleet facts
|
||||||
|
|
||||||
* **Creation**: the whole `omp-docs/` bundle was copied from the legacy `~/.pi/agent/docs` tree and rebuilt as a git-tracked, OKF v0.1-conformant knowledge base inside the new `command-center` repo (repo root = live agent dir; the checkout *is* the deployment). Every checkable claim was re-verified against the live fleet (read-only probes); unverifiable claims are tagged `unverified` in frontmatter.
|
`omp-docs/` copied from legacy `~/.pi/agent/docs`, rebuilt as git-tracked OKF
|
||||||
* **Update — hardware facts corrected**: Dankserver is a **Ryzen 7 7700X + RTX 3090** (not Strix Halo); Strix Halo (gfx1151) is **Dankclaw**. RAM, storage, kernel, and network facts were probed live and corrected across `system/`.
|
v0.1 (checkout *is* the deployment); unverifiable claims tagged `unverified`.
|
||||||
* **Update — conformance pass**: concept-in-disguise indexes promoted to `<dirname>.md` concept files (identity = path, per spec); index.md files stripped of frontmatter (indexes carry none); all wikilinks and relative links resolved or repointed; `scripts/kb-lint.sh` (OKF conformance + link-integrity linter) exits 0 on the bundle. Vendored `references/okf-spec.md` is exempt from link-lint — its example paths are illustrations, not links.
|
Conformance pass promoted concept-in-disguise indexes, stripped index
|
||||||
* **Update — inventory**: home-config census (7 orphaned configs, 10 prune-candidates) recorded under `inventory/` for the pruning session; zero deletions performed. Legacy `~/.pi` tree left untouched and frozen.
|
frontmatter, resolved all links; `scripts/kb-lint.sh` exits 0. Hardware facts
|
||||||
* **Deferred to operator** (decisions, not facts): chezmoi source layout (`research/chezmoi-evaluation.md` recommends a separate chezmoi repo) and the permanent secrets tool (`research/secrets-for-agents.md`; interim = pre-commit secret gate + quarantine list; top candidate sops+age). Gitea Projects unit is disabled instance-wide, so the kanban board is a one-time web-UI setup for the operator.
|
were corrected *then* (3090-on-dankserver since moved — see 2026-10-18).
|
||||||
|
Inventory census recorded under `inventory/` for the pruning session.
|
||||||
|
|
||||||
## 2026-08-23 — Homepage deployed to production; Caddy KB doc
|
## 2026-08-23 — Homepage deployed to production; Caddy KB doc
|
||||||
|
|
||||||
* **Homepage is live on Dankclaw.** `./scripts/deploy.sh dankclaw` deploys the static files to `/srv/homepage`, installs the status checker (systemd timer, every 3 min, writes `status.json`), writes `deployed.js` (the last master deployment datetime), renders the Caddyfile (both basicauth hashes from the host `.env`), reloads Caddy, and checks health. Verified end-to-end: `/admin/` (operator) + all shared files serve 200 under the operator credential; `/` and `/admin/` 401 without/wrong credentials; the LLM is healthy.
|
Homepage live on Dankclaw via `scripts/deploy.sh` (status checker timer,
|
||||||
* **Caddyfile auth design (the hard-won fix).** Caddy *sorts* top-level directives, so a `rewrite` runs before a `basicauth`; the rewrite changed `/admin/` to `/admin.html` and the path-based auth matchers then saw the rewritten path, applying the wrong (family) auth block — the operator password 401'd. Fix: serve each page from its own `handle` block (which preserves file order, so the auth is checked on the original path): `handle /admin*` (operator only), `handle @data` (shared data + assets, family OR operator, so the admin page's fetches reuse the operator credential), and a catch-all `handle` (family). Also: a `handle` inline matcher is a single token (multi-path needs a named matcher); a leading `@name <directive>` is parsed as a matcher *definition* (re-defines `@name`).
|
deployed.js, rendered basicauth hashes, health check). Hard-won Caddy lesson:
|
||||||
* **Caddy mount fix.** The caddy container mounted the repo's `shared/homepage` (static source) to `/srv/homepage`; the generated files (`status.json`, `deployed.js`) are written to the host's `/srv/homepage`. Pointed the mount at the host's `/srv/homepage` so the generated files serve. A compose change needs a container *recreate* (not a reload) — `podman compose up -d caddy` (the deploy skips the compose restart, so this is manual).
|
top-level directives are *sorted*, so `rewrite` runs before `basicauth` and
|
||||||
* **Caddy KB doc** (operator request: read the docs before guessing at syntax): new `caddy/` section in this bundle — [[caddyfile|caddyfile]] (`caddy/caddyfile.md`) + `caddy/index.md`, cross-linked from the root `index.md`. Grounded in caddyserver.com (fetched 2026-08-23) + a researcher subagent brief.
|
auth matchers saw rewritten paths — serve each page from its own `handle`
|
||||||
* **Tests:** 116 green (was 112; +4 for the Caddyfile handle design + the `status.json` 0644 perms). The local `caddy validate` test is skipped (no local caddy binary); the rendered config is validated on the target via the porkbun image before each deploy.
|
block to preserve order; a `handle` inline matcher is a single token; a
|
||||||
|
leading `@name <directive>` parses as a matcher definition. Now documented on
|
||||||
|
[caddy/caddyfile.md](caddy/caddyfile.md). A compose mount change needs a
|
||||||
|
container *recreate*, not a reload. 116 tests green.
|
||||||
|
|
||||||
## 2026-08-23 — Homepage design settled (grilling complete)
|
## 2026-08-23 — Homepage design settled (grilling complete)
|
||||||
|
|
||||||
* **All 38 open questions (Q6–Q43) answered.** The homepage design is now fully settled. Full record: `serverAppConfigs/docs/Infrastructure/Homepage Design Grilling.md` (final decisions + the deltas) and `docs/Infrastructure/Homepage.md`.
|
All 38 open questions answered; full record lives in serverAppConfigs
|
||||||
* **Key deltas from the initial recommendations:** the probe method is **per service** (`tcp` or `http`); the probe timeout is **5 s**; the layout is **TUI-style panels** (a grid), not a flat list; styling is **toggleable** (full theme default, a toggle to bare unstyled HTML); **do not alter the user's cursor**; **no client-side auto-refresh** (read `status.json` once on load); the footer shows the **last master deployment datetime** (not last-check); the deploy is **git-driven** (master = deployed state); the build is **full TDD**.
|
(`docs/Infrastructure/Homepage Design Grilling.md`). Key deltas: per-service
|
||||||
* **Per-service docs workstream:** each service gets a `/docs/<svc>/` page (not `/usage/`), written as a **how-to for a regular user** (no maintenance details), password-protected by the same auth as its homepage section, built as each service goes active. The existing reference docs in `docs/` are NOT linked from the homepage (they may contain secrets).
|
tcp/http probes, 5 s timeout, TUI-style panel grid, no client auto-refresh,
|
||||||
* **Build complete (all 6 slices, full TDD, 112 tests green):** 1 the checker core (tcp/wip/atomic write/generated_at), 2 the http probe, 3 the real `services.json` + schema, 4 the pages + theme + toggle, 5 the Caddyfile auth split (root vs `/admin/`), 6 the git-driven deploy. All committed + pushed. The render logic is verified with a Node DOM shim (9 user panels, correct summary, correct footer datetime).
|
git-driven deploy, full TDD. Correction recorded: the Dankserver media stack
|
||||||
* **To go live:** the operator adds `CADDY_ADMIN_HASH` (the operator-only password, `caddy hash-password`) to the Dankclaw `.env`, then runs `./scripts/deploy.sh dankclaw`. Until then the admin hash stays a placeholder. The per-service `/docs/` pages, the *arr stack, and the optional `/how/` page remain separate threads.
|
runs as bare processes, not containers — never probe it as a compose stack.
|
||||||
* **Correction (media stack):** the Dankserver media stack (RomM, Copyparty, Jellyfin, Plex, the *arr) is **UP** — it runs as **bare processes/services, not yet migrated to podman containers**. The 2026-08-22 "down / missing `.env`" finding was wrong: I was probing for the compose stack, which is not how it runs yet. Do not treat it as an outage and do not probe it as down. Its podman migration is a separate, future effort.
|
|
||||||
|
|
||||||
## 2026-08-22 — vllmScripts merged into serverAppConfigs
|
## 2026-08-22 — vllmScripts merged into serverAppConfigs
|
||||||
|
|
||||||
* **Repo merge**: the `vllmScripts` repo (LLM inference fleet: serve scripts, systemd units, env tunables, deploy runbook, research docs) is merged into `serverAppConfigs` with its full git history (two-parent import commit, then reorganization: `docs/` → `docs/LLM/`, `scripts/` merged, `old/` for retired PP/NPU-era scripts). One repo, one checkout per host (`/home/dank/serverAppConfigs`), one `scripts/deploy.sh` covering both workloads per target (podman stack + LLM service relink/restart + both health checks).
|
Full-history two-parent import; one repo, one checkout per host, one
|
||||||
* **Units**: frank-default-{small,large,npu}.service now point at `/home/dank/serverAppConfigs/scripts/...`. The retired target checkouts (`/home/dank/vLLM` on Dankserver, `/home/dank/vllmScripts` on Dankclaw) are to be archived on the hosts during the production deploy.
|
`deploy.sh`. The plaintext sudo password from old docs was NOT carried over
|
||||||
* **Secrets**: the live sudo password that the old vllmScripts AGENTS.md documented in plaintext is NOT carried into the merged repo (a bats leak-check guards it). It remains in the git history of both repos on the LAN Gitea — rotation recommended.
|
(a bats leak-check guards it); it remains in both repos' history on the LAN
|
||||||
* **KB links**: `system/index.md`, `system/dankserver.md`, `system/dankclaw.md`, and the root `index.md` updated — the "sibling knowledge bases" section is now a single sibling (serverAppConfigs, which contains the LLM fleet KB at `docs/LLM/index.md`).
|
Gitea — rotation recommended.
|
||||||
* **Port move**: `frank-default-large` (dankclaw) moved from `:52625` to `:7770` (operator request: the primary inference server sits on the standard llama.cpp port). Both LLM services now listen on `:7770`, distinguished by host. `models.json` (`llamacpp (dankclaw)`) and the OpenWebUI quadlet (`OPENAI_API_BASE_URL`) re-pointed to `:7770`. `pi/models.md` updated.
|
|
||||||
* **Homepage design (grilling)**: the `serverAppConfigs` homepage (`docs/Infrastructure/Homepage.md`) is WIP — the Caddy site is live (basicauth 401, `:80` 302) but `/srv/homepage/` is not deployed. Settled design: a single site on Dankclaw (root = user services, `/admin/` = admin services behind a second operator-only password, optional `/how/` tourist page). Status is checked **server-side** — a systemd-timer checker (every 3 min) probes each backend `host:port` and writes a same-origin `status.json`; the page reads only that file (the client-side-ping design is blocked by cross-origin + mixed-content + auth). Full decision log + 38 open questions (Q6–Q43) in `docs/Infrastructure/Homepage Design Grilling.md`. A terminal-TUI-styled HTML progress report was generated for the operator to review and answer the open questions. Full-stack baseline verified: Dankclaw fully up; the Dankserver media stack is down (only Calibre-Web + the LLM are up; no `.env` on the target).
|
|
||||||
|
|
||||||
## 2026-08-21 — Fleet ops: passwordless sudo, Hermes SSH keys, sibling KB links
|
## 2026-08-21 — Fleet ops: passwordless sudo, Hermes SSH keys
|
||||||
|
|
||||||
* **Passwordless sudo**: added `/etc/sudoers.d/dank-nopasswd` (`dank ALL=(ALL) NOPASSWD: ALL`) on both Dankserver and Dankclaw. Verified `sudo -n true` on both. The pre-existing drop-ins (`frank-small` on Dankserver; `frank-large` + `frank-ling` on Dankclaw) grant per-command NOPASSWD for the frank restart scripts and remain in place.
|
`/etc/sudoers.d/dank-nopasswd` on both servers (per-command drop-ins remain);
|
||||||
* **Hermes SSH keys**: the Hermes Agent (podman container `hermes`, project `dankclaw` in `serverAppConfigs`, gateway drops to container uid 10000 = host uid 109999, HOME `/opt/data` on a podman volume) now has a dedicated ed25519 key at `/opt/data/.ssh/id_ed25519` (`hermes-agent@dankclaw`) plus an SSH config for `dankclaw` (192.168.0.143), `dankserver` (192.168.0.235), and the workstation. The public key is in `dank`'s `authorized_keys` on all three machines. Verified from inside the container as the hermes user: all three `ssh <host> hostname` calls succeed.
|
dedicated ed25519 key for the Hermes container, verified into all three
|
||||||
* **Sibling KB links**: `system/index.md`, `system/dankserver.md`, `system/dankclaw.md`, and the root `index.md` now link the two sibling knowledge bases on the workstation: `~/Projects/vllmScripts/` (inference fleet scripts + runbook, index `docs/index.md`) and `~/Projects/serverAppConfigs/` (Podman deploy configs for both servers, index `docs/index.md`).
|
machines.
|
||||||
|
|
||||||
## 2026-08-19 — pi-web-access: SearXNG endpoint re-pointed to dankclaw
|
## 2026-08-19 — pi-web-access: SearXNG endpoint re-pointed to dankclaw (pi era)
|
||||||
|
|
||||||
* **Config**: `~/.pi/web-search.json` — `searxngBaseUrl` moved from the dead `192.168.0.235:8081` (dankserver, connection refused) to `192.168.0.143:8081` (dankclaw; verified live: HTTP 200, 25 JSON results, 0.8 s). Deleted the redundant `provider: "all"` (the `searchProvider: "searxng"` pin already wins in v0.22). Added `maxInlineContentChars: 50000`. Kept `workflow: "none"` and `ssrf.allowRanges: ["192.168.0.0/24"]`.
|
`searxngBaseUrl` moved off dead `.235:8081` to `.143:8081`; pi caches
|
||||||
* **Docs**: `pi/packages/pi-web-access.md` bumped 0.14.0 → 0.22.0 (18-provider chain, config block, active-setup note, GitHub-clone section, yt-dlp present). `agentic-web-research/` SearXNG endpoint references repointed `.235` → `.143` (index, searxng-tuning, vpn-routing).
|
`web-search.json` per process — new endpoint needs a new session. `yt-dlp`
|
||||||
* **Note**: pi-web-access caches `web-search.json` in memory per process — the new endpoint takes effect on the next Pi session, not the running one.
|
installed (2026.07.04).
|
||||||
* **Packages**: `yt-dlp` installed on the workstation (2026.07.04) — YouTube frame extraction now works; `ffmpeg` (n9.0.1) was already present.
|
|
||||||
|
|
||||||
## 2026-08-19 — SearXNG + Gluetun bring-up on dankclaw
|
## 2026-08-19 — SearXNG + Gluetun bring-up on dankclaw
|
||||||
|
|
||||||
Field notes from standing up the search stack (gluetun + searxng + valkey) and confirming agentic JSON search. All four are operational; the SearXNG trio runs via podman-compose (not yet boot-persistent quadlet).
|
Durable field notes (now on concept pages — see
|
||||||
|
[agentic-web-research/vpn-routing.md](agentic-web-research/vpn-routing.md),
|
||||||
|
[searxng-tuning.md](agentic-web-research/searxng-tuning.md),
|
||||||
|
[engineering/infra/containers.md](engineering/infra/containers.md)):
|
||||||
|
manual-WireGuard Proton needs `VPN_SERVICE_PROVIDER=custom` + the single-peer
|
||||||
|
`WIREGUARD_*` env names (not `protonvpn`, not `WIREGUARD_PEERS_*`); gluetun
|
||||||
|
ships no curl and `:8000` is the control server, not health; the limiter's
|
||||||
|
JSON cap (`API_MAX=4`/hour) is hardcoded Python — `pass_ip` is the only
|
||||||
|
bypass; rootless podman strips client IPs on published ports, so SearXNG must
|
||||||
|
sit behind Caddy for XFF; quadlet generator rejects `CapAdd=`/`Device=` and
|
||||||
|
generated units enable via `[Install] WantedBy=default.target`. ComfyUI on
|
||||||
|
gfx1151: build on AMD's ROCm base and filter the torch family out of
|
||||||
|
ComfyUI's requirements or you clobber the gfx1151 kernels.
|
||||||
|
|
||||||
* **Gluetun manual WireGuard**: a specific-server ProtonVPN config (private key + peer + endpoint) uses `VPN_SERVICE_PROVIDER=custom`, NOT `protonvpn`. The `protonvpn` provider is account/token-based and rejects a manually-set endpoint with `endpoint port is set`. Current Gluetun uses the single-peer model: `WIREGUARD_PUBLIC_KEY`, `WIREGUARD_ENDPOINT_IP`, `WIREGUARD_ENDPOINT_PORT`, `WIREGUARD_ADDRESSES` (not the old `WIREGUARD_PEERS_*` names). DNS is Gluetun's own `DNS_*` vars, not the WG `DNS` line.
|
## 2026-08-17 — Fleet redo + vLLM tool calling (pi era)
|
||||||
* **Gluetun healthcheck**: the image has no `curl`, and its `:8000` status server 404s on every path. Use `test: ["CMD","sh","-c","test -e /sys/class/net/tun0"]` — tun0 exists only while the tunnel is connected.
|
|
||||||
* **SearXNG trusted group**: lives in `limiter.toml` (not `settings.yml`). A direct hit on the published port has no `X-Forwarded-For` header, so the bot-detection cannot resolve a client and returns 429 regardless of `pass_ip`. SearXNG must be reached through Caddy (which sets XFF); `trusted_proxies` then needs the podman bridge subnet (dynamic in rootless — pin the network CIDR) so Caddy's source is trusted for XFF extraction.
|
|
||||||
* **Rootless podman gotchas**: short image names do not resolve (no unqualified-search registries) — qualify with `docker.io/`. Binding privileged ports (Caddy 80/443) needs the one-time sysctl `net.ipv4.ip_unprivileged_port_start=80`.
|
|
||||||
* **Repo**: `serverAppConfigs` — added `shared/searxng/limiter.toml` + test seam, wired it into `deploy.sh`, fixed the Gluetun compose (peer env vars, qualified images, tun0 healthcheck), documented the rootless-port + Caddy/XFF gotchas, and skipped the ComfyUI GPU tests (migration pending).
|
|
||||||
* **Boot-persistence (quadlet)**: converted the trio from podman-compose to quadlet so it survives reboot. searxng sets `Network=gluetun.container` (reuses gluetun's netns → exits the tunnel); gluetun + valkey join a pinned `searxng.network` (10.89.2.0/24). Deployed via `scripts/deploy-searxng-quadlet.sh`. Field gotchas (also in `engineering/infra/containers.md`): the generator rejects `CapAdd=`/`Device=` (use `AddCapability=`/`AddDevice=`) and silently skips the unit; generated units cannot be `systemctl enable`d — `[Install] WantedBy=default.target` is the enablement (`is-enabled` = "generated"), and linger makes them boot-start. Verified: all three `is-enabled` = generated, tunnel up (US-WA), agentic JSON search returns 27 on-topic results.
|
|
||||||
* **open-webui moved to dankclaw (quadlet)**: the old open-webui was a bare container on Dankserver (:3000) pointing at a dead `:9090` backend. It now runs on Dankclaw as a boot-persistent quadlet (`scripts/deploy-open-webui-quadlet.sh`): CPU image `:main` (Dankclaw is CPU-only), isolated on `Network=slirp4netns`, published `:3005` (:3000 is Gitea's HTTP port), data at `/home/dank/Containers/open-webui/data`. Default LLM backend is the local vLLM (`host.containers.internal:52625`). The Dankserver `searxng` + `open-webui` bare containers were retired (data dirs preserved). `podman inspect <name>` can match an IMAGE, not just a container — a removed container still "exists" to inspect if its image remains; use `podman ps -a --filter name=` to check containers.
|
|
||||||
* **Model/port mapping corrected (live, 2026-08-19)**: the fleet endpoints are the REVERSE of the 2026-08-17 note. Live truth (probed `/v1/models`): `frank-default-large` is on **Dankclaw :52625** (vLLM, completion) and `frank-default-small` is on **Dankserver :7770** (llama.cpp FastMTP, completion+multimodal). The `serverAppConfigs` docs (Stack, OpenWebUI) were corrected; the sibling `vllmScripts` KB still carries the old mapping and should be updated there.
|
|
||||||
* **SearXNG 429 root cause (the "diva")**: the limiter's JSON cap is `API_MAX = 4` requests/IP/hour and that constant is **hardcoded in Python** (`searx/botdetection/ip_limit.py`), NOT settable in `limiter.toml`. The only bypass is `pass_ip` (SearXNG's `filter_request` returns before `ip_limit` runs when the IP is in `pass_ip`). Second gotcha: **rootless podman strips the client IP on published-port hits** — a request to the published port reaches the container with a slirp/bridge source (`10.88.x`/`10.89.x`), never the real client; only Caddy's `X-Forwarded-For` restores it. So even a LAN-IP curl 429'd. Fix: expand `pass_ip` to loopback (`127.0.0.1/32`, `::1`) + the host's private podman/slirp ranges (`10.88.0.0/16`, `10.89.0.0/16`) + the LAN. Verified: 20 concurrent JSON searches → all 200 (real results), while a spoofed public IP (8.8.8.8) still 429s (limiter still armed). The config file is root-owned (uid of the rootless podman namespace) — update with a one-off `sudo cp` + `systemctl --user restart searxng.service`.
|
|
||||||
* **ComfyUI GPU (gfx1151) — DONE (Option 1/B)**: the deferred GPU ComfyUI is now built and running. It was previously a bare CPU venv (`/home/dank/ComfyUI`, py3.14, `torch 2.13.0+cu130` with no NVIDIA → CPU fallback). Now it's a **container** `comfyui-rocm:gfx1151` built from `dankclaw/Containerfile.comfyui` on AMD's `rocm/vllm:rocm7.12.0_gfx1151_..._py3.12_pytorch_2.9.1` base (the only torch build where `torch.cuda.is_available()=True` on the Strix Halo APU). Key gotchas: (1) the base image already ships the ROCm torch/torchvision/torchaudio, so the Containerfile must **skip the torch family** from ComfyUI's `requirements.txt` (an unpinned `torch` would clobber the gfx1151 kernels) — filter with `grep -viE '^(torch|torchvision|torchaudio)([^a-z]|$)'`. (2) **rootless podman cannot `--group-add render`** (gid 990 is outside the subgid range 100000+); GPU access comes from the `/dev/kfd`+`/dev/dri` passthrough checked host-side (the run user is already in `render`), so add only the `video` group. Verified: `/system_stats` → `cuda:0 Radeon 8060S`, logs show `AMD arch: gfx1151` + active HIP backend. Boot-persistent via a user systemd unit running `podman compose up -d comfyui` (old venv unit saved as `comfyui.service.venv.bak`). The 4 `comfyui_rocm.bats` tests are un-skipped (the group test now asserts video present + render ABSENT).
|
|
||||||
|
|
||||||
## 2026-08-17 — Fleet redo + vLLM tool calling
|
Fleet moved off the pipeline-parallel + NPU topology; Quark W8A8 quant chosen
|
||||||
|
over the Avesed INT8 for clean Qwen3 XML tool calls.
|
||||||
|
|
||||||
* **Update**: `pi/models.md` — providers rewritten for the fleet redo: `llamacpp (dankserver)` at :7770 and `vllm (dankclaw)` at :52625; model selection strategy and live defaults brought in line with the new topology and the Quark W8A8 model.
|
## 2026-08-16 — Gitea boot-persistence fix (dankclaw)
|
||||||
* **Context**: the Frank fleet moved off the pipeline-parallel + NPU topology. `frank-default-large` (llama.cpp FastMTP, IQ4_XS) is on Dankserver; `frank-default-small` (vLLM ROCm, nameistoken Qwen3.6-35B-A3B Quark W8A8 INT8) is on Dankclaw with ngram spec and tool calling. The Avesed INT8 quant's tool-call XML fidelity was poor; the Quark quant follows the Qwen3 XML format cleanly.
|
|
||||||
|
|
||||||
|
`unattended-upgrades` reboot exposed that the compose-era Gitea had no
|
||||||
|
systemd units (~1 h down). Stack rewritten as quadlet (names, `systemd-gitea`
|
||||||
|
network, linger + `WantedBy=default.target`, app.ini env-override gotcha, UID
|
||||||
|
100999 ownership) across `tools/gitea/*`; `podman generate systemd` marked
|
||||||
|
deprecated in [engineering/infra/containers.md](engineering/infra/containers.md).
|
||||||
|
|
||||||
## 2026-08-16
|
## 2026-08-13 — Pi subagent limits recorded (pi era)
|
||||||
|
|
||||||
### Gitea Boot-Persistence Fix (dankclaw)
|
Effective caps: globalConcurrencyLimit 2, parallel.maxTasks 8 / concurrency 4,
|
||||||
|
maxSubagentDepth 2, turnBudget 30; no per-model limit. See
|
||||||
|
[pi/packages/pi-subagents.md](pi/packages/pi-subagents.md).
|
||||||
|
|
||||||
* **Incident**: `unattended-upgrades` rebooted dankclaw at 15:01. The legacy podman-compose Gitea stack had no systemd units, so rootless podman did not auto-start the containers. Gitea was down ~1 hour.
|
## 2026-08-11 — Engineering research area; skills leaderboard verified
|
||||||
* **Update**: `tools/gitea/index.md`, `architecture.md`, `management.md`, `data-persistence.md` — rewritten for the quadlet-managed stack: container/unit names, the `systemd-gitea` network (DNS-enabled), boot persistence (linger + `WantedBy=default.target`), the app.ini env-override gotcha, UID 100999 ownership, incident log.
|
|
||||||
* **Update**: `engineering/infra/containers.md` — marked `podman generate systemd` deprecated (podman 5.7), documented Quadlet with a field-tested gotcha list (default network DNS off, `[Service] EnvironmentFile=` for `${VAR}` expansion, unit naming, bind-mount UID mapping).
|
|
||||||
|
|
||||||
## 2026-08-13
|
Created `engineering/` (SDLC, agent instruction files, stacks, infra,
|
||||||
|
plugins, skills ecosystem, Signal remote) via parallel research fleet.
|
||||||
|
Skills leaderboard replaced with the ranked list from the skills.sh API
|
||||||
|
(zero-based pagination, 200-skill pages).
|
||||||
|
|
||||||
### Pi Subagents Limits
|
## 2026-08-11 — Agentic web research section; exhaustive bundle audit
|
||||||
|
|
||||||
* Added a "Parallel Execution and Limits" section to `pi/packages/pi-subagents.md`.
|
Created `agentic-web-research/` (8 pages) and rewrote them on 2025/2026
|
||||||
* Documented the effective values: `globalConcurrencyLimit` 2 (from `extensions/subagent/config.json`), `parallel.maxTasks` 8, `parallel.concurrency` 4, `maxSubagentSpawnsPerSession` unlimited, `maxSubagentDepth` 2, and `turnBudget.maxTurns` 30.
|
evidence (SearXNG valkey deployment, real engine defaults, limiter budgets,
|
||||||
* Noted that no per-model limit exists; all caps are global to a run or a session.
|
Gluetun pattern, open-model claim triage, MCP transports). Audit: 154+
|
||||||
|
wikilinks resolved, dead external links fixed, `MemorySleepMode=deep`
|
||||||
|
verified valid (systemd 256+), ASD-STE100 style pass. `AGENTS.md` created as
|
||||||
|
the system behavior contract.
|
||||||
|
|
||||||
## 2026-08-11 (continued)
|
## 2026-08-06 — Link fixes; ComfyUI docs; major documentation overhaul
|
||||||
|
|
||||||
### Engineering Research — New Area
|
Fixed 15 broken wikilinks (missing frontmatter) + title mismatches → zero
|
||||||
|
broken. Added ComfyUI pages, per-host system docs, pi config tree, tools
|
||||||
|
entries; orphaned root duplicates moved to `.AGENTTRASH/`.
|
||||||
|
|
||||||
* Created the `engineering/` research area: SDLC and tooling, agent instruction files, language and framework stacks, infrastructure and cloud, pi plugins, skills ecosystem, and Signal remote control.
|
## 2026-07-18 — Maintenance tooling
|
||||||
* Ran a parallel research fleet (scout + 6 researchers) grounded in current 2025/2026 web sources.
|
|
||||||
|
|
||||||
### Skills Leaderboard — Live Verification
|
Expanded `references/okf-maintenance.md` (cross-links, health metrics,
|
||||||
|
troubleshooting); created `core/log.md` and `.okf/config.yaml`.
|
||||||
|
|
||||||
* Queried the skills.sh API (`GET /api/skills/all-time/{page}`) for the authoritative top 50 skills by all-time installs.
|
## 2026-01-15 — OKF conversion
|
||||||
* Replaced the inferred category table in `engineering/skills-ecosystem.md` with the verified ranked list.
|
|
||||||
* Noted the API’s zero-based pagination and 200-skill page size for future queries.
|
|
||||||
|
|
||||||
## 2026-08-11 (continued)
|
Docs bundle converted to OKF v0.1; files migrated into `core/`, `tools/`,
|
||||||
|
`packages/`, `references/` with frontmatter on all files.
|
||||||
### Agentic Web Research — Grounded Revision
|
|
||||||
|
|
||||||
* Ran a parallel research fleet (7 researchers + 2 scouts) for current 2025-2026 evidence on SearXNG tuning, VPN split-routing, agentic search, open models, research orchestration, and MCP.
|
|
||||||
* Rewrote all 8 `agentic-web-research/` pages to match verified facts and the local topology: SearXNG at `192.168.0.235:8081`, llama.cpp inference on `7770` (dankserver/dankclaw) and `8080` (local-ornith), main model `openrouter/deepseek-v4-flash-0731`.
|
|
||||||
* SearXNG: corrected to Valkey deployment, real engine defaults (DuckDuckGo/Wikipedia/Google News active, Google inactive, Bing disabled), limiter budgets, current `outgoing` defaults.
|
|
||||||
* VPN: added Gluetun as the recommended pattern, corrected OpenVPN container joins, added `source_ips`/`network: ipv4`.
|
|
||||||
* Open models: clarified Llama 4 claims (10M ctx = Scout only, single-H100 needs INT4, "beats GPT-4o" is Meta-only), added Qwen2.5-VL-7B as best self-hostable vision model.
|
|
||||||
* Web agents: corrected the Browser Use "87.4%" claim (managed Cloud variant, not open-source; peer-reviewed 44.5% for CUA), added Skyvern.
|
|
||||||
* MCP: updated transports (stdio + Streamable HTTP, legacy SSE), current reference servers, added SearXNG MCP, tightened security rules.
|
|
||||||
|
|
||||||
### Exhaustive Bundle Audit
|
|
||||||
|
|
||||||
* Link audit: resolved all 154+ wikilinks by frontmatter title — **0 broken wikilinks**. Fixed relative-link base errors in `core/index-doc.md`, `packages/index.md`, `pi-web-access.md`, `llama-cpp/setup.md`, `npx-skills/skill-commands.md`, `packages/pi-browser-use/index.md`.
|
|
||||||
* External URL audit: fixed dead links (SearXNG limiter path, earendil-works/pi blob URLs, AYayOS/qs -> outfoxxed/quickshell, badlogic/pi-mono -> earendil-works/pi).
|
|
||||||
* Factual fixes: `core/settings.md` stale JSON (defaultProvider/model/thinkingLevel/changelog-version), `pi/models.md` dankclaw auth, `Sleep-Fix-Guide.md` kernel 7.1.3 -> 7.1.4.
|
|
||||||
* Verified (did not change) `MemorySleepMode=deep`: it is a valid systemd 256+ option.
|
|
||||||
* ASD-STE100 style pass: removed contractions, banned `may`, and prose semicolons across research/tools/pi docs.
|
|
||||||
* Added OKF index frontmatter to `core/index.md` and `skills/index.md`.
|
|
||||||
* Added a "Pi-peon is installed" note to the npx skills ecosystem table and a port-7770 system note to llama.cpp docs.
|
|
||||||
|
|
||||||
### Agentic Web Research Section
|
|
||||||
|
|
||||||
* Created `agentic-web-research/` — seven new concept pages plus index covering self-hosted web agents, SearXNG tuning, VPN routing, agentic search practices, open models, research effectiveness, and model context protocol.
|
|
||||||
* Expanded via parallel research: concrete BM25/Pruning/LLM content filters, credibility heuristics, failure-mode guardrails, MCP server and transport guidance.
|
|
||||||
* Added the section to `index.md` under a new `## [Agentic Web Research]` heading.
|
|
||||||
* Linked `pi/packages/pi-web-access.md` to the new section.
|
|
||||||
|
|
||||||
### System Instruction File
|
|
||||||
|
|
||||||
* Created `AGENTS.md` — system behavior contract with a dedicated layer on parallel subagent execution (one `subagent` call with `tasks` + `concurrency`).
|
|
||||||
|
|
||||||
### Bundle Audit
|
|
||||||
|
|
||||||
* Verified all 154 wikilinks resolve (zero broken).
|
|
||||||
* Removed stale empty `Alienware M16a2 Sleep/Wake Issues.md` duplicate into `.AGENTTRASH`.
|
|
||||||
* ran ASD-STE100 style pass: converted `should` to imperative or `must`, and `e.g.` to `for example` across 10 files.
|
|
||||||
* Removed stale `frank-default-omni` model references and corrected dankclaw port `13305` to `8080` per commit `15d4af4`.
|
|
||||||
* **Resolved**: the live config is authoritative. Updated both llama.cpp servers to port `7770` and set the default provider to `openrouter` (deepseek) per `settings.json`. Dankserver/dankclaw now 7770; main session default is openrouter.
|
|
||||||
|
|
||||||
## 2026-08-06 (continued)
|
|
||||||
|
|
||||||
### Link Fixes
|
|
||||||
|
|
||||||
* Fixed 15 broken wikilinks — added missing YAML frontmatter to `ghostty/index.md`, `tmux/index.md`, `linux-power-management/index.md`, `gitea/index.md`, `gitea/architecture.md`, `gitea/data-persistence.md`, `gitea/management.md`
|
|
||||||
* Fixed title mismatches: `OKF v0.1 Specification` → `Open Knowledge Format (OKF) v0.1 Specification`, `Pi Settings` → `Pi Settings System (settings.json)`, `Alienware M16a2 Sleep/Wake Issues` → `Alienware M16a2 Linux Sleep/Wake Issues`, `Architecture Details` → `Gitea Architecture`, `Data Persistence` → `Gitea Data Persistence`, `Management Guide` → `Gitea Management`
|
|
||||||
* Zero broken links remaining (47 links verified)
|
|
||||||
|
|
||||||
## 2026-08-06
|
|
||||||
|
|
||||||
### ComfyUI Documentation
|
|
||||||
|
|
||||||
* `tools/comfyui/index.md` — ComfyUI setup on dankclaw, architecture, service management
|
|
||||||
* `tools/comfyui/troubleshooting.md` — Common errors, diagnostics, recovery steps
|
|
||||||
* `tools/index.md` — Added ComfyUI entry
|
|
||||||
* `system/dankclaw.md` — Added ComfyUI service, hardware specs, services section
|
|
||||||
|
|
||||||
### Major Documentation Overhaul
|
|
||||||
|
|
||||||
**Created — System Documentation**
|
|
||||||
|
|
||||||
* `system/index.md` — System documentation index with infrastructure diagram
|
|
||||||
* Per-host system pages (overview, hardware specifications, display configuration, network interfaces) for the workstation — retired later; see `/system/network.md` and `/tools/niri/displays.md`
|
|
||||||
* `system/dankserver.md` — Primary llama.cpp server documentation
|
|
||||||
* `system/dankclaw.md` — Secondary llama.cpp server documentation
|
|
||||||
|
|
||||||
**Created — Pi Configuration**
|
|
||||||
|
|
||||||
* `pi/index.md` — Pi configuration overview with architecture diagram
|
|
||||||
* `pi/settings.md` — Main settings.json reference (moved from core/)
|
|
||||||
* `pi/models.md` — models.json provider and model definitions
|
|
||||||
* `pi/packages/index.md` — Installed packages index
|
|
||||||
* `pi/packages/pi-peon.md` — CESP/OpenPeon sound-pack player
|
|
||||||
* `pi/packages/pi-web-access.md` — Web search, fetching, video understanding
|
|
||||||
* `pi/packages/pi-subagents.md` — Core subagent delegation
|
|
||||||
* `pi/skills/index.md` — Installed agent skills inventory
|
|
||||||
|
|
||||||
**Created — Tools**
|
|
||||||
|
|
||||||
* `tools/cachyos/index.md` — CachyOS documentation (kernel, repos, gaming stack)
|
|
||||||
* `tools/noctalia-shell/index.md` — Noctalia Shell with Niri integration
|
|
||||||
* `tools/applications/index.md` — Default applications reference
|
|
||||||
|
|
||||||
**Updated**
|
|
||||||
|
|
||||||
* `core/settings.md` — Updated with current config, subagent overrides, cross-links
|
|
||||||
* `tools/niri/index.md` — Rewritten with actual system configuration
|
|
||||||
* `tools/index.md` — Added CachyOS, Noctalia, Applications entries
|
|
||||||
* `index.md` — Complete rewrite with new sections and cross-links
|
|
||||||
|
|
||||||
**Moved / Cleaned**
|
|
||||||
|
|
||||||
* `tmux.md` → `tools/tmux/index.md`
|
|
||||||
* Orphaned root duplicates → `.AGENTTRASH/` (npx-skills.md, okf-maintenance.md, okf-spec.md, vercel-labs-skills.md, vercel-labs-skills-find-skills.md, ornith-llama-cpp-setup.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2026-07-18
|
|
||||||
|
|
||||||
* **Enhancement**: Expanded `okf-maintenance.md` — added cross-link management, index management, health metrics, troubleshooting, and quick reference card
|
|
||||||
* **Creation**: `core/log.md` — change log for the core documentation directory
|
|
||||||
* **Creation**: `.okf/config.yaml` — bundle tooling configuration
|
|
||||||
* **Update**: `core/index.md` — added log.md entry and cross-references
|
|
||||||
|
|
||||||
## 2026-01-15
|
|
||||||
|
|
||||||
* **Initialization**: Converted docs bundle to OKF v0.1 format
|
|
||||||
* **Migration**: Migrated files to `core/`, `tools/`, `packages/`, `references/` directories
|
|
||||||
* **Applied**: OKF frontmatter to all files
|
|
||||||
|
|
||||||
## 2025-08-05 — Gitea documentation
|
## 2025-08-05 — Gitea documentation
|
||||||
|
|
||||||
Added ](tools/gitea/index.md) documentation to tools. Covers:
|
Added [tools/gitea/index.md](tools/gitea/index.md) (topology, management,
|
||||||
|
data persistence) to the tools index.
|
||||||
* [[architecture|architecture]] — Container topology, Caddy reverse proxy, network diagram.
|
|
||||||
* [[management|management]] — Podman commands, upgrades, troubleshooting.
|
|
||||||
* [[data-persistence|data-persistence]] — Volume mounts, backup strategy, restore procedures.
|
|
||||||
|
|
||||||
Updated tools index.
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
# Pi Packages
|
|
||||||
|
|
||||||
Self-contained extensions and tools bundled as npm packages, distributed via the `pi install` system. Each package contains extensions, skills, prompts, or themes.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Concept Files
|
|
||||||
|
|
||||||
* [pi-browser-use](pi-browser-use.md) — Browser automation extension wrapping chrome-devtools-mcp with `browser_` prefix, full config reference, session modes, and vision model setup.
|
|
||||||
* [pi-permission-system](pi-permission-system.md) — Permission enforcement and policy configuration system with 10 interconnected docs.
|
|
||||||
|
|
||||||
## Cross-References
|
|
||||||
|
|
||||||
- All packages are installed via `pi install npm:<name>` and loaded by pi's extension system.
|
|
||||||
- See [Npx Skills](../skills/npx-skills.md) for the pi-package distribution mechanism and package.json manifest format.
|
|
||||||
Loaded 100 of 166 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user