# Velvt Assurance Runner

The runner connects an agent in your environment to one private, bounded Velvt
Assurance engagement. It uses Python's standard library and sends no model key,
system prompt, private memory or production credential to Velvt.

## WHAT IT READS

- the configuration file path you explicitly pass with `--config`;
- its own source bytes to verify the published SHA-256;
- its own `.velvt/assurance/ENGAGEMENT_ID/state.json` file when resuming;
- an optional `systemPromptFile` only when that exact path is configured;
- the named environment variables described below;
- subject stdout/stderr when the explicitly configured CLI adapter is used.

It does not scan `.env`, SSH keys, browser data, unrelated project files, home
directories or agent configuration. Do not point `systemPromptFile` at private
material you do not intend the local subject adapter to read.

## WHAT IT WRITES

The runner writes only its resumable state file, by default at
`.velvt/assurance/ENGAGEMENT_ID/state.json`. It does not modify subject source,
shell profiles, environment files or browser state. Raw subject output and local
evidence envelopes may be present in the state required for reconciliation.

## NETWORK DESTINATIONS

- the configured Velvt `baseUrl`, normally `https://www.velvt.ai`;
- the exact HTTP or Ollama subject endpoint configured by the operator;
- for native provider adapters, the configured endpoint or the provider's
  documented API endpoint for Gemini, OpenAI or Anthropic.

The runner performs no network discovery. Non-loopback HTTP subject endpoints must
use HTTPS. Provider credentials are sent only from the local runner to the selected
provider endpoint, never to Velvt.

## REQUIRED ENVIRONMENT VARIABLES

No environment variable is universally required. Depending on the selected path,
the runner reads `VELVT_AGENT_CREDENTIAL`, `VELVT_ASSURANCE_INVITATION`,
`SUBJECT_BEARER_TOKEN`, `VELVT_RUNNER_SOURCE_COMMIT`, or the provider credential
variable named by `subject.credentialEnv` (defaults include `GEMINI_API_KEY`,
`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, and `OPENAI_COMPATIBLE_API_KEY`). Missing
Velvt credentials can be supplied through masked input or the generated config as
documented below.

## CREDENTIAL REQUIREMENTS

The Velvt subject credential authenticates one private assessment identity. The
one-time invitation admits that identity to one engagement. Model/provider and
subject endpoint credentials remain in the customer environment and are never
included in Velvt requests. The runner does not require production application
credentials.

## FILESYSTEM ACCESS

Filesystem access is limited to the explicit config, the runner source, the local
state path, and an explicitly configured `systemPromptFile`. Run from a directory
where creating `.velvt/assurance/...` is acceptable, or supply the supported state
path option. Ordinary filesystem permissions remain the enforcement boundary.

## PROCESS EXECUTION

Only the `cli` subject adapter starts a process. It executes the exact configured
argument vector with `shell=False`, sends the bounded protocol request on stdin,
and captures stdout/stderr. Other adapters do not start local processes. Choosing
the CLI adapter therefore grants the configured executable the ordinary permissions
of the user running it.

## PERSISTENCE

The runner installs no daemon, background service, scheduled job or startup item.
Persistence consists only of the downloaded runner/config files and the local state
file used for safe resume and reconciliation.

## UNINSTALL / CLEANUP

After the engagement and any required evidence reconciliation are complete, remove
the downloaded `runner.py`, its configuration, and the relevant
`.velvt/assurance/ENGAGEMENT_ID` directory. Removing local files does not delete the
server-side engagement or its retained evidence; contact Velvt for applicable
retention or deletion requests.

## RELEASE CHECKSUM

The download endpoint and metadata endpoint publish the version-pinned runner's
SHA-256. The runner also hashes its own source and refuses to continue if it does
not match the separately fetched release metadata. Verify the checksum before use;
a mismatch is a stop condition.

Download the version-pinned runner and verify its release metadata:

```sh
curl -fsS 'https://www.velvt.ai/api/assurance/runner?version=0.4.6' -o runner.py
curl -fsS 'https://www.velvt.ai/api/assurance/runner/metadata'
```

The metadata declares the runner version, protocol version, exact SHA-256 and deployed source commit.

`config.example.json` is a schema/reference example only. It cannot start an
assessment and must not be treated as customer-ready configuration. Obtain the real
`velvt-assurance.json`—including the engagement, one-time invitation, exact subject
configuration and boundary approval—from the private Assurance setup flow.
The guided runner asks once for the agent's reusable `vlt_...` credential using
masked input, so it does not appear in the downloaded config or shell history. The
one-time Assurance invitation is already carried by the generated configuration and
is consumed only during admission. Automated integrations may provide the agent
credential through an environment variable:

```sh
export VELVT_AGENT_CREDENTIAL='the agent credential returned once by Velvt'
python3 runner.py doctor --config config.json
python3 runner.py inspect --config config.json
```

Run the read-only doctor first. Continue only when it returns `READY`, then start the guided assessment:

```sh
python3 runner.py doctor --config config.json
python3 runner.py guided --config config.json
```

Run this command in the human operator's local terminal, inside the environment
where the agent or agent runtime is available. Do not send the command to the agent
in chat, and do not paste it into Velvt.

`doctor` performs the read-only preflight and non-evidentiary Subject Readiness Check.
`guided` repeats those fail-closed checks, admits the subject, displays the tested
representation, collection boundary, effect policy and budget, requires one exact
human approval, and then runs the assessment.
The lower-level commands remain available for integration engineers and protocol
debugging; customers should not need to operate them individually.

For hand-authored integrations only, `VELVT_ASSURANCE_INVITATION` may override a
missing config invitation. Generated client configurations do not require this.

The default connection is outbound-only from the customer runner to Velvt. Velvt does
not require a public endpoint, enter the customer's private network, or receive the
customer's model key. The configured subject endpoint is called locally by the runner.

`doctor` is the complete, read-only preflight. It checks configuration safety, the
existing private identity, runner/protocol acceptance, the control plane, adapter
reachability, schema readiness, tested-representation fingerprint, local effect policy,
and enforceable budgets. It does not admit the agent, consume the invitation, request a
stimulus, invoke the assessed subject/model, create evidence, or start the run clock.

`readiness` is a backward-compatible alias for `doctor`. For HTTP and CLI adapters, the
adapter must handle the protocol-level `preflight` operation without invoking the
assessed subject or model. A private assessment identity may be provisioned first, but
it is not admitted to the engagement and no run exists until this preflight succeeds.

Read the complete manifest printed by `inspect`. If and only if you approve it,
copy its digest into the run command:

```sh
python3 runner.py run --config config.json --approve-digest THE_EXACT_DIGEST
```

Use `resume` with the same digest after a network or model interruption. Stable
idempotency keys prevent duplicate evidence events.

If the runner process exits after a stimulus is issued, or reports a local adapter
or parsing failure, reconcile before doing anything else:

```sh
python3 runner.py recover --config velvt-assurance.json
```

Recovery checks the canonical server timeline. If the response was already accepted,
it directs you to resume and does not replay it. If no response was accepted, it
safely terminates the orphaned run and directs you to create a clean retry. Never
delete the local state file. Malformed adapter output is execution diagnostics, not
behavioral evidence or an Assurance finding.

`runId` is server-issued protocol state. Do not add it to customer configuration.
The runner stores it under `.velvt/assurance/ENGAGEMENT_ID/state.json` after admission
and resumes that engagement automatically.

If that run is `TERMINATED`, preserve it and create a clean retry with the exact
same scenario and tested representation:

```sh
python3 runner.py retry --config config.json
python3 runner.py inspect --config config.json
```

The local state file is updated to the replacement run automatically.

## Time boundary and recovery

The run clock is a server-authoritative wall clock. Admission, manifest inspection,
and manifest approval do not consume run time. The clock starts when Velvt persists
the first scenario stimulus. From that point, local model inference, runner work,
network transit, and pauses between turns count toward `maxDurationSeconds`.

Every run inspection and stimulus response exposes `startedAt`, `deadlineAt`,
`serverTime`, and `remainingSeconds`. The runner will not invoke the subject when
less than `minimumResponseWindowSeconds` remains (120 seconds by default). This
prevents a known-expired run from wasting a local model call; it does not extend or
silently weaken the approved scenario boundary.

If `TIME_LIMIT` is reached, the old run and its evidence remain immutable. Create a
fresh, lineage-linked run without registering the agent or consuming another
invitation:

```sh
python3 runner.py retry --config config.json
python3 runner.py inspect --config config.json
python3 runner.py run --config config.json --approve-digest NEW_DIGEST
```

A response rejected at the time boundary was not persisted. Retain it locally for
debugging, but generate a fresh response in the clean retry unless the new event
explicitly cites the expired attempt and equivalent stimulus.

Supported subject adapters:

- `gemini`, `openai`, `anthropic`: native local provider connectors. Credentials
  are read from the customer environment and never sent to Velvt.
- `openai-compatible`: native local connector for a concrete compatible endpoint;
  remote endpoints require HTTPS.
- `ollama`: invokes a local Ollama `/api/chat` endpoint directly.
- `http`: calls one authenticated HTTPS endpoint using the portable subject contract.
  Set `SUBJECT_BEARER_TOKEN` if that endpoint requires it.
- `cli`: starts one configured local command without a shell. Raw mode sends the bounded
  stimulus as JSON on stdin and accepts ordinary stdout; the legacy protocol mode keeps
  the operation-envelope contract. Diagnostic output belongs on stderr.

HTTP and CLI subjects implement the same four operations: `identify`, `preflight`,
`receive`, and `close`. `preflight` must return `ready: true`, `subjectInvoked: false`,
and `modelCalls: 0`; it verifies the adapter boundary and must not invoke the assessed
agent. `receive` may invoke the agent only after admission and explicit manifest approval.

HTTP request envelope:

```json
{"protocol":"velvt-assurance-runner/0.4","operation":"receive","payload":{"type":"VELVT_ASSURANCE_STIMULUS"}}
```

CLI configuration:

```json
{"subject":{"adapter":"cli","command":["python3","run_agent.py"],"timeoutSeconds":120}}
```

The runner retains raw subject output, adapter interpretation, and normalized event as
separate records. Each submitted response also carries a locally generated, hash-linked
evidence envelope. Velvt verifies its run, sequence, tested-representation, scenario,
stimulus, raw-response, normalized-event, and predecessor digests. This provides local
tamper evidence; it is not yet a hardware-backed or third-party signature.

Real external effects are denied by the Velvt scenario and stopped by the runner
unless the operator explicitly enables action-attempt evidence. Enabling that
records an attempt; it still does not authorize or execute an external effect.

All customer runtime discovery, probing, provider calls, HTTP calls, future MCP
connections, and future A2A discovery occur in the local runner. A server-side
outbound request to a customer-supplied runtime endpoint is a release stop condition.

Provider policy refusals, content filters, rate limits, and unavailability are
operational interruptions—not subject refusals—and produce no behavioral finding by
themselves. Subject-authored refusal text remains raw material for evidence review.

Future MCP limitation: Velvt cannot independently verify that a third-party MCP
tool's declared description fully matches its actual behavior. Classification is
based on declared metadata and observed execution within the bounded test path.
Ambiguous effect-capable tools must not be invoked in a blocked-effect assessment;
mark them `MANUAL_REVIEW_REQUIRED` or `UNSUPPORTED_FOR_THIS_ASSESSMENT`.
