diff --git a/packages/ui/src/features/loops/hooks/useLoopBuilderTask.ts b/packages/ui/src/features/loops/hooks/useLoopBuilderTask.ts index 987ab6b79a..284a401976 100644 --- a/packages/ui/src/features/loops/hooks/useLoopBuilderTask.ts +++ b/packages/ui/src/features/loops/hooks/useLoopBuilderTask.ts @@ -4,7 +4,7 @@ import { useInboxCloudTaskRunner, } from "@posthog/ui/features/inbox/hooks/useInboxCloudTaskRunner"; import { useCallback, useMemo, useRef } from "react"; -import { buildLoopBuilderPrompt } from "../loopBuilderPrompt"; +import { buildLoopBuilderSystemInstructions } from "../loopBuilderPrompt"; interface UseLoopBuilderTaskReturn { /** Start an auto-mode cloud session that builds a loop from `instructions` and navigate to it. */ @@ -30,13 +30,18 @@ export function useLoopBuilderTask(context?: { const buildInput = useCallback( (ctx: InboxCloudTaskInputContext): TaskCreationInput => { - const prompt = buildLoopBuilderPrompt({ - instructions: instructionsRef.current, + const userPrompt = instructionsRef.current.trim(); + const hasSeed = !!userPrompt; + const systemInstructions = buildLoopBuilderSystemInstructions({ + hasSeed, context: contextRef.current, }); + // createTask rejects empty content and the saga drops customInstructions without message text + const taskContent = hasSeed ? userPrompt : "Build a loop"; return { - content: prompt, - taskDescription: prompt, + content: taskContent, + taskDescription: taskContent, + customInstructions: systemInstructions, // Building a loop is pure PostHog-MCP work (loops-list, integrations-list, // loops-create); it never touches a working tree. Run repo-less so the // sandbox skips the clone and isn't tied to some arbitrary default repo. diff --git a/packages/ui/src/features/loops/loopBuilderPrompt.test.ts b/packages/ui/src/features/loops/loopBuilderPrompt.test.ts index 8575f7a5f1..47cdaace6b 100644 --- a/packages/ui/src/features/loops/loopBuilderPrompt.test.ts +++ b/packages/ui/src/features/loops/loopBuilderPrompt.test.ts @@ -1,17 +1,32 @@ import { describe, expect, it } from "vitest"; -import { buildLoopBuilderPrompt } from "./loopBuilderPrompt"; +import { + buildLoopBuilderPrompt, + buildLoopBuilderSystemInstructions, +} from "./loopBuilderPrompt"; describe("buildLoopBuilderPrompt", () => { it("embeds the seed instructions when provided", () => { const prompt = buildLoopBuilderPrompt({ instructions: "Summarize failing CI runs", }); + expect(prompt).toContain("Summarize failing CI runs"); expect(prompt).toContain( - "Here's what I want automated:\n\nSummarize failing CI runs", + "The user's message describes what they want automated.", ); expect(prompt).not.toContain("Start by asking me"); }); + it("keeps the user prompt out of system instructions", () => { + const instructions = buildLoopBuilderSystemInstructions({ + hasSeed: true, + }); + + expect(instructions).toContain( + "The user's message describes what they want automated.", + ); + expect(instructions).not.toContain("Summarize failing CI runs"); + }); + it.each([ { name: "absent", instructions: undefined }, { name: "whitespace-only", instructions: " \n" }, diff --git a/packages/ui/src/features/loops/loopBuilderPrompt.ts b/packages/ui/src/features/loops/loopBuilderPrompt.ts index e08b9ea9ad..9ac25f977d 100644 --- a/packages/ui/src/features/loops/loopBuilderPrompt.ts +++ b/packages/ui/src/features/loops/loopBuilderPrompt.ts @@ -13,13 +13,28 @@ export function buildLoopBuilderPrompt({ }): string { const seed = instructions?.trim(); + return [ + seed || undefined, + buildLoopBuilderSystemInstructions({ hasSeed: !!seed, context }), + ] + .filter((part): part is string => !!part) + .join("\n\n"); +} + +export function buildLoopBuilderSystemInstructions({ + hasSeed, + context, +}: { + hasSeed: boolean; + context?: { folderId: string; name: string }; +}): string { return `Your job in this session is to help me create a Loop for this PostHog project, then create it for me. A Loop is a named, cloud-executed agent automation: instructions the agent runs whenever a trigger fires (a schedule, a GitHub event, or an API call). Loops run unattended in a sandbox and can post results, open pull requests, and keep a context up to date. ${ - seed - ? `Here's what I want automated:\n\n${seed}\n` + hasSeed + ? "The user's message describes what they want automated.\n" : `Start by asking me what I want automated, and offer a couple of concrete ideas.\n` }${ context