Menu
popagent
publicLatest change 99d92c06b60aa592036b546fad6f57fa7b899194 - Stop learning from runs that ran out of steps, and sweep every checkout by AkurAI Build
import { Cron } from "croner";
import { hookAudit } from "./hook-audit";
import { longTermMemory } from "./long-term-memory";
import { appLogger } from "./observability";
import { storage } from "./storage";
import { agentWorkspaces } from "./agent-workspaces";
import { createWorkspaceSync } from "./workspace-sync";
type MaintenanceStores = {
memory: Pick<typeof longTermMemory, "pruneEpisodes">;
audit: Pick<typeof hookAudit, "cleanupAll">;
observability?: { prune(): Promise<unknown> };
/**
* Registered checkouts drift out of date on their own. A scheduled run
* refreshes the one workspace it uses, but a workspace no schedule touches
* would never be refreshed at all, so maintenance sweeps all of them.
*/
workspaces?: {
list(): Promise<Array<{ id: string }>>;
sync(workspaceId: string): Promise<void>;
};
};
function retentionDays(name: string, fallback: number): number {
const value = Number(process.env[name] ?? fallback);
if (!Number.isInteger(value) || value < 1 || value > 365) throw new Error(`${name} must be an integer from 1 to 365`);
return value;
}
export const observabilityRetention = {
async prune() {
const store = await storage.getStore("observability");
if (!store) throw new Error("Observability storage is unavailable");
const signalDays = retentionDays("POPAGENT_OBSERVABILITY_RETENTION_DAYS", 30);
const feedbackDays = retentionDays("POPAGENT_OBSERVABILITY_FEEDBACK_RETENTION_DAYS", 90);
return store.prune({
spans: { maxAge: `${signalDays}d` },
metrics: { maxAge: `${signalDays}d` },
logs: { maxAge: `${signalDays}d` },
scores: { maxAge: `${signalDays}d` },
feedback: { maxAge: `${feedbackDays}d` },
}, { maxBatches: 100 });
},
};
export async function runMaintenanceOnce(stores: MaintenanceStores = {
memory: longTermMemory,
audit: hookAudit,
observability: observabilityRetention,
workspaces: {
list: async () => agentWorkspaces.list(),
sync: createWorkspaceSync(agentWorkspaces),
},
}): Promise<void> {
await Promise.all([
stores.memory.pruneEpisodes(),
stores.audit.cleanupAll(),
stores.observability?.prune(),
syncWorkspaces(stores.workspaces),
]);
}
/** Refreshing a checkout is best effort; one bad repository must not stop the sweep. */
async function syncWorkspaces(workspaces: MaintenanceStores["workspaces"]): Promise<void> {
if (!workspaces) return;
for (const workspace of await workspaces.list()) {
await workspaces.sync(workspace.id).catch((error) =>
appLogger().warn("maintenance.workspace_sync_failed", { workspaceId: workspace.id, error })
);
}
}
export function startMaintenance(): Cron {
const job = new Cron("0 3 * * *", { timezone: Intl.DateTimeFormat().resolvedOptions().timeZone }, () => {
void runMaintenanceOnce().catch((error) => appLogger().error("maintenance.failed", { error }));
});
void runMaintenanceOnce().catch((error) => appLogger().error("maintenance.failed", { error }));
return job;
}