Agent Skills can bundle scripts under scripts/. An agent runs them in one of two ways:
| Where the skill lives | How scripts run |
|---|---|
| Computer Workspace | The model runs the file with the Workspace exec tool |
| R2 or a manifest | The experimental script runner, through run_skill_script |
When skills are files in a Workspace, a script is a file the agent can run like any other. Give the Workspace a backend, and Think adds the exec tool. Refer to Computer for the setup.
Activating a skill returns its folder as location, for example /workspace/.agents/skills/release-notes. With the JavaScript backend, the skill's instructions can tell the model to run a script from that folder:
export { default } from "./scripts/format-release-notes.js";The script exports a default async function. It receives the input that the model passes to exec, and can use node:fs to read the Workspace:
import { readdir } from "node:fs/promises";
export default async function (input) {
const entries = await readdir(input.root ?? "/workspace");
return { count: entries.length };
}For skills that do not live in a Workspace, skills.runner() runs bundled scripts in a Dynamic Worker and adds the run_skill_script tool. It needs a Worker Loader binding:
{
"worker_loaders": [{ "binding": "LOADER" }],
}[[worker_loaders]]
binding = "LOADER"Return the runner from getSkillScriptRunner():
import { Think, skills } from "@cloudflare/think";
export class MyAgent extends Think {
getSkills() {
return [skills.r2(this.env.SKILLS_BUCKET, { prefix: "skills/" })];
}
getSkillScriptRunner() {
return skills.runner({ loader: this.env.LOADER });
}
}import { Think, skills } from "@cloudflare/think";
export class MyAgent extends Think<Env> {
getSkills() {
return [skills.r2(this.env.SKILLS_BUCKET, { prefix: "skills/" })];
}
getSkillScriptRunner() {
return skills.runner({ loader: this.env.LOADER });
}
}Outside Think, import the runner from agents/skills/runner. It is a separate entry point because it loads just-bash and Code Mode:
import { runner } from "agents/skills/runner";
const scriptRunner = runner({ loader: env.LOADER });import { runner } from "agents/skills/runner";
const scriptRunner = runner({ loader: env.LOADER });The runner runs JavaScript, Python, and Bash scripts under scripts/. Python runs as a Python Dynamic Worker, and Bash runs through just-bash. There is no bundler at runtime: a JavaScript script must be one self-contained module. Compile TypeScript and multi-file scripts, for example with esbuild, before you store the skill.
JavaScript scripts export a default function that takes the input and a context object:
export default async function run(input, ctx) {
const guide = ctx.files["references/style-guide.md"]; // bundled text resources
const docs = await ctx.workspace.readFile("README.md"); // gated by permission
const summary = await ctx.tools.call("summarize", { input }); // explicit tools
await ctx.output.writeFile("notes.md", summary); // scratch artifact
return { ok: true };
}ctx is typed as SkillRunContext and contains:
| Field | Description |
|---|---|
skill |
The skill's name, description, and metadata |
files |
The skill's text resources, by relative path |
workspace |
Workspace reads and writes, gated by the workspace option |
tools |
Only the tools passed to the runner, as tools.call(name, input) or tools.name(input) |
output |
writeFile(name, content) returns scratch files to the model without changing the Workspace |
Python and Bash scripts use files instead of a context object:
/input.jsonholds the input./context.jsonholds the skill metadata./skillholdsSKILL.mdand the skill's resources.- Files written to
/outputare returned to the model.
Python scripts can also define def run(input, ctx).
| Option | Default | Description |
|---|---|---|
loader |
Required | Worker Loader binding |
workspaceInstance |
None | A Computer Workspace that scripts can access |
workspace |
"read" with workspaceInstance, else "none" |
"none", "read", or "read-write" |
tools |
None | Tools scripts can call |
network |
false |
Allow outbound network access |
timeout |
30000 |
Timeout in milliseconds |
Scripts get no network access and no tools unless you grant them. Relative paths in ctx.workspace resolve against /workspace.
- Think tools: how skill tools merge with workspace, custom, MCP, and client tools
- Sandbox: run code in a container