schedules for background agents

This commit is contained in:
Arjun 2026-02-04 13:55:51 +05:30
parent f03a00d2af
commit 858c277bd3
7 changed files with 176 additions and 0 deletions

View file

@ -0,0 +1,43 @@
import { WorkDir } from "../config/config.js";
import { AgentScheduleConfig, AgentScheduleEntry } from "@x/shared/dist/agent-schedule.js";
import fs from "fs/promises";
import path from "path";
import z from "zod";
const DEFAULT_AGENT_SCHEDULES: z.infer<typeof AgentScheduleConfig>["agents"] = {};
export interface IAgentScheduleRepo {
ensureConfig(): Promise<void>;
getConfig(): Promise<z.infer<typeof AgentScheduleConfig>>;
upsert(agentName: string, entry: z.infer<typeof AgentScheduleEntry>): Promise<void>;
delete(agentName: string): Promise<void>;
}
export class FSAgentScheduleRepo implements IAgentScheduleRepo {
private readonly configPath = path.join(WorkDir, "config", "agent-schedule.json");
async ensureConfig(): Promise<void> {
try {
await fs.access(this.configPath);
} catch {
await fs.writeFile(this.configPath, JSON.stringify({ agents: DEFAULT_AGENT_SCHEDULES }, null, 2));
}
}
async getConfig(): Promise<z.infer<typeof AgentScheduleConfig>> {
const config = await fs.readFile(this.configPath, "utf8");
return AgentScheduleConfig.parse(JSON.parse(config));
}
async upsert(agentName: string, entry: z.infer<typeof AgentScheduleEntry>): Promise<void> {
const conf = await this.getConfig();
conf.agents[agentName] = entry;
await fs.writeFile(this.configPath, JSON.stringify(conf, null, 2));
}
async delete(agentName: string): Promise<void> {
const conf = await this.getConfig();
delete conf.agents[agentName];
await fs.writeFile(this.configPath, JSON.stringify(conf, null, 2));
}
}

View file

@ -0,0 +1,63 @@
import { WorkDir } from "../config/config.js";
import { AgentScheduleState, AgentScheduleStateEntry } from "@x/shared/dist/agent-schedule-state.js";
import fs from "fs/promises";
import path from "path";
import z from "zod";
const DEFAULT_AGENT_SCHEDULE_STATE: z.infer<typeof AgentScheduleState>["agents"] = {};
export interface IAgentScheduleStateRepo {
ensureState(): Promise<void>;
getState(): Promise<z.infer<typeof AgentScheduleState>>;
getAgentState(agentName: string): Promise<z.infer<typeof AgentScheduleStateEntry> | null>;
updateAgentState(agentName: string, entry: Partial<z.infer<typeof AgentScheduleStateEntry>>): Promise<void>;
setAgentState(agentName: string, entry: z.infer<typeof AgentScheduleStateEntry>): Promise<void>;
deleteAgentState(agentName: string): Promise<void>;
}
export class FSAgentScheduleStateRepo implements IAgentScheduleStateRepo {
private readonly statePath = path.join(WorkDir, "config", "agent-schedule-state.json");
async ensureState(): Promise<void> {
try {
await fs.access(this.statePath);
} catch {
await fs.writeFile(this.statePath, JSON.stringify({ agents: DEFAULT_AGENT_SCHEDULE_STATE }, null, 2));
}
}
async getState(): Promise<z.infer<typeof AgentScheduleState>> {
const state = await fs.readFile(this.statePath, "utf8");
return AgentScheduleState.parse(JSON.parse(state));
}
async getAgentState(agentName: string): Promise<z.infer<typeof AgentScheduleStateEntry> | null> {
const state = await this.getState();
return state.agents[agentName] ?? null;
}
async updateAgentState(agentName: string, entry: Partial<z.infer<typeof AgentScheduleStateEntry>>): Promise<void> {
const state = await this.getState();
const existing = state.agents[agentName] ?? {
status: "scheduled" as const,
lastRunAt: null,
nextRunAt: null,
lastError: null,
runCount: 0,
};
state.agents[agentName] = { ...existing, ...entry };
await fs.writeFile(this.statePath, JSON.stringify(state, null, 2));
}
async setAgentState(agentName: string, entry: z.infer<typeof AgentScheduleStateEntry>): Promise<void> {
const state = await this.getState();
state.agents[agentName] = entry;
await fs.writeFile(this.statePath, JSON.stringify(state, null, 2));
}
async deleteAgentState(agentName: string): Promise<void> {
const state = await this.getState();
delete state.agents[agentName];
await fs.writeFile(this.statePath, JSON.stringify(state, null, 2));
}
}

View file

@ -1,6 +1,8 @@
import container from "../di/container.js";
import type { IModelConfigRepo } from "../models/repo.js";
import type { IMcpConfigRepo } from "../mcp/repo.js";
import type { IAgentScheduleRepo } from "../agent-schedule/repo.js";
import type { IAgentScheduleStateRepo } from "../agent-schedule/state-repo.js";
import { ensureSecurityConfig } from "./security.js";
/**
@ -11,10 +13,14 @@ export async function initConfigs(): Promise<void> {
// Resolve repos and explicitly call their ensureConfig methods
const modelConfigRepo = container.resolve<IModelConfigRepo>("modelConfigRepo");
const mcpConfigRepo = container.resolve<IMcpConfigRepo>("mcpConfigRepo");
const agentScheduleRepo = container.resolve<IAgentScheduleRepo>("agentScheduleRepo");
const agentScheduleStateRepo = container.resolve<IAgentScheduleStateRepo>("agentScheduleStateRepo");
await Promise.all([
modelConfigRepo.ensureConfig(),
mcpConfigRepo.ensureConfig(),
agentScheduleRepo.ensureConfig(),
agentScheduleStateRepo.ensureState(),
ensureSecurityConfig(),
]);
}

View file

@ -12,6 +12,8 @@ import { FSOAuthRepo, IOAuthRepo } from "../auth/repo.js";
import { FSClientRegistrationRepo, IClientRegistrationRepo } from "../auth/client-repo.js";
import { FSGranolaConfigRepo, IGranolaConfigRepo } from "../knowledge/granola/repo.js";
import { IAbortRegistry, InMemoryAbortRegistry } from "../runs/abort-registry.js";
import { FSAgentScheduleRepo, IAgentScheduleRepo } from "../agent-schedule/repo.js";
import { FSAgentScheduleStateRepo, IAgentScheduleStateRepo } from "../agent-schedule/state-repo.js";
const container = createContainer({
injectionMode: InjectionMode.PROXY,
@ -33,6 +35,8 @@ container.register({
oauthRepo: asClass<IOAuthRepo>(FSOAuthRepo).singleton(),
clientRegistrationRepo: asClass<IClientRegistrationRepo>(FSClientRegistrationRepo).singleton(),
granolaConfigRepo: asClass<IGranolaConfigRepo>(FSGranolaConfigRepo).singleton(),
agentScheduleRepo: asClass<IAgentScheduleRepo>(FSAgentScheduleRepo).singleton(),
agentScheduleStateRepo: asClass<IAgentScheduleStateRepo>(FSAgentScheduleStateRepo).singleton(),
});
export default container;