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/pipelinesstep_library_dir: /data/steps # reusable step definitions; omit to disable librarysecurity_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 "".
server
Section titled “server”| 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
Section titled “writes_enabled”writes_enabled: false # default: trueWhen 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.
database
Section titled “database”url— a SQLAlchemy async database URL.- SQLite default:
sqlite+aiosqlite:////data/db/runs.db(four slashes = an absolute path inside the container’s/datavolume) — 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.
- SQLite default:
auto_migrate— run pending Alembic migrations automatically on boot. Defaulttrue. Setfalseto hand migrations to a DBA — startup then fails fast, naming the pending revisions, instead of applying them. See Deployment for the full adoption mechanism.
notifications
Section titled “notifications”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.
human_approval
Section titled “human_approval”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, notlocalhost, unless every approver is on this same machine. If unset, falls back tohttp://localhost:<port>with a startup warning.default—{channel, <channel>?}used when a run’steamhas no entry underteamsbelow.channelistelegram|slack|msteams.teams.<name>— same{channel, <channel>?}shape asdefault, keyed by team name (resolved from the auth token that triggered the run — see Team attribution).- Per-channel credentials, nested under either
defaultor ateams.<name>entry:telegram: {bot_token, chat_id}— optional; omit to reusenotifications.telegram.slack: {bot_token, app_token, channel_id}— required wheneverchannel: slack.bot_tokenneeds thechat:writescope;app_tokenis an app-level token used for Socket Mode (resolving Approve/Reject button clicks — one listener is started per distinctapp_tokenreferenced anywhere in this block).msteams: {webhook_url}— required wheneverchannel: 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.
executors
Section titled “executors”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.
logging
Section titled “logging”level— log level, e.g.INFO.dir— directory for log files. Omit to disable file logging (stdout only). When set, createsservice.logandaccess.log(rotating, 10 MB × 5 —uvicorn.accessnoise is kept out ofservice.log).
artifacts
Section titled “artifacts”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 setfalse) to disable dedup entirely. Default:true.window_seconds— dedup window in seconds; overridable per-pipeline viatrigger.dedup. Default:300.
See Webhooks for idempotency and deduplication semantics.
approvals
Section titled “approvals”bulk_max— maximum number of approval tokens one bulk approve/reject request (POST /approvals/bulkor the/ui/approvalsbulk form) may carry. Larger requests return422and change nothing. Default:200.
feedback
Section titled “feedback”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.
concurrency
Section titled “concurrency”max_runs— maximum simultaneous pipeline executions. Default:10.POST /webhookreturns 429 when at capacity;GET /healthexposesactive_runs/max_concurrent_runs.
durability
Section titled “durability”max_resume_age_seconds— service-wide default for a durable pipeline’s owndurable.max_resume_age_seconds, used only when the pipeline YAML doesn’t set its own value. Default:3600. A run found stillrunningafter a crash/restart, down longer than this, is markedinterruptedinstead of resumed. Has no effect at all on a pipeline that isn’t opted intodurable: 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.roleis one ofadmin,operator,viewer,webhook.teamis valid only on awebhook-role token and resolves theteamattribution on every run it authenticates — see Team attribution.allow_unauthenticated—true/false. Default:false. VectorStep refuses to start with notokensconfigured unless this is set explicitly. Only appropriate on a trusted local machine.metrics_auth—true/false. Default:false.GET /metricsis unauthenticated by default; set this to requireviewer.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/tokenstill work for backward compatibility (with a startup warning iftokensisn’t set) — usetokensinstead for anything new. If both are present,tokenswins.
security
Section titled “security”allow_shell_checks—true/false. Default:false. Whenfalse(the default), a pipeline or step-library YAML containing atype: shelldeterministic check is rejected at write time (400 onPOST/PUT /pipelines,/steps, and their/validateendpoints); ashellcheck 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 totruerestores the pre-containment behaviour exactly — ashellcheck runsrun: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 atype: webhookcheck (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’sexpect/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 forexecutor: shellpipeline steps (see Executors —shell). A separate, independently-enableable feature fromallow_shell_checksabove — 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 everyallowed[].pathmust 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}.pathis relative toscripts_dir.sha256is 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 loggedERRORat 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: shellstep is rejected at write time (400 onPOST/PUT /pipelines,/steps, and their/validateendpoints) ifshell_steps.enabledisfalse, or if the step’sscriptisn’t a name present inshell_steps.allowed— both cases name the step and explain that an operator, not the pipeline author, has to fix it viaconfig.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.
security_policies
Section titled “security_policies”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 stagerequired.<stage>— list of policy names a pipeline at thatstagemust include in its ownsecurity_policies:list, or its write (POST/PUT /pipelines) is rejected. Anadmintoken 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.
observability
Section titled “observability”otel.enabled—true/false. Omit this block (or setfalse) 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.
calibration
Section titled “calibration”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.
pricing
Section titled “pricing”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 isopenrouter, 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 oflive_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.
replay
Section titled “replay”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 insafe_agentsabove, 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[].modeis alwaysrerender— regression replays never userenderedmode (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.