bots-garden/mini-mepublic Fork
⑂ feature/mini-me-as-a-sdk
Commits
⬇ Clone ▾
git clone https://git.rickub.com/bots-garden/mini-me.git
git clone ssh://git@rickub.com/bots-garden/mini-me.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP
🛟 Updated. dfcd2ed · on feature/mini-me-as-a-sdk · k33g · 14d ago
struggling-models.md · 30 lines · 4.6 KBmarkdown
Blame HistoryOpen raw

Noticing when a model struggles — explanation

What is this about?

mm was written for small local models, and small models struggle in recognisable ways: they repeat the same failing call, they write tool-call arguments that do not parse or never end, they go silent, they call tools that do not exist, they run out of turns, they answer nothing. From the start mm noticed most of these — the loop detector, the runaway guard, the watchdog, the note handed back after a broken call — but it told the model (an instruction in a tool's output) or ended the turn with an error line among the rest of the conversation. The person watching had to read every line to know that the model was going in circles.

Struggle signals (ticket 153) make the same detections visible to the user, apart from the answer, in a colour that says how bad it is. The list of signals and how each front end shows them is in the CLI reference.

Why it is designed this way

Two levels, not a score. A warning (⚠, yellow) means mm noticed something and the turn goes on — the model was told it is looping, a broken call was handed back, the context is nearly full. A severe signal (✖, red) means the turn was stopped or failed because of it. That is the distinction a person acts on: wait and watch, or rephrase and try again (or change model).

Reported where they are detected. Each detection already lived in one place — the loop detector's Record, the model-call wrapper for retries and broken calls, the end of Generate for how a turn ended. Each reports a ui.Struggle there; nothing re-reads the conversation afterwards to guess. The loop detector learned to call a function (WithReport) rather than to know about the screen, so it stays a package with no front-end knowledge.

One event, three renderings, as for everything else in mm (architecture): the window keeps the worst struggle of the turn as a badge in the status bar; the line mode prints a coloured line when it happens; an editor gets a short italic paragraph signed mm in the agent's message, because it has no status bar of ours.

In the window, the status bar, until the next question. The badge sits on the right of the bottom bar, in the version's place: the version is the least useful thing there while something is wrong, and the key hints on the left — Ctrl-C quit above all, the way out of a turn that will not stop — keep their room. It outlives the turn on purpose: once a turn ended badly, the badge says why, and it goes when the next question starts a new turn. A severe signal beats a warning; between two of the same level the latest wins, so loop: bash ×4 replaces ×3; (+N) counts the others.

Failures in a row, not only repeats (ticket 160). The loop detector needs the same call with the same result; a model that tries cat config.yml, then cat conf/config.yml, then cat config.yaml and fails each time is struggling just as much. Whether a call failed is only known inside each tool, so every tool now hands its outcome to the detector with the call (record, recordFailure, recordOutcome); the detector, which already sees every call, counts the failures in a row and reports failing: N tool calls in a row from the third. It is a warning only: the model reads each failure in the tool's output already, and a hard stop would end turns that a fourth attempt would have saved.

A cancelled turn is not a struggle. Esc, Ctrl-C or /abort end a turn on the user's request; reporting stalled or max turns then would blame the model for the user's choice.

Rejected alternatives

  • The header. Considered; the user chose the status bar, which keeps the header for who is answering and how full the context is.
  • A transcript line per signal. The conversation is where the answer is; a loop flagged at every repeat would fill it with lines that repeat the tool's own output. The badge summarises; the line mode, which has no status bar, prints the lines.

How it relates to the rest

The detections themselves are unchanged: the loop detector still tells the model to change approach and still stops the turn after six identical calls; the watchdog, the runaway guard and the broken-call note behave as before. What the turn returns and what the history keeps do not change either — a struggle is shown, never stored. See also context compression, which a context N% full warning usually precedes, and thinking models for why the watchdog stays fed while a model reasons.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# Noticing when a model struggles — explanation

## What is this about?

`mm` was written for small local models, and small models struggle in recognisable ways: they repeat the same failing call, they write tool-call arguments that do not parse or never end, they go silent, they call tools that do not exist, they run out of turns, they answer nothing. From the start `mm` noticed most of these — the loop detector, the runaway guard, the watchdog, the note handed back after a broken call — but it told the **model** (an instruction in a tool's output) or ended the turn with an error line among the rest of the conversation. The person watching had to read every line to know that the model was going in circles.

Struggle signals (ticket 153) make the same detections visible to the **user**, apart from the answer, in a colour that says how bad it is. The list of signals and how each front end shows them is in the [CLI reference](../reference/cli.md#struggle-signals).

## Why it is designed this way

**Two levels, not a score.** A warning (`⚠`, yellow) means `mm` noticed something and the turn goes on — the model was told it is looping, a broken call was handed back, the context is nearly full. A severe signal (`✖`, red) means the turn was stopped or failed because of it. That is the distinction a person acts on: wait and watch, or rephrase and try again (or change model).

**Reported where they are detected.** Each detection already lived in one place — the loop detector's `Record`, the model-call wrapper for retries and broken calls, the end of `Generate` for how a turn ended. Each reports a `ui.Struggle` there; nothing re-reads the conversation afterwards to guess. The loop detector learned to call a function (`WithReport`) rather than to know about the screen, so it stays a package with no front-end knowledge.

**One event, three renderings**, as for everything else in `mm` ([architecture](architecture.md)): the window keeps the worst struggle of the turn as a badge in the status bar; the line mode prints a coloured line when it happens; an editor gets a short italic paragraph signed `mm` in the agent's message, because it has no status bar of ours.

**In the window, the status bar, until the next question.** The badge sits on the right of the bottom bar, in the version's place: the version is the least useful thing there while something is wrong, and the key hints on the left — `Ctrl-C quit` above all, the way out of a turn that will not stop — keep their room. It outlives the turn on purpose: once a turn ended badly, the badge says why, and it goes when the next question starts a new turn. A severe signal beats a warning; between two of the same level the latest wins, so `loop: bash ×4` replaces `×3`; `(+N)` counts the others.

**Failures in a row, not only repeats** (ticket 160). The loop detector needs the same call with the same result; a model that tries `cat config.yml`, then `cat conf/config.yml`, then `cat config.yaml` and fails each time is struggling just as much. Whether a call failed is only known inside each tool, so every tool now hands its outcome to the detector with the call (`record`, `recordFailure`, `recordOutcome`); the detector, which already sees every call, counts the failures in a row and reports `failing: N tool calls in a row` from the third. It is a warning only: the model reads each failure in the tool's output already, and a hard stop would end turns that a fourth attempt would have saved.

**A cancelled turn is not a struggle.** Esc, Ctrl-C or `/abort` end a turn on the user's request; reporting `stalled` or `max turns` then would blame the model for the user's choice.

## Rejected alternatives

- **The header.** Considered; the user chose the status bar, which keeps the header for who is answering and how full the context is.
- **A transcript line per signal.** The conversation is where the answer is; a loop flagged at every repeat would fill it with lines that repeat the tool's own output. The badge summarises; the line mode, which has no status bar, prints the lines.

## How it relates to the rest

The detections themselves are unchanged: the loop detector still tells the model to change approach and still stops the turn after six identical calls; the watchdog, the runaway guard and the broken-call note behave as before. What the turn returns and what the history keeps do not change either — a struggle is shown, never stored. See also [context compression](context-compression.md), which a `context N% full` warning usually precedes, and [thinking models](thinking-models.md) for why the watchdog stays fed while a model reasons.