# T0.5: LLM Agents & Prompts ## Scope Implement LLM activities (Planner, Judge, Implementer), LLM client, and prompt template registry. ## Implementation ### File: `action/llm/client.go` ```go type AnthropicClient struct { apiKey string } func NewClient() *AnthropicClient // Read ANTHROPIC_API_KEY from env // Return client func (c *AnthropicClient) CreateMessage(ctx context.Context, in MessageInput) (string, error) // Call Anthropic API messages.create // Respect model.ModelID, model.Thinking, model.Effort // Return response text ``` ### File: `action/planner.go` ```go type PlanningInput struct { Config OrchestratorConfig BoardState string // JSON or markdown of task board Milestone string } type TaskDispatch struct { TaskID string Prompt PromptSpec BaseTimeout time.Duration // can override default } func PlanningActivity(ctx context.Context, in PlanningInput) ([]TaskDispatch, error) // Read target repo's tasks/INDEX.md + board from shared FS // Render prompt: in.Config.SystemPrompt + in.Config.RolePrompts["planner"] template // Call LLM (Planner model) // Parse response: which tasks to dispatch next, optional tuning overrides // Return task dispatch list ``` ### File: `action/judge.go` ```go type JudgeInput struct { Config OrchestratorConfig Diff string // git diff output IntegrationTestLogs string // test output } type JudgeOutput struct { Verdict string // "pass" or "fail" Critique string // explanation if fail } func JudgeActivity(ctx context.Context, in JudgeInput) (JudgeOutput, error) // Render prompt: in.Config.SystemPrompt + in.Config.RolePrompts["judge"] // Call LLM (Judge model, reasoning) // Parse response: verdict + critique // Return JudgeOutput ``` ### File: `action/implementer.go` ```go type ImplementerInput struct { Config OrchestratorConfig TaskID string WorktreePath string Lessons string // "known errors — do not repeat" section } type ImplementerOutput struct { Success bool Changes string // summary of changes made } func ImplementerActivity(ctx context.Context, in ImplementerInput) (ImplementerOutput, error) // Render prompt: in.Config.SystemPrompt + in.Config.RolePrompts["implementer"] // Inject in.Lessons into Variables // Start tool-call agent loop (run git/cargo/pnpm/etc as needed) // After each tool call, activity.RecordHeartbeat(ctx, progress) // Return success/changes ``` ### File: `prompts/registry.go` ```go // go:embed prompts/*.tmpl func Render(templateRef string, variables map[string]any) (string, error) // Load embedded template via go:embed + text/template // Render with variables // Return rendered string ``` ### Files: `prompts/planner/default.tmpl`, etc. Empty templates for now; will be filled in by Planner/Judge/Implementer activities. ``` You are a Planner agent. Your job: reconcile task state, dispatch work. System prompt: {{.SystemPrompt}} Current board: {{.BoardState}} Current config: {{.Config | json}} What tasks should we dispatch next? (respond in JSON: {"tasks": [{"id": "T0.1", "timeout_override_ms": null}, ...]}) ``` ## Verification ```bash cd /Users/rockliang/workplace/Poimen/workflows go test -v ./tests -run TestPrompts # Test file: tests/prompts_test.go ``` Test cases: - Render template with system prompt + variables → output includes system prompt prefix - Render with RawTemplate override → uses raw template, not embedded - Render with Variables substitution → all {{.Var}} replaced - Mock LLM client responses → activities parse correctly ## Done Criteria - `go test ./tests -run TestPrompts` passes - All templates render without errors - LLM client reads ANTHROPIC_API_KEY from env (or uses mock in tests) - Activities parse LLM responses into structured output