Menu
popagent
publicLatest change da13a7bebe63bf4b2693180d2d4850aabeaa0807 - Add autonomous evolution and self-healing by AkurAI Build
import { Cron } from "croner";
import type { AutonomySettings, AutonomySettingsInput } from "./api-types";
import { initializeAgentRoleStorage } from "./agent-settings";
import { retryableInit } from "./retryable-init";
import { storage } from "./storage";
const AUTONOMY_SETTINGS_COLUMNS = `
enabled,
reflection_interval_ms AS "reflectionIntervalMs",
batch_size AS "batchSize",
max_attempts AS "maxAttempts",
auto_apply_strategies AS "autoApplyStrategies",
auto_create_skills AS "autoCreateSkills",
self_update_enabled AS "selfUpdateEnabled",
self_update_cron AS "selfUpdateCron",
updated_at::text AS "updatedAt"
`;
const initializeEvolutionStorageOnce = retryableInit(async () => {
await initializeAgentRoleStorage();
const schema = await Bun.file(new URL("./evolution.sql", import.meta.url)).text();
await storage.db.none(schema);
});
export function initializeEvolutionStorage(): Promise<void> {
return initializeEvolutionStorageOnce();
}
function validate(input: AutonomySettingsInput): AutonomySettingsInput {
if (!Number.isInteger(input.reflectionIntervalMs)
|| input.reflectionIntervalMs < 60_000
|| input.reflectionIntervalMs > 86_400_000) {
throw new RangeError("Reflection interval must be between 60000 and 86400000 milliseconds");
}
if (!Number.isInteger(input.batchSize) || input.batchSize < 1 || input.batchSize > 100) {
throw new RangeError("Evolution batch size must be between 1 and 100");
}
if (!Number.isInteger(input.maxAttempts) || input.maxAttempts < 1 || input.maxAttempts > 10) {
throw new RangeError("Evolution max attempts must be between 1 and 10");
}
const selfUpdateCron = input.selfUpdateCron.trim();
if (!selfUpdateCron || selfUpdateCron.length > 100) {
throw new RangeError("Self-update cron must be between 1 and 100 characters");
}
try {
new Cron(selfUpdateCron, { paused: true }).nextRun();
} catch {
throw new RangeError("Self-update cron is invalid");
}
return { ...input, selfUpdateCron };
}
export class AutonomySettingsStore {
readonly storage = storage;
private readonly listeners = new Set<(settings: AutonomySettings) => void>();
init(): Promise<void> {
return initializeEvolutionStorage();
}
async get(): Promise<AutonomySettings> {
await this.init();
return this.storage.db.one<AutonomySettings>(`
SELECT ${AUTONOMY_SETTINGS_COLUMNS}
FROM popagent_autonomy_settings
WHERE singleton = TRUE
`);
}
async update(rawInput: AutonomySettingsInput): Promise<AutonomySettings> {
const input = validate(rawInput);
await this.init();
const settings = await this.storage.db.one<AutonomySettings>(`
UPDATE popagent_autonomy_settings
SET enabled = $1,
reflection_interval_ms = $2,
batch_size = $3,
max_attempts = $4,
auto_apply_strategies = $5,
auto_create_skills = $6,
self_update_enabled = $7,
self_update_cron = $8,
updated_at = NOW()
WHERE singleton = TRUE
RETURNING ${AUTONOMY_SETTINGS_COLUMNS}
`, [
input.enabled,
input.reflectionIntervalMs,
input.batchSize,
input.maxAttempts,
input.autoApplyStrategies,
input.autoCreateSkills,
input.selfUpdateEnabled,
input.selfUpdateCron,
]);
for (const listener of this.listeners) listener(settings);
return settings;
}
subscribe(listener: (settings: AutonomySettings) => void): () => void {
this.listeners.add(listener);
return () => this.listeners.delete(listener);
}
}
export const autonomySettings = new AutonomySettingsStore();