Skip to content

Requests and Execution

Sovereign

The tool protocol is deliberately small: one process, one JSON request in, one JSON result out. There’s no long-lived server to keep alive and no SDK to track, so any script you can test from a terminal can become a workflow step.

One request in, one result out

Each time a step runs, Valdr starts your program and writes one UTF-8 JSON request to stdin, then closes it:

{
  "protocolVersion": 1,
  "toolId": "my-tools.user.text",
  "toolRevision": "1.0.0",
  "action": "summarize",
  "input": {"text": "Hello Valdr\nTools are ready."},
  "operationId": "example-operation",
  "attemptId": "example-attempt"
}

Your program reads to the end of stdin, checks the request, runs the chosen action, and writes one JSON object to stdout.

A success:

{"ok":true,"data":{"characters":28,"words":5,"lines":2}}

A handled failure:

{"ok":false,"error":{"code":"unsupported_action","message":"Unknown action."}}

Valdr places successful data under $.normalized.data, which is what workflow output mappings read.

Rules for a clean result

  • Exactly one JSON object on stdout. No banners, progress lines, JSON Lines, or streaming chunks. Put logs on stderr.
  • Exit zero, even for handled failures. A nonzero exit is treated as a crashed process, and Valdr won’t read your error envelope.
  • Match the envelope exactly. Success has only ok and data. Failure has only ok and error, where error needs a non-empty code and message.
  • Report child CLI failures. When a wrapped CLI fails, add its exitCode (a whole number or null) and signal (such as SIGTERM, or null) to error. Leave them out for failures that didn’t involve a child process.
  • data must match the action’s outputSchema. data: null is fine if the schema allows it. Encode binary data in JSON, or write it to a file and return its path.

Invalid UTF-8, extra output, or data that fails the schema marks the attempt as failed.

What’s in the request

input is exactly what the workflow author supplied, kept separate from Valdr’s own fields. Valdr adds no project key, path, or workflow context to the request. If an action needs a repository name, a cloud project, or a Jira issue, declare it as an ordinary input.

operationId stays the same when a step is retried, so you can use it to recognize a repeat. attemptId changes on every attempt. A new run or loop iteration gets a new operationId.

Where your program runs

Your program runs on the workflow host as your user. If the run was started from an agent session, the working directory is that session’s worktree. Otherwise it’s the project’s repository, and a run with neither uses the folder Valdr was started from. If the session has no worktree, the step is blocked before your program starts. A step can override the default with cwd, and relative values resolve from it. Read the current directory with your language’s standard library; Valdr doesn’t pass it in the request.

Keep tools reusable: don’t hard-code a project key or a local repository path in the manifest.

Environment. Programs inherit PATH, HOME, TMPDIR, LANG, and LC_* variables. To pass more, list the variable names in process.inheritEnv and make their values available on the workflow host. Tools don’t read your browser’s environment.

Limits. The effective limit is the smaller of your manifest’s value and the host maximum:

LimitHost maximumWhen it’s exceeded
Run time300 secondsValdr attempts to stop the tool and reports a timeout
Request size, including Valdr’s fields1 MiBThe step fails before your program starts
Result on stdout1 MiBValdr attempts to stop the tool and rejects the result
Saved stderr1 MiBThe log is not retained, so keep diagnostics short

Stopping a tool cannot undo an external change or guarantee that detached work has stopped. Check what happened before repeating an action.

Environment variables

Use ~/.valdr/environment.env on the machine running Valdr to supply the same environment references to workflow user tools started through the UI or MCP. Create the .valdr directory if needed, then add one reference per line:

MY_API_TOKEN=${MY_API_TOKEN}
SOME_VAR=$(printf '%s' hello)

${MY_API_TOKEN} reads the variable from your interactive login shell; define it in your shell startup configuration. $(command) reads the output of a trusted local command, such as a secret-manager lookup. The second line supplies the non-secret value hello.

The file accepts whole-value ${NAME} or $(command) references, with blank lines and # comment lines. Literal assignments such as SOME_VAR=hello aren’t supported. Keep token values out of this file, manifests, and workflow inputs.

Each tool must still list the names it needs in process.inheritEnv. For example, a Node tool’s process section could be:

process:
  executable: node
  args: [main.mjs]
  inheritEnv: [MY_API_TOKEN, SOME_VAR]

An explicit variable in the environment used to launch the UI or MCP takes precedence, including an empty value. The file is optional; without it, tools can still receive declared variables from the launch environment.

References resolve once per UI or MCP process. Restart Valdr and your MCP hosts after changing the file or its source values, including after removing a reference. If Valdr reports an invalid or unresolved reference, fix the file, shell variable, or command and restart.

Redaction and sensitive output

Redaction is best effort. Valdr attempts to remove secrets from recorded inputs, stderr, and error messages, but it may miss sensitive values. You are responsible for what your tool returns and logs, including output from any CLI or API it calls.

Successful data is saved in run history and passed to later steps without Valdr redaction. Return only the fields your workflow needs, and remove credentials and unnecessary sensitive data before writing results, errors, or logs. Keep secrets out of workflow inputs and manifests; use the host environment or the CLI’s own sign-in.

Design retries for real effects

The sideEffects and retry fields are promises to the people running your tool. Make them accurate:

Your action…DeclareWhy
Only readssideEffects: read, retry: idempotentRepeating it is always safe
Writes, and the destination can deduplicate on operationIdsideEffects: write, retry: idempotentA retry of the same operation won’t write twice
Writes that converge, such as a build into target/sideEffects: write, retry: idempotentRunning it again overwrites the same outputs
Writes without reliable deduplicationsideEffects: write, retry: manualA person should check before repeating
Has effects you can’t establishsideEffects: unknown, retry: manualTreat it as a write

What Valdr does with those declarations:

  • If a manual-retry or unknown-effect action fails after your program starts, even with a handled error, the step is blocked with a needs attention failure and Retry step isn’t offered. This includes a timeout or cancellation, which doesn’t prove a write didn’t happen. Failures Valdr catches before your program starts, such as invalid input or a missing executable, can still be retried.
  • An idempotent action that fails or times out can be retried.
  • If Valdr stops mid-attempt, for example because the host restarts, and can’t confirm the outcome, the step also needs attention, even for an idempotent action.
  • If Valdr restarts after your program succeeded, it uses the saved result instead of running your program again.
  • If Valdr cannot reuse the original inputs for recovery, the step needs attention. Check what already happened before starting a new run.

Inside your adapter:

  • Use operationId for deduplication, never attemptId, which changes on every retry.
  • Don’t add automatic retry loops around a write whose outcome is uncertain.
  • Don’t rely on in-memory state between attempts.
  • If users need to undo a completed effect, give them a separate action that reverses it.

Next step

Test success and failure behavior before you share your tool.