feat(hodlmm-bin-guardian): detect grid-edge pinned pools — distinct PINNED action instead of an unpassable slippage HOLD#408
Conversation
…NNED action instead of unpassable slippage HOLD Field audit F-5 (HIGH). At the grid edge the active bin freezes by contract design while the market moves — pool-vs-market divergence becomes structural and a slippage-gated monitor deadlocks (can neither repair nor exit). Observed live ~36h on dlmm_3, 2026-07-13. No official pinned signal exists in the API surface; detection is derived. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
arc0btc
left a comment
There was a problem hiding this comment.
Good catch, and a clean fix. We've hit exactly this kind of deadlock pattern before on our own rebalancer skills (hodlmm-move-liquidity's auto mode enforces a 4h cooldown + gas/slippage gates the same way this skill does) — a slippage gate that can never clear because the divergence is structural rather than transient is a real trap for autonomous agents, not a hypothetical.
What works well:
- The derivation logic is sound:
atGridEdge = active_bin_id <= 0 || active_bin_id >= 1000correctly matches the unsigned grid convention used elsewhere in this skill family (e.g.hodlmm-move-liquidity's bin examples center around 500, consistent with a 0–1000 grid). pinned = atGridEdge && !slippageResult.okis a conservative definition — being at the edge alone doesn't trigger PINNED, only edge + failing slippage gate. That avoids false positives when a pool is legitimately parked at an extreme bin but still tracking market price fine.- Branch ordering is correct:
pinned && !inRangeis checked before the genericinRange/!canRebalancebranches, so a pinned pool where the user's position happens to still be in range correctly falls through to HOLD (earning fees) rather than being mislabeled PINNED — pinning only matters when a rebalance decision is actually being blocked. - Purely read-only/derived — no transaction behavior changes, verified against the diff (no touches to signing, gas estimation, or the actual rebalance path).
- Field-audit framing (specific incident, specific pool, specific duration) is exactly the kind of operational evidence that makes a "trust me" derived-signal PR credible.
[suggestion] Add a PINNED example to the SKILL.md output docs (hodlmm-bin-guardian/SKILL.md)
The doc's "Live terminal output" section has worked examples for doctor, the no-position HOLD, and the in-range HOLD, but no PINNED example — and the two existing run JSON blocks weren't updated to show the new at_grid_edge/pinned fields (they still show only the pre-PR field set). Not blocking since the new prose section at the bottom accurately describes the behavior, but a full JSON block showing "action": "PINNED — ..." alongside at_grid_edge: true, pinned: true would make the output contract easier to consume for anyone building automation on top of this skill without reading the source.
[nit] The inline comment block above the new logic (hodlmm-bin-guardian.ts:364-372) is thorough to the point of restating the SKILL.md prose almost verbatim. Not a problem, just flagging in case you want a shorter comment + pointer to the SKILL.md section instead, to avoid the two drifting out of sync later.
Operational note: We haven't hit a grid-edge deadlock ourselves on hodlmm-move-liquidity, but the 4a284c7-era gate stack (volume/slippage/gas/cooldown refusals) is structurally identical to this skill's, so this pattern is worth watching for on our side too — a HOLD that can never resolve on its own is a silent failure mode for anything running unattended.
… on existing examples Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Suggestion taken — pushed a SKILL.md addition with a full PINNED JSON example ( 🤖 Generated with Claude Code |
biwasxyz
left a comment
There was a problem hiding this comment.
Reviewed with independent verification against the deployed core contract and live BFF API. The detection logic and its factual basis check out completely. Two doc/robustness items to address — neither invalidates the approach.
Verified ✅
- Scale is right.
active_bin_idcomes from the BFF bins endpoint, which uses the unsigned 0–1000 grid (live check today: dlmm_1 returns 1001 bins with ids 0–1000, active 489; other pools at 546/867 — clustered near 500, not 0). The contract internally uses signed −500..+500 (MIN_BIN_ID -500/MAX_BIN_ID 500, dlmm-core-v-1-1:86-87), and the API remaps — the guardian reads the API value, so<= 0 || >= 1000is the correct edge test, and the comment "0 = raw −500 floor, 1000 = raw +500 ceiling" is accurate. - Pinning is real contract behavior. Deployed source confirms the active bin only decrements while
(> bin-id MIN_BIN_ID)(swap x-for-y, ~:1338) and only increments while(< bin-id MAX_BIN_ID)(mirrored, ~:1481) — at the edge it stays put and pool price freezes, so pool-vs-market divergence there is structural, exactly as described. - Branch ordering is sound.
pinned && inRangecorrectly falls through to HOLD (still earning at the edge; the data fields still expose it), and every state that now reaches PINNED previously landed in the unpassable "HOLD — rebalance blocked: price slippage" deadlock — no state that could REBALANCE before can PINNED now.slippageResultis computed unconditionally before the chain;.pctis always a number. - No code in the repo parses the guardian's action string (checked all consumers), so the new vocabulary breaks nothing programmatically.
Please address
-
The docs' action vocabulary wasn't updated — and docs are the consumer here. The SKILL.md Output-contract table (line 162) still says the action is "
HOLD,REBALANCE, orCHECKwith reason", and AGENT.md:44 + its data schema (lines 45-69) don't mentionPINNED,at_grid_edge, orpinnedat all. Since downstream consumers are LLM agents reading these docs as the vocabulary, only appending a prose section at the tail partially defeats the PR's purpose. Please update the table and AGENT.md. -
Degraded API response manufactures a false PINNED.
active_bin_iddefaults via?? 0(ts:171) and the response'ssuccess/errorfields are ignored — a bins response missing the field yieldsactive_bin_id = 0→atGridEdge = true, andpriceByBinId.get(0) ?? 0→ pool price 0 → 100% "divergence" → slippage fails → confidentPINNEDtelling the agent to route to exit/withdraw. Before this PR that state was a blocked HOLD; now it's an action recommendation. Suggest guarding PINNED on the response actually carryingactive_bin_id(orpriceByBinId.has(active_bin_id)).
Nits
- Pre-existing, related:
doctortreatsactive_bin_id > 0as healthy (ts:438), so a genuinely pinned-at-0 pool makes doctor report degraded — worth a follow-up. - Script
.version("2.1.0")not bumped.
Approving direction; would like item 1 (docs) and ideally item 2 fixed before merge.
|
Timing note: my review crossed with your
The detection logic itself is verified and solid (unsigned scale confirmed against live BFF data; edge-freeze confirmed in deployed dlmm-core-v-1-1 source) — these two are the gap between good code and a safely consumable contract. [Review assisted by Claude Code] |
…md schema; guard PINNED against degraded bins reads; bump version - Docs are the consumer contract for LLM agents: output table now lists PINNED and the at_grid_edge/pinned fields; AGENT.md schema and a PINNED section added. - Degraded-read guard: a bins response missing active_bin_id (defaulted 0) plus a missing price (pool price 0 => fake 100% divergence) previously became a confident false PINNED action recommendation; edge detection now requires the read to verifiably carry the active bin and a nonzero price. - .version 2.1.0 -> 2.2.0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Both items fixed (pushed):
Nits: 🤖 Generated with Claude Code |
biwasxyz
left a comment
There was a problem hiding this comment.
Verified 3f38ddaa: PINNED and both data fields are now in the canonical SKILL.md output-contract table and AGENT.md schema, and the degraded-read guard closes the false-PINNED path (atGridEdge now requires the response to verifiably carry the active bin with a positive price — documented accurately in both docs). Version bumped too. Approving.
Problem
When price falls off (or rises past) a DLMM pool's bin grid, the active bin freezes at the edge by contract design (dlmm-core only walks the active bin while
bin-id > MIN_BIN_ID/< MAX_BIN_ID). Pool price then stops tracking the market, so pool-vs-market 'slippage' becomes structural — and a slippage-gated monitor deadlocks: it can neither rebalance nor recognize why. Observed live on dlmm_3 (2026-07-13): 1.4–4.8% divergence for ~36 hours at unsigned bin 0. There is no pinned/at-edge signal anywhere in the official API surface (verified against bff-api source and the wiki) — it must be derived.Change
at_grid_edge(unsigned active bin ≤ 0 or ≥ 1000) andpinned(at edge AND slippage gate failing) added to the JSON output.PINNEDaction instructing: do NOT rebalance or blind-swap; route to exit/withdraw (withdraw minimums remain enforceable while pinned) or escalate.Read-only skill; no transaction behavior changes. Basis: 2026-07-15 contract-source audit + live incident. Full audit: https://github.com/k9dreamer-graphite-elan/guides-for-ai-bitcoin-agents (Handbook v0.10).
🤖 Generated with Claude Code