Browse Source

Prompts tuning (#22)

* Disable default agents and tmux for now

* Update workflow

* Remove short description

* Add descriptions

* Fix prompts

* Hard gates

* Version
Alvin 2 months ago
parent
commit
0e2422d4cd

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "oh-my-opencode-slim",
-  "version": "0.3.5",
+  "version": "0.3.6",
   "description": "Lightweight agent orchestration plugin for OpenCode - a slimmed-down fork of oh-my-opencode",
   "main": "dist/index.js",
   "types": "dist/index.d.ts",

+ 2 - 2
src/agents/document-writer.ts

@@ -3,11 +3,11 @@ import type { AgentDefinition } from "./orchestrator";
 export function createDocumentWriterAgent(model: string): AgentDefinition {
   return {
     name: "document-writer",
-    description: "Technical documentation and READMEs",
+    description: "Technical documentation writer. Use for README files, API docs, architecture docs, and user guides.",
     config: {
       model,
       temperature: 0.3,
-      system: DOCUMENT_WRITER_PROMPT,
+      prompt: DOCUMENT_WRITER_PROMPT,
     },
   };
 }

+ 2 - 2
src/agents/explore.ts

@@ -3,11 +3,11 @@ import type { AgentDefinition } from "./orchestrator";
 export function createExploreAgent(model: string): AgentDefinition {
   return {
     name: "explore",
-    description: "Fast codebase search and pattern matching",
+    description: "Fast codebase search and pattern matching. Use for finding files, locating code patterns, and answering 'where is X?' questions.",
     config: {
       model,
       temperature: 0.1,
-      system: EXPLORE_PROMPT,
+      prompt: EXPLORE_PROMPT,
     },
   };
 }

+ 2 - 2
src/agents/frontend.ts

@@ -3,11 +3,11 @@ import type { AgentDefinition } from "./orchestrator";
 export function createFrontendAgent(model: string): AgentDefinition {
   return {
     name: "frontend-ui-ux-engineer",
-    description: "UI/UX implementation and visual changes",
+    description: "UI/UX design and implementation. Use for styling, responsive design, component architecture, CSS/Tailwind, and visual polish.",
     config: {
       model,
       temperature: 0.7,
-      system: FRONTEND_PROMPT,
+      prompt: FRONTEND_PROMPT,
     },
   };
 }

+ 17 - 25
src/agents/index.ts

@@ -16,36 +16,28 @@ type AgentFactory = (model: string) => AgentDefinition;
 function applyOverrides(agent: AgentDefinition, override: AgentOverrideConfig): void {
   if (override.model) agent.config.model = override.model;
   if (override.temperature !== undefined) agent.config.temperature = override.temperature;
-  if (override.prompt) agent.config.system = override.prompt;
+  if (override.prompt) agent.config.prompt = override.prompt;
   if (override.prompt_append) {
-    agent.config.system = `${agent.config.system}\n\n${override.prompt_append}`;
+    agent.config.prompt = `${agent.config.prompt}\n\n${override.prompt_append}`;
   }
 }
 
 type SubagentName = Exclude<AgentName, "orchestrator">;
-type SubagentInfo = { factory: AgentFactory; shortDesc: string };
 
-/** Short descriptions for each subagent (used in tool descriptions) */
-export const SUBAGENT_INFO = {
-  explore: { factory: createExploreAgent, shortDesc: "codebase grep" },
-  librarian: { factory: createLibrarianAgent, shortDesc: "docs/GitHub" },
-  oracle: { factory: createOracleAgent, shortDesc: "strategy" },
-  "frontend-ui-ux-engineer": { factory: createFrontendAgent, shortDesc: "UI/UX" },
-  "document-writer": { factory: createDocumentWriterAgent, shortDesc: "docs" },
-  "multimodal-looker": { factory: createMultimodalAgent, shortDesc: "image/visual analysis" },
-  "code-simplicity-reviewer": { factory: createSimplicityReviewerAgent, shortDesc: "code review" },
-} as const satisfies Record<SubagentName, SubagentInfo>;
-
-/** Generate agent list string for tool descriptions */
-export function getAgentListDescription(): string {
-  return (Object.entries(SUBAGENT_INFO) as [SubagentName, SubagentInfo][])
-    .map(([name, { shortDesc }]) => `${name} (${shortDesc})`)
-    .join(", ");
-}
+/** Agent factories indexed by name */
+const SUBAGENT_FACTORIES: Record<SubagentName, AgentFactory> = {
+  explore: createExploreAgent,
+  librarian: createLibrarianAgent,
+  oracle: createOracleAgent,
+  "frontend-ui-ux-engineer": createFrontendAgent,
+  "document-writer": createDocumentWriterAgent,
+  "multimodal-looker": createMultimodalAgent,
+  "code-simplicity-reviewer": createSimplicityReviewerAgent,
+};
 
 /** Get list of agent names */
 export function getAgentNames(): SubagentName[] {
-  return Object.keys(SUBAGENT_INFO) as SubagentName[];
+  return Object.keys(SUBAGENT_FACTORIES) as SubagentName[];
 }
 
 export function createAgents(config?: PluginConfig): AgentDefinition[] {
@@ -53,8 +45,8 @@ export function createAgents(config?: PluginConfig): AgentDefinition[] {
   const agentOverrides = config?.agents ?? {};
 
   // 1. Gather all sub-agent proto-definitions
-  const protoSubAgents = (Object.entries(SUBAGENT_INFO) as [SubagentName, SubagentInfo][]).map(
-    ([name, { factory }]) => factory(DEFAULT_MODELS[name])
+  const protoSubAgents = (Object.entries(SUBAGENT_FACTORIES) as [SubagentName, AgentFactory][]).map(
+    ([name, factory]) => factory(DEFAULT_MODELS[name])
   );
 
   // 2. Apply common filtering and overrides
@@ -71,7 +63,7 @@ export function createAgents(config?: PluginConfig): AgentDefinition[] {
   // 3. Create Orchestrator (with its own overrides)
   const orchestratorModel =
     agentOverrides["orchestrator"]?.model ?? DEFAULT_MODELS["orchestrator"];
-  const orchestrator = createOrchestratorAgent(orchestratorModel, allSubAgents);
+  const orchestrator = createOrchestratorAgent(orchestratorModel);
   const oOverride = agentOverrides["orchestrator"];
   if (oOverride) {
     applyOverrides(orchestrator, oOverride);
@@ -82,5 +74,5 @@ export function createAgents(config?: PluginConfig): AgentDefinition[] {
 
 export function getAgentConfigs(config?: PluginConfig): Record<string, SDKAgentConfig> {
   const agents = createAgents(config);
-  return Object.fromEntries(agents.map((a) => [a.name, a.config]));
+  return Object.fromEntries(agents.map((a) => [a.name, { ...a.config, description: a.description }]));
 }

+ 2 - 2
src/agents/librarian.ts

@@ -3,11 +3,11 @@ import type { AgentDefinition } from "./orchestrator";
 export function createLibrarianAgent(model: string): AgentDefinition {
   return {
     name: "librarian",
-    description: "External documentation and library research",
+    description: "External documentation and library research. Use for official docs lookup, GitHub examples, and understanding library internals.",
     config: {
       model,
       temperature: 0.1,
-      system: LIBRARIAN_PROMPT,
+      prompt: LIBRARIAN_PROMPT,
     },
   };
 }

+ 2 - 2
src/agents/multimodal.ts

@@ -3,11 +3,11 @@ import type { AgentDefinition } from "./orchestrator";
 export function createMultimodalAgent(model: string): AgentDefinition {
   return {
     name: "multimodal-looker",
-    description: "Image and UI analysis",
+    description: "Image and visual content analysis. Use for PDFs, screenshots, diagrams, mockups, and extracting info from visuals.",
     config: {
       model,
       temperature: 0.1,
-      system: MULTIMODAL_PROMPT,
+      prompt: MULTIMODAL_PROMPT,
     },
   };
 }

+ 2 - 2
src/agents/oracle.ts

@@ -3,11 +3,11 @@ import type { AgentDefinition } from "./orchestrator";
 export function createOracleAgent(model: string): AgentDefinition {
   return {
     name: "oracle",
-    description: "Architecture, debugging, and code review",
+    description: "Strategic technical advisor. Use for architecture decisions, complex debugging, code review, and engineering guidance.",
     config: {
       model,
       temperature: 0.1,
-      system: ORACLE_PROMPT,
+      prompt: ORACLE_PROMPT,
     },
   };
 }

+ 136 - 40
src/agents/orchestrator.ts

@@ -2,68 +2,164 @@ import type { AgentConfig } from "@opencode-ai/sdk";
 
 export interface AgentDefinition {
   name: string;
-  description: string;
+  description?: string;
   config: AgentConfig;
 }
 
-export function createOrchestratorAgent(model: string, subAgents: AgentDefinition[]): AgentDefinition {
-  const agentTable = subAgents
-    .map((a) => `| @${a.name} | ${a.description} |`)
-    .join("\n");
-
-  const prompt = ORCHESTRATOR_PROMPT_TEMPLATE.replace("{{AGENT_TABLE}}", agentTable);
-
+export function createOrchestratorAgent(model: string): AgentDefinition {
   return {
     name: "orchestrator",
-    description: "AI coding orchestrator with access to specialized subagents",
     config: {
       model,
       temperature: 0.1,
-      system: prompt,
+      prompt: ORCHESTRATOR_PROMPT,
     },
   };
 }
 
-const ORCHESTRATOR_PROMPT_TEMPLATE = `<Role>
-You are an AI coding orchestrator with access to specialized subagents.
+const ORCHESTRATOR_PROMPT = `<Role>
+You are an AI coding orchestrator. You DO NOT implement - you DELEGATE.
+
+**Your Identity:**
+- You are a CONDUCTOR, not a musician
+- You are a MANAGER, not a worker  
+- You are a ROUTER, not a processor
 
-**Core Competencies**:
-- Parse implicit requirements from explicit requests
-- Delegate specialized work to the right subagents
-- Sensible parallel execution
+**Core Rule:** If a specialist agent can do the work, YOU MUST delegate to them.
 
+**Why Delegation Matters:**
+- @frontend-ui-ux-engineer → 10x better designs than you
+- @librarian → finds docs you'd miss
+- @explore → searches faster than you
+- @oracle → catches architectural issues you'd overlook
+- @document-writer → writes cleaner docs for less cost
+- @code-simplicity-reviewer → spots complexity you're blind to
+- @multimodal-looker → understands images you can't parse
+
+**Your value is in orchestration, not implementation.**
 </Role>
 
-<Subagents>
-| Agent | Purpose / When to Use |
-|-------|-----------------------|
-{{AGENT_TABLE}}
-</Subagents>
+<Agents>
+## Research Agents (Background-friendly)
+
+@explore - Fast codebase search and pattern matching
+  Triggers: "find", "where is", "search for", "which file", "locate"
+  Example: background_task(agent="explore", prompt="Find all authentication implementations")
+
+@librarian - External documentation and library research  
+  Triggers: "how does X library work", "docs for", "API reference", "best practice for"
+  Example: background_task(agent="librarian", prompt="How does React Query handle cache invalidation")
+
+## Advisory Agents (Usually sync)
+
+@oracle - Architecture, debugging, and strategic code review
+  Triggers: "should I", "why does", "review", "debug", "what's wrong", "tradeoffs"
+  Use when: Complex decisions, mysterious bugs, architectural uncertainty
+
+@code-simplicity-reviewer - Complexity analysis and YAGNI enforcement
+  Triggers: "too complex", "simplify", "review for complexity", after major refactors
+  Use when: After writing significant code, before finalizing PRs
 
-<Delegation>
-Delegate when specialists are available.
+## Implementation Agents (Sync)
+
+@frontend-ui-ux-engineer - UI/UX design and implementation
+  Triggers: "styling", "responsive", "UI", "UX", "component design", "CSS", "animation"
+  Use when: Any visual/frontend work that needs design sense
+
+@document-writer - Technical documentation and knowledge capture
+  Triggers: "document", "README", "update docs", "explain in docs"
+  Use when: After features are implemented, before closing tasks
+
+@multimodal-looker - Image and visual content analysis
+  Triggers: User provides image, screenshot, diagram, mockup
+  Use when: Need to extract info from visual inputs
+</Agents>
+
+<Workflow>
+## Phase 1: Understand
+Parse the request. Identify explicit and implicit requirements.
+
+## Phase 2: Delegation Gate (MANDATORY - DO NOT SKIP)
+
+STOP. Before ANY implementation, you MUST complete this checklist:
 
-## Background Tasks
-Use background_task for parallel work when needed:
 \`\`\`
-background_task(agent="explore", prompt="Find all auth implementations")
-background_task(agent="librarian", prompt="How does library X handle Y")
+DELEGATION CHECKLIST (complete before coding):
+[ ] UI/styling/design/visual/CSS/animation? → @frontend-ui-ux-engineer MUST handle
+[ ] Need codebase context? → @explore first  
+[ ] External library/API docs needed? → @librarian first
+[ ] Architecture decision or debugging? → @oracle first
+[ ] Image/screenshot/diagram provided? → @multimodal-looker first
+[ ] Documentation to write? → @document-writer handles
 \`\`\`
 
-## When to Delegate
-- Use the subagent most relevant to the task description.
-- Use background tasks for research or search while you continue working.
+**CRITICAL RULES:**
+1. If ANY checkbox applies → delegate BEFORE you write code
+2. Reading files for context ≠ completing the task. Context gathering is Phase 1, not Phase 3.
+3. Your job is to DELEGATE task when specialize provide improved speed, quality or cost, not to DO it yourself this time.
 
-## Skills
-- For browser-related tasks (verification, screenshots, scraping, testing), call the "omo_skill" tool with name "playwright" before taking action. Use relative filenames for screenshots (e.g., 'screenshot.png'); they are saved within subdirectories of '/tmp/playwright-mcp-output/'. Use the "omo_skill_mcp" tool to invoke browser actions with camelCase parameters: skillName, mcpName, toolName, and toolArgs.
-</Delegation>
+**Anti-patterns to avoid:**
+- Reading files → feeling productive → implementing yourself (WRONG)
+- Creating todos → feeling like you planned → skipping delegation (WRONG)
+- "I can handle this" → doing specialist work yourself (WRONG)
 
-<Workflow>
-1. Understand the request fully
-2. If multi-step: create TODO list first
-3. For search: fire parallel explore agents
-4. Use LSP tools for refactoring (safer than text edits)
-5. Verify with lsp_diagnostics after changes
-6. Mark TODOs complete as you finish each
+## Phase 2.1: Task Planning
+1. If task has 2+ steps → Create todo list with delegations noted
+2. Mark current task \`in_progress\` before starting
+3. Mark \`completed\` immediately when done
+
+## Phase 3: Execute
+1. Fire background research (explore, librarian) in parallel
+2. DELEGATE implementation to specialists based on Phase 2 checklist
+3. Only do work yourself if NO specialist applies
+4. Integrate results from specialists
+
+## Phase 4: Verify
+- Run lsp_diagnostics to check for errors
+- @code-simplicity-reviewer for complex changes
+- Update documentation if behavior changed
 </Workflow>
+
+### Clarification Protocol (when asking):
+
+\`\`\`
+I want to make sure I understand correctly.
+
+**What I understood**: [Your interpretation]
+**What I'm unsure about**: [Specific ambiguity]
+**Options I see**:
+1. [Option A] - [effort/implications]
+2. [Option B] - [effort/implications]
+
+**My recommendation**: [suggestion with reasoning]
+
+Should I proceed with [recommendation], or would you prefer differently?
+\`\`\`
+
+## Communication Style
+
+### Be Concise
+- Start work immediately. No acknowledgments ("I'm on it", "Let me...", "I'll start...") 
+- Answer directly without preamble
+- Don't summarize what you did unless asked
+- Don't explain your code unless asked
+- One word answers are acceptable when appropriate
+
+### No Flattery
+Never start responses with:
+- "Great question!"
+- "That's a really good idea!"
+- "Excellent choice!"
+- Any praise of the user's input
+
+### When User is Wrong
+If the user's approach seems problematic:
+- Don't blindly implement it
+- Don't lecture or be preachy
+- Concisely state your concern and alternative
+- Ask if they want to proceed anyway
+
+## Skills
+For browser tasks (verification, screenshots, scraping), call omo_skill with name "playwright" first.
+Use omo_skill_mcp to invoke browser actions. Screenshots save to '/tmp/playwright-mcp-output/'.
 `;

+ 2 - 2
src/agents/simplicity-reviewer.ts

@@ -3,11 +3,11 @@ import type { AgentDefinition } from "./orchestrator";
 export function createSimplicityReviewerAgent(model: string): AgentDefinition {
   return {
     name: "code-simplicity-reviewer",
-    description: "Ruthless code simplification and YAGNI principle enforcement",
+    description: "Code complexity analysis and YAGNI enforcement. Use after major refactors or before finalizing PRs to simplify code.",
     config: {
       model,
       temperature: 0.1,
-      system: SIMPLICITY_REVIEWER_PROMPT,
+      prompt: SIMPLICITY_REVIEWER_PROMPT,
     },
   };
 }

+ 26 - 0
src/cli/config-manager.ts

@@ -348,6 +348,32 @@ export function writeLiteConfig(installConfig: InstallConfig): ConfigMergeResult
   }
 }
 
+/**
+ * Disable OpenCode's default subagents since the plugin provides its own
+ */
+export function disableDefaultAgents(): ConfigMergeResult {
+  const configPath = getConfigJson()
+
+  try {
+    ensureConfigDir()
+    let config = parseConfig(configPath) ?? {}
+
+    const agent = (config.agent ?? {}) as Record<string, unknown>
+    agent.explore = { disable: true }
+    agent.general = { disable: true }
+    config.agent = agent
+
+    writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n")
+    return { success: true, configPath }
+  } catch (err) {
+    return {
+      success: false,
+      configPath,
+      error: `Failed to disable default agents: ${err}`,
+    }
+  }
+}
+
 export function detectCurrentConfig(): DetectedConfig {
   const result: DetectedConfig = {
     isInstalled: false,

+ 37 - 26
src/cli/install.ts

@@ -8,6 +8,7 @@ import {
   addAuthPlugins,
   addProviderConfig,
   addServerConfig,
+  disableDefaultAgents,
   detectCurrentConfig,
   isTmuxInstalled,
   generateLiteConfig,
@@ -137,8 +138,10 @@ async function askYesNo(
 
 async function runInteractiveMode(detected: DetectedConfig): Promise<InstallConfig> {
   const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
-  const tmuxInstalled = await isTmuxInstalled()
-  const totalQuestions = tmuxInstalled ? 4 : 3
+  // TODO: tmux has a bug, disabled for now
+  // const tmuxInstalled = await isTmuxInstalled()
+  // const totalQuestions = tmuxInstalled ? 4 : 3
+  const totalQuestions = 3
 
   try {
     console.log(`${BOLD}Question 1/${totalQuestions}:${RESET}`)
@@ -154,20 +157,21 @@ async function runInteractiveMode(detected: DetectedConfig): Promise<InstallConf
     const cerebras = await askYesNo(rl, "Do you have access to Cerebras API?", detected.hasCerebras ? "yes" : "no")
     console.log()
 
-    let tmux: BooleanArg = "no"
-    if (tmuxInstalled) {
-      console.log(`${BOLD}Question 4/4:${RESET}`)
-      printInfo(`${BOLD}Tmux detected!${RESET} We can enable tmux integration for you.`)
-      printInfo("This will spawn new panes for sub-agents, letting you watch them work in real-time.")
-      tmux = await askYesNo(rl, "Enable tmux integration?", detected.hasTmux ? "yes" : "no")
-      console.log()
-    }
+    // TODO: tmux has a bug, disabled for now
+    // let tmux: BooleanArg = "no"
+    // if (tmuxInstalled) {
+    //   console.log(`${BOLD}Question 4/4:${RESET}`)
+    //   printInfo(`${BOLD}Tmux detected!${RESET} We can enable tmux integration for you.`)
+    //   printInfo("This will spawn new panes for sub-agents, letting you watch them work in real-time.")
+    //   tmux = await askYesNo(rl, "Enable tmux integration?", detected.hasTmux ? "yes" : "no")
+    //   console.log()
+    // }
 
     return {
       hasAntigravity: antigravity === "yes",
       hasOpenAI: openai === "yes",
       hasCerebras: cerebras === "yes",
-      hasTmux: tmux === "yes",
+      hasTmux: false,
     }
   } finally {
     rl.close()
@@ -181,9 +185,10 @@ async function runInstall(config: InstallConfig): Promise<number> {
   printHeader(isUpdate)
 
   // Calculate total steps dynamically
-  let totalSteps = 3 // Base: check opencode, add plugin, write lite config
+  let totalSteps = 4 // Base: check opencode, add plugin, disable default agents, write lite config
   if (config.hasAntigravity) totalSteps += 2 // auth plugins + provider config
-  if (config.hasTmux) totalSteps += 1 // server config
+  // TODO: tmux has a bug, disabled for now
+  // if (config.hasTmux) totalSteps += 1 // server config
 
   let step = 1
 
@@ -195,6 +200,10 @@ async function runInstall(config: InstallConfig): Promise<number> {
   const pluginResult = await addPluginToOpenCodeConfig()
   if (!handleStepResult(pluginResult, "Plugin added")) return 1
 
+  printStep(step++, totalSteps, "Disabling OpenCode default agents...")
+  const agentResult = disableDefaultAgents()
+  if (!handleStepResult(agentResult, "Default agents disabled")) return 1
+
   if (config.hasAntigravity) {
     printStep(step++, totalSteps, "Adding auth plugins...")
     const authResult = await addAuthPlugins(config)
@@ -205,11 +214,12 @@ async function runInstall(config: InstallConfig): Promise<number> {
     if (!handleStepResult(providerResult, "Providers configured")) return 1
   }
 
-  if (config.hasTmux) {
-    printStep(step++, totalSteps, "Configuring OpenCode HTTP server for tmux...")
-    const serverResult = addServerConfig(config)
-    if (!handleStepResult(serverResult, "Server configured")) return 1
-  }
+  // TODO: tmux has a bug, disabled for now
+  // if (config.hasTmux) {
+  //   printStep(step++, totalSteps, "Configuring OpenCode HTTP server for tmux...")
+  //   const serverResult = addServerConfig(config)
+  //   if (!handleStepResult(serverResult, "Server configured")) return 1
+  // }
 
   printStep(step++, totalSteps, "Writing oh-my-opencode-slim configuration...")
   const liteResult = writeLiteConfig(config)
@@ -237,14 +247,15 @@ async function runInstall(config: InstallConfig): Promise<number> {
   console.log(`     ${BLUE}$ opencode auth login${RESET}`)
   console.log()
 
-  if (config.hasTmux) {
-    console.log(`  ${nextStep++}. Run OpenCode inside tmux:`)
-    console.log(`     ${BLUE}$ tmux${RESET}`)
-    console.log(`     ${BLUE}$ opencode${RESET}`)
-  } else {
-    console.log(`  ${nextStep++}. Start OpenCode:`)
-    console.log(`     ${BLUE}$ opencode${RESET}`)
-  }
+  // TODO: tmux has a bug, disabled for now
+  // if (config.hasTmux) {
+  //   console.log(`  ${nextStep++}. Run OpenCode inside tmux:`)
+  //   console.log(`     ${BLUE}$ tmux${RESET}`)
+  //   console.log(`     ${BLUE}$ opencode${RESET}`)
+  // } else {
+  console.log(`  ${nextStep++}. Start OpenCode:`)
+  console.log(`     ${BLUE}$ opencode${RESET}`)
+  // }
   console.log()
 
   return 0

+ 2 - 3
src/tools/background.ts

@@ -1,6 +1,6 @@
 import { tool, type PluginInput, type ToolDefinition } from "@opencode-ai/plugin";
 import type { BackgroundTaskManager } from "../features";
-import { getAgentListDescription, getAgentNames } from "../agents";
+import { getAgentNames } from "../agents";
 import {
   POLL_INTERVAL_MS,
   MAX_POLL_TIME_MS,
@@ -23,13 +23,12 @@ export function createBackgroundTools(
   manager: BackgroundTaskManager,
   tmuxConfig?: TmuxConfig
 ): Record<string, ToolDefinition> {
-  const agentList = getAgentListDescription();
   const agentNames = getAgentNames().join(", ");
 
   const background_task = tool({
     description: `Run agent task. Use sync=true to wait for result, sync=false (default) to run in background.
 
-Agents: ${agentList}.
+Agents: ${agentNames}.
 
 Async mode returns task_id immediately - use \`background_output\` to get results.
 Sync mode blocks until completion and returns the result directly.`,