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.
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
| Value | Default | Purpose |
|---|---|---|
images.controlPlane.repository | quay.io/webrix/mcp-s-harness | Control-plane image |
images.controlPlane.tag | — required | Pin a concrete build; there is no default |
images.controlPlane.pullPolicy | IfNotPresent | |
images.runtime.repository | quay.io/webrix/mcp-s-harness-runtime | Agent runtime image |
images.runtime.tag | — required | Pin 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
| Value | Default | Purpose |
|---|---|---|
replicas | 1 | Control-plane replicas |
resources | 250m / 512Mi requests, 1Gi memory limit | Control-plane resources |
service.type | ClusterIP | |
service.port | 3000 | |
reconcileIntervalSeconds | 300 | How often every agent is re-applied. 0 disables the loop |
defaults.model | anthropic:claude-sonnet-4-6 | Model used only when the agent's Willow platform configuration leaves it unset |
Willow connection
| Value | Default | Purpose |
|---|---|---|
willow.mode | push | push: Willow calls the harness. pull: the harness collects its work outbound and needs no ingress |
willow.runUrl | "" | MCP gateway base; fallback when a sync snapshot omits mcp_url |
willow.connectUrl | "" | Willow connect portal; reserved for future end-user flows |
willow.dbServiceUrl | "" | Pull only, required: https://<org>.withwillow.ai/api/on-prem-db-service |
willow.pollIntervalSeconds | 5 | Pull only: delay between triggering an agent in Willow and its run starting |
Ingress
Required under push delivery; leave disabled under pull, where nothing calls in.
| Value | Default | Purpose |
|---|---|---|
ingress.enabled | false | |
ingress.className | nginx | |
ingress.host | harness.example.com | Public hostname Willow calls |
ingress.tls | [] | Standard Kubernetes ingress TLS block |
ingress.annotations | SSE-friendly defaults | Keep proxy-buffering: "off" and a long proxy-read-timeout |
Agents namespace
| Value | Default | Purpose |
|---|---|---|
agents.namespace | mcp-s-agents | Where per-agent workloads are created |
agents.createNamespace | true | Create it as part of the release |
The control plane's Role is bound to this namespace only.
Database
| Value | Default | Purpose |
|---|---|---|
database.existingSecret | "" | Name of an existing Secret holding the connection string |
database.secretKey | DATABASE_URL | Key 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
The same secrets are used across all providers:
| Name | Required | Purpose |
|---|---|---|
willowWebhookToken | Yes | Bearer token Willow sends on sync, message, and sessions calls. Under pull delivery it identifies the harness instead |
modelApiKey | Yes | Model provider key handed to each agent pod |
willowGatewaySecret | Only with pull delivery | Your org's gateway secret; authenticates the harness to Willow when it collects work |
githubToken | Only with GitOps | Read 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: ""
willowGatewaySecret: "" # pull delivery only
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
willowGatewaySecret: mcp-s-harness/willow#gateway_secret # pull delivery only
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
willowGatewaySecret: mcp-s-harness/willow-gateway-secret # pull delivery only
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:
mode: push # or "pull" — see below
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
# Pull delivery only:
db_service_url: https://your-org.withwillow.ai/api/on-prem-db-service
gateway_auth:
secretRef: WILLOW_GATEWAY_SECRET
poll_interval_seconds: 5 # 1–300
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.
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.
willow.mode decides which direction the control channel runs. Under push (the default) Willow calls /willow/sync and /willow/message, so the harness needs an ingress reachable from Willow. Under pull nothing is sent: the control plane polls Willow every poll_interval_seconds for queued work and uploads conversation events back, which is the only mode that works when your network permits no inbound traffic from Willow. db_service_url and gateway_auth are required in that mode and ignored otherwise. See Pull delivery.
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
| Endpoint | Purpose |
|---|---|
POST /willow/sync | agent.sync deploys or updates an agent and returns {external_agent_id, status}. agent.delete tears it down and returns 204 |
POST /willow/message | Delivers one conversation turn; returns {session_id, session_key} |
These are the two URLs you register on the Willow custom agent type. They stay mounted under pull delivery, but nothing calls them: the control plane collects the same events itself and applies them through the same handlers.
Willow endpoints used under pull delivery
These are calls the control plane makes to Willow, listed so you can recognize them in a proxy log or a network policy. All of them go to willow.db_service_url with the gateway secret.
| Endpoint | Purpose |
|---|---|
GET /harness/outbox | Collect the next batch of queued events |
POST /harness/outbox/:id/ack | Report whether an event was applied; resolves a pending deploy in the dashboard |
POST /harness/agents/:agentId/session-events | Upload conversation events so the dashboard can show the transcript |
Agents and sessions
| Endpoint | Purpose |
|---|---|
GET /agents | Inventory of deployed agents with status |
DELETE /agents/:agentId | Tear down an agent's workload |
GET /agents/:agentId/sessions | List sessions. limit (max 200), offset, order |
POST /agents/:agentId/sessions | Create a session; an optional message starts a run immediately |
POST /agents/:agentId/sessions/:sessionId/messages | Send a message to an existing session |
GET /agents/:agentId/sessions/:sessionId/events | The event log. after_seq, limit (max 1000) |
GET /agents/:agentId/sessions/:sessionId/stream | Live 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
| Endpoint | Purpose |
|---|---|
GET /config/effective | The merged configuration actually in use |
GET /config | The API-override layer only |
PUT /config | Replace the override layer. 403 when config_api is read_only, 400 when the document is invalid |
GET /config/revisions | Revision history with source, version, and validation status |
POST /config/git-sync/trigger | Pull from Git now; also usable as a GitHub webhook target |
Operations
| Endpoint | Purpose |
|---|---|
GET /healthz | Liveness. 200 once the process is up |
GET /readyz | Readiness. 503 database unavailable when Postgres is unreachable |
GET /metrics | Prometheus metrics |
Status values
GET /agents reports one of:
| Status | Meaning |
|---|---|
deploying | A sync is in flight |
ready | Workload applied and the agent is active in Willow |
disabled | The agent is toggled off in Willow; its Deployment is scaled to zero |
error | The last deploy failed; the reason is in error |
Sessions are idle or running.
Related
- Background Agents on Kubernetes — architecture, installation, and troubleshooting
- Run Agents in Your Own Cluster — the dashboard side, for administrators
- Background Agent Settings — registering the platform type in Willow
- Harness API: New Sessions and Messages — the payloads Willow delivers