Files
rohitg00__agentmemory/test/health-thresholds.test.ts
Rohit Ghumare 7013722314 fix(health+cli): move heap_tight to notes, harden demo readiness probe
## Health: heap_tight note vs alert

CHANGELOG v0.9.0 (#160) said the sub-floor heap_tight marker is a
"non-alerting note attached to the snapshot — visibility without the
false positive", but the implementation still pushed it into the same
alerts[] array as real critical/degraded conditions. Viewer rendered
it under a red "Alerts (1)" card on fresh installs (~108 MB RSS is
below the 512 MB floor).

- thresholds.ts now returns { status, alerts, notes }, with heap_tight
  routed to notes[].
- HealthSnapshot gains optional notes: string[].
- Viewer: new "Notes (N)" card in neutral color next to Alerts. Heap
  gauge only reddens when RSS is above the 512 MB floor — below the
  floor, heap percent is informational.
- Test suite updated to assert heap_tight lives in notes, not alerts.

## Demo readiness probe

`npx @agentmemory/agentmemory demo` 404'd on /agentmemory/session/start
when iii-engine was up but the agentmemory worker hadn't registered
(e.g., right after a shutdown that left iii lingering). isEngineRunning
probed `/` and accepted any response, including iii's own 404.

Split the probe: isEngineRunning still probes root (used by main/start
flow to detect whether to spawn iii), and a new isAgentmemoryReady
probes /agentmemory/livez with res.ok. runDemo now uses the stricter
probe with a specific error explaining the distinction.
2026-04-21 00:39:37 +01:00

98 lines
3.3 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { evaluateHealth } from "../src/health/thresholds.js";
import type { HealthSnapshot } from "../src/types.js";
function snap(over: Partial<HealthSnapshot> = {}): HealthSnapshot {
return {
connectionState: "connected",
workers: [],
memory: { heapUsed: 0, heapTotal: 1, rss: 0, external: 0 },
cpu: { userMicros: 0, systemMicros: 0, percent: 0 },
eventLoopLagMs: 0,
uptimeSeconds: 1,
kvConnectivity: { status: "ok", latencyMs: 1 },
status: "healthy",
alerts: [],
...over,
};
}
describe("evaluateHealth memory severity", () => {
it("stays healthy when heap fills a tiny steady-state process (issue #158)", () => {
const s = snap({
memory: {
heapUsed: 45 * 1024 * 1024,
heapTotal: 46 * 1024 * 1024,
rss: 120 * 1024 * 1024,
external: 0,
},
});
const { status, alerts, notes } = evaluateHealth(s);
expect(status).toBe("healthy");
expect(alerts.find((a) => a.startsWith("memory_critical_"))).toBeUndefined();
expect(alerts.find((a) => a.startsWith("memory_warn_"))).toBeUndefined();
expect(alerts.find((a) => a.startsWith("memory_heap_tight_"))).toBeUndefined();
expect(notes.find((n) => n.startsWith("memory_heap_tight_"))).toBeDefined();
});
it("goes critical when heap ratio is high AND RSS is above the floor", () => {
const s = snap({
memory: {
heapUsed: 970 * 1024 * 1024,
heapTotal: 1000 * 1024 * 1024,
rss: 1100 * 1024 * 1024,
external: 0,
},
});
const { status, alerts } = evaluateHealth(s);
expect(status).toBe("critical");
expect(alerts.some((a) => a.startsWith("memory_critical_"))).toBe(true);
});
it("records heap_tight in the warn band when RSS is below the floor", () => {
const s = snap({
memory: {
heapUsed: 85 * 1024 * 1024,
heapTotal: 100 * 1024 * 1024,
rss: 50 * 1024 * 1024,
external: 0,
},
});
const { status, alerts, notes } = evaluateHealth(s);
expect(status).toBe("healthy");
expect(notes.some((n) => n.startsWith("memory_heap_tight_"))).toBe(true);
expect(alerts.some((a) => a.startsWith("memory_heap_tight_"))).toBe(false);
expect(alerts.some((a) => a.startsWith("memory_warn_"))).toBe(false);
expect(alerts.some((a) => a.startsWith("memory_critical_"))).toBe(false);
});
it("goes degraded when heap is above warn AND RSS is above the floor", () => {
const s = snap({
memory: {
heapUsed: 850 * 1024 * 1024,
heapTotal: 1000 * 1024 * 1024,
rss: 900 * 1024 * 1024,
external: 0,
},
});
const { status, alerts } = evaluateHealth(s, { memoryRssFloorBytes: 800 * 1024 * 1024 });
expect(status).toBe("degraded");
expect(alerts.some((a) => a.startsWith("memory_warn_"))).toBe(true);
});
it("respects caller-supplied memoryRssFloorBytes", () => {
const s = snap({
memory: {
heapUsed: 98,
heapTotal: 100,
rss: 50 * 1024 * 1024,
external: 0,
},
});
const loose = evaluateHealth(s, { memoryRssFloorBytes: 10 * 1024 * 1024 });
expect(loose.status).toBe("critical");
const strict = evaluateHealth(s, { memoryRssFloorBytes: 1024 * 1024 * 1024 });
expect(strict.status).toBe("healthy");
});
});