# Manifest and Schemas
{{< tier level="sovereign" >}}

The manifest is your tool's contract. It tells Valdr what the tool is called, which files it needs, how to start it, and exactly what each action accepts and returns. Because the contract is explicit, the Builder can offer the right input fields, Valdr can reject bad input before your code runs, and later steps can rely on the output shape.

Start with the [working example](../quickstart/), then use this page to look up a field.

## The manifest at a glance

A manifest file ends in `.tool.yaml`, `.tool.yml`, or `.tool.json` and lives in a folder the pack includes. The finished example looks like this:

```text
my-tools/
  pack.yaml
  tools/
    text/
      text.tool.yaml
      main.mjs
```

| Field | What it holds |
| --- | --- |
| `schemaVersion` | Always `1` |
| `id` | The tool's identity, in the form `<prefix>.user.<name>`, such as `my-tools.user.text`. Use the `pack` value from `pack.yaml` (here `my-tools`) as the prefix so your IDs don't collide with another pack's. The name after `.user.` starts with a lowercase letter and uses lowercase letters and digits, with single dots, underscores, or hyphens between them |
| `revision` | The exact revision string, such as `1.0.0`. Use semantic versions so the newest revision is easy to tell apart |
| `name`, `description` | Human-readable labels. `name` is what the Builder library shows |
| `icon` | Optional icon name, such as `code-bracket`. See the supported list below |
| `files` | Every file the tool needs, as `{path, executable?}` entries relative to the manifest |
| `process` | How to start the program: `executable`, a literal `args` list, and optional `inheritEnv` variable names; see [environment setup](../runtime/#environment-variables) |
| `limits` | Positive whole numbers for `timeoutSeconds`, `maxInputBytes`, `maxResultBytes`, and `maxLogBytes` |
| `actions` | One or more actions the tool supports |

Supported icons are `command-line`, `code-bracket`, `code-bracket-square`, `variable`, `cube`, `cpu-chip`, `server-stack`, `cloud`, `ticket`, and `document-text`. An omitted or unknown name uses the default tool icon.

The pack's `version` and the tool's `revision` are separate. Bump the tool revision whenever anything in the manifest or its listed files changes; Valdr rejects changed content under a revision it has already imported, even if that revision was removed from the catalog. Renaming a tool ID creates a new tool; workflows that pinned the old ID keep using it.

On each Valdr host, a tool ID belongs to the pack that first imported it. Importing the same ID from a different pack is rejected, even after the owner's revisions are removed from the catalog, so ship updates from the owning pack and keep its `pack` value unchanged, or give your tool a new ID.

## Actions

One tool can expose many actions from a single program. Valdr sends the chosen action's `id` in the request, and your program dispatches on it.

| Action field | What it holds |
| --- | --- |
| `id` | Unique within the tool, lowercase, such as `summarize` or `repo-view`. Valdr sends this as the request's `action` |
| `path` | A list of words describing the command, such as `[repo, view]`. It is catalog metadata only and never becomes process arguments |
| `name`, `description` | Human-readable labels |
| `inputSchema` | The shape of the action's input. Its root must be an object |
| `outputSchema` | The shape of the successful `data` your program returns |
| `execution` | `supported`, or `unsupported` for actions you list but haven't implemented |
| `unsupportedReason` | Required for unsupported actions, and shown to users |
| `sideEffects` | `read`, `write`, or `unknown`. Describe what the action really does |
| `retry` | `idempotent` if repeating the same operation is safe, otherwise `manual` |

Put related actions in one tool. Use separate tools when programs need different runtimes, environment access, or release schedules.

To list a command you haven't implemented yet, add it with `execution: unsupported` and a reason, such as `unsupportedReason: "Interactive sign-in must be completed on the host."` It stays visible in the Builder with that reason but can't run.

## Files, runtimes, and dependencies

List every file your tool needs under `files`. Valdr keeps exactly those files, verifies them before every run, and carries them through export and import.

| Program kind | `process.executable` | `process.args` | The workflow host provides |
| --- | --- | --- | --- |
| Node | `node` | `[main.mjs]` | Node; list `main.mjs` in `files` |
| TypeScript | `bun` | `[main.ts]` | Bun; list `main.ts` |
| Python | `python3` | `[main.py]` | Python and any packages you import; list `main.py` |
| Shell | `sh` | `[main.sh]` | A shell plus a real JSON parser such as `jq`; list `main.sh` |
| Packaged native program | `./bin/my-tool` | `[]` | A compatible OS and architecture; list `bin/my-tool` with `executable: true` |
| Program already on the host | `my-tool` | `[]` | A program on the host's `PATH` that speaks the tool protocol |

Good to know:

- **Arguments that name a listed file become absolute paths** to the tool's retained copy, so `main.mjs` still works when the step runs in a project folder. Other arguments stay literal, and Valdr never expands shell syntax, variables, or globs.
- **A bare executable name** is found on the host's `PATH`. A relative path containing `/` resolves inside the tool's folder.
- **Import never installs anything.** No package managers, install hooks, or compilers run. Bundle the helper files you need, or document what must be installed on the host. A lockfile alone doesn't install dependencies.
- **Treat the tool's own folder as read-only.** Write output to the working directory, a destination given as input, or a temp folder. In Node, find bundled files with `new URL('./asset.json', import.meta.url)` and workflow files with `process.cwd()`. In Python, use `Path(__file__).parent` and `Path.cwd()`.
- **Don't set `process.cwd`.** The workflow step chooses the working directory.
- **Wrapping a CLI?** Most CLIs don't speak the tool protocol, so point `executable` at an adapter program rather than at `gh` or `aws` directly. See [Wrap a CLI](../cli-adapters/).

{{% details title="File and path rules" closed="true" %}}
Use relative paths with `/` separators and no empty, `.`, or `..` segments. Valdr rejects missing or excluded files, symlinks, paths that escape the tool folder, duplicate IDs, and references to other tool manifests. A packaged executable must be listed with `executable: true`. An absolute executable path works but ties the tool to one host.
{{% /details %}}

## Input and output schemas

Schemas are where the contract gets its teeth. Valdr checks input before your program starts and checks your result before later steps see it.

- **Input:** `inputSchema` describes the `input` object your program receives. Its root must be `type: object`.
- **Output:** `outputSchema` describes only the successful `data` value, not the whole result envelope. It can be a schema object or `true`.
- **In the Builder:** an input schema appears as named fields when its root sets `additionalProperties: false`, uses only `type`, `properties`, `required`, `additionalProperties`, `title`, and `description`, and every property is a string, number, integer, or boolean. Any other schema, including nested objects and arrays, uses JSON input. Both paths are validated against the same schema.
- **No silent fixes:** Valdr doesn't insert defaults, coerce strings to numbers, or strip extra properties. A `default` annotation is documentation only, so apply defaults in your program.

Use ordinary JSON Schema keywords: `type`, `properties`, `required`, `additionalProperties`, `enum`, `minimum`, `maximum`, `minLength`, `maxLength`, `pattern`, `items`, and `$defs`. For a nullable string, write `type: [string, "null"]`.

Keep schemas specific. `outputSchema: true` accepts any JSON, but then users can't count on stable fields to map.

For the summary example, the input is `{"text":"Hello Valdr\nTools are ready."}` and the successful data is `{"characters":28,"words":5,"lines":2}`. Both schemas reject extra properties.

Run `valdr validate-pack` to check your schemas before you import.

{{% details title="Strict validation rules" closed="true" %}}
Schemas use a strict subset of **JSON Schema 2020-12**. Local `$ref` pointers within the same schema work. Remote references, `$id`, anchors, dynamic references, and custom keywords don't. String `format` values such as `date-time` aren't supported and fail validation. Older `definitions` and `dependencies` keywords, OpenAPI's `nullable`, and vendor extensions also fail validation.

Manifests must be JSON-compatible: YAML files must contain one document with unique keys and no non-finite numbers or non-JSON values. Equivalent YAML and JSON manifests describe the same tool.

Unknown fields are rejected at every level: manifest, action, process, limits, and file entries. An optional top-level `$schema` is allowed as an editor hint.
{{% /details %}}

## Next step

[Learn the request and result format](/valdr/docs/workflows/user-tools/runtime/) your program reads and writes.

