jevsnes.git / tools / watch / README.md
1# Chapter 26: watch, one line of status
2
3Jev costs money, and the window can run for days. Something should check,
4once a minute, that questions are still being asked, and shout if they have
5stopped for a reason that will not clear by itself. `budget.sh` is the thing
6that something runs. It asks the running window over MCP and prints one
7line:
8
9```
102026-10-02T14:03:11Z task=… mode=overworld link=2096,2000 lifetime_remaining=unknown q/min=4 stopped=no
11```
12
13time, the bot's current task, the game mode, Link's position, a lifetime
14figure, questions in the last minute, and what (if anything) is stopping
15questions. Then it exits with a code a watchdog can act on.
16
17```bash
18nix develop -c tools/watch/budget.sh
19```
20
21It needs no API key: it only talks to the window's MCP endpoint
22(`http://127.0.0.1:7637/mcp`), and it never turns dev mode on, because every
23tool it calls (`state`, `budget`, `bot`) is always there.
24
25> **Aside: "I don't know" is not "fine".** The most important exit code is
26> 4. If the window answers but the `budget` tool is missing, or the field
27> that says whether asking has stopped cannot be read, the script exits 4,
28> never 0. Every field goes through one function, `get`, which returns the
29> literal word `unknown` for a missing or null field: never `0`, never
30> `false`, never `no`. A watchdog must not mistake an old build that cannot
31> answer for a healthy one.
32
33| Code | Meaning |
34| --- | --- |
35| 0 | Asking works, or is throttled (the question bucket or the hour breaker) and will clear by itself; the line says which. |
36| 2 | Stopped for good: the vendor said the account is out of credit. Waiting will not clear it. |
37| 3 | No window answered at all (no `Mcp-Session-Id` came back). |
38| 4 | A window answered, but the `budget` tool, or whether asking is stopped, could not be read. Treat as stopped. |
39
40Meant for a once-a-minute watchdog:
41
42```bash
43tools/watch/budget.sh
44case $? in
45    0) ;;                    # fine
46    2) page-someone "stopped for good" ;;
47    3) page-someone "window down" ;;
48    4) page-someone "can't tell - treat as stopped" ;;
49esac
50```
51
52> **Try it.** No window needed:
53>
54>     nix develop -c bash tools/watch/budget.sh --self-test
55>
56> runs the parsing and formatting against recorded JSON-RPC bodies: a tool
57> present with nothing stopping questions, one throttled (not a stop), one
58> stopped for good, a tool missing entirely (the error shape an old build
59> really returns), and a leading SSE keepalive ahead of the real payload.
60> Every case that should read `unknown` does.
61
62## For the people who maintain it
63
64`lifetime_remaining` reads `unknown` against today's window. The ledger
65stopped reporting a remaining figure when the self-imposed lifetime cap was
66removed (2026-09-22); the `budget` tool now carries `lifetime_usd`, spent so
67far, and the script still asks for `lifetime_remaining_usd`. The exit code
68does not depend on it, so a watchdog is unaffected.
69
70### In this folder
71
72| Path | What |
73| --- | --- |
74| [budget.sh](budget.sh) | The one-line status, its exit codes, and `--self-test`. Shares its MCP session dance with `../hotpatch/patch.sh`. |
75| [README.md](README.md) | This chapter. |
76| [CLAUDE.md](CLAUDE.md) | The subshell and keepalive traps, for agents. |
77
78← Previous: [Chapter 25, mcp/](../mcp/) · Up: [tools](../) · Next: [Chapter 27, hotpatch/](../hotpatch/) →