skills.workspace() reads Agent Skills from files in a Workspace from @cloudflare/computer ↗︎. A skill is a folder in the Workspace. Writing, editing, or deleting a SKILL.md changes the agent's skills on its next turn, with no deploy.
By default, every /workspace/.agents/skills/<name>/SKILL.md is a skill. The files beside it are its resources:
/workspace/.agents/skills/
release-notes/
SKILL.md
references/style-guide.md
scripts/format-release-notes.js
pirate-voice/
SKILL.mdReturn the source from getSkills(). In Think, this.workspace is already a Computer Workspace:
import { Think, skills } from "@cloudflare/think";
export class MyAgent extends Think {
getSkills() {
return [skills.workspace(this.workspace)];
}
}import { Think, skills } from "@cloudflare/think";
export class MyAgent extends Think<Env> {
getSkills() {
return [skills.workspace(this.workspace)];
}
}To read skills from another directory, pass an absolute root:
skills.workspace(this.workspace, { root: "/workspace/skills" });If the root directory does not exist, the source lists no skills. .git, node_modules, and .DS_Store inside a skill directory are ignored.
Skills are plain files, so anything that writes to the Workspace can add one: the agent's own write tool, a user upload, or your code. For example, write the starter skills the first time the agent starts:
import { Think, skills } from "@cloudflare/think";
const ROOT = "/workspace/.agents/skills";
const STARTER_SKILLS = {
"pirate-voice/SKILL.md": `---
name: pirate-voice
description: Answer in a pirate voice. Use when the user asks for a pirate tone.
---
Answer accurately, then rewrite the answer in a light pirate voice.
`,
};
export class MyAgent extends Think {
getSkills() {
return [skills.workspace(this.workspace)];
}
async onStart() {
for (const [path, content] of Object.entries(STARTER_SKILLS)) {
const file = `${ROOT}/${path}`;
await this.workspace.fs.mkdir(file.slice(0, file.lastIndexOf("/")), {
recursive: true,
});
await this.workspace.fs.writeFile(file, content);
}
}
}import { Think, skills } from "@cloudflare/think";
const ROOT = "/workspace/.agents/skills";
const STARTER_SKILLS: Record<string, string> = {
"pirate-voice/SKILL.md": `---
name: pirate-voice
description: Answer in a pirate voice. Use when the user asks for a pirate tone.
---
Answer accurately, then rewrite the answer in a light pirate voice.
`,
};
export class MyAgent extends Think<Env> {
getSkills() {
return [skills.workspace(this.workspace)];
}
async onStart() {
for (const [path, content] of Object.entries(STARTER_SKILLS)) {
const file = `${ROOT}/${path}`;
await this.workspace.fs.mkdir(file.slice(0, file.lastIndexOf("/")), {
recursive: true,
});
await this.workspace.fs.writeFile(file, content);
}
}
}Each skill is checked against the specification when it is listed. A skill whose name does not match its folder still loads with a warning. A skill with no description is skipped and logged. Use skills.parseSkill() to check a skill before you write it: it follows the specification when it parses with no warnings.
Skill scripts are Workspace files too. Give the Workspace a backend, and the model runs scripts with the exec tool. Activating a skill tells the model where the skill's folder is, so it can run a script from there:
import { Workspace } from "@cloudflare/computer";
import { WorkerJavaScriptBackend } from "@cloudflare/computer/backends/worker-javascript";
import { Think, skills } from "@cloudflare/think";
export class MyAgent extends Think {
workspace = new Workspace({
storage: this.ctx.storage,
backends: [
new WorkerJavaScriptBackend({
id: "js",
loader: this.env.LOADER,
access: "read-write",
}),
],
});
getSkills() {
return [skills.workspace(this.workspace)];
}
}import { Workspace } from "@cloudflare/computer";
import { WorkerJavaScriptBackend } from "@cloudflare/computer/backends/worker-javascript";
import { Think, skills } from "@cloudflare/think";
export class MyAgent extends Think<Env> {
workspace = new Workspace({
storage: this.ctx.storage,
backends: [
new WorkerJavaScriptBackend({
id: "js",
loader: this.env.LOADER,
access: "read-write",
}),
],
});
getSkills() {
return [skills.workspace(this.workspace)];
}
}{
"worker_loaders": [{ "binding": "LOADER" }],
}[[worker_loaders]]
binding = "LOADER"With the JavaScript backend, a skill can tell the model to run a script by re-exporting it, with cwd set to the skill's folder:
export { default } from "./scripts/format-release-notes.js";Refer to Execution for the options.
The computer-skills example ↗︎ stores skills in a Workspace, runs their scripts with exec, and lets you add and delete skills from the UI.