How to Build and Test opencode Agent Skills in VS Code
Learn how to create, test, and deploy custom opencode Agent skills directly from VS Code, turning abstract concepts into production‑ready code.
The Need for Custom opencode Agent Skills: Problems You’ll Solve
Default agents fall short of business requirements
Out‑of‑the‑box opencode agents handle plain‑language intents well, but they treat every request as a one‑size‑fits‑all problem. In many environments three common pain points emerge.
- Domain‑specific terminology. Support engineers use abbreviations like
CRQ,RFC, and product codes such asPX-5000. A vanilla model may misinterpret “CRQ” as “crock” and suggest an unrelated recipe. - Regulatory compliance. Certain data fields (PCI, GDPR) must never be echoed back to the user. The default agent lacks a built‑in redaction filter, so it could inadvertently return a masked credit‑card number.
- Multi‑step orchestration. A single user request often triggers a chain of internal API calls: validate the ticket, fetch the SLA tier, then push a notification to Slack. The stock agent can only emit a single text response and has no notion of “call A, then B, then C”.
Teams frequently write a thin wrapper around the agent to address these gaps, but that approach quickly becomes a patchwork of string replacements and conditional branches. The resulting code is brittle, hard to test, and requires a full regression run whenever a new product line is added.
Custom opencode Agent skills solve this class of problems. By defining the skill yourself, you gain full control over input pre‑processing, decision logic, and output formatting. The skill lives alongside your other services, versioned in the same repository, and can be subjected to the same CI/CD rigor.
When bespoke logic wins over generic solutions
- Validate the user’s authentication token against an internal SSO service.
- Query a fraud‑detection microservice with the transfer amount and destination account.
- If the risk score is below 0.3, invoke the core banking API; otherwise, reply with a compliance‑approved escalation message.
Encoding this workflow directly in the default agent would require exposing internal APIs to the model—a security risk—or relying on the model to guess the correct sequence, which it cannot guarantee. A custom skill lets you encode the exact flow, keep business rules in code, and still benefit from the model’s language understanding for intent and entity extraction.
import { AgentSkill, SkillContext } from '@opencode/agents';
import { verifyToken } from './auth';
import { assessRisk } from './risk';
import { sendTransfer } from './bank';
export const fundTransferSkill: AgentSkill = async (ctx: SkillContext) => {
// 1️⃣ Pull out the raw user message and the parsed entities
const { message, entities } = ctx.input;
const { amount, destinationAccount, authToken } = entities;
// 2️⃣ Defensive checks before we touch any internal service
if (!authToken) {
return ctx.reply('I need you to log in before I can move money.');
}
const user = await verifyToken(authToken);
if (!user) {
return ctx.reply('Your session has expired. Please log in again.');
}
// 3️⃣ Risk assessment – this is pure business logic, not “AI magic”
const riskScore = await assessRisk({ userId: user.id, amount, destinationAccount });
if (riskScore >= 0.3) {
return ctx.reply(
'I flagged this transfer as high‑risk. I’ve opened a ticket for our compliance team.'
);
}
// 4️⃣ All checks passed – perform the real transfer
const result = await sendTransfer({ userId: user.id, amount, destinationAccount });
if (result.success) {
return ctx.reply(`✅ Transfer of $${amount} to ${destinationAccount} completed.`);
}
// 5️⃣ Fallback for unexpected errors
return ctx.reply('Something went wrong while processing the transfer. Please try again later.');
};
- Explicit control flow. Each step is a discrete async call that can be unit‑tested in isolation.
- Clear separation of concerns. The language model only handles intent extraction; compliance and security logic remain in trusted code.
- Observability. Wiring the skill through existing logging middleware provides end‑to‑end traces from “user says X” to “bank API responded Y”.
export const emailToCrmSkill: AgentSkill = async (ctx) => {
const { emailBody, sender } = ctx.input;
const { name, company, phone } = extractContactInfo(emailBody);
// PII redaction – enforced by policy
const safePhone = redactPhone(phone);
const contact = await crm.createContact({
name,
company,
phone: safePhone,
source: 'email',
owner: mapOwner(sender)
});
return ctx.reply(`Created contact ${contact.id} for ${name}.`);
};
The skill encapsulates a business rule (redactPhone) that would be impossible to guarantee with a generic model response. The rule lives in a single function, covered by tests, and can be updated without retraining any model.
These examples illustrate why, after plugging a pre‑built agent into a product, many teams encounter limitations. The challenge is not the model’s capabilities but the surrounding ecosystem of security, compliance, and orchestration that must be addressed explicitly.