v0.41.0 6084eb1

zutfiles.md RawHistoryFolder

zutfile agents

A zutfile packages an agent's behavior into one portable .zut file. It can contain the agent's instructions, reusable skills, static assets, and metadata describing the runtime, model, operating-system, binary, and tool permissions it needs.

The current implementation supports creating, inspecting, verifying, and running local directories and .zut archives. It can also run an agent repository or directory directly from any public GitHub repository without keeping a clone. zut has no built-in owner or official collection. Indexed registry distribution, installation, signatures, bundled executable extensions, network permissions, and environment permissions are not implemented yet. The native public-web capability (web_search, web_open, web_find, and web_click) is unavailable to packaged agents until network permissions are enforceable.

Quick start

Create a directory for the agent:

reviewer/
├── manifest.json
├── AGENT.md
└── skills/
    └── code-review/SKILL.md

Add a minimal manifest.json:

{
  "zutfile": 1,
  "name": "code-reviewer",
  "version": "0.1.0",
  "description": "Reviews a repository and reports actionable findings.",
  "runtime": {
    "min_zut": "0.2.76"
  },
  "model": {
    "requires": ["tools"],
    "min_context": 64000
  },
  "permissions": {
    "fs": {
      "read": ["${workspace}"],
      "write": []
    },
    "bash": {
      "mode": "none"
    }
  },
  "requirements": {
    "os": ["darwin", "linux", "windows"]
  },
  "entry": {
    "greeting": "What should I review?",
    "default_prompt": null
  }
}

Add the agent's standing instructions to AGENT.md:

# Code reviewer

Review the current repository without modifying it.

Prioritize correctness, security, regressions, and missing tests. Report findings
in severity order with file and line references. Do not praise the code or
summarize files that have no findings.

Test the directory directly during development:

zut inspect ./reviewer
zut run ./reviewer
zut run ./reviewer "Review the authentication package"

Package it when ready:

zut pack ./reviewer
# wrote code-reviewer.zut
# digest sha256:...

zut verify ./code-reviewer.zut
zut run ./code-reviewer.zut

The first run displays the declared permissions and asks for consent before the agent starts. A packaged agent with filesystem read permission can use the built-in grep tool without any Bash permission; grep checks the requested search root against the declared read scopes before running its direct read-only subprocess.

Directory layout

my-agent/
├── manifest.json          # identity, requirements, and permissions, required
├── AGENT.md               # persona and standing instructions
├── skills/                # SKILL.md directories, optional
│   └── my-skill/SKILL.md
├── assets/                # static files, optional
└── README.md              # human-facing documentation, optional

Only manifest.json. Other ordinary files and directories are included in the archive, but the runtime gives no special behavior to assets/ or README.md yet. Files in assets/ are not automatically added to the model context or granted filesystem access.

Do not include executable extensions in extensions/. The local runtime rejects any bundled extension directory containing extension.json because extension subprocesses cannot yet be confined to the manifest permissions. This is a deliberate fail-closed restriction.

Symlinks are not supported and cause zut pack to fail.

AGENT.md

AGENT.md is optional. When present, it is appended to zut's system prompt by default. It should describe the agent's role, workflow, constraints, output format, and when to load bundled skills. When absent, it contributes no agent-specific text; other enabled global addenda still follow their normal rules.

Keep capability and security declarations out of this file. Permissions come only from manifest.json and are enforced independently of agent-authored prose.

Set replace_system_prompt to true in the manifest to use AGENT.md as the replacement base identity:

{
  "replace_system_prompt": true
}

Replacement is intended for fully specialized agents. It replaces zut's built-in identity, while global append addenda such as enabled Ponytail coding guidance, project context, and skills retain their normal inclusion rules. The default layering behavior is usually preferable because it retains zut's normal identity and tool-use guidance.

Bundled skills

Place skills under skills/<name>/SKILL.md using the normal zut skill format:

skills/
└── investigate-failure/
    └── SKILL.md
---
name: investigate-failure
description: Diagnose a failing command from its output and relevant source files.
---

# Investigate failure

1. Reproduce the failure if permissions allow it.
2. Identify the first meaningful error rather than downstream noise.
3. Read the implementation and nearest tests.
4. Propose the smallest correction and validation plan.

Bundled skills are added to normal skill discovery while the zutfile is running. The model sees their name and description in the skill manifest and can load the full body through the skill tool. See skills.md for the complete skill format.

Manifest reference

The current manifest shape is:

{
  "zutfile": 1,
  "name": "code-reviewer",
  "version": "0.1.0",
  "description": "Reviews a repository and reports actionable findings.",
  "license": "MIT",
  "runtime": {
    "min_zut": "0.2.76"
  },
  "model": {
    "requires": ["tools", "reasoning"],
    "min_context": 64000,
    "preferred": ["claude-opus-4-5", "gpt-5.5"],
    "min_tier": ""
  },
  "permissions": {
    "fs": {
      "read": ["${workspace}", "${agent_data}"],
      "write": ["${agent_data}"]
    },
    "bash": {
      "mode": "allowlist",
      "allow": ["git", "go"]
    },
    "net": {
      "allow": []
    },
    "env": {
      "read": []
    }
  },
  "requirements": {
    "bin": ["git", "go"],
    "os": ["darwin", "linux"]
  },
  "entry": {
    "greeting": "What should I review?",
    "default_prompt": null
  },
  "replace_system_prompt": false
}

Top-level fields

Field Required Current behavior
zutfile yes Format version. Must be 1.
name yes Local agent identity and session/data namespace. See naming rules below.
version no Displayed in consent and inspection output. Semver is recommended but not currently validated.
description no Displayed by zut inspect.
license no Package metadata. Not otherwise interpreted by the local runtime.
runtime.min_zut no Minimum zut version. Older binaries refuse to run the agent. Unversioned development builds cannot satisfy a non-empty minimum.
model no Model capabilities, minimum context, and preferences.
permissions no Filesystem and bash permission ceiling. Omitted scopes deny access. Native web search remains unavailable because network permissions are not enforced.
requirements no Required operating systems and executables.
entry no Initial presentation and prompt metadata.
replace_system_prompt no Replaces the built-in identity rather than appending the agent body when true; global append addenda retain their normal inclusion rules. Defaults to false.

Name rules

The local runtime currently accepts a flat lowercase name containing only:

  • a to z
  • 0 to 9
  • ., -, and _

Examples:

code-reviewer
repo_onboarder
acme.audit

Names containing /, uppercase letters, spaces, or path-like values such as .. are rejected. Registry-style namespace/agent names are reserved for the future distribution runtime and are not accepted locally yet.

Model requirements

model.requires currently recognizes:

Capability Behavior
tools Accepted. Every model in the current catalog is treated as tool-capable.
reasoning Requires a catalog model marked as supporting reasoning.
vision Recognized, but currently fails closed because vision support is not represented in the model catalog. No model qualifies.

Unknown capabilities are rejected. Use tools, not the draft spelling tool_calling.

model.min_context requires a catalog model with at least that many context tokens.

model.preferred is an ordered list of model IDs. It is a preference, not a provider requirement. Selection proceeds as follows:

  1. An explicitly selected model, if present, must satisfy the requirements.
  2. The user's configured default is retained if it satisfies them.
  3. The first compatible model in preferred is selected.
  4. Otherwise, zut chooses a compatible active catalog model.
  5. If none qualifies, execution stops with an error.

model.min_tier is present in the format shape but is not supported by the local runtime. Leave it empty or omit it. A non-empty value is rejected.

Runtime and host requirements

Use requirements.os to restrict the agent to Go operating-system names:

{
  "requirements": {
    "os": ["darwin", "linux", "windows"]
  }
}

Use requirements.bin for commands that must already be on PATH:

{
  "requirements": {
    "bin": ["git", "python3"]
  }
}

Zut checks these requirements before requesting consent. These zutfiles do not have install or postinstall hooks, so authors must document how users can obtain missing programs.

Entry fields

entry.pre is auto-submitted once when zut run starts, before the initial prompt is applied. Values that begin with ! use the interactive shell-escape path (or BashTool in --print / --stream / --json) and always receive the built-in 600-second (10-minute) timeout. Shell output streams live to the TUI (interactive) or stderr (non-interactive). Other values are sent as a normal user turn; in --stream mode that turn's assistant text also streams to stdout. After pre finishes, zut reloads extensions and rediscovers skills so anything installed by that command is available for the following turn.

{
  "entry": {
    "pre": "!npx -y skills add tt-a1i/archify --skill archify --agent cursor --copy --yes"
  }
}

entry.default_prompt is used when the user runs the agent without a prompt. It may be a string or null:

{
  "entry": {
    "default_prompt": "Review the current repository"
  }
}

In interactive mode, when no prompt is given on the command line, default_prompt pre-fills the editor after pre finishes (or immediately when pre is empty). It is not auto-submitted. A prompt supplied on the command line takes precedence the same way.

In --print / --stream / --json modes, pre still runs first: shell escapes abort the run on failure; plain-text pre is sent as a first agent turn before the main prompt (default_prompt or the CLI prompt).

entry.greeting is part of the manifest, but the current local runtime does not render it yet.

Filesystem permissions

Filesystem permissions are enforced by the built-in read, write, edit, grep, and worktree tools. Empty or omitted scopes deny the operation.

Two variables are available in filesystem scopes:

Variable Resolves to
${workspace} The current working directory used for the run.
${agent_data} The agent's persistent private data directory under $ZUT_HOME/agents/<name>/data/.

Relative paths are resolved beneath ${workspace}. For example:

{
  "permissions": {
    "fs": {
      "read": ["src", "go.mod"],
      "write": ["${agent_data}"]
    }
  }
}

This allows reads under <workspace>/src and at <workspace>/go.mod, plus writes under the private agent data directory. It does not grant general workspace writes.

Common permission profiles follow.

Read-only repository agent:

{
  "permissions": {
    "fs": {
      "read": ["${workspace}"],
      "write": []
    },
    "bash": { "mode": "none" }
  }
}

Repository editor with private persistent state:

{
  "permissions": {
    "fs": {
      "read": ["${workspace}", "${agent_data}"],
      "write": ["${workspace}", "${agent_data}"]
    },
    "bash": { "mode": "none" }
  }
}

Data-only agent:

{
  "permissions": {
    "fs": {
      "read": ["${agent_data}"],
      "write": ["${agent_data}"]
    },
    "bash": { "mode": "none" }
  }
}

Filesystem checks canonicalize existing paths, or the nearest existing parent for a new path, so symlinks cannot be used to escape a declared scope.

The worktree tool's create action resolves its destination without prompting. A local zut.worktrees.path Git configuration entry (an absolute path) overrides the default for that repository. Repositories configured by an older zut release with the relative value .worktrees must remove it with git config --local --unset zut.worktrees.path before using the new default. Otherwise the tool checks out under <default-root>/<repo-id>, where the default root is $ZUT_HOME/worktrees and <repo-id> combines a readable repository slug with a hash of the canonical Git common directory. The same repository therefore keeps a stable directory across sessions, while repositories that share a name stay separate. The tool changes no repository files, never copies uncommitted or ignored files, and refuses an existing branch or worktree path.

The tool needs read and write scopes covering the repository root, Git metadata, and the resolved worktree destination. Because the default destination lives outside the workspace, an unconfigured repository also needs a scope covering $ZUT_HOME/worktrees; set zut.worktrees.path to a directory inside a declared scope for a fully contained layout. It also requires Bash permission for git, even though it invokes Git directly without a shell:

{
  "permissions": {
    "fs": {
      "read": ["${workspace}", "/home/you/.local/state/zut/worktrees"],
      "write": ["${workspace}", "/home/you/.local/state/zut/worktrees"]
    },
    "bash": { "mode": "allowlist", "allow": ["git"] }
  }
}

Replace the worktrees path with the resolved $ZUT_HOME/worktrees directory for the host, or configure zut.worktrees.path per repository instead.

The tool disables Git hooks, but a repository checkout can still use configured Git filters. Treat git permission for worktree creation as permission to run repository-controlled checkout behavior; jail mode remains an accident-prevention guardrail, not a security sandbox.

If the working directory changes during a run, ${workspace} permissions are expanded again for the new directory.

Bash permissions

permissions.bash.mode supports three values.

none

{
  "bash": { "mode": "none" }
}

All bash tool calls are denied. This is also the default when mode is omitted.

ask

{
  "bash": { "mode": "ask" }
}

Bash is available after the user approves the agent at launch. Consent for an agent using ask is requested on every launch and is not cached.

In the current local runtime, ask is launch-time capability consent. It does not open a separate confirmation dialog for every individual bash invocation.

allowlist

{
  "bash": {
    "mode": "allowlist",
    "allow": ["git", "go", "grep"]
  }
}

Every command in a shell expression must be listed. Zut checks commands separated by ;, &&, ||, and pipes. Paths are reduced to their base command name, so /usr/bin/git is checked as git. A leading environment assignment is skipped when identifying the command.

The allowlist rejects shell substitution, newlines, redirection, backticks, $(), <, and >. This intentionally supports simple command pipelines rather than arbitrary shell programs.

An allowlist mode with an empty allow array is invalid.

Only declare commands the agent genuinely needs. requirements.bin checks that a command exists, while permissions.bash.allow controls whether the agent may invoke it. Agents that execute a required command normally need it in both places.

Network and environment permissions

The manifest shape includes permissions.net.allow and permissions.env.read, but the local runtime does not enforce them yet. To avoid presenting unenforced declarations as security controls, it rejects manifests containing any network host or environment variable. permissions.net is rejected rather than ignored; it does not authorize the built-in public-web capability. Packaged agents receive no native public-web tools until a future runtime design enforces network permissions.

Use empty arrays or omit these sections:

{
  "permissions": {
    "net": { "allow": [] },
    "env": { "read": [] }
  }
}

Bundled executable extensions are rejected for the same reason. Network filtering, scrubbed extension environments, and extension confinement belong to a later runtime milestone.

Before the first run, zut prints the agent identity and expanded filesystem and bash permissions, then asks:

Agent code-reviewer@0.1.0 wants to run.

  fs read: /path/to/workspace
  fs write: none
  bash: none

Allow? [y/N]

For modes other than bash: ask, approval is cached for the exact artifact digest under:

$ZUT_HOME/agents/<name>/consents/<digest>.json

Any change to the packaged artifact produces a different digest and requires consent again. bash: ask always requires fresh launch consent.

Non-interactive runs refuse to bypass consent by default. For controlled automation, pass -y / --yes or set:

zut run -y ./code-reviewer.zut --print "Review this repository"
ZUT_AGENT_CONSENT=1 zut run ./code-reviewer.zut --print "Review this repository"

This environment variable skips the consent prompt. Only use it after independently inspecting and trusting the exact artifact being run.

Persistent agent data lives under:

$ZUT_HOME/agents/<name>/data/

The directory is created for every run, but the agent can access it only when ${agent_data} appears in the relevant filesystem permission scope.

Agent-scoped sessions

Sessions created by a zutfile are isolated from ordinary zut sessions and from other agents:

$ZUT_HOME/sessions/agents/<name>/

Normal session flags still apply, including --continue, --resume, --session, and --no-session, but their default storage root is scoped to the active agent. The interactive /sessions picker and /session import, fork, and tree commands use that same agent-scoped root; /fork is the direct alias for selecting a user message and branching after that turn. /session tree shows the current session's family within that root (its ancestor and descendants), keeps pre-compaction user and assistant history available for checkout, and hides tool/internal rows from the fork picker. Tree-navigation branches stay hidden from the flat /sessions picker. Resuming honors the selected session's stored provider/model pair; a failed provider/model rebuild leaves the current session unchanged.

Commands

zut run

Run an unpackaged directory during development:

zut run ./my-agent
zut run -y ./my-agent
zut run ./my-agent "Do the task"
zut run ./my-agent -y --print "Do the task"

Run a packed artifact:

zut run ./my-agent.zut
zut run ./my-agent.zut --print "Do the task"
zut run ./my-agent.zut --stream "Do the task"
zut run ./my-agent.zut --json "Do the task"

Arguments after the reference use zut's normal CLI parser, so model, provider, reasoning, cwd, session, tool, and output-mode flags remain available. The manifest still imposes its permission and compatibility ceiling. In interactive mode, enabling show loaded resources at startup in /settings displays the manifest name in an [Agent] section above the transcript.

Local filesystem directories, local archive paths, GitHub shorthand, and public GitHub agent-directory URLs are accepted. A single-part name resolves only to a matching local directory or .zut archive:

zut run my-agent

A two-part name is treated as a GitHub owner/repository reference. Resolution remains local-first, then uses the repository root:

zut run frkr/zot-archify

Use three or more parts to select an agent directory from any public GitHub collection:

zut run acme/agents/reviewer
zut run acme/clankers/reviewers/go

There is no built-in owner, official collection, collection configuration, or allowlist. Every shorthand part must use lowercase letters, digits, dots, hyphens, or underscores. Backslashes are also accepted as separators on Windows. Prefix a reference with ./, use an absolute path, retain the .zut suffix, or provide a complete GitHub URL to force local or explicit remote resolution.

For GitHub, zut downloads the repository archive into a temporary directory, selects the requested agent subdirectory, validates it, runs it, and removes the downloaded files when the command exits:

zut run https://github.com/acme/agents/reviewer

A standard GitHub tree URL is also supported:

zut run https://github.com/acme/agents/tree/main/reviewer

Short forms read the repository's default branch through GitHub's HEAD archive. A tree URL uses the branch or tag in the URL. Private repositories and branch or tag names containing / are not currently supported. The downloaded source is temporary, but normal agent data, consent receipts, and session transcripts remain under $ZUT_HOME.

owner/repository and owner/repository/path are direct GitHub mappings, not an indexed or signed registry. Installed names, arbitrary URLs, and OCI references are not resolved yet.

zut pack

zut pack [directory] [output]

Examples:

zut pack
zut pack ./my-agent
zut pack ./my-agent ./dist/reviewer.zut

The default directory is the current directory. When output is omitted, zut uses <manifest-name>.zut. If the output has no .zut suffix, zut adds one.

Packing validates the manifest and directory, then creates a zstd-compressed tar archive. Entries are sorted and tar metadata is normalized with fixed timestamps and numeric ownership. The output archive itself is excluded when it is located inside the source directory. Zut prints the SHA-256 digest of the resulting compressed artifact.

zut inspect

zut inspect ./my-agent
zut inspect ./my-agent.zut
zut inspect https://github.com/acme/agents/code-reviewer

Inspection validates and prints the agent's name, version, description, digest, declared permissions, and complete file list. It does not execute the agent.

For a directory, the digest is computed from its canonical uncompressed tar representation. For a .zut archive, the digest is computed from the archive bytes. Therefore a source-directory digest and its packed-archive digest are not expected to match.

zut verify

zut verify ./my-agent.zut

The local verify command validates that the archive can be safely loaded, validates the manifest and required layout, and prints its SHA-256 digest.

Despite the command name, it does not verify a publisher signature in the current runtime. Signature and namespace verification are planned for registry distribution.

Packaging and extraction limits

The archive loader applies these limits:

Limit Value
Compressed .zut file 100 MiB
One extracted entry 64 MiB
Total extracted content 256 MiB

Archive extraction rejects absolute paths and parent traversal. Unsupported tar entry types are ignored. New packages use zstd; gzip-compressed tar archives are accepted only as a compatibility fallback for earlier experiments.

Authoring guidance

  1. Start with the narrowest permissions possible. A reviewer usually needs repository reads, not writes or bash.
  2. Keep AGENT.md focused on stable behavior. Put task-specific procedures in skills so the model loads them only when relevant.
  3. Declare every external command in both requirements.bin and the bash allowlist when the agent must execute it.
  4. Test from a directory first with zut inspect and zut run.
  5. Test denied behavior as well as allowed behavior. Ask the agent to attempt an out-of-scope read, write, and command.
  6. Pack the same source twice and compare behavior and output digests before distributing it.
  7. Inspect and verify the final archive, not only its source directory.
  8. Do not claim that network, environment, signatures, extensions, installation, or registry delivery are secured or supported by the local runtime.

Current scope

Implemented now:

  • local directories and .zut archives
  • local-only resolution for single-part names
  • local-first owner/repository and owner/repository/path resolution to any public GitHub repository
  • temporary execution of agent directories from public GitHub repositories
  • zut pack, zut inspect, zut verify, and zut run
  • canonical tar creation with zstd compression
  • archive digest reporting and safe extraction limits
  • AGENT.md append or replacement behavior
  • bundled skills
  • runtime, OS, and binary requirements
  • model context and reasoning requirements
  • filesystem scope enforcement
  • bash none, ask, and allowlist modes
  • digest-scoped consent receipts
  • private agent data directories
  • agent-scoped sessions

Not implemented yet:

  • zut install, zut agents, zut use, zut update, or zut publish
  • installed-name, arbitrary URL, OCI, configurable registry, or GitHub index resolution
  • network allowlist enforcement
  • environment-variable filtering
  • safely confined bundled executable extensions
  • automatic artifact theme loading
  • agent dependency composition
  • per-agent Telegram, bot, or RPC selection
  • --no-sandbox, --trust-bash, or registry trust override flags

Treat the unsupported fields and commands in the broader zutfile proposal as forward-looking design, not as current runtime behavior.

source
1# zutfile agents
2
3A zutfile packages an agent's behavior into one portable `.zut` file. It can contain the agent's instructions, reusable skills, static assets, and metadata describing the runtime, model, operating-system, binary, and tool permissions it needs.
4
5The current implementation supports creating, inspecting, verifying, and running local directories and `.zut` archives. It can also run an agent repository or directory directly from any public GitHub repository without keeping a clone. zut has no built-in owner or official collection. Indexed registry distribution, installation, signatures, bundled executable extensions, network permissions, and environment permissions are not implemented yet. The native public-web capability (`web_search`, `web_open`, `web_find`, and `web_click`) is unavailable to packaged agents until network permissions are enforceable.
6
7## Quick start
8
9Create a directory for the agent:
10
11```text
12reviewer/
13├── manifest.json
14├── AGENT.md
15└── skills/
16    └── code-review/SKILL.md
17```
18
19Add a minimal `manifest.json`:
20
21```json
22{
23  "zutfile": 1,
24  "name": "code-reviewer",
25  "version": "0.1.0",
26  "description": "Reviews a repository and reports actionable findings.",
27  "runtime": {
28    "min_zut": "0.2.76"
29  },
30  "model": {
31    "requires": ["tools"],
32    "min_context": 64000
33  },
34  "permissions": {
35    "fs": {
36      "read": ["${workspace}"],
37      "write": []
38    },
39    "bash": {
40      "mode": "none"
41    }
42  },
43  "requirements": {
44    "os": ["darwin", "linux", "windows"]
45  },
46  "entry": {
47    "greeting": "What should I review?",
48    "default_prompt": null
49  }
50}
51```
52
53Add the agent's standing instructions to `AGENT.md`:
54
55```markdown
56# Code reviewer
57
58Review the current repository without modifying it.
59
60Prioritize correctness, security, regressions, and missing tests. Report findings
61in severity order with file and line references. Do not praise the code or
62summarize files that have no findings.
63```
64
65Test the directory directly during development:
66
67```bash
68zut inspect ./reviewer
69zut run ./reviewer
70zut run ./reviewer "Review the authentication package"
71```
72
73Package it when ready:
74
75```bash
76zut pack ./reviewer
77# wrote code-reviewer.zut
78# digest sha256:...
79
80zut verify ./code-reviewer.zut
81zut run ./code-reviewer.zut
82```
83
84The first run displays the declared permissions and asks for consent before the agent starts. A packaged agent with filesystem read permission can use the built-in `grep` tool without any Bash permission; `grep` checks the requested search root against the declared read scopes before running its direct read-only subprocess.
85
86## Directory layout
87
88```text
89my-agent/
90├── manifest.json          # identity, requirements, and permissions, required
91├── AGENT.md               # persona and standing instructions
92├── skills/                # SKILL.md directories, optional
93│   └── my-skill/SKILL.md
94├── assets/                # static files, optional
95└── README.md              # human-facing documentation, optional
96```
97
98Only `manifest.json`. Other ordinary files and directories are included in the archive, but the runtime gives no special behavior to `assets/` or `README.md` yet. Files in `assets/` are not automatically added to the model context or granted filesystem access.
99
100Do not include executable extensions in `extensions/`. The local runtime rejects any bundled extension directory containing `extension.json` because extension subprocesses cannot yet be confined to the manifest permissions. This is a deliberate fail-closed restriction.
101
102Symlinks are not supported and cause `zut pack` to fail.
103
104## `AGENT.md`
105
106`AGENT.md` is optional. When present, it is appended to zut's system prompt by default. It should describe the agent's role, workflow, constraints, output format, and when to load bundled skills. When absent, it contributes no agent-specific text; other enabled global addenda still follow their normal rules.
107
108Keep capability and security declarations out of this file. Permissions come only from `manifest.json` and are enforced independently of agent-authored prose.
109
110Set `replace_system_prompt` to `true` in the manifest to use `AGENT.md` as the replacement base identity:
111
112```json
113{
114  "replace_system_prompt": true
115}
116```
117
118Replacement is intended for fully specialized agents. It replaces zut's built-in identity, while global append addenda such as enabled Ponytail coding guidance, project context, and skills retain their normal inclusion rules. The default layering behavior is usually preferable because it retains zut's normal identity and tool-use guidance.
119
120## Bundled skills
121
122Place skills under `skills/<name>/SKILL.md` using the normal zut skill format:
123
124```text
125skills/
126└── investigate-failure/
127    └── SKILL.md
128```
129
130```markdown
131---
132name: investigate-failure
133description: Diagnose a failing command from its output and relevant source files.
134---
135
136# Investigate failure
137
1381. Reproduce the failure if permissions allow it.
1392. Identify the first meaningful error rather than downstream noise.
1403. Read the implementation and nearest tests.
1414. Propose the smallest correction and validation plan.
142```
143
144Bundled skills are added to normal skill discovery while the zutfile is running. The model sees their name and description in the skill manifest and can load the full body through the `skill` tool. See [skills.md](skills.md) for the complete skill format.
145
146## Manifest reference
147
148The current manifest shape is:
149
150```json
151{
152  "zutfile": 1,
153  "name": "code-reviewer",
154  "version": "0.1.0",
155  "description": "Reviews a repository and reports actionable findings.",
156  "license": "MIT",
157  "runtime": {
158    "min_zut": "0.2.76"
159  },
160  "model": {
161    "requires": ["tools", "reasoning"],
162    "min_context": 64000,
163    "preferred": ["claude-opus-4-5", "gpt-5.5"],
164    "min_tier": ""
165  },
166  "permissions": {
167    "fs": {
168      "read": ["${workspace}", "${agent_data}"],
169      "write": ["${agent_data}"]
170    },
171    "bash": {
172      "mode": "allowlist",
173      "allow": ["git", "go"]
174    },
175    "net": {
176      "allow": []
177    },
178    "env": {
179      "read": []
180    }
181  },
182  "requirements": {
183    "bin": ["git", "go"],
184    "os": ["darwin", "linux"]
185  },
186  "entry": {
187    "greeting": "What should I review?",
188    "default_prompt": null
189  },
190  "replace_system_prompt": false
191}
192```
193
194### Top-level fields
195
196| Field | Required | Current behavior |
197|---|---:|---|
198| `zutfile` | yes | Format version. Must be `1`. |
199| `name` | yes | Local agent identity and session/data namespace. See naming rules below. |
200| `version` | no | Displayed in consent and inspection output. Semver is recommended but not currently validated. |
201| `description` | no | Displayed by `zut inspect`. |
202| `license` | no | Package metadata. Not otherwise interpreted by the local runtime. |
203| `runtime.min_zut` | no | Minimum zut version. Older binaries refuse to run the agent. Unversioned development builds cannot satisfy a non-empty minimum. |
204| `model` | no | Model capabilities, minimum context, and preferences. |
205| `permissions` | no | Filesystem and bash permission ceiling. Omitted scopes deny access. Native web search remains unavailable because network permissions are not enforced. |
206| `requirements` | no | Required operating systems and executables. |
207| `entry` | no | Initial presentation and prompt metadata. |
208| `replace_system_prompt` | no | Replaces the built-in identity rather than appending the agent body when `true`; global append addenda retain their normal inclusion rules. Defaults to `false`. |
209
210### Name rules
211
212The local runtime currently accepts a flat lowercase name containing only:
213
214- `a` to `z`
215- `0` to `9`
216- `.`, `-`, and `_`
217
218Examples:
219
220```text
221code-reviewer
222repo_onboarder
223acme.audit
224```
225
226Names containing `/`, uppercase letters, spaces, or path-like values such as `..` are rejected. Registry-style `namespace/agent` names are reserved for the future distribution runtime and are not accepted locally yet.
227
228### Model requirements
229
230`model.requires` currently recognizes:
231
232| Capability | Behavior |
233|---|---|
234| `tools` | Accepted. Every model in the current catalog is treated as tool-capable. |
235| `reasoning` | Requires a catalog model marked as supporting reasoning. |
236| `vision` | Recognized, but currently fails closed because vision support is not represented in the model catalog. No model qualifies. |
237
238Unknown capabilities are rejected. Use `tools`, not the draft spelling `tool_calling`.
239
240`model.min_context` requires a catalog model with at least that many context tokens.
241
242`model.preferred` is an ordered list of model IDs. It is a preference, not a provider requirement. Selection proceeds as follows:
243
2441. An explicitly selected model, if present, must satisfy the requirements.
2452. The user's configured default is retained if it satisfies them.
2463. The first compatible model in `preferred` is selected.
2474. Otherwise, zut chooses a compatible active catalog model.
2485. If none qualifies, execution stops with an error.
249
250`model.min_tier` is present in the format shape but is not supported by the local runtime. Leave it empty or omit it. A non-empty value is rejected.
251
252### Runtime and host requirements
253
254Use `requirements.os` to restrict the agent to Go operating-system names:
255
256```json
257{
258  "requirements": {
259    "os": ["darwin", "linux", "windows"]
260  }
261}
262```
263
264Use `requirements.bin` for commands that must already be on `PATH`:
265
266```json
267{
268  "requirements": {
269    "bin": ["git", "python3"]
270  }
271}
272```
273
274Zut checks these requirements before requesting consent. These zutfiles do not have install or postinstall hooks, so authors must document how users can obtain missing programs.
275
276### Entry fields
277
278`entry.pre` is auto-submitted once when `zut run` starts, before the initial prompt is applied. Values that begin with `!` use the interactive shell-escape path (or `BashTool` in `--print` / `--stream` / `--json`) and always receive the built-in 600-second (10-minute) timeout. Shell output streams live to the TUI (interactive) or stderr (non-interactive). Other values are sent as a normal user turn; in `--stream` mode that turn's assistant text also streams to stdout. After `pre` finishes, zut reloads extensions and rediscovers skills so anything installed by that command is available for the following turn.
279
280```json
281{
282  "entry": {
283    "pre": "!npx -y skills add tt-a1i/archify --skill archify --agent cursor --copy --yes"
284  }
285}
286```
287
288`entry.default_prompt` is used when the user runs the agent without a prompt. It may be a string or `null`:
289
290```json
291{
292  "entry": {
293    "default_prompt": "Review the current repository"
294  }
295}
296```
297
298In interactive mode, when no prompt is given on the command line, `default_prompt` pre-fills the editor after `pre` finishes (or immediately when `pre` is empty). It is not auto-submitted. A prompt supplied on the command line takes precedence the same way.
299
300In `--print` / `--stream` / `--json` modes, `pre` still runs first: shell escapes abort the run on failure; plain-text `pre` is sent as a first agent turn before the main prompt (`default_prompt` or the CLI prompt).
301
302`entry.greeting` is part of the manifest, but the current local runtime does not render it yet.
303
304## Filesystem permissions
305
306Filesystem permissions are enforced by the built-in `read`, `write`, `edit`, `grep`, and `worktree` tools. Empty or omitted scopes deny the operation.
307
308Two variables are available in filesystem scopes:
309
310| Variable | Resolves to |
311|---|---|
312| `${workspace}` | The current working directory used for the run. |
313| `${agent_data}` | The agent's persistent private data directory under `$ZUT_HOME/agents/<name>/data/`. |
314
315Relative paths are resolved beneath `${workspace}`. For example:
316
317```json
318{
319  "permissions": {
320    "fs": {
321      "read": ["src", "go.mod"],
322      "write": ["${agent_data}"]
323    }
324  }
325}
326```
327
328This allows reads under `<workspace>/src` and at `<workspace>/go.mod`, plus writes under the private agent data directory. It does not grant general workspace writes.
329
330Common permission profiles follow.
331
332Read-only repository agent:
333
334```json
335{
336  "permissions": {
337    "fs": {
338      "read": ["${workspace}"],
339      "write": []
340    },
341    "bash": { "mode": "none" }
342  }
343}
344```
345
346Repository editor with private persistent state:
347
348```json
349{
350  "permissions": {
351    "fs": {
352      "read": ["${workspace}", "${agent_data}"],
353      "write": ["${workspace}", "${agent_data}"]
354    },
355    "bash": { "mode": "none" }
356  }
357}
358```
359
360Data-only agent:
361
362```json
363{
364  "permissions": {
365    "fs": {
366      "read": ["${agent_data}"],
367      "write": ["${agent_data}"]
368    },
369    "bash": { "mode": "none" }
370  }
371}
372```
373
374Filesystem checks canonicalize existing paths, or the nearest existing parent for a new path, so symlinks cannot be used to escape a declared scope.
375
376The `worktree` tool's `create` action resolves its destination without prompting. A local `zut.worktrees.path` Git configuration entry (an absolute path) overrides the default for that repository. Repositories configured by an older zut release with the relative value `.worktrees` must remove it with `git config --local --unset zut.worktrees.path` before using the new default. Otherwise the tool checks out under `<default-root>/<repo-id>`, where the default root is `$ZUT_HOME/worktrees` and `<repo-id>` combines a readable repository slug with a hash of the canonical Git common directory. The same repository therefore keeps a stable directory across sessions, while repositories that share a name stay separate. The tool changes no repository files, never copies uncommitted or ignored files, and refuses an existing branch or worktree path.
377
378The tool needs read and write scopes covering the repository root, Git metadata, and the resolved worktree destination. Because the default destination lives outside the workspace, an unconfigured repository also needs a scope covering `$ZUT_HOME/worktrees`; set `zut.worktrees.path` to a directory inside a declared scope for a fully contained layout. It also requires Bash permission for `git`, even though it invokes Git directly without a shell:
379
380```json
381{
382  "permissions": {
383    "fs": {
384      "read": ["${workspace}", "/home/you/.local/state/zut/worktrees"],
385      "write": ["${workspace}", "/home/you/.local/state/zut/worktrees"]
386    },
387    "bash": { "mode": "allowlist", "allow": ["git"] }
388  }
389}
390```
391
392Replace the worktrees path with the resolved `$ZUT_HOME/worktrees` directory for the host, or configure `zut.worktrees.path` per repository instead.
393
394The tool disables Git hooks, but a repository checkout can still use configured Git filters. Treat `git` permission for `worktree` creation as permission to run repository-controlled checkout behavior; jail mode remains an accident-prevention guardrail, not a security sandbox.
395
396If the working directory changes during a run, `${workspace}` permissions are expanded again for the new directory.
397
398## Bash permissions
399
400`permissions.bash.mode` supports three values.
401
402### `none`
403
404```json
405{
406  "bash": { "mode": "none" }
407}
408```
409
410All bash tool calls are denied. This is also the default when `mode` is omitted.
411
412### `ask`
413
414```json
415{
416  "bash": { "mode": "ask" }
417}
418```
419
420Bash is available after the user approves the agent at launch. Consent for an agent using `ask` is requested on every launch and is not cached.
421
422In the current local runtime, `ask` is launch-time capability consent. It does not open a separate confirmation dialog for every individual bash invocation.
423
424### `allowlist`
425
426```json
427{
428  "bash": {
429    "mode": "allowlist",
430    "allow": ["git", "go", "grep"]
431  }
432}
433```
434
435Every command in a shell expression must be listed. Zut checks commands separated by `;`, `&&`, `||`, and pipes. Paths are reduced to their base command name, so `/usr/bin/git` is checked as `git`. A leading environment assignment is skipped when identifying the command.
436
437The allowlist rejects shell substitution, newlines, redirection, backticks, `$()`, `<`, and `>`. This intentionally supports simple command pipelines rather than arbitrary shell programs.
438
439An `allowlist` mode with an empty `allow` array is invalid.
440
441Only declare commands the agent genuinely needs. `requirements.bin` checks that a command exists, while `permissions.bash.allow` controls whether the agent may invoke it. Agents that execute a required command normally need it in both places.
442
443## Network and environment permissions
444
445The manifest shape includes `permissions.net.allow` and `permissions.env.read`, but the local runtime does not enforce them yet. To avoid presenting unenforced declarations as security controls, it rejects manifests containing any network host or environment variable. `permissions.net` is rejected rather than ignored; it does not authorize the built-in public-web capability. Packaged agents receive no native public-web tools until a future runtime design enforces network permissions.
446
447Use empty arrays or omit these sections:
448
449```json
450{
451  "permissions": {
452    "net": { "allow": [] },
453    "env": { "read": [] }
454  }
455}
456```
457
458Bundled executable extensions are rejected for the same reason. Network filtering, scrubbed extension environments, and extension confinement belong to a later runtime milestone.
459
460## Consent and persistent data
461
462Before the first run, zut prints the agent identity and expanded filesystem and bash permissions, then asks:
463
464```text
465Agent code-reviewer@0.1.0 wants to run.
466
467  fs read: /path/to/workspace
468  fs write: none
469  bash: none
470
471Allow? [y/N]
472```
473
474For modes other than `bash: ask`, approval is cached for the exact artifact digest under:
475
476```text
477$ZUT_HOME/agents/<name>/consents/<digest>.json
478```
479
480Any change to the packaged artifact produces a different digest and requires consent again. `bash: ask` always requires fresh launch consent.
481
482Non-interactive runs refuse to bypass consent by default. For controlled automation, pass `-y` / `--yes` or set:
483
484```bash
485zut run -y ./code-reviewer.zut --print "Review this repository"
486```
487
488```bash
489ZUT_AGENT_CONSENT=1 zut run ./code-reviewer.zut --print "Review this repository"
490```
491
492This environment variable skips the consent prompt. Only use it after independently inspecting and trusting the exact artifact being run.
493
494Persistent agent data lives under:
495
496```text
497$ZUT_HOME/agents/<name>/data/
498```
499
500The directory is created for every run, but the agent can access it only when `${agent_data}` appears in the relevant filesystem permission scope.
501
502## Agent-scoped sessions
503
504Sessions created by a zutfile are isolated from ordinary zut sessions and from other agents:
505
506```text
507$ZUT_HOME/sessions/agents/<name>/
508```
509
510Normal session flags still apply, including `--continue`, `--resume`, `--session`, and `--no-session`, but their default storage root is scoped to the active agent. The interactive `/sessions` picker and `/session` import, fork, and tree commands use that same agent-scoped root; `/fork` is the direct alias for selecting a user message and branching after that turn. `/session tree` shows the current session's family within that root (its ancestor and descendants), keeps pre-compaction user and assistant history available for checkout, and hides tool/internal rows from the fork picker. Tree-navigation branches stay hidden from the flat `/sessions` picker. Resuming honors the selected session's stored provider/model pair; a failed provider/model rebuild leaves the current session unchanged.
511
512## Commands
513
514### `zut run`
515
516Run an unpackaged directory during development:
517
518```bash
519zut run ./my-agent
520zut run -y ./my-agent
521zut run ./my-agent "Do the task"
522zut run ./my-agent -y --print "Do the task"
523```
524
525Run a packed artifact:
526
527```bash
528zut run ./my-agent.zut
529zut run ./my-agent.zut --print "Do the task"
530zut run ./my-agent.zut --stream "Do the task"
531zut run ./my-agent.zut --json "Do the task"
532```
533
534Arguments after the reference use zut's normal CLI parser, so model, provider, reasoning, cwd, session, tool, and output-mode flags remain available. The manifest still imposes its permission and compatibility ceiling. In interactive mode, enabling **show loaded resources at startup** in `/settings` displays the manifest name in an `[Agent]` section above the transcript.
535
536Local filesystem directories, local archive paths, GitHub shorthand, and public GitHub agent-directory URLs are accepted. A single-part name resolves only to a matching local directory or `.zut` archive:
537
538```bash
539zut run my-agent
540```
541
542A two-part name is treated as a GitHub `owner/repository` reference. Resolution remains local-first, then uses the repository root:
543
544```bash
545zut run frkr/zot-archify
546```
547
548Use three or more parts to select an agent directory from any public GitHub collection:
549
550```bash
551zut run acme/agents/reviewer
552zut run acme/clankers/reviewers/go
553```
554
555There is no built-in owner, official collection, collection configuration, or allowlist. Every shorthand part must use lowercase letters, digits, dots, hyphens, or underscores. Backslashes are also accepted as separators on Windows. Prefix a reference with `./`, use an absolute path, retain the `.zut` suffix, or provide a complete GitHub URL to force local or explicit remote resolution.
556
557For GitHub, zut downloads the repository archive into a temporary directory, selects the requested agent subdirectory, validates it, runs it, and removes the downloaded files when the command exits:
558
559```bash
560zut run https://github.com/acme/agents/reviewer
561```
562
563A standard GitHub tree URL is also supported:
564
565```bash
566zut run https://github.com/acme/agents/tree/main/reviewer
567```
568
569Short forms read the repository's default branch through GitHub's `HEAD` archive. A tree URL uses the branch or tag in the URL. Private repositories and branch or tag names containing `/` are not currently supported. The downloaded source is temporary, but normal agent data, consent receipts, and session transcripts remain under `$ZUT_HOME`.
570
571`owner/repository` and `owner/repository/path` are direct GitHub mappings, not an indexed or signed registry. Installed names, arbitrary URLs, and OCI references are not resolved yet.
572
573### `zut pack`
574
575```bash
576zut pack [directory] [output]
577```
578
579Examples:
580
581```bash
582zut pack
583zut pack ./my-agent
584zut pack ./my-agent ./dist/reviewer.zut
585```
586
587The default directory is the current directory. When output is omitted, zut uses `<manifest-name>.zut`. If the output has no `.zut` suffix, zut adds one.
588
589Packing validates the manifest and directory, then creates a zstd-compressed tar archive. Entries are sorted and tar metadata is normalized with fixed timestamps and numeric ownership. The output archive itself is excluded when it is located inside the source directory. Zut prints the SHA-256 digest of the resulting compressed artifact.
590
591### `zut inspect`
592
593```bash
594zut inspect ./my-agent
595zut inspect ./my-agent.zut
596zut inspect https://github.com/acme/agents/code-reviewer
597```
598
599Inspection validates and prints the agent's name, version, description, digest, declared permissions, and complete file list. It does not execute the agent.
600
601For a directory, the digest is computed from its canonical uncompressed tar representation. For a `.zut` archive, the digest is computed from the archive bytes. Therefore a source-directory digest and its packed-archive digest are not expected to match.
602
603### `zut verify`
604
605```bash
606zut verify ./my-agent.zut
607```
608
609The local `verify` command validates that the archive can be safely loaded, validates the manifest and required layout, and prints its SHA-256 digest.
610
611Despite the command name, it does **not** verify a publisher signature in the current runtime. Signature and namespace verification are planned for registry distribution.
612
613## Packaging and extraction limits
614
615The archive loader applies these limits:
616
617| Limit | Value |
618|---|---:|
619| Compressed `.zut` file | 100 MiB |
620| One extracted entry | 64 MiB |
621| Total extracted content | 256 MiB |
622
623Archive extraction rejects absolute paths and parent traversal. Unsupported tar entry types are ignored. New packages use zstd; gzip-compressed tar archives are accepted only as a compatibility fallback for earlier experiments.
624
625## Authoring guidance
626
6271. Start with the narrowest permissions possible. A reviewer usually needs repository reads, not writes or bash.
6282. Keep `AGENT.md` focused on stable behavior. Put task-specific procedures in skills so the model loads them only when relevant.
6293. Declare every external command in both `requirements.bin` and the bash allowlist when the agent must execute it.
6304. Test from a directory first with `zut inspect` and `zut run`.
6315. Test denied behavior as well as allowed behavior. Ask the agent to attempt an out-of-scope read, write, and command.
6326. Pack the same source twice and compare behavior and output digests before distributing it.
6337. Inspect and verify the final archive, not only its source directory.
6348. Do not claim that network, environment, signatures, extensions, installation, or registry delivery are secured or supported by the local runtime.
635
636## Current scope
637
638Implemented now:
639
640- local directories and `.zut` archives
641- local-only resolution for single-part names
642- local-first `owner/repository` and `owner/repository/path` resolution to any public GitHub repository
643- temporary execution of agent directories from public GitHub repositories
644- `zut pack`, `zut inspect`, `zut verify`, and `zut run`
645- canonical tar creation with zstd compression
646- archive digest reporting and safe extraction limits
647- `AGENT.md` append or replacement behavior
648- bundled skills
649- runtime, OS, and binary requirements
650- model context and reasoning requirements
651- filesystem scope enforcement
652- bash `none`, `ask`, and `allowlist` modes
653- digest-scoped consent receipts
654- private agent data directories
655- agent-scoped sessions
656
657Not implemented yet:
658
659- `zut install`, `zut agents`, `zut use`, `zut update`, or `zut publish`
660- installed-name, arbitrary URL, OCI, configurable registry, or GitHub index resolution
661- network allowlist enforcement
662- environment-variable filtering
663- safely confined bundled executable extensions
664- automatic artifact theme loading
665- agent dependency composition
666- per-agent Telegram, bot, or RPC selection
667- `--no-sandbox`, `--trust-bash`, or registry trust override flags
668
669Treat the unsupported fields and commands in the broader zutfile proposal as forward-looking design, not as current runtime behavior.

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close