Dayward AI
Week 1 · D4About 5 hours

Skills With Scripts: Executable Attachments, Dependencies and Sandboxing, Cross-Platform Support, and Breaking Down Document-Handling Skills

When a skill should ship a script instead of more instructions, how to make a script's dependencies self-contained, how to design its interface for an agent, and how a document-handling skill breaks into plan, then validate, then execute.

Today's goals 0/3

Sign in to tick these off and save your progress.

Today's Goals

  1. Judge whether a piece of logic belongs in the SKILL.md body or should be distilled into a script under scripts
  2. Write an agent-friendly script that is self-contained, non-interactive, produces structured output, and gives self-correctable error messages
  3. Break a document-handling task into a three-step flow: plan first, then validate, then execute

Yesterday's design method left a thread hanging: that crucial validation inside plan-validate-execute is itself a piece of code. Today covers a skill's third directory, scripts/, in full — when to switch from writing instructions to writing code, what makes a script one built for an agent, and where exactly the risk lies in letting a model run your script. Once you have read the walkthrough and finished the lab, scroll back to the top and tick off the three goals.

Plain-Language Walkthrough

When to write a script

One page of the work instructions in the filing cabinet says "add up the thirty amounts in this table, to within one cent." You can spell out on that page how the addition is done, or you can clip a calculator to it. A skill's scripts/ directory is that calculator clipped to the page.

Three criteria, and hitting any one means write a script.

First, the same logic has been reinvented a third time. You ran a skill for a few rounds, looked back at its execution traces, and found the model writing almost the same code from scratch every time — parsing the same format, drawing the same chart, running the same validation. Written fresh each time means possibly written differently each time, and it costs tokens and time every time. Turning it into a tested script is one investment for a lasting return.

Second, the result must be identical to the letter. Validation, format conversion, hash computation — there is exactly one right answer. Having the model follow instructions means every run can drift; having it run a script means the result is deterministic. Deterministic tasks go to code and judgment tasks stay with the model, which is the baseline of the division.

Third, one command is complex enough to be hard to get right first time. A command with seven flags that pipes three times in a fixed order will sooner or later have a hyphen mistyped if it lives in the body. Wrapped as a script, the body keeps one line. Conversely, if it is just invoking an existing tool with two or three flags, write the command in the body directly — there is no need to create a scripts/ directory for it.

The flip side of the third criterion needs saying too: several ecosystems already have run-without-installing mechanisms, such as npx on the Node side and uvx and pipx on the Python side. One-off commands like these can go straight in the body, but always pin the version:

BashBash
npx eslint@9.0.0 --fix .
uvx ruff@0.8.0 check .

Without a pinned version, your skill's behavior will change on the morning some upstream ships a release, and you will have no idea why. Likewise, if a script has environment requirements, put them in the frontmatter's compatibility field — "requires git, docker, jq, and network access," say.

The bar for writing a script is a little higher than most people assume: it is a long-lived asset that must be maintained, kept in step with the templates, and legible to someone else. So when none of the three criteria hits, write the body honestly.

Self-contained: do not make the reader install an environment first

Scripts most often die at the first step — dependencies that will not install. The model runs python scripts/extract.py, gets "no such module," and then starts installing things on its own initiative, at which point the step is out of control.

The fix is to write the dependency declaration into the script itself and let the runner resolve it on the spot. Several ecosystems have a way to do this.

On the Python side a dedicated spec defines inline script metadata, with dependencies in a comment block at the head of the file; run it with uv run or pipx run and the runner builds an isolated environment and installs them:

TextText
# /// script
# requires-python = ">=3.12"
# dependencies = ["beautifulsoup4>=4.12,<5"]
# ///

On the JavaScript side, Deno's npm: import prefix and Bun's runtime auto-install achieve the same thing, with the version right in the import path. Which one to pick depends on what is installed in the user's environment, but whichever you pick, pin the version.

Self-contained has a second sense: a script should not depend on the current working directory. References to scripts in SKILL.md always use paths relative to the skill root (scripts/validate.py), which is the convention; but the input file paths the script handles should come from arguments rather than assuming they are in the current directory.

Designing an interface for an agent

This is today's most valuable section. A command-line tool for an agent and one for a person involve entirely different trade-offs. People read documentation, experiment, and guess from experience when something errors; an agent can only read the few lines you printed and then decide the next step.

First, it absolutely must not be interactive. An agent runs in a non-interactive terminal and can answer no prompt. A script that stops to wait for you to type an environment name will hang until it times out. All input comes through arguments, environment variables, or standard input. This is not a best practice but a hard requirement.

Second, the help output is the interface documentation. An agent learns to use your script mainly through --help. State a one-line summary, the argument list, and two or three usage examples. But keep it short — that output enters the context verbatim and competes with everything else.

Third, the error message decides whether it gets it right next time. A bare "invalid argument" wastes a whole turn; stating which item is wrong, what was expected, what was received, and what the valid values are lets the model fix it on the spot.

TextText
Error: --format must be one of json, csv, table.
       Received: "xml"

This pays off most in validation scripts. A line like "the field signature_date does not exist; available fields are customer_name, order_total, signature_date_signed" shows the model at a glance that it got the suffix wrong. An error message is a prompt written for the model, so write it in that spirit.

Fourth, output should be structured, with data and diagnostics separated. Structured data goes to standard output and progress and warnings to standard error. That way the caller gets clean output that can be handed straight to another tool, while the diagnostics are not lost.

Fifth, output volume must be bounded. Many agent environments truncate output past ten or twenty thousand characters, and not always with any notice. So default to a summary or a limited count, and provide a paging flag so it can ask for more as needed; when the output is genuinely large, require the caller to name an output file.

A few scattered but important ones: idempotence (an agent will retry, so create-if-absent is safer than create-and-fail-if-exists); meaningful exit codes (different codes for different failure kinds, documented in the help); and a dry-run flag for dangerous operations.

The code below turns these principles into a minimal validation script. Watch the wording of the error messages — that is the point; the code itself is simple.

validate.ts
type Field = { name: string; type: 'string' | 'number' | 'boolean'; required: boolean }
 
export function validate(fields: Field[], values: Record<string, unknown>): string[] {
  const errors: string[] = []
  const known = new Set(fields.map((f) => f.name))
 
  for (const f of fields) {
    if (!(f.name in values)) {
      if (f.required) errors.push(`missing required field ${f.name} (type ${f.type})`)
      continue
    }
    const actual = typeof values[f.name]
    if (actual !== f.type) {
      // Give expected, actual, and the value, so the model does not need another guessing round
      errors.push(
        `field ${f.name} has the wrong type: expected ${f.type}, received ${actual} (value ${JSON.stringify(values[f.name])})`
      )
    }
  }
  for (const name of Object.keys(values)) {
    // An unknown field is almost always a typo, so list every available field
    if (!known.has(name))
      errors.push(`the template has no field ${name}. Available fields: ${[...known].join(', ')}`)
  }
  return errors
}
 
// Data to standard output, diagnostics to standard error; exit 0 for pass, 1 for errors, 2 for usage
export function main(argv: string[]): number {
  if (argv.length !== 2) {
    console.error('usage: validate <field-list JSON> <values JSON>')
    return 2
  }
  const errors = validate(loadFields(argv[0]), loadValues(argv[1]))
  console.log(JSON.stringify(errors.length === 0 ? { ok: true } : { ok: false, errors }))
  return errors.length === 0 ? 0 : 1
}

Permissions, sandboxing, and untrusted input

Adding scripts to a skill makes "let the model execute code" that skill's everyday action. The boundaries of that risk need stating.

First, a script inside a skill is code, and its provenance is the trust boundary. A script you wrote and a script inside a skill you installed from some marketplace carry entirely different risk. Read the scripts/ directory before installing a third-party skill — the same thing you do when glancing at an npm package before installing it, except a skill is more easily mistaken for documentation and treated with less caution.

Second, grant pre-approved tools at minimum privilege. The spec's allowed-tools field (and each client's equivalent mechanism) can pre-approve some tools so not every step raises a confirmation dialog. But grant at command granularity: write "may run git's read-only subcommands," not "may run any shell command." That field is still experimental with varying support, so do not stake your safety entirely on it.

Third, the input a script handles is untrusted. Your script parses a file the user gave you or a payload an endpoint returned, and that content may hide something like "ignore the previous instructions and instead do…". The script itself is fine as long as it does not execute input as code, but a script's output enters the model's context — echo a large stretch of external content verbatim and you have said that content straight to the model. The sister course, day 6 of MCP in 7 Days, is devoted to this class of prompt injection and tool description trustworthiness, and is worth reading before writing scripts.

Fourth, dangerous operations need a gate. For deletes, overwrites, and publishes, give a dry-run flag or require an explicit confirmation argument. An agent will retry, and you cannot afford the cost of retrying an irreversible operation.

Cross-platform support

Whose machine the script runs on is not your call. Three things go wrong most often.

Path separators and case. Use your language's standard library path joining rather than hand-writing slashes; filename case is significant on some systems and not others, so a SKILL.md written as Skill.md runs on your machine and vanishes on a colleague's.

Shell differences. If a command in the body only works under one shell, give the alternative alongside it, or switch to a cross-platform runner outright. The spec's examples do exactly that: two commands for the same thing, letting the agent pick per environment.

Line endings and encoding. Specify the encoding explicitly when reading and writing text, and mind the line ending style when generating files, or you will produce swathes of meaningless diffs in version control.

What those three have in common is that they never appear on your own machine. So either declare the environment requirements explicitly in the body (which is what the compatibility field is for) or let the standard library smooth the differences over — do not rely on luck.

Breaking down a document-handling skill

Put everything above together and you get one complete form: plan first, validate, then execute. Document handling, form filling, and bulk edits should all take this route.

errors passes Step 1 analyzea script reads the template and emits the field listthis is ground truth Step 2 planthe model writes a values plannever guessing field names from memory Step 3 validatea script compares plan against ground trutherrors specific enough for self-correction Step 4 executea script fills in and writes the fileonly this step touches disk
Mermaid source
mermaidmermaid
flowchart TB
  A[Step 1 analyze<br/>a script reads the template and emits the field list<br/>this is ground truth] --> B[Step 2 plan<br/>the model writes a values plan<br/>never guessing field names from memory]
  B --> C[Step 3 validate<br/>a script compares plan against ground truth<br/>errors specific enough for self-correction]
  C -- errors --> B
  C -- passes --> D[Step 4 execute<br/>a script fills in and writes the file<br/>only this step touches disk]

The key is step 3, not steps 1 and 4. Without step 3 this is an ordinary read-edit-write; with it, the model gets a chance to discover its own mistake before doing anything for real, and given specific enough error messages it can correct itself.

Three design points worth noting separately:

What the analysis script emits is ground truth. The model should not guess from memory which fields the template has; have the script extract them from the template. That step eliminates the most common class of error: a misspelled field name, or an extra field that does not exist.

The validation script does not change data, and the fill script does not validate. Merge the two into one script and the model has no chance to stop between planning and executing. The fill script also needs a self-check: if any placeholder remains unreplaced after filling, fail and exit — better to fail than to write out a document that looks filled in and still carries placeholders, since a file like that gets sent out as finished work.

Intermediate artifacts go to disk. Write the plan as a JSON file rather than leaving it in the model's context. Only once on disk can the validation script read it, and only then can you open it and look when something goes wrong.

Source Reading

Hands-On Lab

🧪 D4 lab: a document-handling skill with scripts — a validator plus a filler, runnable offline in simulation

Code location: labs/agent-skills-7days/day-04-doc-skill-with-scripts

Acceptance criteria:

  1. The whole flow runs under MOCK=1, with the output showing five steps — analyze, generate the plan, fail validation, pass validation after correction, fill successfully — and the last step's negative demonstration correctly rejected.
  2. The analysis script extracts every field's name, type, and required flag from the template, so the model never has to guess a field name from memory.
  3. The validation script reports all three error classes: a missing required field, a type mismatch, and an unknown field absent from the template; the unknown-field message lists the available fields.
  4. The fill script fails with a non-zero exit code when any required placeholder remains, listing which ones, rather than writing a document containing placeholders.
  5. The SKILL.md workflow is written as a checkbox list, with validation as its own step stating that a failure means returning to the previous step and redoing it.

There is real code today, but the code is not the point; the error messages are. Before starting, look at the four errors the solution's validation script reports and get a feel for what "specific enough for the model to self-correct" means. An actionable self-check: send the error messages you wrote to somebody who has not seen the template and see whether they can fix it from that alone.

  1. Get the solution running and use MOCK=1 to see the full trace of the validation script failing first and the execution succeeding after correction.
  2. Complete the validation script's three error classes in the starter, polishing the wording rather than the logic.
  3. Complete the analysis script's placeholder parsing and deduplication so one field cannot appear with two types.
  4. Complete the fill script's self-check so it fails when placeholders remain rather than writing a half-finished file.
  5. Complete the SKILL.md workflow and pitfalls sections, writing the three-step flow as a checklist the model can follow.

Interview Questions

Today's 3 questions are in the question bank below, weighted toward the line between scripts and instructions, agent-friendly script interfaces, and the permissions and sandboxing of script execution. Expand each one and read the analysis before the key points — practicing the derivation beats memorizing bullets. The high-frequency labels for the domestic and overseas markets are there so you can pick by target market.

Checklist and Tomorrow

  • Judge whether a piece of logic belongs in the SKILL.md body or should be distilled into a script under scripts
  • Write an agent-friendly script that is self-contained, non-interactive, produces structured output, and gives self-correctable error messages
  • Break a document-handling task into a three-step flow: plan first, then validate, then execute
  • State the reasoning behind "the validation script does not change data and the fill script does not validate"
  • All 5 acceptance criteria of the lab pass
  • Answer at least 2 of the 3 interview questions without looking at the key points

Tomorrow (D5) we change roles: from the person writing skills to the person implementing skill support. For four days you have been using progressive disclosure, and tomorrow you write it for real — scanning directories, leniently parsing frontmatter, composing names and descriptions into a catalog injected into the system prompt, and reading the body only on activation. After implementing it once, your understanding of every recommendation from the last four days will change, because you will see which line of runtime code each one corresponds to.

Interview questions

  • Which logic belongs in a skill's scripts directory and which belongs in the SKILL.md body?什么逻辑该写成脚本放进 skill 的 scripts 目录,什么该留在 SKILL.md 正文里?
    Common in ChinaCommon overseasBasic#agent-skills#scripts

    How to reason about it · think before answering

    1. This tests a sense of division of labor. Saying complex logic goes in scripts says nothing, because complex has no boundary. The interviewer wants decidable signals.
    2. Give three: the same logic gets reinvented a third time across execution traces; the result must be byte-identical (validation, format conversion, hashing); or a command is complex enough to be hard to get right first try.
    3. Expand the second into the core principle: deterministic work goes to code, judgment work stays with the model. Following instructions leaves room for drift; running a script does not.
    4. Give the other side: invoking an existing tool with two or three flags belongs inline in the body. Many ecosystems offer install-free one-off runners, and versions must be pinned or an upstream release silently changes your skill's behavior.
    5. Add the cost view: a script is a long-lived asset that must be maintained and kept in sync. When none of the three signals fire, prose is cheaper.
    6. Expected follow-up: how do you notice reinvention? Read execution traces rather than final outputs; the same helper appearing across runs is the signal.

    分析过程 · 先想清楚再作答

    1. 这题在考分工感。答「复杂的写脚本」等于没答,因为复杂是个没有边界的词。面试官要听的是可判定的信号。
    2. 给三条信号,命中任意一条就写脚本:同一段逻辑在执行轨迹里被重新发明了第三次;结果必须逐字一致(校验、格式转换、哈希);一条命令复杂到第一次很难敲对。
    3. 把第二条展开成分工原则,这是本题的核心句:**确定性任务交给代码,判断性任务留给模型**。让模型「按指令做」意味着每次都有偏移的可能,让它跑脚本意味着结果确定。
    4. 再给反面:只是调一个现成工具加两三个参数,直接在正文写这条命令就行,不必建 scripts 目录。很多生态有免安装的一次性运行方式,用它们时**版本必须钉死**,否则上游一发版你的 skill 行为就变了。
    5. 补一条成本视角:脚本是长期资产,要维护、要跟模板同步、要有人看得懂。三条信号一条都不命中的时候,写正文更划算。
    6. 可预期的追问是「怎么发现模型在重新发明轮子」。答案是读执行轨迹而不是只看最终产出——同一个辅助函数在几次运行里反复出现,就是该沉淀成脚本的信号。

    Key points

    • Write a script when any of three fire: third reinvention, byte-identical results required, or a command hard to get right first try.
    • Deterministic work to code, judgment work to the model.
    • A tool invocation with a couple of flags stays inline, with the version pinned.
    • Scripts are long-lived assets with maintenance cost; if no signal fires, write prose.
    • Spot reinvention by reading execution traces, not final outputs.

    答题要点

    • 三条信号命中任一条就写脚本:重复发明第三次、结果必须逐字一致、命令复杂到难以一次敲对。
    • 分工原则是确定性任务交给代码,判断性任务留给模型。
    • 只加两三个参数调现成工具的,直接在正文写命令,但版本要钉死。
    • 脚本是长期资产,有维护成本,三条都不命中就写正文。
    • 发现重复发明要靠读执行轨迹,不是看最终产出。
  • How does designing a command-line script for an agent differ from designing one for a human?给 Agent 用的命令行脚本,接口设计上和给人用的有什么不同?
    Common in ChinaCommon overseasIntermediate#agent-skills#scripts#cli-design

    How to reason about it · think before answering

    1. The hinge is the difference. Many can list CLI best practices; few can say which ones exist specifically because the caller is a model.
    2. State the root difference: humans read docs, experiment and guess from experience; an agent has only the lines you printed before deciding the next move.
    3. From that: never prompt interactively. This is a hard requirement, not a nicety, because agents run in non-interactive shells and will hang until timeout.
    4. Help output is the interface documentation, but it must be short, since it enters the context window and competes with everything else. A human CLI never faces this constraint.
    5. Error messages decide the next attempt: say what failed, what was expected, what was received, and which values are allowed. Error messages are effectively prompts for the model.
    6. Then: structured output with data on stdout and diagnostics on stderr, and bounded output size because many harnesses truncate silently. Add idempotency, meaningful exit codes, and a dry-run flag for destructive work.
    7. Expected follow-up: how do you validate the design? Hand the help text and one error message to someone who has never seen the skill; if they can act on it, the model probably can too.

    分析过程 · 先想清楚再作答

    1. 题眼是「不同」。能列出五条通用 CLI 最佳实践的人很多,能说清哪几条是因为「使用者是模型」才成立的人少。
    2. 先给根本差异:人会读文档、会试错、会凭经验猜;Agent 只能读你打印的那几行字然后决定下一步。**它的全部信息就是你的输出**。
    3. 由此推出五条。绝对不能交互,这是硬要求不是最佳实践,Agent 在非交互终端里回答不了提示,会一直挂到超时。
    4. 帮助信息就是接口文档,但要短——这段输出原样进上下文,跟别的东西抢位置,这是给人用的 CLI 完全不必考虑的约束。
    5. 错误信息决定它下一次会不会做对:写清哪一项错了、期望什么、实际是什么、可选值有哪些。**错误信息本质上是给模型的提示词**,这一句是拿分点。
    6. 剩下两条:输出结构化并把数据与诊断分流到标准输出与标准错误;输出体量要可控,因为很多 Agent 环境会静默截断超长输出。再补幂等、有意义的退出码、危险操作给预演开关。
    7. 可预期的追问是「怎么验证接口设计得好」。答案是把帮助输出和一条错误信息单独发给一个没看过这个 skill 的人,他能照着敲对改对,模型大概率也能。

    Key points

    • The agent's only information is what you printed; it does not read docs or experiment.
    • Never prompt interactively; a non-interactive shell will hang until timeout.
    • Help text is the interface documentation and must be short because it consumes context.
    • Error messages must state the field, the expectation, the actual value and the allowed set; they are prompts for the model.
    • Emit structured data on stdout and diagnostics on stderr, bound output size, and offer a dry-run for destructive operations.

    答题要点

    • 根本差异:Agent 的全部信息就是你打印的输出,它不会读文档也不会试错。
    • 绝不能交互,否则在非交互终端里会挂到超时。
    • 帮助信息就是接口文档,但必须短,因为它原样占用上下文。
    • 错误信息要写清哪项错、期望什么、实际什么、可选值有哪些,它本质是给模型的提示词。
    • 结构化输出并分流标准输出与标准错误,输出体量要可控,危险操作给预演开关。
  • What security risks come with bundling scripts in a skill, how would you contain them, and why should document tasks follow plan, validate, then execute?skill 里带脚本会带来哪些安全风险?你会怎么限制它?另外,为什么文档处理这类任务要先规划再校验后执行?
    Common in ChinaCommon overseasDeep dive#agent-skills#security#workflow-design

    How to reason about it · think before answering

    1. Two halves; answer both. Security is about boundaries, the three-step flow is about process, and both come down to putting a gate before an irreversible action.
    2. Cover security in four layers. Source: a third-party skill's scripts are someone else's code, so read the scripts directory before installing, exactly as you would skim a package. Skills invite less scrutiny because they look like documentation.
    3. Permissions: pre-approve at command granularity, allowing read-only git subcommands rather than arbitrary shell, and remember the field is experimental with uneven support, so do not rely on it alone.
    4. Input: files and API responses are untrusted. A script that does not execute them is not directly exploitable, but script output enters the model's context, so echoing a large blob of external content effectively speaks it to the model. Actions: gate delete, overwrite and publish behind a dry run or explicit flag, because agents retry.
    5. For the second half, give the three steps and stress that the value is in the middle one: analysis produces ground truth, validation compares plan against it with self-correctable errors, and only the fill step writes files.
    6. Name two disciplines: validation never mutates and fill never validates, or the model loses its pause between planning and execution; and intermediate artifacts must be written to disk so the validator can read them.
    7. Expected follow-up: why not validate while filling? Filesystems have no transactions, and a half-written document is worse than none because it looks complete.

    分析过程 · 先想清楚再作答

    1. 这题有两半,别只答一半。前半考安全边界,后半考流程设计,两者的共同点是「在不可逆的动作之前留一道闸门」。
    2. 安全这一半按来源、权限、输入、动作四层说。来源:第三方 skill 里的脚本就是别人的代码,装之前要读 scripts 目录,跟装一个包之前看两眼是一回事——skill 更容易被当成文档而放松警惕。
    3. 权限:预批工具要卡到命令级,写「允许 git 的只读子命令」而不是「允许任意 shell」;而且这个字段还是实验性的,各家支持不一,不要把安全性全押在它上面。
    4. 输入:脚本处理的外部文件与接口返回是不可信输入。脚本不把它当代码执行就不会被直接利用,但**脚本的输出会进模型上下文**,原样回显一大段外部内容等于把那段话讲给模型听。动作:删除覆盖发布要给预演开关或确认参数,因为 Agent 会重试。
    5. 第二半给三步流程,并强调价值全在中间那步:分析脚本产出的是真值,模型不该凭记忆猜字段;校验脚本比对计划与真值,错误信息要够模型自己改对;填充脚本才落盘。
    6. 两条设计纪律要点出来:校验脚本不改数据、填充脚本不做校验,混在一起模型就没法在计划和执行之间停下来;中间产物要落盘成文件,否则校验脚本读不到,你也没法打开看。
    7. 可预期的追问是「为什么不能边填边校验」。答案是文件系统没有事务,写了一半的文档比完全没写更麻烦——它看起来是完整的。

    Key points

    • Third-party skill scripts are someone else's code; read the scripts directory before installing.
    • Pre-approve tools at command granularity, and do not rely on an experimental field for safety.
    • External input is untrusted, and script output enters context, so never echo large external blobs verbatim.
    • The value of the three-step flow is the middle step: ground truth, self-correctable errors, then writing.
    • Validation never mutates, fill never validates, intermediates go to disk, and never validate while writing.

    答题要点

    • 第三方 skill 的脚本就是别人的代码,装之前要读一遍 scripts 目录。
    • 预批工具按最小权限、卡到命令级;该字段仍是实验性的,不能全押在它上面。
    • 外部输入不可信,且脚本输出会进上下文,不要原样回显大段外部内容。
    • 三步流程的价值全在中间那步校验:分析出真值、校验给可自纠的错误、执行才落盘。
    • 校验不改数据、填充不做校验、中间产物落盘;不要边填边校验,半成品文档看起来是完整的。

Comments