summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAiden Cline <[email protected]>2026-03-11 10:24:55 -0500
committerGitHub <[email protected]>2026-03-11 10:24:55 -0500
commit0f6bc8ae71d18645212cb954bd210f047b5416d8 (patch)
tree4da73a2a3686a752b11a4ec4cae00791f27269dd
parent7291e282738a3745330e20180371bd7c6fb11e5b (diff)
downloadopencode-0f6bc8ae71d18645212cb954bd210f047b5416d8.tar.gz
opencode-0f6bc8ae71d18645212cb954bd210f047b5416d8.zip
tweak: adjust way skills are presented to agent to increase likelyhood of skill invocations. (#17053)
-rw-r--r--packages/opencode/src/session/prompt.ts7
-rw-r--r--packages/opencode/src/session/system.ts16
-rw-r--r--packages/opencode/src/skill/skill.ts23
-rw-r--r--packages/opencode/src/tool/skill.ts28
4 files changed, 50 insertions, 24 deletions
diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts
index 655afd2b1..54adf1104 100644
--- a/packages/opencode/src/session/prompt.ts
+++ b/packages/opencode/src/session/prompt.ts
@@ -650,7 +650,12 @@ export namespace SessionPrompt {
await Plugin.trigger("experimental.chat.messages.transform", {}, { messages: msgs })
// Build system prompt, adding structured output instruction if needed
- const system = [...(await SystemPrompt.environment(model)), ...(await InstructionPrompt.system())]
+ const skills = await SystemPrompt.skills(agent)
+ const system = [
+ ...(await SystemPrompt.environment(model)),
+ ...(skills ? [skills] : []),
+ ...(await InstructionPrompt.system()),
+ ]
const format = lastUser.format ?? { type: "text" }
if (format.type === "json_schema") {
system.push(STRUCTURED_OUTPUT_SYSTEM_PROMPT)
diff --git a/packages/opencode/src/session/system.ts b/packages/opencode/src/session/system.ts
index a61dd8cba..0f0f6b51b 100644
--- a/packages/opencode/src/session/system.ts
+++ b/packages/opencode/src/session/system.ts
@@ -10,6 +10,9 @@ import PROMPT_GEMINI from "./prompt/gemini.txt"
import PROMPT_CODEX from "./prompt/codex_header.txt"
import PROMPT_TRINITY from "./prompt/trinity.txt"
import type { Provider } from "@/provider/provider"
+import type { Agent } from "@/agent/agent"
+import { PermissionNext } from "@/permission/next"
+import { Skill } from "@/skill"
export namespace SystemPrompt {
export function instructions() {
@@ -34,6 +37,7 @@ export namespace SystemPrompt {
`Here is some useful information about the environment you are running in:`,
`<env>`,
` Working directory: ${Instance.directory}`,
+ ` Workspace root folder: ${Instance.worktree}`,
` Is directory a git repo: ${project.vcs === "git" ? "yes" : "no"}`,
` Platform: ${process.platform}`,
` Today's date: ${new Date().toDateString()}`,
@@ -51,4 +55,16 @@ export namespace SystemPrompt {
].join("\n"),
]
}
+
+ export async function skills(agent: Agent.Info) {
+ if (PermissionNext.disabled(["skill"], agent.permission).has("skill")) return
+
+ const list = await Skill.available(agent)
+
+ return [
+ "Skills provide specialized instructions and workflows for specific tasks.",
+ "Use the skill tool to load a skill when a task matches its description.",
+ list.length === 0 ? "No skills are currently available." : "\n" + Skill.fmt(list),
+ ].join("\n")
+ }
}
diff --git a/packages/opencode/src/skill/skill.ts b/packages/opencode/src/skill/skill.ts
index c474c94dd..09cc787c8 100644
--- a/packages/opencode/src/skill/skill.ts
+++ b/packages/opencode/src/skill/skill.ts
@@ -13,6 +13,9 @@ import { Bus } from "@/bus"
import { Session } from "@/session"
import { Discovery } from "./discovery"
import { Glob } from "../util/glob"
+import { pathToFileURL } from "url"
+import type { Agent } from "@/agent/agent"
+import { PermissionNext } from "@/permission/next"
export namespace Skill {
const log = Log.create({ service: "skill" })
@@ -186,4 +189,24 @@ export namespace Skill {
export async function dirs() {
return state().then((x) => x.dirs)
}
+
+ export async function available(agent?: Agent.Info) {
+ const list = await all()
+ if (!agent) return list
+ return list.filter((skill) => PermissionNext.evaluate("skill", skill.name, agent.permission).action !== "deny")
+ }
+
+ export function fmt(list: Info[]) {
+ return [
+ "<available_skills>",
+ ...list.flatMap((skill) => [
+ ` <skill>`,
+ ` <name>${skill.name}</name>`,
+ ` <description>${skill.description}</description>`,
+ ` <location>${pathToFileURL(skill.location).href}</location>`,
+ ` </skill>`,
+ ]),
+ "</available_skills>",
+ ].join("\n")
+ }
}
diff --git a/packages/opencode/src/tool/skill.ts b/packages/opencode/src/tool/skill.ts
index 8fcfb592d..6d2a48b0e 100644
--- a/packages/opencode/src/tool/skill.ts
+++ b/packages/opencode/src/tool/skill.ts
@@ -3,24 +3,14 @@ import { pathToFileURL } from "url"
import z from "zod"
import { Tool } from "./tool"
import { Skill } from "../skill"
-import { PermissionNext } from "../permission/next"
import { Ripgrep } from "../file/ripgrep"
import { iife } from "@/util/iife"
export const SkillTool = Tool.define("skill", async (ctx) => {
- const skills = await Skill.all()
-
- // Filter skills by agent permissions if agent provided
- const agent = ctx?.agent
- const accessibleSkills = agent
- ? skills.filter((skill) => {
- const rule = PermissionNext.evaluate("skill", skill.name, agent.permission)
- return rule.action !== "deny"
- })
- : skills
+ const list = await Skill.available(ctx?.agent)
const description =
- accessibleSkills.length === 0
+ list.length === 0
? "Load a specialized skill that provides domain-specific instructions and workflows. No skills are currently available."
: [
"Load a specialized skill that provides domain-specific instructions and workflows.",
@@ -34,18 +24,10 @@ export const SkillTool = Tool.define("skill", async (ctx) => {
"The following skills provide specialized sets of instructions for particular tasks",
"Invoke this tool to load a skill when a task matches one of the available skills listed below:",
"",
- "<available_skills>",
- ...accessibleSkills.flatMap((skill) => [
- ` <skill>`,
- ` <name>${skill.name}</name>`,
- ` <description>${skill.description}</description>`,
- ` <location>${pathToFileURL(skill.location).href}</location>`,
- ` </skill>`,
- ]),
- "</available_skills>",
+ Skill.fmt(list),
].join("\n")
- const examples = accessibleSkills
+ const examples = list
.map((skill) => `'${skill.name}'`)
.slice(0, 3)
.join(", ")
@@ -62,7 +44,7 @@ export const SkillTool = Tool.define("skill", async (ctx) => {
const skill = await Skill.get(params.name)
if (!skill) {
- const available = await Skill.all().then((x) => Object.keys(x).join(", "))
+ const available = await Skill.all().then((x) => x.map((skill) => skill.name).join(", "))
throw new Error(`Skill "${params.name}" not found. Available skills: ${available || "none"}`)
}