cd /news/ai-agents/the-lock-trick-how-to-write-portable… · home topics ai-agents article
[ARTICLE · art-138970] src=dev.to ↗ pub= topic=ai-agents verified=true sentiment=· neutral

The Lock Trick: How to Write Portable Agent Skills with Reproducible External Dependencies

A software engineer describes a technique for making agent skills portable across harnesses like Claude Cowork and ChatGPT Work by bundling external Python dependencies into a single self-contained script, since skill directories are mounted read-only in cloud sandboxes and standard `uv sync` or `pip install` setups fail. The approach keeps skills restricted to the standard library where possible, delegating deterministic sub-workflows to scripts to cut token costs and execution time.

by read8 min views1 publishedSep 24, 2026

I've been writing a lot of agent skills at my day job.

For many enterprises, the humble SKILL.md file gives them a compelling reason to finally document their workflows, processes, and protocols. These agent skills come in all shapes and sizes:

Interestingly, many of these sub-workflows can be done deterministically (e.g., JSON transformations, HTML rendering, condition checks, and basic classification). Their entry points and decision criteria just happen to be in fuzzy natural language, but the overall orchestration tends to be over mostly deterministic sub-procedures.

That's why my job (as a software engineer) is to wrangle these skills and delegate as much bespoke logic as possible into deterministic scripts (e.g., the scripts/ directory) and even potentially hoist some of them to the infrastructure layer (e.g., data pipelines, bespoke MCP connectors, custom harnesses, etc.). Not only does this make the skill more reliable, but it also significantly reduces token costs and workflow execution time.<sup>1</sup>

However, this approach comes with a significant drawback: packaged scripts typically don't have access to powerful external packages such as pydantic and beautifulsoup4. For maximum portability across harnesses (e.g., Claude Code, Claude Cowork, Codex, ChatGPT Work, etc.), usage must be restricted to the standard library.

ℹ️ INFO: It's worth clarifying here what I mean by "portable".

I work with enterprise clients who use Claude Cowork and ChatGPT Work for their daily knowledge work. That means the agent skills that I write should work within the cloud sandboxes of Claude Cowork and ChatGPT Work.

The common denominator between the two harnesses is that they both support Python (python + uv/ pip) and JavaScript ( node + npm). Therefore, a "portable" agent skill with scripts should only use what's available in Python and JavaScript out of the box.

To Python's credit, its standard library is quite formidable. In fact, when writing portable agent skill scripts, I default to Python over JavaScript just because the experience is far more "batteries-included" than the equivalent offering by the Node.js standard modules. But if you'd ask me, I'd much rather write in TypeScript.

But, once you start dealing with advanced data transformations involving JSON, CSV, HTML emails, and spreadsheets-as-data-frames (among other staples of the office experience), you quickly run into the limitations of the standard library and need to start using external dependencies to keep your sanity.<sup>2</sup>

So, how do we solve this problem? How does one conveniently, reliably, securely, and quickly pull in external dependencies for agent skill scripts?

pip install). We have several solutions here, but only one of them ticks all of these boxes. That's what we'll talk about for the rest of this article. But first, here are my failed attempts.

The most obvious first attempt at this problem is to place a pyproject.toml or a requirements.txt at the root directory of the agent skill. Then, in the SKILL.md, simply prompt that uv sync or pip install is a mandatory setup step. With an accompanying lockfile like uv.lock, we can even ensure secure and reproducible dependencies.

Unfortunately, this method fails the convenience rubric. We shouldn't have to introduce instruction noise just to install dependencies. Never mind the fact that we have to repeat this boilerplate for every single agent skill that we distribute!

Most importantly, this doesn't even work! When agent skills are mounted onto the Claude Cowork cloud sandbox, the skill directories are read-only. There are two major consequences from this crucial implementation detail:

uv operates in virtual environments, a simple uv sync immediately fails to create the required .venv/ directory beside the pyproject.toml manifest in the read-only skill root.pip operates at the system level, we are forced to invoke pip install --break-system-packages instead. So, if the problem is the read-only mount, what if we just hoist the virtual environment into the connected workspace directory?

ℹ️ INFO: In Claude Cowork parlance, the "connected workspace directory" is whatever host directory you chose to mount into the cloud sandbox. In the UI, this is presented as "Claude Projects". This is analogous to the "current working directory" as in shell environments with Claude Code.

Again, this method fails the convenience rubric, but in several spectacular ways this time around:

SKILL.md to assets/pyproject.toml cp assets/pyproject.toml dance and clobber a previous skill's Thus far, all of our woes come from the fact that we require a virtual environment to install Python packages. But what if the virtual environment setup was abstracted away?

That's exactly what PEP 723 gives us: inline script metadata (i.e., the ability to define dependencies inline with the script). The syntax looks like this:


from pydantic import BaseModel

You can think of it as a script-local equivalent of pyproject.toml. In the example above, we declare a tilde-versioned dependency on pydantic.

To run the script:

uv run example.py

No setup step required! The uv package manager abstracts the virtual environment setup by down the packages into a global cache and then materializing them into an ad-hoc virtual environment in the uv cache directory.

As far as the skill scripts are concerned, uv run just works transparently. The SKILL.md can simply invoke the script as it normally would.

So, problem solved? Not yet!

We've solved the convenience rubric, but this approach has a major security flaw. The dependencies field in the metadata only declare semver-compatible ranges for direct dependencies. Future invocations of uv run (likely in a different sandbox session) can end up installing newer semver-compatible versions, which may break the skill script without warning. In the worst-case scenario, this is a supply chain attack waiting to happen!

⚠️ WARNING: We can instead define an exact semver specifier for pydantic, but that doesn't lock the versions of its transitive dependencies. Nothing stops uv from installing newer versions of semver-compatible transitive dependencies during the next uv run in a different sandbox session.

As I've hinted previously, we need a way to lock the versions of the entire dependency tree. The uv.lock file served this purpose, but without a pyproject.toml, it's ambiguous what dependencies are being locked.

Fortunately for us, there is a way to produce a script-specific lockfile!

uv lock --script example.py

In the SKILL.md, update all prompt call sites as follows:

uv run --locked --script example.py

The generated example.py.lock file (which uses the same format as uv.lock) is collocated with the example.py script. Like any other lockfile, this must be committed to version control.

💡 TIP: Don't forget to generate the lockfile for each skill script entrypoint. If it's meant to be invoked as __main__, then it must be accompanied by a collocated *.lock file! Internal helper modules do not need a lockfile.

When loaded as a plugin, Claude Cowork can now mount the entire skill directory as read-only. Then, uv handles the per-script ad-hoc virtual environment setup, the frozen dependency tree resolution, and the semver-compatible package deduplication. Repeat invocations simply reuse what already exists in the global cache.

And just like that, we've ticked all the boxes!

uv run with --locked and --script. No extra setup prompts required. In this article, we discussed:

These are all hard lessons that I learned through a trial by fire at the frontier of AI enablement, skill governance, and workflow adoption in the enterprise. Oftentimes, some out-of-the-box thinking is required to work around harness limitations and constraints.

Before I sign off, I think it's worth revisiting why I chose Python for writing portable agent skills with reproducible dependencies. It's easy to take for granted that I took you on this long journey without considering alternative ecosystems like that of JavaScript.

Aside from the richer standard library, there is just no equivalent mechanism (yet!) in the JavaScript ecosystem for self-contained scripts with collocated lockfiles. The closest equivalents are in Bun and Deno, but neither of those runtimes are available in the Claude Cowork and Codex cloud sandboxes yet.

I like to believe that both Ofek Lev (who authored PEP 723) and the Astral team (who maintain the uv package manager) had the infinite foresight and wisdom to bless me with this elegant solution. Frankly though, they probably weren't thinking about me in particular when they considered these enhancements for the Python ecosystem. 😅

Nevertheless, I would like to take this opportunity to personally thank the individuals who made this feature possible.

For obvious reasons, I can't disclose internal data and benchmarks, but I think we can all intuit that substituting chatty agentic loops (powered by expensive + high-latency token inference) with fast deterministic scripts can lead to more reliable and cost-effective agent skills. ↩

There was a point in which I longed for pydantic so badly that I practically wrote my own schema validation library for each agent skill script just to validate incoming JSON from the standard input. Yep... there was a lot of duplication as you might've imagined. ↩

── more in #ai-agents 4 stories · sorted by recency
── more on @claude cowork 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/the-lock-trick-how-t…] indexed:0 read:8min 2026-09-24 ·