agent-usage-manager 0.2.0__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (20) hide show
  1. {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/PKG-INFO +109 -10
  2. {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/README.md +108 -9
  3. {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/agent_usage_manager/agents.yaml +32 -4
  4. {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/agent_usage_manager/app.py +324 -21
  5. {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/agent_usage_manager/cli.py +33 -0
  6. {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/agent_usage_manager/static/index.html +35 -4
  7. agent_usage_manager-0.2.2/docs/design/HLD.md +115 -0
  8. agent_usage_manager-0.2.2/docs/design/LLD.md +327 -0
  9. {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/pyproject.toml +1 -1
  10. {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/tests/test_smoke.py +77 -8
  11. agent_usage_manager-0.2.2/tests/test_synthetic.py +317 -0
  12. {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/uv.lock +1 -1
  13. {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/.github/workflows/ci.yml +0 -0
  14. {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/.gitignore +0 -0
  15. {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/LICENSE +0 -0
  16. {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/agent_usage_manager/__init__.py +0 -0
  17. {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/demo.tape +0 -0
  18. {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/docs/dashboard.png +0 -0
  19. {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/requirements.txt +0 -0
  20. {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/run.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-usage-manager
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: htop for AI agents — liveness, CPU/mem/GPU usage, and a kill switch for headless agents (openclaw, hermes, ollama, vllm, claude-code).
5
5
  Project-URL: Homepage, https://github.com/minglong51/agent-usage-manager
6
6
  Project-URL: Repository, https://github.com/minglong51/agent-usage-manager
@@ -25,8 +25,10 @@ shows which agents are alive and what they're costing you (CPU, memory, GPU), an
25
25
  gives you a **kill button** per agent. Think `htop`, scoped to just your agents —
26
26
  the [screenshot below](docs/dashboard.png) is a real run on a fleet node.
27
27
 
28
- No database, no auth layer, no dependencies beyond FastAPI + psutil. Runs on
29
- macOS and Linux. Meant to be cloned, configured, and run on any node in a fleet.
28
+ No database, no auth framework (one static token file gates the kill switch),
29
+ no dependencies beyond FastAPI + psutil. Runs on macOS and Linux. It is a
30
+ per-node monitor and guarded local control panel: fleet schedulers may consume
31
+ its read-only telemetry, but should own their own scheduling and actuation.
30
32
 
31
33
  ![agent-usage-manager — live dashboard](docs/dashboard.png)
32
34
 
@@ -80,7 +82,14 @@ ollama 50122 ● running 3.1 9210 14080 6h 02m ollama ru
80
82
  command: 'terminal-notifier -title agents -message "$AUM_MSG"'
81
83
  cooldown: 600
82
84
  flags: [hot, churn, leak] # the default; add idle to opt in
85
+ leak_floor_mb: 1536 # optional: leak alerts only above this RSS
83
86
  ```
87
+
88
+ `leak_floor_mb` mutes leak *alerts* (the badge still shows) until the agent's
89
+ absolute footprint crosses the floor — agents that accrue working state, like
90
+ a chat bot growing its session context, ratchet RSS exactly like leakers, so
91
+ relative growth alone can be noisy. Crossing the floor counts as the
92
+ appearance, so a genuine ratchet still alerts.
84
93
  - **Prometheus `/metrics`.** Per-agent CPU/mem/instances/restarts and badge states
85
94
  in text exposition format, aggregated per label (no pid-churn series bloat) —
86
95
  point Grafana or any Prometheus scraper at `http://127.0.0.1:8765/metrics`.
@@ -94,6 +103,19 @@ ollama 50122 ● running 3.1 9210 14080 6h 02m ollama ru
94
103
  while your pointer is over the table, so the kill button can't shift under your
95
104
  cursor mid-click.
96
105
 
106
+ ## Product boundary
107
+
108
+ `agent-usage-manager` is intentionally **not** a fleet scheduler, dispatcher, or
109
+ multi-host orchestrator. It answers local process questions: what agent process is
110
+ running here, what resources is its process tree using, did it enter a suspicious
111
+ state, and can this local operator safely stop it?
112
+
113
+ If you run a separate fleet control plane, treat AUM as an optional read-only
114
+ input. Scrape `list --json`, `/api/agents`, or `/metrics` for local OS facts, then
115
+ make scheduling, budget, restart, and kill/retire decisions in your own
116
+ deterministic control layer. Do not route irreversible fleet operations through
117
+ AUM's kill endpoint as a central substrate.
118
+
97
119
  ## Safety
98
120
 
99
121
  This is the important part — a web page that can kill processes needs guardrails:
@@ -113,9 +135,29 @@ This is the important part — a web page that can kill processes needs guardrai
113
135
  kill request carrying a foreign `Origin` is refused (CSRF guard) — so a
114
136
  malicious page can't kill your agents or read your process list. `curl` and
115
137
  the dashboard itself are unaffected.
116
- - **Bind local by default.** It listens on `127.0.0.1`. Don't expose it to a network
117
- without putting auth in front of it (reverse proxy + basic auth, SSH tunnel, etc.) —
118
- it has no built-in authentication.
138
+ - **Kill requires a token (caller authorization).** The allowlist above says what
139
+ *may* be killed; the token says *who* may kill. The monitored agents are
140
+ themselves untrusted HTTP callers — a prompt-injected agent with an HTTP tool
141
+ and localhost reach could otherwise `POST /api/kill` and take down its
142
+ siblings (Origin headers are trivially forged outside a browser). The token is
143
+ auto-generated on first run into a `0600` file —
144
+ `~/Library/Application Support/agent-usage-manager/kill_token` on macOS,
145
+ `$XDG_STATE_HOME/agent-usage-manager/kill_token` (default
146
+ `~/.local/state/…`) elsewhere — and every kill must send it as an
147
+ `X-Kill-Token` header. It is **never served over HTTP** (anything that can
148
+ curl the dashboard could read it): the dashboard asks you to paste it once on
149
+ your first kill and keeps it in the browser's localStorage. Delete the file
150
+ to rotate the token.
151
+ - **Action log.** Every kill attempt — success *and* every refusal — appends a
152
+ JSON line to `actions.log` next to the token file: timestamp, caller address,
153
+ target pid/command, outcome. "What was killed at 3am" and "what's been
154
+ probing the kill endpoint" both have an answer. Append-only, no rotation;
155
+ one line per attempt stays tiny.
156
+ - **Non-loopback binds fail closed.** It listens on `127.0.0.1`; asking it to
157
+ bind anything else (`--host 0.0.0.0`, a LAN IP) refuses to start unless you
158
+ also pass `--unsafe-expose`. Exposing the port means one static token is all
159
+ that stands between the network and your agents — put real auth in front
160
+ (reverse proxy + basic auth, SSH tunnel, etc.) before using that flag.
119
161
 
120
162
  ## Limits & known issues
121
163
 
@@ -177,7 +219,9 @@ git clone <this-repo> && cd agent-usage-manager
177
219
  ```
178
220
 
179
221
  It opens the dashboard in your browser automatically. Flags: `--host`, `--port`,
180
- `--config /path/to/agents.yaml`, `--no-browser` (for headless/server use).
222
+ `--config /path/to/agents.yaml`, `--no-browser` (for headless/server use),
223
+ `--unsafe-expose` (required for any non-loopback `--host` — see
224
+ [Safety](#safety)).
181
225
 
182
226
  ## Configure which processes are "agents"
183
227
 
@@ -211,6 +255,23 @@ a generically-named binary (Kiro.app → `Electron`) are still matched by app na
211
255
  `protect:` keeps a matched process listed but refuses to kill it; `ignore:`
212
256
  drops it from agent classification entirely.
213
257
 
258
+ **Telling identical agents apart (`tmux_labels:`)** — a fleet of same-binary
259
+ agents (say five `claude` bots, one per tmux session) all hits one `agents:`
260
+ entry and shows N indistinguishable rows; a distinguishing flag deeper in
261
+ their command lines is invisible to matching *by design* (see above). When
262
+ each instance runs in its own tmux session, the session name is its identity:
263
+
264
+ ```yaml
265
+ tmux_labels: "^bot-(.+)$" # session bot-coder_1 → row label coder_1
266
+ ```
267
+
268
+ If a matched agent root (or an ancestor) is a tmux pane process whose session
269
+ name matches the regex, the row's label becomes the first capture group (the
270
+ whole session name if there's no group). Churn tracking, alert transitions,
271
+ and `/metrics` series all use the derived label, so each instance gets its own
272
+ state. Sessions that don't match the regex keep their `agents:` label, and the
273
+ key is ignored where tmux isn't installed or running.
274
+
214
275
  **Which `agents.yaml` is used** — resolved once at startup, first hit wins:
215
276
 
216
277
  1. `AGENTS_CONFIG=/path/to/agents.yaml` env var (the `--config` flag sets this)
@@ -253,11 +314,23 @@ on Macs — CPU and memory are the meaningful resource signals there.
253
314
 
254
315
  ## API
255
316
 
256
- - `GET /api/agents` → `{ agents: [...], host, cpu_count, mem_total_mb, mem_used_pct, config_path, config_error, ts }`
257
- — each agent includes `trend` (recent CPU samples) and `flag` (`"hot"` / `"idle"` / `null`)
317
+ - `GET /api/agents` → `{ api_version, aum_version, agents: [...], host, cpu_count, mem_total_mb, mem_used_pct, config_path, config_error, ts }`
318
+ — each agent includes read-only telemetry such as `pid`, `create_time`, `label`,
319
+ resource totals, recent CPU `trend`, flags (`hot`, `idle`, `churn`, `leak` when
320
+ present), protection state, and supervised-process guidance. Pair `pid` with
321
+ `create_time` when caching rows so PID reuse cannot alias two different agents.
322
+ This endpoint is suitable as an input to external tools, not as a fleet-control
323
+ contract.
258
324
  - `GET /api/tree/{pid}` → the agent's process subtree (per-child pid/name/cpu/mem/cmdline);
259
325
  only works on recognized agents, same authorization as kill
260
- - `POST /api/kill/{pid}?force=false` → SIGTERM (or SIGKILL with `force=true`)
326
+ - `POST /api/kill/{pid}?force=false` → SIGTERM (or SIGKILL with `force=true`).
327
+ Requires the `X-Kill-Token` header — the token lives in the `0600` file shown
328
+ in the 403 message (see [Safety](#safety)):
329
+
330
+ ```bash
331
+ curl -X POST -H "X-Kill-Token: $(cat ~/Library/Application\ Support/agent-usage-manager/kill_token)" \
332
+ http://127.0.0.1:8765/api/kill/48213
333
+ ```
261
334
 
262
335
  ## Run as a service
263
336
 
@@ -290,6 +363,25 @@ CI runs the test suite on Linux + macOS (Python 3.9 and 3.12) on every push and
290
363
  Cross-platform note: kill uses psutil's `terminate()`/`kill()`, which map to
291
364
  SIGTERM/SIGKILL on POSIX and TerminateProcess on Windows.
292
365
 
366
+ ## Release notes
367
+
368
+ ### 0.2.2 — unreleased
369
+
370
+ - Added `api_version` and `aum_version` to `/api/agents` and `list --json`.
371
+ - Added per-agent `create_time` so external telemetry consumers can pair it with
372
+ `pid` and avoid PID-reuse aliasing.
373
+ - Added `tmux_labels:` — derive per-instance row labels from tmux session names,
374
+ so a fleet of identical agents stops rendering as N indistinguishable rows.
375
+
376
+ ### 0.2.1 — security and verification hardening
377
+
378
+ - Kill endpoint now requires caller authorization via the static token file.
379
+ - Non-loopback binds fail closed unless `--unsafe-expose` is explicitly passed.
380
+ - Kill attempts and refusals append to the local action log.
381
+ - Added deterministic kill-path regression tests, including pid/create_time pins.
382
+ - Added synthetic hot/idle/churn/leak trace fixtures.
383
+ - Added adversarial matcher cases so lookalike process names stay test-covered.
384
+
293
385
  ## Troubleshooting
294
386
 
295
387
  - **`pip install` fails building psutil** — no prebuilt wheel for your
@@ -310,6 +402,13 @@ SIGTERM/SIGKILL on POSIX and TerminateProcess on Windows.
310
402
  - **HTTP 403 on every request** — the DNS-rebinding guard refuses non-local
311
403
  hostnames. Use `http://127.0.0.1:8765` (or a bare IP) instead of a custom
312
404
  DNS name pointing at the box.
405
+ - **HTTP 403 on kill only ("Kill requires the X-Kill-Token header")** — send
406
+ the token from the file named in the message. In the dashboard, the paste
407
+ prompt reappears on your next kill click (a stored stale token is forgotten
408
+ automatically when the server rejects it).
409
+ - **"refusing to bind …" at startup** — non-loopback `--host` values fail
410
+ closed; add `--unsafe-expose` only with auth in front (see
411
+ [Safety](#safety)).
313
412
 
314
413
  ## License
315
414
 
@@ -6,8 +6,10 @@ shows which agents are alive and what they're costing you (CPU, memory, GPU), an
6
6
  gives you a **kill button** per agent. Think `htop`, scoped to just your agents —
7
7
  the [screenshot below](docs/dashboard.png) is a real run on a fleet node.
8
8
 
9
- No database, no auth layer, no dependencies beyond FastAPI + psutil. Runs on
10
- macOS and Linux. Meant to be cloned, configured, and run on any node in a fleet.
9
+ No database, no auth framework (one static token file gates the kill switch),
10
+ no dependencies beyond FastAPI + psutil. Runs on macOS and Linux. It is a
11
+ per-node monitor and guarded local control panel: fleet schedulers may consume
12
+ its read-only telemetry, but should own their own scheduling and actuation.
11
13
 
12
14
  ![agent-usage-manager — live dashboard](docs/dashboard.png)
13
15
 
@@ -61,7 +63,14 @@ ollama 50122 ● running 3.1 9210 14080 6h 02m ollama ru
61
63
  command: 'terminal-notifier -title agents -message "$AUM_MSG"'
62
64
  cooldown: 600
63
65
  flags: [hot, churn, leak] # the default; add idle to opt in
66
+ leak_floor_mb: 1536 # optional: leak alerts only above this RSS
64
67
  ```
68
+
69
+ `leak_floor_mb` mutes leak *alerts* (the badge still shows) until the agent's
70
+ absolute footprint crosses the floor — agents that accrue working state, like
71
+ a chat bot growing its session context, ratchet RSS exactly like leakers, so
72
+ relative growth alone can be noisy. Crossing the floor counts as the
73
+ appearance, so a genuine ratchet still alerts.
65
74
  - **Prometheus `/metrics`.** Per-agent CPU/mem/instances/restarts and badge states
66
75
  in text exposition format, aggregated per label (no pid-churn series bloat) —
67
76
  point Grafana or any Prometheus scraper at `http://127.0.0.1:8765/metrics`.
@@ -75,6 +84,19 @@ ollama 50122 ● running 3.1 9210 14080 6h 02m ollama ru
75
84
  while your pointer is over the table, so the kill button can't shift under your
76
85
  cursor mid-click.
77
86
 
87
+ ## Product boundary
88
+
89
+ `agent-usage-manager` is intentionally **not** a fleet scheduler, dispatcher, or
90
+ multi-host orchestrator. It answers local process questions: what agent process is
91
+ running here, what resources is its process tree using, did it enter a suspicious
92
+ state, and can this local operator safely stop it?
93
+
94
+ If you run a separate fleet control plane, treat AUM as an optional read-only
95
+ input. Scrape `list --json`, `/api/agents`, or `/metrics` for local OS facts, then
96
+ make scheduling, budget, restart, and kill/retire decisions in your own
97
+ deterministic control layer. Do not route irreversible fleet operations through
98
+ AUM's kill endpoint as a central substrate.
99
+
78
100
  ## Safety
79
101
 
80
102
  This is the important part — a web page that can kill processes needs guardrails:
@@ -94,9 +116,29 @@ This is the important part — a web page that can kill processes needs guardrai
94
116
  kill request carrying a foreign `Origin` is refused (CSRF guard) — so a
95
117
  malicious page can't kill your agents or read your process list. `curl` and
96
118
  the dashboard itself are unaffected.
97
- - **Bind local by default.** It listens on `127.0.0.1`. Don't expose it to a network
98
- without putting auth in front of it (reverse proxy + basic auth, SSH tunnel, etc.) —
99
- it has no built-in authentication.
119
+ - **Kill requires a token (caller authorization).** The allowlist above says what
120
+ *may* be killed; the token says *who* may kill. The monitored agents are
121
+ themselves untrusted HTTP callers — a prompt-injected agent with an HTTP tool
122
+ and localhost reach could otherwise `POST /api/kill` and take down its
123
+ siblings (Origin headers are trivially forged outside a browser). The token is
124
+ auto-generated on first run into a `0600` file —
125
+ `~/Library/Application Support/agent-usage-manager/kill_token` on macOS,
126
+ `$XDG_STATE_HOME/agent-usage-manager/kill_token` (default
127
+ `~/.local/state/…`) elsewhere — and every kill must send it as an
128
+ `X-Kill-Token` header. It is **never served over HTTP** (anything that can
129
+ curl the dashboard could read it): the dashboard asks you to paste it once on
130
+ your first kill and keeps it in the browser's localStorage. Delete the file
131
+ to rotate the token.
132
+ - **Action log.** Every kill attempt — success *and* every refusal — appends a
133
+ JSON line to `actions.log` next to the token file: timestamp, caller address,
134
+ target pid/command, outcome. "What was killed at 3am" and "what's been
135
+ probing the kill endpoint" both have an answer. Append-only, no rotation;
136
+ one line per attempt stays tiny.
137
+ - **Non-loopback binds fail closed.** It listens on `127.0.0.1`; asking it to
138
+ bind anything else (`--host 0.0.0.0`, a LAN IP) refuses to start unless you
139
+ also pass `--unsafe-expose`. Exposing the port means one static token is all
140
+ that stands between the network and your agents — put real auth in front
141
+ (reverse proxy + basic auth, SSH tunnel, etc.) before using that flag.
100
142
 
101
143
  ## Limits & known issues
102
144
 
@@ -158,7 +200,9 @@ git clone <this-repo> && cd agent-usage-manager
158
200
  ```
159
201
 
160
202
  It opens the dashboard in your browser automatically. Flags: `--host`, `--port`,
161
- `--config /path/to/agents.yaml`, `--no-browser` (for headless/server use).
203
+ `--config /path/to/agents.yaml`, `--no-browser` (for headless/server use),
204
+ `--unsafe-expose` (required for any non-loopback `--host` — see
205
+ [Safety](#safety)).
162
206
 
163
207
  ## Configure which processes are "agents"
164
208
 
@@ -192,6 +236,23 @@ a generically-named binary (Kiro.app → `Electron`) are still matched by app na
192
236
  `protect:` keeps a matched process listed but refuses to kill it; `ignore:`
193
237
  drops it from agent classification entirely.
194
238
 
239
+ **Telling identical agents apart (`tmux_labels:`)** — a fleet of same-binary
240
+ agents (say five `claude` bots, one per tmux session) all hits one `agents:`
241
+ entry and shows N indistinguishable rows; a distinguishing flag deeper in
242
+ their command lines is invisible to matching *by design* (see above). When
243
+ each instance runs in its own tmux session, the session name is its identity:
244
+
245
+ ```yaml
246
+ tmux_labels: "^bot-(.+)$" # session bot-coder_1 → row label coder_1
247
+ ```
248
+
249
+ If a matched agent root (or an ancestor) is a tmux pane process whose session
250
+ name matches the regex, the row's label becomes the first capture group (the
251
+ whole session name if there's no group). Churn tracking, alert transitions,
252
+ and `/metrics` series all use the derived label, so each instance gets its own
253
+ state. Sessions that don't match the regex keep their `agents:` label, and the
254
+ key is ignored where tmux isn't installed or running.
255
+
195
256
  **Which `agents.yaml` is used** — resolved once at startup, first hit wins:
196
257
 
197
258
  1. `AGENTS_CONFIG=/path/to/agents.yaml` env var (the `--config` flag sets this)
@@ -234,11 +295,23 @@ on Macs — CPU and memory are the meaningful resource signals there.
234
295
 
235
296
  ## API
236
297
 
237
- - `GET /api/agents` → `{ agents: [...], host, cpu_count, mem_total_mb, mem_used_pct, config_path, config_error, ts }`
238
- — each agent includes `trend` (recent CPU samples) and `flag` (`"hot"` / `"idle"` / `null`)
298
+ - `GET /api/agents` → `{ api_version, aum_version, agents: [...], host, cpu_count, mem_total_mb, mem_used_pct, config_path, config_error, ts }`
299
+ — each agent includes read-only telemetry such as `pid`, `create_time`, `label`,
300
+ resource totals, recent CPU `trend`, flags (`hot`, `idle`, `churn`, `leak` when
301
+ present), protection state, and supervised-process guidance. Pair `pid` with
302
+ `create_time` when caching rows so PID reuse cannot alias two different agents.
303
+ This endpoint is suitable as an input to external tools, not as a fleet-control
304
+ contract.
239
305
  - `GET /api/tree/{pid}` → the agent's process subtree (per-child pid/name/cpu/mem/cmdline);
240
306
  only works on recognized agents, same authorization as kill
241
- - `POST /api/kill/{pid}?force=false` → SIGTERM (or SIGKILL with `force=true`)
307
+ - `POST /api/kill/{pid}?force=false` → SIGTERM (or SIGKILL with `force=true`).
308
+ Requires the `X-Kill-Token` header — the token lives in the `0600` file shown
309
+ in the 403 message (see [Safety](#safety)):
310
+
311
+ ```bash
312
+ curl -X POST -H "X-Kill-Token: $(cat ~/Library/Application\ Support/agent-usage-manager/kill_token)" \
313
+ http://127.0.0.1:8765/api/kill/48213
314
+ ```
242
315
 
243
316
  ## Run as a service
244
317
 
@@ -271,6 +344,25 @@ CI runs the test suite on Linux + macOS (Python 3.9 and 3.12) on every push and
271
344
  Cross-platform note: kill uses psutil's `terminate()`/`kill()`, which map to
272
345
  SIGTERM/SIGKILL on POSIX and TerminateProcess on Windows.
273
346
 
347
+ ## Release notes
348
+
349
+ ### 0.2.2 — unreleased
350
+
351
+ - Added `api_version` and `aum_version` to `/api/agents` and `list --json`.
352
+ - Added per-agent `create_time` so external telemetry consumers can pair it with
353
+ `pid` and avoid PID-reuse aliasing.
354
+ - Added `tmux_labels:` — derive per-instance row labels from tmux session names,
355
+ so a fleet of identical agents stops rendering as N indistinguishable rows.
356
+
357
+ ### 0.2.1 — security and verification hardening
358
+
359
+ - Kill endpoint now requires caller authorization via the static token file.
360
+ - Non-loopback binds fail closed unless `--unsafe-expose` is explicitly passed.
361
+ - Kill attempts and refusals append to the local action log.
362
+ - Added deterministic kill-path regression tests, including pid/create_time pins.
363
+ - Added synthetic hot/idle/churn/leak trace fixtures.
364
+ - Added adversarial matcher cases so lookalike process names stay test-covered.
365
+
274
366
  ## Troubleshooting
275
367
 
276
368
  - **`pip install` fails building psutil** — no prebuilt wheel for your
@@ -291,6 +383,13 @@ SIGTERM/SIGKILL on POSIX and TerminateProcess on Windows.
291
383
  - **HTTP 403 on every request** — the DNS-rebinding guard refuses non-local
292
384
  hostnames. Use `http://127.0.0.1:8765` (or a bare IP) instead of a custom
293
385
  DNS name pointing at the box.
386
+ - **HTTP 403 on kill only ("Kill requires the X-Kill-Token header")** — send
387
+ the token from the file named in the message. In the dashboard, the paste
388
+ prompt reappears on your next kill click (a stored stale token is forgotten
389
+ automatically when the server rejects it).
390
+ - **"refusing to bind …" at startup** — non-loopback `--host` values fail
391
+ closed; add `--unsafe-expose` only with auth in front (see
392
+ [Safety](#safety)).
294
393
 
295
394
  ## License
296
395
 
@@ -58,6 +58,17 @@ ignore:
58
58
  - shipit # Squirrel.framework auto-updater
59
59
  - kiro-cli-term # Kiro's integrated-terminal shells (zsh), not the agent
60
60
 
61
+ # Optional: per-instance labels from tmux session names. A fleet of identical
62
+ # agents (e.g. several claude-code bots, one per tmux session bot-*) all hit
63
+ # one agents: entry above and land as N indistinguishable rows — their
64
+ # cmdlines can't tell them apart, because matching deliberately sees only the
65
+ # executable + first args. The tmux session each one runs in IS its identity:
66
+ # when a matched root (or an ancestor) is a tmux pane whose session name
67
+ # matches this regex, the row is labeled with the first capture group (the
68
+ # whole session name if there is no group). Sessions that don't match keep
69
+ # their agents: label, so incidental tmux use never renames rows.
70
+ tmux_labels: "^bot-(.+)$" # session bot-coder_1 → row label coder_1
71
+
61
72
  # GPU sampling: nvidia-smi is used automatically when present (Linux/NVIDIA).
62
73
  # On Apple Silicon there is no per-process GPU API, so the GPU column is blank.
63
74
 
@@ -70,7 +81,24 @@ ignore:
70
81
  # fleet of agents that wait for work, idle is the NORMAL state, and alerting on
71
82
  # it floods the channel every time the server restarts and re-learns the fleet.
72
83
  #
73
- # alerts:
74
- # command: 'terminal-notifier -title agent-usage-manager -message "$AUM_MSG"'
75
- # cooldown: 600 # seconds, default 600
76
- # flags: [hot, churn, leak] # default; add idle only if you really want it
84
+ # Example (desktop notification):
85
+ # alerts:
86
+ # command: 'terminal-notifier -title agent-usage-manager -message "$AUM_MSG"'
87
+ # cooldown: 600 # seconds, default 600
88
+ # flags: [hot, churn, leak] # default; add idle only if you really want it
89
+ #
90
+ # Live wiring on this host: record to the attention feed, never ring
91
+ # (2026-07-06, Ming: "most of them are noise"). First live day pushed two false
92
+ # positives — "codex churn" during legitimate spawn work and "claude-code hot"
93
+ # during a working session; for inference agents, pegged-CPU IS the job.
94
+ # Badges stay live on the dashboard + machinery panel; the feed line rides the
95
+ # next digest. Genuine breakage has louder, independent signals (mcp-autoheal
96
+ # GAVE UP, cron-health, sync-bot liveness). `hot` dropped from alerting
97
+ # entirely; notify.py is a `uv run` script, hence the inline PATH prefix.
98
+ alerts:
99
+ command: 'PATH="$HOME/.local/bin:$PATH"; "$HOME/.claude/skills/_finance_lib/scripts/notify.py" --plain --priority digest --source usage-manager --feed-only --text "$AUM_MSG"'
100
+ cooldown: 7200 # 2 h per (agent, flag)
101
+ flags: [churn, leak]
102
+ leak_floor_mb: 1536 # bots legitimately ratchet RSS as session
103
+ # context grows; only push leak alerts once
104
+ # the absolute footprint is actually large