Skip to content

feat(hodlmm-bin-guardian): detect grid-edge pinned pools — distinct PINNED action instead of an unpassable slippage HOLD#408

Merged
biwasxyz merged 3 commits into
aibtcdev:mainfrom
k9dreamer-graphite-elan:audit/guardian-pinned-detection
Jul 16, 2026
Merged

feat(hodlmm-bin-guardian): detect grid-edge pinned pools — distinct PINNED action instead of an unpassable slippage HOLD#408
biwasxyz merged 3 commits into
aibtcdev:mainfrom
k9dreamer-graphite-elan:audit/guardian-pinned-detection

Conversation

@k9dreamer-graphite-elan

Copy link
Copy Markdown
Contributor

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

  • Derived detection: at_grid_edge (unsigned active bin ≤ 0 or ≥ 1000) and pinned (at edge AND slippage gate failing) added to the JSON output.
  • When pinned and out of range, the guardian emits a distinct PINNED action instructing: do NOT rebalance or blind-swap; route to exit/withdraw (withdraw minimums remain enforceable while pinned) or escalate.
  • SKILL.md documents the behavior and its derivation.

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

…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 arc0btc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 >= 1000 correctly 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.ok is 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 && !inRange is checked before the generic inRange / !canRebalance branches, 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>
@k9dreamer-graphite-elan

Copy link
Copy Markdown
Contributor Author

Suggestion taken — pushed a SKILL.md addition with a full PINNED JSON example (action + at_grid_edge: true, pinned: true + failing slippage fields) and a note that the two pre-existing run examples predate the new fields, which normal output now carries as false/false. Left the inline comment as-is for now per your nit framing (non-blocking); if it drifts from the SKILL.md prose we'll shorten it to a pointer.

🤖 Generated with Claude Code

@biwasxyz biwasxyz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_id comes 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 || >= 1000 is 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 && inRange correctly 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. slippageResult is computed unconditionally before the chain; .pct is 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

  1. 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, or CHECK with reason", and AGENT.md:44 + its data schema (lines 45-69) don't mention PINNED, at_grid_edge, or pinned at 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.

  2. Degraded API response manufactures a false PINNED. active_bin_id defaults via ?? 0 (ts:171) and the response's success/error fields are ignored — a bins response missing the field yields active_bin_id = 0atGridEdge = true, and priceByBinId.get(0) ?? 0 → pool price 0 → 100% "divergence" → slippage fails → confident PINNED telling 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 carrying active_bin_id (or priceByBinId.has(active_bin_id)).

Nits

  • Pre-existing, related: doctor treats active_bin_id > 0 as 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.

@biwasxyz

Copy link
Copy Markdown
Contributor

Timing note: my review crossed with your 85755c71 push. The PINNED JSON example is a good addition, but it lands in the tail section — both of my items are still open on the head:

  1. The canonical vocabulary definitions still omit PINNED. SKILL.md's output-contract table (line ~162) still reads "HOLD, REBALANCE, or CHECK with reason", and AGENT.md:44 still documents "action": "HOLD | REBALANCE | CHECK | <error description>" with a data schema lacking at_grid_edge/pinned. Agents consuming these docs read the table/schema as the contract — an agent that never scrolls to the tail section doesn't learn PINNED exists. One-line table edit + AGENT.md schema update.

  2. False-PINNED on a degraded bins response. active_bin_id defaults via ?? 0 (ts:171) and the response's success/error fields aren't checked — a response missing the field yields active_bin_id = 0atGridEdge = true, pool price 0 → 100% "divergence" → slippage fails → a confident PINNED that instructs routing to exit/withdraw. Before this PR that state was a blocked HOLD; the PR upgrades it to an action recommendation, which raises the stakes on the default. Guarding PINNED on the response actually carrying active_bin_id (or priceByBinId.has(active_bin_id)) closes it.

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>
@k9dreamer-graphite-elan

Copy link
Copy Markdown
Contributor Author

Both items fixed (pushed):

  1. Docs as the consumer contract: SKILL.md's output table now lists PINNED in the action vocabulary and documents at_grid_edge/pinned rows; AGENT.md's action string and data schema updated, plus a PINNED section describing the semantics and the degraded-read guarantee.
  2. False-PINNED on degraded reads — good catch, fixed: edge detection now requires the bins read to verifiably carry the active bin (priceByBinId.has(active_bin_id) with a nonzero price) before at_grid_edge can be true, so a response missing the field (defaulted 0 + pool price 0 → fake 100% divergence) can no longer manufacture an action recommendation. Your point that pre-PR this failure was a safe HOLD and post-PR it would have been a confident PINNED is exactly the right severity lens.

Nits: .version bumped to 2.2.0. The pre-existing doctor active_bin>0 healthiness check is real but left for a follow-up as you suggested.

🤖 Generated with Claude Code

@biwasxyz biwasxyz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@biwasxyz
biwasxyz merged commit e0334ef into aibtcdev:main Jul 16, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants