Manifest and Schemas
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, 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:
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 |
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.mjsstill 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 withprocess.cwd(). In Python, usePath(__file__).parentandPath.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
executableat an adapter program rather than atghorawsdirectly. See Wrap a CLI.
File and path rules
/ 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.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:
inputSchemadescribes theinputobject your program receives. Its root must betype: object. - Output:
outputSchemadescribes only the successfuldatavalue, not the whole result envelope. It can be a schema object ortrue. - In the Builder: an input schema appears as named fields when its root sets
additionalProperties: false, uses onlytype,properties,required,additionalProperties,title, anddescription, 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
defaultannotation 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.
Strict validation rules
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.
Next step
Learn the request and result format your program reads and writes.