spb/worthdoing Public
Autonomous investigation agent that discovers, challenges, and ranks things genuinely worth doing — Claude + Firecrawl, Next.js 16, PostgreSQL
TypeScript 91.5%
SQL 5.8%
CSS 2.2%
1/**2 * WorthDoing.ai3 * Author: Simon-Pierre Boucher4 * Contact: contact@spboucher.ai5 * File: src/lib/agent/runner.ts6 * Description: Investigation lifecycle — create with default budgets, launch/resume the engine in the background.7 */8import { eq } from "drizzle-orm";9import { db } from "@/lib/db/client";10import { investigations, type BudgetLimits, type BudgetUsed } from "@/lib/db/schema";11import { agentModel } from "@/lib/anthropic/client";12import { PROMPT_VERSION } from "./prompts";13import { TOOL_SCHEMA_VERSION } from "./tools";14import { runInvestigation } from "./engine";1516export const DEFAULT_BUDGET: BudgetLimits = {17 maxAgentSteps: 30,18 maxSearches: 20,19 maxScrapes: 60,20 maxCrawls: 3,21 maxWallTimeMs: 25 * 60 * 1000,22};2324const ZERO_USED: BudgetUsed = {25 agentSteps: 0,26 searches: 0,27 scrapes: 0,28 crawls: 0,29 inputTokens: 0,30 outputTokens: 0,31 costUsd: 0,32};3334const globalForRunner = globalThis as unknown as { __wdRunning?: Set<string> };35const running = (globalForRunner.__wdRunning ??= new Set<string>());3637/** Create an investigation and start the agent in the background. */38export async function createAndStartInvestigation(objective: string, userId?: string) {39 const [inv] = await db40 .insert(investigations)41 .values({42 objective,43 userId: userId ?? null,44 status: "pending",45 phase: "scouting",46 budget: DEFAULT_BUDGET,47 budgetUsed: ZERO_USED,48 promptVersion: PROMPT_VERSION,49 toolSchemaVersion: TOOL_SCHEMA_VERSION,50 model: agentModel(),51 })52 .returning();5354 launch(inv.id);55 return inv;56}5758/** Resume a stuck "running" investigation (e.g. after a server restart). */59export async function resumeInvestigation(investigationId: string): Promise<boolean> {60 const [inv] = await db.select().from(investigations).where(eq(investigations.id, investigationId));61 if (!inv || inv.status === "completed" || inv.status === "failed" || inv.status === "cancelled") return false;62 launch(investigationId);63 return true;64}6566export function isRunning(investigationId: string): boolean {67 return running.has(investigationId);68}6970function launch(investigationId: string): void {71 if (running.has(investigationId)) return;72 running.add(investigationId);73 // Detached background execution inside the Next.js server process (V1 scope).74 void runInvestigation(investigationId)75 .catch((err) => {76 console.error(`[worthdoing] investigation ${investigationId} crashed:`, err);77 })78 .finally(() => {79 running.delete(investigationId);80 });81}82