The zotfile: Agents You Can Share Like Files
A portable, permission-scoped packaging format for AI agents. Persona, skills, requirements, and enforced permissions in one .zot artifact that runs locally with your own keys.
There is no docker run for agents.
Sharing an agent today means sharing a README with a dozen setup steps, or locking the agent into someone's cloud. MCP distributes tools, not agents. Nothing distributes behavior: persona, instructions, skills, and required capabilities as one runnable artifact.
So I specified and built one for zot: the zotfile.
A zotfile is an agent you can share like a file. It packages an agent's
persona and standing instructions, reusable skills, requirements, and enforced
tool permissions into one portable .zot artifact. The user runs it locally,
with their own model credentials, in their own workspace, using zot as the
runtime.
zot run ./code-reviewer.zot "Review the authentication package"That's it. No setup steps, no cloud account, no "please configure these seven environment variables first."
The Docker lesson
Docker did not invent containers. It invented the image as a shareable
artifact, and every shared image made Docker the standard. The same mechanism
applies here: every shared .zot file makes zot the runtime.
But that only works if the runtime is trivially installable. A universal agent format dies at setup if running it requires a Node version, a Python environment, or a package-manager dance. A single static binary is the only credible answer, and zot already is one. That was one of the reasons I built zot in Go in the first place, and it pays off here directly.
The artifact
A zotfile is a compressed tarball with a canonical layout:
code-reviewer/
manifest.json # identity, requirements, permissions (required)
AGENT.md # persona + standing instructions (required)
skills/
review-change/
SKILL.md # loaded on demand, like normal zot skills
assets/ # optional static files
README.md # optional human documentationEverything the agent needs is inside the artifact. No postinstall scripts, no
network fetches at unpack time. Programs the agent depends on must be declared
in requirements.bin instead of installed behind your back.
The artifact is content-addressed: its identity is the SHA-256 of the canonical tarball (sorted entries, normalized metadata, fixed timestamps). Same content, same digest, every time.
AGENT.md layers on top of zot's system prompt the same way AGENTS.md does
today, so the agent keeps zot's standard tool-use and safety guidance. A
manifest flag replace_system_prompt opts into full replacement for
completely specialized agents.
The manifest
The manifest is where the interesting design decisions live:
{
"zotfile": 1,
"name": "code-reviewer",
"version": "0.1.0",
"description": "Reviews a repository without modifying it.",
"runtime": { "min_zot": "0.2.76" },
"model": {
"requires": ["tools", "reasoning"],
"min_context": 64000
},
"permissions": {
"fs": { "read": ["${workspace}"], "write": [] },
"bash": { "mode": "none" }
},
"requirements": {
"bin": ["git"],
"os": ["darwin", "linux", "windows"]
},
"entry": {
"greeting": "What should I review?"
}
}Two decisions matter most here.
Capability-based model requirements, not provider pins. An agent declares
what it needs: tool calling, a minimum context window, reasoning. The runtime
maps that onto whatever providers and keys the user has, using zot's
existing model catalog. preferred is a hint, never a requirement. The same
artifact works whether you run Claude, GPT, or a local model, as long as it
qualifies. If nothing qualifies, you get a clear error instead of a silent
downgrade.
Permissions are declarations, enforced by the runtime. Not documentation, not an honor system. This is the part that has to be right for the format to be trustworthy.
The permission model
zotfile permissions are an enforced ceiling for the built-in file and bash tools:
fs.readandfs.writescope what the read, write, and edit tools may touch. Empty or omitted scopes deny access. Canonical path checks prevent escaping a scope through symlinks.bash: nonerejects every bash call. This is the default.bash: allowlistallows only listed command names and simple pipelines, and rejects substitution, redirection, backticks, and newlines.bash: askrequires fresh consent on every agent launch.
${workspace} resolves to the directory you run the agent in.
${agent_data} resolves to a private persistent directory per agent under
$ZOT_HOME, so an agent can keep state without touching anything else.
Before an agent runs, zot prints its identity and expanded permissions in plain language and asks you to approve. Approval is cached for the exact artifact digest. Any change to the artifact creates a new digest and requires approval again. Non-interactive runs refuse to bypass consent by default.
And importantly: the consent text is generated from the manifest, never from
agent-authored prose. A malicious AGENT.md can try to social-engineer the
model, but it cannot escalate past its declared permission ceiling, and it
cannot lie to you on the consent screen.
Permissions the local runtime cannot enforce yet, like network and environment scoping, are rejected instead of silently accepted. Fail closed, not open.
What works today
The current release supports the local end of the story:
# Develop directly from a source directory
zot inspect ./code-reviewer
zot run ./code-reviewer
# Create and validate the portable artifact
zot pack ./code-reviewer
zot verify ./code-reviewer.zot
zot run ./code-reviewer.zot
# Run directly from a public GitHub repository
zot run https://github.com/patriceckhart/agents/zot-maintenance \
--cwd /path/to/zotzot pack validates and builds the canonical archive and prints its digest.
zot inspect shows identity, digest, permissions, and files without executing
anything, so you can look before you run. Agent sessions are isolated under
their own directory, so a packaged agent's history does not bleed into your
normal zot sessions.
The GitHub URL form matters more than it looks: any public repository
directory with a manifest.json and an AGENT.md is already a distributable
agent. No registry, no publishing step, just a URL.
OCI registries, sigstore signing, zot install and zot update with
permission diffs, a searchable index on zot.sh, and agent composition are
specified and on the roadmap, but they are future work. I would rather ship a
small, honest v1 than overclaim sandbox strength.
Why this is cool (to me)
zot started as a learning vehicle, and this is the part of the stack I wanted to understand next: not how an agent runs, but how an agent travels.
Building the format forced the questions that only become concrete when you implement them. What is the identity of an agent? (A content digest.) What is the trust boundary? (The manifest, not the prose.) What does consent actually mean when the thing you consent to can talk? (It means the ceiling must be enforced by the runtime, and the consent screen must be derived from machine- readable declarations.)
It also collapses a bunch of things I was maintaining separately. A persona, a skill set, a set of constraints, and a distribution story used to be four different problems. Now they are one file. The personal-assistant use case, the CI code reviewer, the internal company agent: all the same artifact shape, all running through the same consent flow.
And it makes agents shareable in the most literal sense. I can pack an agent, send the file to a friend, and they run it with their own keys, on their own machine, against their own workspace. Nothing leaves their computer that they did not already send to their own model provider.
If you integrate it into your stack: even cooler
The format is deliberately open. The manifest schema and runtime contract are
versioned (zotfile: 1) precisely so that other runtimes could implement
them. Being open is what makes something a standard rather than a feature.
So if you build a harness, a CI system, an internal platform, or anything else
that runs agents: I would genuinely love to see zotfiles show up there. Ship
your team's code reviewer as a .zot in your repository. Point your pipeline
at a pinned digest. Publish an agent directory on GitHub and let people run it
with one command. The acceptance test for the whole idea is that a stranger on
a fresh machine goes from nothing to a useful agent with exactly two commands,
sees a truthful permission prompt in between, and the agent works with
whatever model subscription they happen to have.
Small tools compose well. Small artifacts travel well.
Try it
- Website: zot.sh
- zotfile docs: zot.sh/docs/zotfiles
- GitHub repository: github.com/patriceckhart/zot
Write a manifest.json and an AGENT.md, add some skills, run zot pack,
and send someone an agent like you would send them a file. Because now it is
one.