Skip to content

Commit 4efa586

Browse files
committed
Add skill support to sisyphus agent
🤖 Generated with assistance of OhMyOpenCode (https://github.com/code-yeongyu/oh-my-opencode)
1 parent fbae3ae commit 4efa586

File tree

2 files changed

+89
-16
lines changed

2 files changed

+89
-16
lines changed

src/agents/sisyphus-prompt-builder.ts

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ export interface AvailableTool {
1111
category: "lsp" | "ast" | "search" | "session" | "command" | "other"
1212
}
1313

14+
export interface AvailableSkill {
15+
name: string
16+
description: string
17+
location: "user" | "project" | "plugin"
18+
}
19+
1420
export function categorizeTools(toolNames: string[]): AvailableTool[] {
1521
return toolNames.map((name) => {
1622
let category: AvailableTool["category"] = "other"
@@ -51,27 +57,73 @@ function formatToolsForPrompt(tools: AvailableTool[]): string {
5157
return parts.join(", ")
5258
}
5359

54-
export function buildKeyTriggersSection(agents: AvailableAgent[]): string {
60+
export function buildKeyTriggersSection(agents: AvailableAgent[], skills: AvailableSkill[] = []): string {
5561
const keyTriggers = agents
5662
.filter((a) => a.metadata.keyTrigger)
5763
.map((a) => `- ${a.metadata.keyTrigger}`)
5864

59-
if (keyTriggers.length === 0) return ""
65+
const skillTriggers = skills
66+
.filter((s) => s.description)
67+
.map((s) => `- **Skill \`${s.name}\`**: ${extractTriggerFromDescription(s.description)}`)
68+
69+
const allTriggers = [...keyTriggers, ...skillTriggers]
70+
71+
if (allTriggers.length === 0) return ""
6072

6173
return `### Key Triggers (check BEFORE classification):
62-
${keyTriggers.join("\n")}
74+
75+
**BLOCKING: Check skills FIRST before any action.**
76+
If a skill matches, invoke it IMMEDIATELY via \`skill\` tool.
77+
78+
${allTriggers.join("\n")}
6379
- **GitHub mention (@mention in issue/PR)** → This is a WORK REQUEST. Plan full cycle: investigate → implement → create PR
6480
- **"Look into" + "create PR"** → Not just research. Full implementation cycle expected.`
6581
}
6682

67-
export function buildToolSelectionTable(agents: AvailableAgent[], tools: AvailableTool[] = []): string {
83+
function extractTriggerFromDescription(description: string): string {
84+
const triggerMatch = description.match(/Trigger[s]?[:\s]+([^.]+)/i)
85+
if (triggerMatch) return triggerMatch[1].trim()
86+
87+
const activateMatch = description.match(/Activate when[:\s]+([^.]+)/i)
88+
if (activateMatch) return activateMatch[1].trim()
89+
90+
const useWhenMatch = description.match(/Use (?:this )?when[:\s]+([^.]+)/i)
91+
if (useWhenMatch) return useWhenMatch[1].trim()
92+
93+
return description.split(".")[0] || description
94+
}
95+
96+
export function buildToolSelectionTable(
97+
agents: AvailableAgent[],
98+
tools: AvailableTool[] = [],
99+
skills: AvailableSkill[] = []
100+
): string {
68101
const rows: string[] = [
69-
"### Tool Selection:",
102+
"### Tool & Skill Selection:",
103+
"",
104+
"**Priority Order**: Skills → Direct Tools → Agents",
70105
"",
71-
"| Tool | Cost | When to Use |",
72-
"|------|------|-------------|",
73106
]
74107

108+
// Skills section (highest priority)
109+
if (skills.length > 0) {
110+
rows.push("#### Skills (INVOKE FIRST if matching)")
111+
rows.push("")
112+
rows.push("| Skill | When to Use |")
113+
rows.push("|-------|-------------|")
114+
for (const skill of skills) {
115+
const shortDesc = extractTriggerFromDescription(skill.description)
116+
rows.push(`| \`${skill.name}\` | ${shortDesc} |`)
117+
}
118+
rows.push("")
119+
}
120+
121+
// Tools and Agents table
122+
rows.push("#### Tools & Agents")
123+
rows.push("")
124+
rows.push("| Resource | Cost | When to Use |")
125+
rows.push("|----------|------|-------------|")
126+
75127
if (tools.length > 0) {
76128
const toolsDisplay = formatToolsForPrompt(tools)
77129
rows.push(`| ${toolsDisplay} | FREE | Not Complex, Scope Clear, No Implicit Assumptions |`)
@@ -88,7 +140,7 @@ export function buildToolSelectionTable(agents: AvailableAgent[], tools: Availab
88140
}
89141

90142
rows.push("")
91-
rows.push("**Default flow**: explore/librarian (background) + tools → oracle (if required)")
143+
rows.push("**Default flow**: skill (if match) → explore/librarian (background) + tools → oracle (if required)")
92144

93145
return rows.join("\n")
94146
}

src/agents/sisyphus.ts

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { AgentConfig } from "@opencode-ai/sdk"
22
import { isGptModel } from "./types"
3-
import type { AvailableAgent, AvailableTool } from "./sisyphus-prompt-builder"
3+
import type { AvailableAgent, AvailableTool, AvailableSkill } from "./sisyphus-prompt-builder"
44
import {
55
buildKeyTriggersSection,
66
buildToolSelectionTable,
@@ -36,10 +36,25 @@ Named by [YeonGyu Kim](https://github.com/code-yeongyu).
3636
3737
</Role>`
3838

39-
const SISYPHUS_PHASE0_STEP1_3 = `### Step 1: Classify Request Type
39+
const SISYPHUS_PHASE0_STEP1_3 = `### Step 0: Check Skills FIRST (BLOCKING)
40+
41+
**Before ANY classification or action, scan for matching skills.**
42+
43+
\`\`\`
44+
IF request matches a skill trigger:
45+
→ INVOKE skill tool IMMEDIATELY
46+
→ Do NOT proceed to Step 1 until skill is invoked
47+
\`\`\`
48+
49+
Skills are specialized workflows. When relevant, they handle the task better than manual orchestration.
50+
51+
---
52+
53+
### Step 1: Classify Request Type
4054
4155
| Type | Signal | Action |
4256
|------|--------|--------|
57+
| **Skill Match** | Matches skill trigger phrase | **INVOKE skill FIRST** via \`skill\` tool |
4358
| **Trivial** | Single file, known location, direct answer | Direct tools only (UNLESS Key Trigger applies) |
4459
| **Explicit** | Specific file/line, clear command | Execute directly |
4560
| **Exploratory** | "How does X work?", "Find Y" | Fire explore (1-3) + tools in parallel |
@@ -375,9 +390,13 @@ const SISYPHUS_SOFT_GUIDELINES = `## Soft Guidelines
375390
376391
`
377392

378-
function buildDynamicSisyphusPrompt(availableAgents: AvailableAgent[], availableTools: AvailableTool[] = []): string {
379-
const keyTriggers = buildKeyTriggersSection(availableAgents)
380-
const toolSelection = buildToolSelectionTable(availableAgents, availableTools)
393+
function buildDynamicSisyphusPrompt(
394+
availableAgents: AvailableAgent[],
395+
availableTools: AvailableTool[] = [],
396+
availableSkills: AvailableSkill[] = []
397+
): string {
398+
const keyTriggers = buildKeyTriggersSection(availableAgents, availableSkills)
399+
const toolSelection = buildToolSelectionTable(availableAgents, availableTools, availableSkills)
381400
const exploreSection = buildExploreSection(availableAgents)
382401
const librarianSection = buildLibrarianSection(availableAgents)
383402
const frontendSection = buildFrontendSection(availableAgents)
@@ -456,12 +475,14 @@ function buildDynamicSisyphusPrompt(availableAgents: AvailableAgent[], available
456475
export function createSisyphusAgent(
457476
model: string = DEFAULT_MODEL,
458477
availableAgents?: AvailableAgent[],
459-
availableToolNames?: string[]
478+
availableToolNames?: string[],
479+
availableSkills?: AvailableSkill[]
460480
): AgentConfig {
461481
const tools = availableToolNames ? categorizeTools(availableToolNames) : []
482+
const skills = availableSkills ?? []
462483
const prompt = availableAgents
463-
? buildDynamicSisyphusPrompt(availableAgents, tools)
464-
: buildDynamicSisyphusPrompt([], tools)
484+
? buildDynamicSisyphusPrompt(availableAgents, tools, skills)
485+
: buildDynamicSisyphusPrompt([], tools, skills)
465486

466487
const base = {
467488
description:

0 commit comments

Comments
 (0)