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.
- {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/PKG-INFO +109 -10
- {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/README.md +108 -9
- {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/agent_usage_manager/agents.yaml +32 -4
- {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/agent_usage_manager/app.py +324 -21
- {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/agent_usage_manager/cli.py +33 -0
- {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/agent_usage_manager/static/index.html +35 -4
- agent_usage_manager-0.2.2/docs/design/HLD.md +115 -0
- agent_usage_manager-0.2.2/docs/design/LLD.md +327 -0
- {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/pyproject.toml +1 -1
- {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/tests/test_smoke.py +77 -8
- agent_usage_manager-0.2.2/tests/test_synthetic.py +317 -0
- {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/uv.lock +1 -1
- {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/.github/workflows/ci.yml +0 -0
- {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/.gitignore +0 -0
- {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/LICENSE +0 -0
- {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/agent_usage_manager/__init__.py +0 -0
- {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/demo.tape +0 -0
- {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/docs/dashboard.png +0 -0
- {agent_usage_manager-0.2.0 → agent_usage_manager-0.2.2}/requirements.txt +0 -0
- {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.
|
|
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
|
|
29
|
-
|
|
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
|

|
|
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
|
-
- **
|
|
117
|
-
|
|
118
|
-
|
|
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
|
|
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
|
|
10
|
-
|
|
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
|

|
|
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
|
-
- **
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
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
|
-
#
|
|
74
|
-
#
|
|
75
|
-
#
|
|
76
|
-
#
|
|
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
|