live: heartbeat sync 2026-10-30T21:32:40-07:00 (Refs #11)

This commit is contained in:
Frank committed 2026-10-30 21:32:40 -07:00
1 parent d03fe57bbd
commit d627368f71
13 files changed
+280 -26

No files matched your search

@@ -1,9 +1,9 @@
---
name: agenttrash-purger-invocation
description: "Invoke the .AGENTTRASH purger (scripts/purge-agenttrash.sh) — plus its two refusal traps: ENOSPC masquerading as a stale lock, and an unreadable container-storage directory poisoning the whole root scan."
description: "Invoke the .AGENTTRASH purger (scripts/purge-agenttrash.sh) — plus its refusal traps (ENOSPC as a fake stale lock, unreadable dirs poisoning a root) and the per-root scope trap."
---
# Purging .AGENTTRASH — invocation, and the two traps that make it refuse
# Purging .AGENTTRASH — invocation, and the traps that make it refuse or over-reach
`scripts/purge-agenttrash.sh` (serverAppConfigs, merged in PR #79 `fad87aa`) is the tool form of the fleet's deletion policy: files land in `.AGENTTRASH`, and an approved purge runs this instead of being improvised per session.
@@ -19,6 +19,10 @@ sh scripts/purge-agenttrash.sh --execute /some/root # scope to one root (defaul
- Every printable line is fixed at plan time in `$HOME/.agenttrash-purge.lock/plan`, so dry-run and `--execute` output cannot drift. Exit `0` ok · `1` any refusal or failure · `2` usage.
- Deleting a big staged item by hand (e.g. `find <dir> -depth -delete`) is the fallback **only** when the tool cannot run at all — see trap 1 — and the operator's purge approval covers exactly that.
## Trap 0: the approval is per item, the scope is per root
The plan verb `empty <root>/.AGENTTRASH` means **the whole trash directory**, not the one item the operator approved. So before `--execute`, list the root and prove it holds only the approved staged item — `ls -A <root>/.AGENTTRASH && du -sh <root>/.AGENTTRASH/*` — and note that a second `.AGENTTRASH` deeper in the tree is also in scope (`plan remove` on it = it was empty, safe). Measured 2026-10-30 on dankserver: `/mnt/entropy/.AGENTTRASH` held exactly the approved `20261030-movies-monk` (124 files, 32 G) plus an empty `qwen38-w5nl64/.AGENTTRASH`; plan → `--execute /mnt/entropy` reported `removed=2 deleted=2 refused=0` and free space rose 187 G → 218 G.
## Trap 1: a full filesystem makes it lie about a lock (measured 2026-10-30)
With **0 bytes free**, three invocations refused with
@@ -39,4 +43,4 @@ and no lock directory existed anywhere. The script writes its lock *and* its pla
## Verified good path (dankserver, 2026-10-30)
After freeing space by hand: `/tmp`-scoped plan → `--execute` deleted the staged item and reported `removed=1 deleted=1`; a second `--execute /mnt/entropy` cleaned that volume's root (`refused=0`). The cleanup log carries one dated line per run; read it instead of re-deriving what happened.
After freeing space by hand: `/tmp`-scoped plan → `--execute` deleted the staged item and reported `removed=1 deleted=1`; a second `--execute /mnt/entropy` cleaned that volume's root (`refused=0`). A 32 G staged item on an 87 T volume scans + purges in ~90 s per pass (the scan, not the deletion, is the slow part). The cleanup log carries one dated line per run — both the dry run and the execute log the verb `plan`, so tell them apart by `removed=`; read it instead of re-deriving what happened.
@@ -1,24 +1,42 @@
---
name: line-edit-anchor-discipline
description: "Avoid the recurring edit-tool line-op traps: empty PUT deletes lines, PUT ranges swallowing multi-line block delimiters, and anchors landing mid-expression. Use whenever issuing CUT/PUT line edits on tagged files."
description: "Avoid the recurring edit-tool line-op traps: empty PUT deletes lines, PUT ranges swallowing neighbours, wrong-anchored PUTs replacing the wrong line, literal markdown bullet rows arriving verbatim as \"+-\"."
---
# Line-edit anchor discipline (CUT/PUT ops on tagged files)
# Line-edit anchor discipline
Five self-inflicted syntax wounds in one session (franksign, 2026-10-21) all trace to three rules.
The `edit` tool addresses lines, not intent. Three traps cost real time; all three
fail *silently* or with a repair notice that is easy to skim past.
## Rules
1. **An empty PUT body DELETES the targeted lines** (tool warns: "Interpreted an empty PUT body as deletion"). Twice it ate a whole log entry / a closing `}`. For no-ops use nothing; for deletes use explicit `CUT`.
2. **Never PUT a range whose endpoints cut through a multi-line block.** If a `| {`-style union variant, a frontmatter `---` fence, or a `test(...)` closer straddles the range boundary, the replacement swallows the opener/closer and strands the body. Pre-condition: line N-1 and M+1 must be block-complete. Fix-after-the-fact pattern: read the broken region raw, then `PUT k.=k:` to restore the single swallowed line (e.g. the missing `---`, the orphaned ` | {`, `}` → `});`).
3. **Search-hit reads do not authorize PUT.** A grep/folded-summary read marks lines as "never displayed"; the edit tool refuses and echoes actual content. That refusal is a free verification — re-check the echoed lines match intent, then straight-retry with the same tag.
## 1. Empty PUT deletes the range
## Costly mistakes seen
- Empty `PUT 7-7:` deleted a just-written log entry; restoring required re-issuing the full entry text.
- `PUT 6.=7` replacing `timestamp`+`---` dropped the frontmatter close fence → lint-breaking file silently.
- `PUT 10.=11` on a union type swallowed ` | {` opening a 12-line variant → cascade of LSP "unexpected label" warnings.
- `CUT` of a stranded opener left its dangling `.json())` behind; balance errors surface only at EOF (`expected ')' but file ends`) — locate by reading regions raw, not by widening the payload.
`PUT n.=m:` with no body rows deletes lines n..m. To insert, use `PUT >n:`
(after line n) or `PUT <n:` (before line n). Never send a PUT range whose body is
empty unless deletion is the goal.
## Discipline
- After any edit that reports a syntax error or a warning: read the touched region with `:raw` before the next op; fix the single wrong line, do not resend the block.
- "no change: byte-identical" means your anchor math is off — re-read, don't append more content.
- Prefer replacing whole coherent blocks (start line = block start, end line = block end) over edits that end mid-statement.
## 2. PUT ranges must be the complete final content of exactly those lines
- Replacing one line: `PUT n.=n:` with exactly one body row. If the body carries
the *previous* line as its first row (a copy-paste of the anchor), that line is
duplicated and the real target line is overwritten — no error, and the
"auto-repaired replacement boundary" note is the only hint.
- Anchor only on lines the current `[path#tag]` display actually showed. If the
tool answers "anchors to lines you never displayed", the line numbers are
wrong: re-read that range, then re-issue. Do not retry blind.
- Multi-op edits: order ops bottom-up (highest line numbers first) so earlier
inserts cannot shift later anchors.
## 3. Literal `- ` rows (markdown bullets) cannot go through `edit`
A body row starting with `-` parses as a CUT op. The error says to prefix it with
`+` (`+- item`) — **that advice is wrong in practice**: the file ends up
containing the literal `+- item`. When the content legitimately needs leading
`- ` bullets (prompt law text, changelog entries, lists), write it with `write`
after a full `read` of the file, or restructure the line to avoid the leading
dash.
## Verification habit
After any PUT that replaces a range, `read` the touched range (not the whole
file) and confirm the neighbouring lines survived — especially the line after the
range, which is what a swallowed boundary eats. Cheap; catches the silent cases.
@@ -0,0 +1,75 @@
---
name: magic-number-provenance-audit
description: "Audit every numeric threshold in a change before shipping it: classify provenance (inherited port / declared authority / invented), delete invented arithmetic, and replace budgets with derivations quoted from the repo's own config. Use when adding or reviewing a cap, window, timeout, ratio, or size ladder, or when asked \"what's with the magic numbers?\"."
---
# Magic-number provenance audit
A threshold with no source is a bug with a comment on it. The deliverable is either a
derivation from an authority the repo already trusts, or deletion of the arithmetic.
## 1. Census, do not defend
Grep the changed paths for every numeric literal (exclude the module being rewritten so
its own digits don't hide the pattern):
```
grep -rn "1536\|4000\|700\|6000\|n_ctx\|contextWindow\|maxTokens\|tokens" src/*.ts
```
Table per number: value | file:line | **provenance** | verdict. Provenance is exactly one of:
- **inherited port** — copied from upstream, pinned by a contract test. Keep, but say so in
the doc comment.
- **declared authority** — a field the runtime/fleet config already publishes.
- **derived** — computed from beats it guards (e.g. lease TTL = 3 heartbeats).
- **invented** — yours. This is the finding. Say "mine, invented", not "chosen".
Also flag the *application* of a number, not just its value: a slice like
`text.slice(0, MAX_INPUT)` is a **head-only silent drop** (loses the tail of a dump);
a floor is a different animal from a cap even at the same digit.
## 2. Find the authority before writing a digit
Rank of acceptable sources, best first:
1. **The runtime's own published field.** `~/.omp/agent/models.yml` model rows carry
`contextWindow` (PER-SLOT for llama.cpp: it divides `--ctx-size` across slots) and
`maxTokens`. The fleet's own clamp law is quoted in the comments there — reuse its shape:
`chars = (contextWindow - maxTokens - 4096) * 4 - lawChars` (`4` = the file's `chars/4`
token estimator, `4096` = its clamp margin). Then every figure in the derivation is a
quotation, not a guess.
2. **A shipping style/behaviour law in the repo.** e.g. "two or three sentences, one breath
each" → the ceiling belongs in the prompt as *words the model reads* ("at most three
sentences; fewer if little happened"), not as `sqrt(chars / 1200)` in code.
3. **A physical constant of the medium** (speech rate, packet size), measured against the
actual device. Last resort: it is still a number you must defend.
Traps when parsing that config: commented decoy lines (`# contextWindow: 200,000` with a
comma) must be skipped, so strip only lines whose trimmed form starts with `#`; the row
scanner that returns on the `id:` line never sees the fields below it — keep scanning to the
next `- id:`/provider key.
## 3. Encode the fix so the digit cannot come back
- Make the budget a **required parameter** of the pure module (no default: a module with no
default cannot guess); the caller that resolved the endpoint does the derivation.
- A derived budget that comes out non-positive is a **refusal** (`null`, zero requests),
never a truncation — the caller's cursor/queue must hold and retry.
- When an old cap must survive as a fallback for endpoints that declare nothing, rename it
to what it is (`INHERITED_FLOOR`) and comment: floor, used only when nothing is declared.
## 4. Tests that pin the law, not the digit
- **Size-invariance:** assert the instruction/behaviour is *identical* for a tiny input and a
huge one, proving volume no longer drives the removed ladder.
- **Honours caller budget:** same span folded under an injected small window.
- **Refusal, not truncation:** zero-window → null, zero model calls.
- **DELETE** tests that pinned the deleted arithmetic; never re-pin them (house rule).
- Pinning shipped prompt *words* is legitimate where the repo already does (prompt-contract
test precedent); pinning an env/config value is a config assertion and belongs nowhere.
## 5. Report
Table first, verdict per number, and the net list of digits that remain after the change —
each with its quoted source. If any digit survives with no source, that is the finding.
@@ -0,0 +1,47 @@
---
name: media-journal-chain-verification
description: "Verify a journal-guarded media relayout end to end: counts/bytes/size-mismatch from the JSONL journals, plus cross-journal accounting that proves \"missing\" destinations were moved onward rather than lost."
---
# Verifying a media relayout across a chain of journals
`scripts/media-apply.py` (serverAppConfigs) writes a write-ahead JSONL journal beside the library root — one line per intent, `op` ∈ `mkdir|move`, a `move` record carrying `src`, `dst`, `bytes`, `mode`. Multiple runs over the same library produce **several journals** (relayout, then TV move, then duplicate staging), and the same file legitimately appears in more than one. Verifying one journal in isolation then reads as data loss.
## Verify counts and bytes from the journals, never from scratch files
`/tmp` and shell history are not records; the journals are. Read-only pass over every journal:
```python
import json, os
def recs(j): return [r for r in (json.loads(l) for l in open(j) if l.strip()) if r.get("op")=="move"]
for j in journals:
m = recs(j)
gone = sum(1 for r in m if not os.path.exists(r["src"]))
at = sum(1 for r in m if os.path.exists(r["dst"]))
bad = sum(1 for r in m if os.path.exists(r["dst"])
and os.path.getsize(r["dst"]) != r.get("bytes"))
print(os.path.basename(j), len(m), sum(r.get("bytes",0) for r in m)/2**30, gone, at, bad)
```
Assertions that matter: `src-gone == moves` (nothing half-moved), `size-mismatch == 0` (no truncation or clobber), `dst-present == moves` **for the newest journal only**.
## The chain step: account for every missing destination
An older journal's `dst` disappears when a later journal moves the file onward (episodes shelled out to the TV library, duplicates staged into `.AGENTTRASH`). Prove it instead of asserting it:
```python
later = {}
for j in later_journals:
for r in recs(j): later.setdefault(r["src"], []).append(os.path.basename(j))
missing = [r["dst"] for r in older_recs if not os.path.exists(r["dst"])]
print(len(missing), sum(1 for d in missing if d in later),
[d for d in missing if d not in later])
```
Expect `unexplained = 0`. Measured 2026-10-30 on dankserver: 8,189 movie moves / 22,380 GiB with 174 destinations gone — exactly 124 (duplicate staging) + 6 (hand-filed strays) + 44 (mover episodes), `accounted-by-later-journal=174 unexplained=0`, zero size mismatches across all three journals.
## Post-purge consequence to state in the report
After an approved `.AGENTTRASH` purge the staging journal still parses, but `dst-present=0`: `media-apply.py rollback` on it recreates the folders **empty**. Say so — that journal is a directory-shape undo, not a byte restore; the keeper copy is the one that stayed in the library.
Do not trust a `dst-present` shortfall as loss until the cross-journal check has run, and never report counts from a `| grep -c` pipeline — mask-free exit status only.
@@ -0,0 +1,70 @@
---
name: omp-session-journal-cursor-access
description: "Read a live omp session's transcript mid-run from an extension (cursor = journal entry id via ctx.sessionManager), with the entry-type noise list, append lag, and the ctx.setInterval metronome."
---
# Reading the live session journal from an extension
Use when an extension must summarise/narrate/index **everything said since some point**
rather than reacting to one event payload, or when you need a durable "where I left off"
pointer into a session. Verified 2026-10-30 with a throwaway probe under `omp -p`
(v18.12.5), 1 s ticks over a two-tool-round run.
## The accessor (no plugin install needed)
`ctx.sessionManager` is on every `ExtensionContext` (`ReadonlySessionManager`,
`@oh-my-pi/pi-coding-agent/dist/types/extensibility/extensions/types.d.ts:485`):
```
getEntries() getBranch() getEntry(id) getLeafId() getLeafEntry()
SessionMessageEntry = { id, type: "message", message: AgentMessage }
```
- **The cursor is an entry id.** Persist `getLeafId()` (or the id of the last entry you
actually consumed); "everything since" = entries after that id. Advance is O(1).
- **`getEntries()` grows mid-run** — proven by a timer, with no handler help:
`entries=3 → 6 → 7 → 9 → 11` while `idle=false`, leaf ids rotating.
- **Append lags `message_end` by ~0.5–1 s.** So "the live edge" always under-reads by a
second. Consume only up to the last id you actually read; the sub-second tail rides into
the next window. Never advance past what you read.
- **`ctx.setInterval(cb, ms)` / `ctx.setTimeout`** (`types.d.ts:555-572`): throws contained,
`unref`'d, auto-cleared on `session_shutdown`. Use these, never raw `setInterval`.
`ctx.isIdle()` is the cheap "is this session even working" guard.
## The journal is not the conversation (allowlist, never denylist)
`agent_end` reported `msgs=6` while the journal held `11` entries. Bookkeeping types in the
union: `custom` (zero content, ~3 per tool round), `model_usage`, `thinking_level_change`,
`mode_change`, `model_change`, `service_tier_change`, `label`, `title_change`,
`ttsr_injection`, `session_init`, `credential_pin`, `reset_boundary`. Speech-relevant:
`message` (roles `user` / `assistant` / `toolResult`) and, as context, `compaction` /
`branch_summary`. New types appear on the harness's own schedule → an allowlist keeps them
silent until someone decides they are speech.
Re-export the per-message readers instead of re-parsing content: an assistant message's
narration-flagged `thinking` part is **answer text**, unflagged `thinking` is deliberation
(`narration: true` discriminator).
## Robustness rules that came out of the failure modes
- Empty window ⇒ leave the cursor where it was (nothing new must not read as news).
- Cursor id no longer in the journal (compaction, branch rewrite) ⇒ treat as "from the top
of what is left", never as "nothing" — a lost pointer must not mute a session forever.
`session_compact` and `session_branch` are dispatched events if you want to re-anchor eagerly.
## Probe recipe (proves any of the above in ~60 s, touches nothing live)
Project-local extensions load without installing: write `<run-dir>/.omp/extensions/probe.js`
(default-export `function (pi)`), log to an absolute path, then
```bash
: > /tmp/probe.log
timeout 220 omp -p --thinking off --cwd <run-dir> \
"Run these bash commands strictly one after the other: first: sleep 7; echo FIRST. then: sleep 7; echo SECOND. Then reply: FIRST SECOND"
```
Register the tick inside `session_start` (`ctx.setInterval(() => stamp(info(ctx)), 1000)`),
and give every entry you expect to exclude a speech-shaped payload with a sentinel word —
an absence assertion with an empty producer proves nothing. Do NOT pass `--no-session`:
the journal is the thing under test. Teardown: nothing to undo (no plugin link, no
`~/.omp/plugins/package.json` write) — delete the run dir.
@@ -0,0 +1,32 @@
---
name: radiance-endpoint-silent-wedge-triage
description: "Diagnose a radiance (frank-archon) endpoint that is wedged or crawling while systemd says active(running) and /health answers 200 — separate engine-state wedge from config, hardware, swap and clock causes, and prove the restart fixed it."
---
# Silent wedge: `active (running)` + `/health` 200, but no throughput
For radiance endpoints (`frank-archon`, root unit whose `ExecStart` is `exec podman wait`). The unit can only exit when the container dies, so a stalled engine loop **never** triggers `Restart=always`, `up` stays `1`, and Alertmanager stays empty (every rule keys on `up==0`, `num_requests_waiting>0`, or TTFT p95). Verified 2026-10-30 21:00 on dankserver.
## Triage ladder (in this order — each step kills a class)
1. **Front end vs engine split.** `curl -w '%{time_total}' /health` vs `/v1/models`. A 0.4 ms `/health` beside a 20 s `/v1/models` timeout = front end alive, engine loop stalled.
2. **Unit truth, not the port.** `systemctl status frank-archon -n 0` + `systemctl show -p MainPID,DropInPaths,ExecMainStartTimestamp`. A 4 h-old unit on a 2-week-old host means a restart happened today — cross-check `journalctl -u <svc> --since <day>` for `Starting/Stopping/exit` and `git log -- scripts/<svc>.*` to see whether a deploy caused it. Non-empty `DropInPaths` = leftover bench arm (separate trap).
3. **Container storage namespace.** The unit is `User=dank`, so the container lives in **rootless** storage: `sudo podman ps -a` lists nothing and `podman inspect` says `no such object` — query as the user, without sudo.
4. **Engine's own voice (the decisive artifact).** `podman logs --timestamps frank-archon | grep "E device:"`. `a synchronisation has seen no queue advance for 5 s` naming `barrier-and dep[0]=1` on both ranks = the TP all-reduce never completed. Timestamp the first occurrence; that is the incident start.
5. **Rule out the engine's own suggested causes before believing anything else:** `cat /sys/module/amdgpu/parameters/cwsr_enable` (its message blames `cwsr_enable=0`; `1` falsifies it). Swap: read `/proc/<engine-pid>/status` VmSwap — do NOT infer from `free`/`/proc/swaps` totals (host-wide swap can be 32 GiB while the engine holds 378 MiB). Clocks during load: fire a request, then `rocm-smi --showclocks --showperflevel --showpower` + `/sys/class/drm/cardN/device/gpu_busy_percent` — 100 % busy at full sclk means neither throttle nor starvation.
6. **Quantify the collapse from counters, not vibes.** `radiance:engine_step_seconds_decode_total` ÷ `radiance:engine_steps_decode_total` = mean decode step time; diff two scrapes (Δseconds ÷ Δsteps) for the current rate. Compare to the shape's recorded baseline in the endpoint's vault page (15.5 ms at frank-archon's 4×200000 fp8 shape; 209 ms = 13× collapse).
## Proof of fix (same-request A/B, not a health check)
Submit one fixed request before and after the restart and diff the engine's own `timings` object — an 8-token probe measures only first-token noise, use `max_tokens>=128`:
```
curl -sS -m 240 -o /tmp/q.json -X POST :7770/v1/chat/completions \
-H 'Content-Type: application/json' -d @/tmp/query.json # then read timings.predicted_per_token_ms / predicted_per_second / ttft_ms / draft_n / draft_n_accepted
```
Before/after on the identical request: 90.76 → 11.06 ms/token, 11.02 → 90.40 t/s, ttft 420 → 148 ms, acceptance unchanged (266/266). Then re-read the boot ledger (`elastic / kv cache / vram_weights / unclaimed / sampler rows`) and confirm it matches the page's recorded ledger — a successful restart proves nothing about the flags. Close with the restore oracle: `is-active` **and** `is-enabled` **and** no `/run/systemd/system/<unit>` mask symlink **and** container owned by the unit **and** `DropInPaths` empty.
## Law
A wedged radiance engine has **no in-place recovery** (its own wording: "a queue the driver evicted and could not restore") — `systemctl restart` is the only cure, and the fix must be proven by a rate, never by `/health`. Durable gap: no alert fires on a live-but-stalled endpoint; the missing rule is a decode-step-time ratio gated on `up==1` in `shared/prometheus/rules/llm-fleet.yml`.