Add agent metric archive diagnostic switch

Add an environment-controlled kill switch that removes kind 44200 from
archive relay subscriptions without disturbing observer or other archive
kinds. Prevent startup seeding from re-adding the metric kind while the
switch is active.

Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
This commit is contained in:
Wes
2026-08-11 12:07:29 -06:00
co-authored by Carl
parent cd2aa5c12d
commit 85e468a6ba
4 changed files with 76 additions and 4 deletions
@@ -425,6 +425,43 @@ test("manager_builds_correct_filter_for_owner_p", async () => {
mgr.destroy();
});
test("manager_metric_kill_switch_filters_only_kind_44200", async () => {
const relay = makeFakeRelayClient();
const archive = makeFakeArchive();
archive.setSubs([
{
scopeType: "owner_p",
scopeValue: "mixed",
kinds: [24200, 44200, 9],
identityPubkey: "pk",
relayUrl: "wss://r",
createdAt: 0,
},
{
scopeType: "owner_p",
scopeValue: "metric-only",
kinds: [44200],
identityPubkey: "pk",
relayUrl: "wss://r",
createdAt: 0,
},
]);
const mgr = makeManager(relay, archive, {
disableAgentMetricArchive: true,
});
await mgr.start();
await tick();
const activeFilters = [...relay.subs.values()]
.filter((entry) => !entry.unsubbed)
.map((entry) => entry.filter);
assert.deepEqual(activeFilters, [
{ kinds: [24200, 9], limit: 0, "#p": ["mixed"] },
]);
mgr.destroy();
});
test("manager_forwards_events_to_archive_events_on_flush", async () => {
const relay = makeFakeRelayClient();
const archive = makeFakeArchive();
@@ -1,6 +1,7 @@
import { relayClient as defaultRelayClient } from "@/shared/api/relayClient";
import type { RelaySubscriptionFilter } from "@/shared/api/relayClientShared";
import type { RelayEvent } from "@/shared/api/types";
import { KIND_AGENT_TURN_METRIC } from "@/shared/constants/kinds";
import {
archiveEvents as defaultArchiveEvents,
listSaveSubscriptions as defaultListSaveSubscriptions,
@@ -15,6 +16,8 @@ import {
const FLUSH_BATCH_SIZE = 25;
const FLUSH_IDLE_MS = 2_000;
const DISABLE_AGENT_METRIC_ARCHIVE =
import.meta.env?.VITE_BUZZ_DISABLE_AGENT_METRIC_ARCHIVE === "1";
// ── Types ─────────────────────────────────────────────────────────────────────
@@ -34,6 +37,7 @@ export interface ArchiveSyncDeps {
}>,
) => Promise<ArchiveBatchResult>;
onSubscriptionChange: (listener: () => void) => () => void;
disableAgentMetricArchive?: boolean;
flushBatchSize?: number;
flushIdleMs?: number;
}
@@ -81,8 +85,12 @@ function scopeKey(scopeType: ScopeType, scopeValue: string): string {
*/
export class ArchiveSyncManager {
private readonly deps: Required<
Omit<ArchiveSyncDeps, "flushBatchSize" | "flushIdleMs">
Omit<
ArchiveSyncDeps,
"disableAgentMetricArchive" | "flushBatchSize" | "flushIdleMs"
>
>;
private readonly disableAgentMetricArchive: boolean;
private readonly flushBatchSize: number;
private readonly flushIdleMs: number;
@@ -112,6 +120,8 @@ export class ArchiveSyncManager {
onSubscriptionChange:
deps?.onSubscriptionChange ?? defaultOnSubscriptionChange,
};
this.disableAgentMetricArchive =
deps?.disableAgentMetricArchive ?? DISABLE_AGENT_METRIC_ARCHIVE;
this.flushBatchSize = deps?.flushBatchSize ?? FLUSH_BATCH_SIZE;
this.flushIdleMs = deps?.flushIdleMs ?? FLUSH_IDLE_MS;
}
@@ -202,6 +212,15 @@ export class ArchiveSyncManager {
if (this.destroyed) return;
if (this.disableAgentMetricArchive) {
subs = subs.flatMap((sub) => {
const kinds = sub.kinds.filter(
(kind) => kind !== KIND_AGENT_TURN_METRIC,
);
return kinds.length > 0 ? [{ ...sub, kinds }] : [];
});
}
// Full keys (scope+kinds) for the current subscription list.
const wanted = new Set(
subs.map((s) => subKey(s.scopeType, s.scopeValue, s.kinds)),
@@ -10,11 +10,16 @@ import test from "node:test";
// ── Fake deps factory ────────────────────────────────────────────────────────
function makeDeps({ hasExplicitChoice = false, mergeShouldFail = false } = {}) {
function makeDeps({
hasExplicitChoice = false,
mergeShouldFail = false,
disableAgentMetricArchive = false,
} = {}) {
const calls = { mergeSaveSubscriptionKinds: [], setExplicitChoice: [] };
return {
calls,
disableAgentMetricArchive,
mergeSaveSubscriptionKinds: async (kind) => {
if (mergeShouldFail) throw new Error("merge failed");
calls.mergeSaveSubscriptionKinds.push({ kind });
@@ -31,7 +36,7 @@ function makeDeps({ hasExplicitChoice = false, mergeShouldFail = false } = {}) {
const KIND_AGENT_TURN_METRIC = 44200;
async function runSeed(pubkey, deps) {
if (!pubkey) return;
if (!pubkey || deps.disableAgentMetricArchive) return;
if (deps.hasExplicitChoice(pubkey)) return;
try {
@@ -71,6 +76,14 @@ test("test_default_enabled_persists_explicit_choice_after_seed", async () => {
assert.equal(deps.calls.setExplicitChoice[0].enabled, true);
});
test("test_kill_switch_does_not_seed_or_persist_choice", async () => {
const deps = makeDeps({ disableAgentMetricArchive: true });
await runSeed("pubkey123", deps);
assert.equal(deps.calls.mergeSaveSubscriptionKinds.length, 0);
assert.equal(deps.calls.setExplicitChoice.length, 0);
});
test("test_explicit_choice_set_does_not_reseed", async () => {
const deps = makeDeps({ hasExplicitChoice: true });
await runSeed("pubkey123", deps);
@@ -30,12 +30,15 @@ export interface AgentMetricArchiveSeedDeps {
mergeSaveSubscriptionKinds: (kind: number) => Promise<void>;
hasExplicitChoice: (pubkey: string) => boolean;
setExplicitChoice: (pubkey: string, enabled: boolean) => void;
disableAgentMetricArchive: boolean;
}
const defaultDeps: AgentMetricArchiveSeedDeps = {
mergeSaveSubscriptionKinds,
hasExplicitChoice: hasExplicitAgentMetricArchiveChoice,
setExplicitChoice: setExplicitAgentMetricArchiveChoice,
disableAgentMetricArchive:
import.meta.env?.VITE_BUZZ_DISABLE_AGENT_METRIC_ARCHIVE === "1",
};
/**
@@ -51,7 +54,7 @@ export function useAgentMetricArchiveSeed(
deps: AgentMetricArchiveSeedDeps = defaultDeps,
): void {
React.useEffect(() => {
if (!pubkey) return;
if (!pubkey || deps.disableAgentMetricArchive) return;
// Already made an explicit choice for this identity — never re-seed.
if (deps.hasExplicitChoice(pubkey)) return;