Skip to content
Beta. VectorStep is in beta.Let us know if something breaks.

Service configuration reference

This page is the field-by-field reference for the VectorStep service’s config.yaml — every top-level key and sub-key, what it does, its valid values, and its default. For the operational how-to (where the file lives, what you need to set to get a working deployment running end to end), see Deployment.

The installer writes ~/.vectorstep/config/vectorstep.yaml, bind-mounted read-only into the container at /etc/vectorstep/config.yaml. pipeline_config_dir, step_library_dir, artifacts.dir, logging.dir, and the SQLite path all resolve inside the container’s /data volume, not a path relative to the process’s own working directory:

server:
host: 0.0.0.0
port: 8000
pipeline_config_dir: /data/pipelines
step_library_dir: /data/steps # reusable step definitions; omit to disable library
security_policies_dir: /data/security_policies # optional — see the security_policies section below
database:
url: sqlite+aiosqlite:////data/db/runs.db # four slashes = absolute path
# url: postgresql+asyncpg://vectorstep:vectorstep@postgres:5432/vectorstep # production
notifications:
telegram:
bot_token: ${TELEGRAM_BOT_TOKEN}
chat_id: ${TELEGRAM_CHAT_ID}
executors:
openclaw:
url: ws://127.0.0.1:18789/rpc # OpenClaw Gateway WebSocket URL
gateway:
url: ws://gateway:18780/rpc # VectorStep Gateway WebSocket URL — the compose service name
token: ${VECTORSTEP_GATEWAY_TOKEN} # Bearer token; empty string for local dev
rest_url: http://gateway:18780 # VectorStep Gateway REST base URL (used by Agents UI)
logging:
level: INFO
dir: /data/logs # omit to disable file logging (stdout only)
# creates service.log and access.log (rotating, 10 MB × 5)
artifacts:
dir: /data/artifacts # omit this block entirely to disable artifact storage
retention_days: 7 # artifact directories older than this are removed daily at 02:00
dedup:
enabled: true # omit this block (or set false) to disable dedup entirely
window_seconds: 300 # overridable per-pipeline via trigger.dedup
approvals:
bulk_max: 200 # max pending approvals one bulk approve/reject may carry
feedback:
bulk_max: 500 # max runs/steps one bulk accuracy-feedback call may carry
concurrency:
max_runs: 10 # maximum simultaneous pipeline executions (default: 10).
# POST /webhook returns 429 when at capacity.
# GET /health exposes active_runs / max_concurrent_runs.
auth:
tokens: # named tokens, each carrying a role — see /docs/operations/security/
- name: platform-admin
token: ${VS_TOKEN_PLATFORM_ADMIN}
role: admin
- name: payments-webhook
token: ${VS_TOKEN_PAYMENTS}
role: webhook # POST /webhook only
team: payments # webhook-role only — resolves `team` attribution on every
# run it authenticates. Alertmanager sends its token via
# http_config.authorization.credentials — route different
# teams' alerts to different receivers with different tokens.
# allow_unauthenticated: true # refuses to start with no tokens configured unless this is
# set — only appropriate on a trusted local machine.
security:
allow_shell_checks: false # default: false — a pipeline author gets a shell on this
# host if true (type: shell deterministic checks run
# unsandboxed). See the section below before enabling.
template_sandbox: true # default: true — Jinja2 rendering of pipeline-authored
# content is sandboxed against arbitrary code execution.
shell_steps: # executor: shell steps — see the section below. Separate
enabled: false # feature, separate flag from allow_shell_checks above.
scripts_dir: ./scripts # every allowed[].path must resolve inside this directory
allowed: []
# allowed:
# - name: restart-service # referenced from a step's executor_config.script
# path: restart-service.sh # relative to scripts_dir
# sha256: 3f2a1c9e... # required — compute with: sha256sum restart-service.sh
observability:
otel:
enabled: false # omit this block (or set false) to disable tracing entirely
exporter: otlp # otlp | console
endpoint: http://localhost:4318/v1/traces
service_name: vectorstep-service
calibration: # omit this block entirely for the defaults shown below
n_min: 20 # marked outcomes required before a bucket is "validated"
bin_width: 0.1 # must evenly divide 1.0
cache_ttl_seconds: 300 # how long the in-process bucket cache is reused before refetching
pricing: # omit this whole block to run fully unpriced
currency: USD # display label only — no FX conversion anywhere
models:
- match: {provider: anthropic, model: "claude-sonnet-4-6"}
input_per_mtok: 3.00 # currency units per 1,000,000 input tokens
output_per_mtok: 15.00
team_budgets:
payments: 500 # currency units per calendar month, UTC — advisory only
live_pricing:
enabled: false # optional — persisted, real cost from OpenRouter's public catalog
refresh_interval_seconds: 3600
allow_cross_provider: false # opt-in — see the pricing section below

${ENV_VAR} placeholders are resolved at startup. Unresolved placeholders become "".

Key Values Default
host bind address 0.0.0.0
port bind port 8000

pipeline_config_dir / step_library_dir / security_policies_dir

Section titled “pipeline_config_dir / step_library_dir / security_policies_dir”
  • pipeline_config_dir — directory pipeline YAMLs are loaded from.
  • step_library_dir — directory reusable step definitions are loaded from. Omit to disable the step library. See Steps.
  • security_policies_dir — directory named security-policy YAMLs are loaded from. Default: ./security_policies. A missing directory is not an error — it just means no policies are loaded. See security_policies below and Security policies.

On Kubernetes, point security_policies_dir at the volume (/data/security_policies) so policies can be changed without a restart — the shipped ConfigMap does. See Changing config on Kubernetes.

writes_enabled: false # default: true

When false, every pipeline and step write endpoint (POST/PUT/DELETE on /pipelines and /steps) refuses with 403 and {"type": "writes_disabled"}. Use it when pipelines and steps are managed declaratively — mounted read-only from ConfigMaps — so the write API says so clearly rather than failing against a read-only filesystem. /pipelines/validate, /steps/validate and POST /reload keep working, and the check runs after the admin-role check, so an unauthenticated caller can’t use it to probe the setting. Re-read on POST /reload. See Changing config on Kubernetes.

A pipeline’s own name:, a library step’s name:, and a security policy’s name: all become filenames under their respective directories, so all three are restricted to [a-zA-Z0-9][a-zA-Z0-9_-]{0,63} — see Pipeline schema.

  • url — a SQLAlchemy async database URL.
    • SQLite default: sqlite+aiosqlite:////data/db/runs.db (four slashes = an absolute path inside the container’s /data volume) — zero infrastructure.
    • Production: postgresql+asyncpg://user:password@postgres:5432/vectorstep — same code path, dialect swap via config only. See Deployment for the operational detail on making this switch.
  • auto_migrate — run pending Alembic migrations automatically on boot. Default true. Set false to hand migrations to a DBA — startup then fails fast, naming the pending revisions, instead of applying them. See Deployment for the full adoption mechanism.

Per-channel notification config. The example above shows telegram (bot_token, chat_id); see Pipeline notification channels for the full set of supported channels and how pipelines and steps route to them.

Per-team channel routing for executor: human steps — independent of notifications.telegram above, since a team may want Slack- or Teams-only approvals with no Telegram bot at all. Omit this whole block to keep every human step falling back to the single notifications.telegram bot (telegram only — slack/msteams have no equivalent implicit fallback; an entry using either must supply its own nested credentials block).

  • ui_base_url — base URL VectorStep’s own UI is reachable at, used to build the /ui/approvals/{token} link sent to a Teams approval message. Must be reachable by whoever clicks the link, not localhost, unless every approver is on this same machine. If unset, falls back to http://localhost:<port> with a startup warning.
  • default — {channel, <channel>?} used when a run’s team has no entry under teams below. channel is telegram | slack | msteams.
  • teams.<name> — same {channel, <channel>?} shape as default, keyed by team name (resolved from the auth token that triggered the run — see Team attribution).
  • Per-channel credentials, nested under either default or a teams.<name> entry:
    • telegram: {bot_token, chat_id} — optional; omit to reuse notifications.telegram.
    • slack: {bot_token, app_token, channel_id} — required whenever channel: slack. bot_token needs the chat:write scope; app_token is an app-level token used for Socket Mode (resolving Approve/Reject button clicks — one listener is started per distinct app_token referenced anywhere in this block).
    • msteams: {webhook_url} — required whenever channel: msteams. A Power Automate “Post to a channel when a webhook request is received” flow URL. Posts a one-way notification linking to /ui/approvals/{token} — a real interactive Teams card needs a public Bot Framework callback endpoint this deployment doesn’t expose.
  • openclaw.url — OpenClaw Gateway WebSocket URL.
  • openclaw.identity_dir — directory holding OpenClaw’s Ed25519 device identity (device.json/device-auth.json). Default: ~/.openclaw/identity. Only needed if OpenClaw runs on a different machine — copy its identity files here and point this at them.
  • gateway.url — VectorStep Gateway WebSocket URL.
  • gateway.token — Bearer token for the Gateway (${VECTORSTEP_GATEWAY_TOKEN}); empty string is fine for local dev.
  • gateway.rest_url — VectorStep Gateway REST base URL, used by the Agents UI.

See Executors for the adapter pattern these keys configure.

  • level — log level, e.g. INFO.
  • dir — directory for log files. Omit to disable file logging (stdout only). When set, creates service.log and access.log (rotating, 10 MB × 5 — uvicorn.access noise is kept out of service.log).
  • dir — directory step artifacts are written to. Omit this block entirely to disable artifact storage.
  • retention_days — artifact directories older than this are removed daily at 02:00. Default: 7.

See Artifact storage for the full artifact model.

  • enabled — true/false. Omit this block (or set false) to disable dedup entirely. Default: true.
  • window_seconds — dedup window in seconds; overridable per-pipeline via trigger.dedup. Default: 300.

See Webhooks for idempotency and deduplication semantics.

  • bulk_max — maximum number of approval tokens one bulk approve/reject request (POST /approvals/bulk or the /ui/approvals bulk form) may carry. Larger requests return 422 and change nothing. Default: 200.
  • bulk_max — maximum number of runs (or step executions) one bulk accuracy-feedback call (POST /feedback/runs/bulk, POST /feedback/steps/bulk) may carry. Default: 500.
  • max_runs — maximum simultaneous pipeline executions. Default: 10. POST /webhook returns 429 when at capacity; GET /health exposes active_runs/max_concurrent_runs.
  • max_resume_age_seconds — service-wide default for a durable pipeline’s own durable.max_resume_age_seconds, used only when the pipeline YAML doesn’t set its own value. Default: 3600. A run found still running after a crash/restart, down longer than this, is marked interrupted instead of resumed. Has no effect at all on a pipeline that isn’t opted into durable: true — this is a fallback, not a global switch. See Durability & resume.

Full model, route requirements, UI login, and the audit log: Security.

  • tokens — list of {name, token, role, team?} entries. role is one of admin, operator, viewer, webhook. team is valid only on a webhook-role token and resolves the team attribution on every run it authenticates — see Team attribution.
  • allow_unauthenticated — true/false. Default: false. VectorStep refuses to start with no tokens configured unless this is set explicitly. Only appropriate on a trusted local machine.
  • metrics_auth — true/false. Default: false. GET /metrics is unauthenticated by default; set this to require viewer.
  • session_secret — HMAC key for signing UI session cookies. If unset, a random one is generated at startup and every session is invalidated on restart.
  • session_max_age_seconds — UI session lifetime. Default: 28800 (8h).
  • session_max_age_remembered_seconds — UI session lifetime when “Remember me” is checked at login. Default: 2592000 (30d). See Logging in.
  • teams/token still work for backward compatibility (with a startup warning if tokens isn’t set) — use tokens instead for anything new. If both are present, tokens wins.
  • allow_shell_checks — true/false. Default: false. When false (the default), a pipeline or step-library YAML containing a type: shell deterministic check is rejected at write time (400 on POST/PUT /pipelines, /steps, and their /validate endpoints); a shell check already on disk (e.g. loaded from a file placed directly on the host) logs a startup warning instead of failing to boot, and aborts the run the first time that check would actually execute. Setting this to true restores the pre-containment behaviour exactly — a shell check runs run: through the real shell, inheriting the service process’s full environment and permissions, with no sandboxing. Only enable it if you fully trust everyone who can write pipeline config; prefer a type: webhook check (see Deterministic checks) wherever the thing you’re checking has an HTTP API.
  • template_sandbox — true/false. Default: true. Controls whether Jinja2 rendering of pipeline-authored content (prompt_template, when:, every deterministic check’s expect/url/payload/message, notification templates) runs inside a sandbox that blocks attribute-traversal tricks (e.g. {{ ''.__class__.__mro__[1].__subclasses__() }}) which would otherwise execute arbitrary Python during rendering. Disabling it removes that protection for every template render site in the service — only do so if you understand that this allows arbitrary code execution from pipeline config.
  • shell_steps — config for executor: shell pipeline steps (see Executors — shell). A separate, independently-enableable feature from allow_shell_checks above — a check gates one step’s pass/fail, a step’s output is consumed by everything after it, so it gets a different config key and a tighter trust model (no free-text command field exists on this executor at all).
    • shell_steps.enabled — true/false. Default: false.
    • shell_steps.scripts_dir — directory every allowed[].path must resolve inside (rejected at startup, not sanitized, if an entry resolves outside it — including via .. or a symlink escape). Default: ./scripts.
    • shell_steps.allowed — list of {name, path, sha256}. path is relative to scripts_dir. sha256 is required — there is no unpinned option — and is re-verified before every single execution, not just at startup, so a script swapped on disk after boot (a compromised volume mount, a less-trusted bind-mount source) is caught rather than silently run. A malformed or unresolvable entry is dropped with a logged ERROR at startup, naming the entry and why — it never prevents the service from booting over one bad allowlist entry. The list is read once at startup and is not refreshed by /reload, so a changed script or pin needs a restart. On Kubernetes, see Shell-step scripts.
    • Writing a pipeline/step-library YAML with an executor: shell step is rejected at write time (400 on POST/PUT /pipelines, /steps, and their /validate endpoints) if shell_steps.enabled is false, or if the step’s script isn’t a name present in shell_steps.allowed — both cases name the step and explain that an operator, not the pipeline author, has to fix it via config.yaml. This can never be resolved by retrying with different pipeline YAML: adding a script to the allowlist is a filesystem-level change, not reachable through any pipeline-write endpoint.

Sibling to security_policies_dir above (which points at the directory policy files live in) — this block is config.yaml-only settings about how declared policies are enforced. Omit entirely if no pipeline is required to declare any policy.

security_policies:
required:
production: [pci-dss-lite] # every stage: production pipeline must declare this
# testing: [] # omit or empty — no requirement at that stage
  • required.<stage> — list of policy names a pipeline at that stage must include in its own security_policies: list, or its write (POST/PUT /pipelines) is rejected. An admin token cannot weaken or remove this requirement through the pipeline-write API — see Threat model for why this is a genuine, deliberate exception to that token’s otherwise-total write authority.

See Security policies for the full feature: policy file schema, write-time vs. runtime enforcement, and the read-only GET /security-policies* endpoints.

  • otel.enabled — true/false. Omit this block (or set false) to disable tracing entirely. Default: false.
  • otel.exporter — otlp | console.
  • otel.endpoint — OTLP endpoint, e.g. http://localhost:4318/v1/traces.
  • otel.service_name — service name reported to the tracing backend.

See Observability for the full metrics/tracing reference.

Omit this block entirely for the defaults shown below.

  • n_min — marked outcomes required before a bucket is “validated”. Default: 20.
  • bin_width — width of each confidence bucket; must evenly divide 1.0. Default: 0.1.
  • cache_ttl_seconds — how long the in-process calibration bucket cache is reused before refetching. Default: 300.

See Calibration for what these buckets measure and how they’re used.

  • currency — display label only, no FX conversion. Default: USD.
  • models — the rate table: a list of {match: {provider, model}, input_per_mtok, output_per_mtok} entries, resolved by longest-prefix match on the step’s model string, scoped by provider.
  • team_budgets — {team: amount} map, currency units per calendar month (UTC). Advisory only, never blocks a run.
  • live_pricing.enabled — optional, opts a component with no manual match into OpenRouter’s public catalog instead. When the component’s own provider genuinely is openrouter, this is a real, persisted rate for the exact model called — not an approximation. Off by default.
  • live_pricing.allow_cross_provider — a second, separate opt-in: lets a component that ran against a different provider be priced off a similar-sounding OpenRouter listing anyway. This is a genuine estimate (different vendor, possibly different contract terms), which is why it needs its own flag on top of live_pricing.enabled. Off by default.
  • live_pricing.refresh_interval_seconds — how often the OpenRouter catalog is re-fetched. Default: 3600.

Omit this whole block to run fully unpriced (every step’s cost stays NULL, every money surface shows “unpriced”). See Cost accounting for the full pricing model, budget guardrails, and live/approximate pricing semantics.

Unset or empty safe_agents means replay is off entirely — POST /steps/{name}/replay (and every other replay entry point) 403s pointing back at this key. A replay batch re-runs real agent calls against live tools, so only list an agent here once you’ve verified it makes no side-effecting tool calls.

  • safe_agents — explicit allowlist of "executor:agent" identities (e.g. "gateway:sre-investigation") a replay batch is allowed to run. Both the recorded step’s agent and the candidate’s agent must appear here, or the request is rejected.
  • judge — optional LLM auto-judge, grading a candidate’s output against a recorded or synthetic reference whenever human marks and deterministic checks alone don’t produce a label. Unset means no auto-judge; replay still runs, just gradable only via human marks/deterministic checks.
    • judge.agent — "executor:agent" key; must also appear in safe_agents above, since the judge fires a real agent call in the same batch. Default: replay-judge.
    • judge.executor — default: gateway.
    • judge.executor_config — passed through to that executor, e.g. {model: ...}.
    • judge.timeout_seconds — default: 120.
    • judge.max_output_chars — per-side (reference/candidate) truncation cap before judging. Default: 4000.
  • regression — list of scheduled golden-set regression jobs; empty means none configured. Each entry:
    • regression[].golden_set — must already exist (create it under Golden sets… in the UI, or author synthetic members via the REST/MCP API). An unknown name logs a warning at startup/reload and is skipped, not a hard failure.
    • regression[].cron — standard 5-field cron, evaluated in the server’s local time.
    • regression[].alert_on_regressed — alert once at least this many members regress versus their pinned label. Default: 1.
    • regression[].alert_on_accuracy_drop — or once combined accuracy drops by this much — either threshold firing an alert. Default: 0.10.
    • regression[].notify — {channel, template, config}, the same shape every pipeline notification uses (see Pipeline notification channels).
    • regression[].mode is always rerender — regression replays never use rendered mode (resending the recorded prompt verbatim would make a prompt-caused regression invisible by construction); setting anything else is rejected at config-load time, not silently ignored.

See Replay / shadow evaluation for the full workflow, including scheduled regression and the LLM auto-judge.