Skip to main content

Agent Harness Configuration Reference

The complete reference for the agent harness chart and control plane. For the guided install and an explanation of how the pieces fit together, see Background Agents on Kubernetes.

Infrastructure only

Nothing on this page configures agent behavior. Prompts, tools, skills, rules, and model selection are defined in Willow and synced. The configuration schema accepts infrastructure settings only.

Helm values

Images

ValueDefaultPurpose
images.controlPlane.repositoryquay.io/webrix/mcp-s-harnessControl-plane image
images.controlPlane.tagrequiredPin a concrete build; there is no default
images.controlPlane.pullPolicyIfNotPresent
images.runtime.repositoryquay.io/webrix/mcp-s-harness-runtimeAgent runtime image
images.runtime.tagrequiredPin a concrete build
imagePullSecrets[]For example [{ name: webrix-registry }]

Both tags are required. See Choose an image tag for why a moving tag is unsafe here.

Control plane

ValueDefaultPurpose
replicas1Control-plane replicas
resources250m / 512Mi requests, 1Gi memory limitControl-plane resources
service.typeClusterIP
service.port3000
reconcileIntervalSeconds300How often every agent is re-applied. 0 disables the loop
defaults.modelanthropic:claude-sonnet-4-6Model used only when the agent's Willow platform configuration leaves it unset

Ingress

ValueDefaultPurpose
ingress.enabledfalse
ingress.classNamenginx
ingress.hostharness.example.comPublic hostname Willow calls
ingress.tls[]Standard Kubernetes ingress TLS block
ingress.annotationsSSE-friendly defaultsKeep proxy-buffering: "off" and a long proxy-read-timeout

Agents namespace

ValueDefaultPurpose
agents.namespacemcp-s-agentsWhere per-agent workloads are created
agents.createNamespacetrueCreate it as part of the release

The control plane's Role is bound to this namespace only.

Database

ValueDefaultPurpose
database.existingSecret""Name of an existing Secret holding the connection string
database.secretKeyDATABASE_URLKey within that Secret
database.url""Inline URL, rendered into a chart-managed Secret. Evaluation only

Setting database.url also renders a copy of the Secret into the agents namespace, because agent pods mount the same connection string as their conversation checkpointer. When you bring your own Secret with existingSecret, make sure an equivalent Secret named mcp-s-harness-db exists in the agents namespace.

Schema migrations run automatically when the control plane starts, so there is no separate migration step.

Secret providers

Three secrets are used across all providers:

NameRequiredPurpose
willowWebhookTokenYesBearer token Willow sends on sync, message, and sessions calls
modelApiKeyYesModel provider key handed to each agent pod
githubTokenOnly with GitOpsRead access to the configuration repository

env (default)

Values come from a Kubernetes Secret, mounted rather than only injected so the kubelet can refresh them in place on rotation.

secrets:
provider: env
env:
existingSecret: "" # bring your own Secret
willowWebhookToken: "" # or let the chart render one (evaluation)
modelApiKey: ""
githubToken: ""

Preferred for real installations, the chart can instead render an ExternalSecret and manage no secret material itself:

secrets:
provider: env
env:
externalSecret:
secretName: my-org/mcp-s-harness-secret # required
secretStoreName: aws-secretsmanager # default
secretStoreKind: ClusterSecretStore # or SecretStore
refreshInterval: 1h # default

The backend entry is a JSON object whose keys land verbatim in the Secret: WILLOW_WEBHOOK_TOKEN, MODEL_API_KEY, and optionally GITHUB_TOKEN. The external-secrets operator and its store are cluster infrastructure and are not installed by this chart — it only references an existing store.

vault

HashiCorp Vault KV v2, authenticated with the Kubernetes auth method. References are path#key within the mount.

secrets:
provider: vault
vault:
addr: https://vault.your-domain.com
mountPath: secret
k8sRole: mcp-s-harness
k8sAuthMount: kubernetes
refs:
willowWebhookToken: mcp-s-harness/willow#webhook_token
modelApiKey: mcp-s-harness/models#anthropic_api_key
githubToken: mcp-s-harness/github#token

aws

AWS Secrets Manager via IRSA. References are a secret name, optionally with a JSON key.

secrets:
provider: aws
aws:
refs:
willowWebhookToken: mcp-s-harness/willow-webhook-token
modelApiKey: mcp-s-harness/model-api-key
githubToken: mcp-s-harness/github-token

Configuration schema

The chart renders the bootstrap layer of this document into a ConfigMap and mounts it. The Git and API layers, when enabled, override it at runtime. secretRef values are resolved by the active provider at use time; plaintext secrets are rejected.

willow:
run_url: https://run.mcp-s.com # MCP gateway base; fallback when a sync omits mcp_url
connect_url: https://willow.example.com # optional, reserved for end-user flows
webhook_auth:
secretRef: WILLOW_WEBHOOK_TOKEN

defaults:
model: anthropic:claude-sonnet-4-6
model_api_key:
secretRef: MODEL_API_KEY
runtime_image: quay.io/webrix/mcp-s-harness-runtime:<build>
resources:
cpu: "500m"
memory: "1Gi"

git_sync:
enabled: false
repo: my-org/harness-config
branch: main
path: harness.yaml
auth:
secretRef: GITHUB_TOKEN
poll_interval_seconds: 300

config_api: read_write

defaults.resources applies to agent pods. Control-plane resources are set with the resources Helm value instead.

There is no agents: section, and there will not be one. Per-agent behavior lives in Willow.

Webhook auth is what enables authentication

The control plane enforces the bearer token only when willow.webhook_auth resolves to a value. If it is unset, the webhook and sessions endpoints accept unauthenticated calls — intended for deployments that authenticate at the mesh level with mTLS. The chart always configures it.

GitOps configuration

Set gitSync.enabled and point the harness at a repository to keep infrastructure configuration versioned and reviewable:

gitSync:
enabled: true
repo: my-org/harness-config
branch: main
path: harness.yaml
pollIntervalSeconds: 300

configApi: read_only

POST /config/git-sync/trigger doubles as a GitHub push-webhook target, so changes apply immediately; polling is the fallback. Each pull is validated against the schema and stored as a revision. An invalid document is rejected and recorded with its error rather than applied — inspect the outcome with GET /config/revisions.

configApi: read_only makes PUT /config return 403, which is what you want in a pure-GitOps setup: the repository is the only way in.

Control-plane API

All endpoints are on the harness host. Willow webhooks and the agents/sessions API authenticate with the willowWebhookToken; the config API uses a separate admin token when ADMIN_TOKEN_SECRET_REF is set, and otherwise falls back to the same token.

Authorization: Bearer <token>

Willow webhooks

EndpointPurpose
POST /willow/syncagent.sync deploys or updates an agent and returns {external_agent_id, status}. agent.delete tears it down and returns 204
POST /willow/messageDelivers one conversation turn; returns {session_id, session_key}

These are the two URLs you register on the Willow custom agent type.

Agents and sessions

EndpointPurpose
GET /agentsInventory of deployed agents with status
DELETE /agents/:agentIdTear down an agent's workload
GET /agents/:agentId/sessionsList sessions. limit (max 200), offset, order
POST /agents/:agentId/sessionsCreate a session; an optional message starts a run immediately
POST /agents/:agentId/sessions/:sessionId/messagesSend a message to an existing session
GET /agents/:agentId/sessions/:sessionId/eventsThe event log. after_seq, limit (max 1000)
GET /agents/:agentId/sessions/:sessionId/streamLive SSE stream; replays history first, then follows

:agentId accepts either the harness id or the Willow agent id, so you can address an agent by whichever you have.

Example — read a conversation as it happens:

curl -N https://harness.your-domain.com/agents/<agentId>/sessions/<sessionId>/stream \
-H "Authorization: Bearer <token>" \
-H "Accept: text/event-stream"
event: user.message
data: {"id":"…","type":"user.message","content":[{"type":"text","text":"Summarize today's PRs."}]}

event: agent.tool_use
data: {"id":"…","type":"agent.tool_use","name":"github__list_pull_requests","input":{"state":"merged"}}

event: agent.tool_result
data: {"id":"…","type":"agent.tool_result","tool_use_id":"…","is_error":false,"content":[…]}

event: agent.message
data: {"id":"…","type":"agent.message","content":[{"type":"text","text":"Seven PRs merged today…"}]}

The stream sends a : keep-alive comment every 15 seconds. Pass after_seq to resume without replaying events you already have.

Configuration

EndpointPurpose
GET /config/effectiveThe merged configuration actually in use
GET /configThe API-override layer only
PUT /configReplace the override layer. 403 when config_api is read_only, 400 when the document is invalid
GET /config/revisionsRevision history with source, version, and validation status
POST /config/git-sync/triggerPull from Git now; also usable as a GitHub webhook target

Operations

EndpointPurpose
GET /healthzLiveness. 200 once the process is up
GET /readyzReadiness. 503 database unavailable when Postgres is unreachable
GET /metricsPrometheus metrics

Status values

GET /agents reports one of:

StatusMeaning
deployingA sync is in flight
readyWorkload applied and the agent is active in Willow
disabledThe agent is toggled off in Willow; its Deployment is scaled to zero
errorThe last deploy failed; the reason is in error

Sessions are idle or running.