Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,17 @@ async function isEngineRunning(): Promise<boolean> {
}
}

async function isAgentmemoryReady(): Promise<boolean> {
try {
const res = await fetch(`http://localhost:${getRestPort()}/agentmemory/livez`, {
signal: AbortSignal.timeout(2000),
});
return res.ok;
} catch {
return false;
}
}

function findIiiConfig(): string {
const candidates = [
join(__dirname, "iii-config.yaml"),
Expand Down Expand Up @@ -616,9 +627,11 @@ async function runDemo() {
const base = `http://localhost:${port}`;
p.intro("agentmemory demo");

if (!(await isEngineRunning())) {
p.log.error(`Not running — no response on port ${port}`);
p.log.info("Start the server first: npx @agentmemory/agentmemory");
if (!(await isAgentmemoryReady())) {
p.log.error(
`agentmemory worker not reachable on port ${port} (livez probe failed). Something may be on the port but it isn't serving /agentmemory/*.`,
);
p.log.info("Start it with: npx @agentmemory/agentmemory");
process.exit(1);
}

Expand Down
1 change: 1 addition & 0 deletions src/health/monitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export function registerHealthMonitor(
const evaluated = evaluateHealth(snapshot);
snapshot.status = evaluated.status;
snapshot.alerts = evaluated.alerts;
snapshot.notes = evaluated.notes;

await kv.set(KV.health, "latest", snapshot).catch(() => {});
return snapshot;
Expand Down
7 changes: 4 additions & 3 deletions src/health/thresholds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ const DEFAULTS: ThresholdConfig = {
export function evaluateHealth(
snapshot: HealthSnapshot,
config: Partial<ThresholdConfig> = {},
): { status: "healthy" | "degraded" | "critical"; alerts: string[] } {
): { status: "healthy" | "degraded" | "critical"; alerts: string[]; notes: string[] } {
const cfg = { ...DEFAULTS, ...config };
const alerts: string[] = [];
const notes: string[] = [];
let critical = false;
let degraded = false;

Expand Down Expand Up @@ -72,9 +73,9 @@ export function evaluateHealth(
alerts.push(`memory_warn_${Math.round(memPercent)}%_rss${memMb}mb`);
degraded = true;
} else if (memPercent > cfg.memoryWarnPercent) {
alerts.push(`memory_heap_tight_${Math.round(memPercent)}%_rss${memMb}mb`);
notes.push(`memory_heap_tight_${Math.round(memPercent)}%_rss${memMb}mb`);
}

const status = critical ? "critical" : degraded ? "degraded" : "healthy";
return { status, alerts };
return { status, alerts, notes };
}
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ export interface HealthSnapshot {
kvConnectivity?: { status: string; latencyMs?: number; error?: string };
status: "healthy" | "degraded" | "critical";
alerts: string[];
notes?: string[];
}

export interface CircuitBreakerState {
Expand Down
11 changes: 10 additions & 1 deletion src/viewer/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1078,7 +1078,8 @@ <h1>agentmemory</h1>
var heapTotal = Math.round((snap.memory.heapTotal || 0) / 1024 / 1024);
var rss = Math.round((snap.memory.rss || 0) / 1024 / 1024);
var heapPct = heapTotal > 0 ? Math.round((heapUsed / heapTotal) * 100) : 0;
var heapColor = heapPct > 80 ? 'var(--red)' : heapPct > 60 ? 'var(--yellow)' : 'var(--green)';
var rssAboveFloor = rss >= 512;
var heapColor = (heapPct > 80 && rssAboveFloor) ? 'var(--red)' : (heapPct > 60 && rssAboveFloor) ? 'var(--yellow)' : 'var(--green)';
html += '<div class="gauge"><span class="gauge-label">Heap</span><div class="gauge-bar"><div class="gauge-fill" style="width:' + heapPct + '%;background:' + heapColor + '"></div></div><span class="gauge-value">' + heapUsed + ' / ' + heapTotal + ' MB</span></div>';
html += '<div class="gauge"><span class="gauge-label">RSS</span><div class="gauge-bar"><div class="gauge-fill" style="width:' + Math.min(100, Math.round(rss / 512 * 100)) + '%;background:var(--blue)"></div></div><span class="gauge-value">' + rss + ' MB</span></div>';
if (snap.memory.external) {
Expand Down Expand Up @@ -1113,6 +1114,14 @@ <h1>agentmemory</h1>
html += '</div>';
}

if (snap.notes && snap.notes.length > 0) {
html += '<div class="card" style="margin-bottom:16px;"><div class="card-title" style="color:var(--ink-muted);">Notes (' + snap.notes.length + ')</div>';
snap.notes.forEach(function(n) {
html += '<div style="font-size:12px;color:var(--ink-muted);padding:4px 0;border-bottom:1px solid var(--border-light);font-family:var(--font-ui);">' + esc(n) + '</div>';
});
html += '</div>';
}

html += '<div class="two-col">';

html += '<div class="card"><div class="card-title">Recent Sessions</div>';
Expand Down
10 changes: 6 additions & 4 deletions test/health-thresholds.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ describe("evaluateHealth memory severity", () => {
external: 0,
},
});
const { status, alerts } = evaluateHealth(s);
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_"))).toBeDefined();
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", () => {
Expand All @@ -57,9 +58,10 @@ describe("evaluateHealth memory severity", () => {
external: 0,
},
});
const { status, alerts } = evaluateHealth(s);
const { status, alerts, notes } = evaluateHealth(s);
expect(status).toBe("healthy");
expect(alerts.some((a) => a.startsWith("memory_heap_tight_"))).toBe(true);
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);
});
Expand Down
Loading