Skip to main content
ClaudeWave
Skill1.2k repo starsupdated 3d ago

hns-lsel-curator

>

Install in Claude Code
Copy
git clone --depth 1 https://github.com/modu-ai/moai-adk /tmp/hns-lsel-curator && cp -r /tmp/hns-lsel-curator/.claude/skills/hns-lsel-curator ~/.claude/skills/hns-lsel-curator
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

# hns-lsel-curator — LSEL CLUSTER + drain engine

> **Namespace:** `hns-lsel-*` is user-owned dogfood (CLAUDE.local.md §24). This skill is
> NOT mirrored into `internal/template/templates/` — it lives only in this repo. Graduation
> to `moai-lsel-*` + 16-language distribution is a separate SPEC (out of scope per spec.md §G).
>
> **M1 scope:** drain + cluster + stage candidates. NO APPROVE, NO APPLY (M3).
> **M2 scope:** drain + cluster + **PROPOSE shadow** (no APPROVE, no APPLY). The PROPOSE stage
> emits shadow proposals + self-critiques; APPROVE/APPLY land in M3 via the fresh
> `hns-lsel-applier` path. M2 does NOT write to `memory/` — the first `feedback_*.md` topic
> file is an M3+ deliverable after APPROVE.

## What this skill does

The MoAI-ADK repo accumulates tool-failure stubs in `.moai/lessons-inbox.jsonl` (624 stubs at
M1 start, re-measured — a moving target). The constitution names the orchestrator as the drain
actor, but until this skill there was **zero mechanical drain code** — the drain existed only as
a doctrine paragraph (`moai-constitution.md:147`). This skill closes that gap in user-owned
surfaces, without touching the frozen Go applier (`internal/harness/applier.go:22` —
its write-flag stays `false`; REQ-LSEL-003: bypass, never unfreeze).

The drain is split into a **mechanical core** (`drain.sh`, deterministic, testable) and a
**model-mediated layer** (this SKILL.md + your judgment, invoked for M2+ importance refinement
and proposal drafting).

## The mechanical core — `drain.sh`

`drain.sh` is a portable bash + jq script that lives next to this SKILL.md. It performs the
deterministic half of the drain:

```
drain.sh --inbox <path-to-lessons-inbox.jsonl> --state-dir <path-to-lsel-state>
```

Pipeline (REQ-LSEL-009 + AC-LSEL-009 / AC-LSEL-010):

1. **Companion offset** — read `<state-dir>/drain-offset.json` (seed `{"offset":0}` if absent).
   The inbox is append-only and is NEVER mutated; the offset marks consumed stubs
   (SPEC-HARNESS-RATCHET-REWIRE-001 D3 companion-offset pattern).
2. **Slice** — read stubs from the offset onwards (`tail -n +<offset+1>`).
3. **Drain-side severity filter** (AC-LSEL-010) — discard noise BEFORE clustering:
   - `tool_failure:Bash:UnknownFailure` — the opaque ~65% timeout/sandbox bucket (the dominant
     noise share; report §2).
   - `tool_failure:Bash:SandboxViolation` — environment constraint, not a code defect.
   - any `*:TimeoutError` (Bash + MCP timeouts).
   The filter is drain-side because `internal/hook/failure_observer.go` (the inbox writer) is
   OUTSIDE the six loop-writable surfaces (plan.md §F.1 [DECISION RESOLVED]), so the loop cannot
   edit the writer — it filters on read instead.
4. **Cluster** by `event_key` with frequency count, first/last seen, and up to 3 sample summaries.
5. **Singleton gate** — discard clusters with `frequency < 2` (single-occurrence noise per the
   constitution Lessons Protocol drain paragraph).
6. **Importance** — score each survivor with a Generative-Agents-style 1-10 gate:
   `importance = min(10, frequency)` (frequency as proxy; the model augments this in M2+ with a
   severity hint and retrieval-weighted judgment).
7. **Emit** candidates to `<state-dir>/clusters.json`; advance the companion offset.

### `clusters.json` schema

```json
{
  "drained_at": "2026-08-04T08:41:00Z",
  "offset_before": 0,
  "offset_after": 624,
  "total_read": 624,
  "noise_discarded": 533,
  "singletons_discarded": 4,
  "candidates": [
    {
      "event_key": "tool_failure:Agent:UnknownFailure",
      "frequency": 41,
      "first_seen": "...",
      "last_seen": "...",
      "sample_summaries": ["...", "...", "..."],
      "source": "tool:Agent",
      "importance": 10
    }
  ]
}
```

### Empty-delta no-op

If the inbox has not grown past the offset, `drain.sh` writes an empty-candidate `clusters.json`
and leaves the offset unchanged. Not a failure (acceptance.md §E edge case).

## The model-mediated layer (you, when invoked)

`drain.sh` produces the deterministic candidate set. When this skill is invoked for a real
curation pass (M2+), your job on top of the mechanical output is:

- **Read `clusters.json`** and rank candidates by `importance` then `frequency`.
- **Augment importance** with a severity hint the mechanical core cannot see: a recurring
  `Bash:ExitError` cluster points at a real command-shape defect (high signal); a recurring
  `Agent:ContextCancelled` cluster may be session-teardown noise (lower signal). Record the
  rationale in the candidate's prose when you draft the M2 proposal — do NOT rewrite
  `clusters.json` (it is the mechanical artifact; your augmentation lives in the proposal).
- **Do NOT write to `memory/` in M1.** Candidates stage in `clusters.json` only. The first
  `feedback_*.md` topic file is produced by the M2 PROPOSE stage after retrieval-before-propose
  and self-critique (REQ-LSEL-010).

## What this skill does NOT do (M1 boundaries)

- **No APPROVE / APPLY** — the parallel user-owned applier (`hns-lsel-applier`) is M3.
- **No edits to frozen doctrine** — `.claude/rules/moai/**`, `CLAUDE.md`,
  `internal/template/templates/**`, retained agents, `moai-*` skills, and the frozen Go
  applier / `curator_dispatch.go` are all byte-for-byte untouched (REQ-LSEL-001 / §B.3).
- **No new `.moai/config/sections/` file** — loop state lives under `.moai/state/lsel/`
  (a new section file would be wiped on `moai update`; plan.md §B.4 / AP-LSEL-005).
- **No orchestrator-only synchronous user-question channel** — this is a subagent-owned
  mechanism skill; it never invokes the orchestrator's user gate. On a missing input,
  return a structured blocker report; the orchestrator runs the user gate (CLAUDE.md §8).

## Durable operations — the session-start trigger (`session_drain.sh`)

The drain's original "schedule" was a session-scoped `/loop` recipe that died with its
owning session (2026-08-04) and executed nothing even while alive — the inbox stalled
for 3 weeks with nobody notified (SPE