Enterprise early access is open. Request access

Docs Reference Helm values

About this reference#

Generated from the values schema included with the release chart. Defaults and descriptions come straight from the chart, so this page cannot describe a value the chart does not ship.

Override any key with --set or a values file. See Profiles for the curated combinations.

nameOverride#

1 keys

KeyDefaultDescription
nameOverride "" —

fullnameOverride#

1 keys

KeyDefaultDescription
fullnameOverride "" —

global#

19 keys

KeyDefaultDescription
global — —
global.imageRegistry "" —
global.imagePullPolicy IfNotPresent —
global.namespaceOverride "" —
global.tenant default —
global.provider local —
global.region local —
global.clusterName local —
global.labels {} —
global.annotations {} —
global.imagePullSecrets [] —
global.automountServiceAccountToken true —
global.podSecurityContext {} —
global.securityContext {} —
global.nodeSelector {} —
global.tolerations [] —
global.affinity {} —
global.topologySpreadConstraints [] —
global.priorityClassName "" —

rbac#

2 keys

KeyDefaultDescription
rbac — —
rbac.create true —

serviceAccount#

4 keys

KeyDefaultDescription
serviceAccount — —
serviceAccount.create true —
serviceAccount.name "" —
serviceAccount.annotations {} —

migrations#

33 keys

KeyDefaultDescription
migrations — Production schema ownership is separated from long-running services. Enable this hook only with PostgreSQL stores whose autoMigrate setting is false.
migrations.enabled false —
migrations.image — —
migrations.image.repository ghcr.io/bitfid-inc/agentfleet-db-migrate —
migrations.image.tag v0.1.0 —
migrations.timeout "2m" —
migrations.backoffLimit 1 —
migrations.ttlSecondsAfterFinished 300 —
migrations.secrets — Empty existingSecret values fall back to the component runtime Secret. Production platforms should provide separate schema-owner credentials.
migrations.secrets.registry — —
migrations.secrets.registry.existingSecret "" —
migrations.secrets.registry.dsnKey dsn —
migrations.secrets.context — —
migrations.secrets.context.existingSecret "" —
migrations.secrets.context.dsnKey dsn —
migrations.secrets.approval — —
migrations.secrets.approval.existingSecret "" —
migrations.secrets.approval.dsnKey dsn —
migrations.secrets.console — —
migrations.secrets.console.existingSecret "" —
migrations.secrets.console.dsnKey dsn —
migrations.secrets.discovery — Only used when discovery.store.enabled; empty falls back to discovery.store.existingSecret.
migrations.secrets.discovery.existingSecret "" —
migrations.secrets.discovery.dsnKey dsn —
migrations.secrets.identity — The identity binding database, used by the Gateway (entra_app), the Model Router (workloadIdentity.bindings) and the Console (registration). Required whenever one of them is enabled, and with no fallback: the services open the store without running DDL, so only a schema owner's Secret creates the tables. The roles behind their own Secrets are provisioned by the operator (the Gateway needs SELECT only); the chart does not enforce their privileges.
migrations.secrets.identity.existingSecret "" —
migrations.secrets.identity.dsnKey dsn —
migrations.secrets.credentials — The credential vault's database. The chart does not configure AgentFleet's own vault mode (#553), so naming a Secret here is how a vault's schema is migrated by the Job; empty leaves the credentials component out. Nothing falls back to a runtime Secret: the Model Router would otherwise create the tables itself. The render refuses a Secret here with modelRouter.credentials.mode=azure-key-vault.
migrations.secrets.credentials.existingSecret "" —
migrations.secrets.credentials.dsnKey dsn —
migrations.resources {} —
migrations.podLabels {} —
migrations.podAnnotations {} —

identityPrune#

14 keys

KeyDefaultDescription
identityPrune — Deletes the identity binding store's expired tombstones and release records on a schedule. The store's reads never prune, so without this a tombstone (a workspace name and an agent id kept only for the audit retention period) stays in storage past its expiry. Enable it whenever the identity binding store is in use (the Gateway's entra_app mode and console.registration use it; the latter requires this job). The job runs agentfleet-db-migrate -prune-identity from the migrations image, as the database role in existingSecret. That role needs SELECT and DELETE on identity_binding_tombstones and identity_binding_releases (a DELETE with a WHERE clause also reads the column) and nothing on identity_bindings, so the prune credential cannot remove a live binding. A tombstone stays about its retention period plus one schedule interval while the job keeps succeeding, and longer if a run starts late, a tick is skipped, or the job fails; Kubernetes starts a CronJob approximately, so there is no exact bound.
identityPrune.enabled false —
identityPrune.schedule "17 * * * *" —
identityPrune.timeout "2m" —
identityPrune.existingSecret "" Required when enabled: a Secret holding the identity database DSN.
identityPrune.dsnKey dsn —
identityPrune.concurrencyPolicy Forbid —
identityPrune.startingDeadlineSeconds 300 —
identityPrune.successfulJobsHistoryLimit 1 —
identityPrune.failedJobsHistoryLimit 3 —
identityPrune.backoffLimit 1 —
identityPrune.resources {} —
identityPrune.podLabels {} —
identityPrune.podAnnotations {} —

podDisruptionBudget#

4 keys

KeyDefaultDescription
podDisruptionBudget — —
podDisruptionBudget.enabled false —
podDisruptionBudget.minAvailable 1 —
podDisruptionBudget.maxUnavailable "" —

networkPolicy#

3 keys

KeyDefaultDescription
networkPolicy — —
networkPolicy.enabled false —
networkPolicy.additionalIngressFrom [] Additional ingress peers can be added for an external gateway or metrics collector.

operator#

32 keys

KeyDefaultDescription
operator — —
operator.enabled true —
operator.replicaCount 1 —
operator.image — —
operator.image.repository ghcr.io/bitfid-inc/agentfleet-operator —
operator.image.tag v0.1.0 —
operator.image.digest "" —
operator.service — —
operator.service.type ClusterIP —
operator.service.metricsPort 8080 —
operator.metrics — —
operator.metrics.port 8080 —
operator.probes — —
operator.probes.port 8081 —
operator.leaderElection — —
operator.leaderElection.enabled false —
operator.registry — —
operator.registry.enabled true —
operator.registry.url "" —
operator.protection — Settings for agents in protected namespaces, which a platform administrator marks with the label agentfleet.io/protection=managed. Each such agent runs under its own ServiceAccount with a Model Router token, a hardened pod, and only the Secrets listed in the namespace annotation agentfleet.io/allowed-secrets. Other namespaces are unaffected.
operator.protection.modelRouterURL "" Injected into protected agents as AGENTFLEET_MODEL_ROUTER_URL. Defaults to the in-chart Model Router when it is enabled.
operator.protection.runAsUser 65532 UID for protected agent pods. 0 relies on a numeric USER in the image.
operator.protection.enforcementProbe — Optional Helm hook that proves the cluster actually enforces an egress deny policy. Enable this wherever protected-path claims are made.
operator.protection.enforcementProbe.enabled false —
operator.protection.enforcementProbe.image — —
operator.protection.enforcementProbe.image.repository busybox —
operator.protection.enforcementProbe.image.tag "1.37.0" —
operator.protection.enforcementProbe.image.digest "" —
operator.protection.enforcementProbe.timeoutSeconds 10 —
operator.resources {} —
operator.podLabels {} —
operator.podAnnotations {} —

approval#

57 keys

KeyDefaultDescription
approval — Approval is optional because not every routing policy requires a human gate. Credentials are always read from caller-managed Secrets, never chart values.
approval.enabled false —
approval.replicaCount 1 —
approval.image — —
approval.image.repository ghcr.io/bitfid-inc/agentfleet-approval —
approval.image.tag v0.1.0 —
approval.image.digest "" —
approval.service — —
approval.service.type ClusterIP —
approval.service.port 8088 —
approval.external — —
approval.external.url "" Set a remote Approval base URL instead of deploying approval.enabled=true.
approval.external.allowInsecureHTTP false —
approval.clients — —
approval.clients.gateway — —
approval.clients.gateway.enabled true —
approval.clients.modelRouter — —
approval.clients.modelRouter.enabled true —
approval.clients.console — Approver access is opt-in because it requires authenticated tenant RBAC.
approval.clients.console.enabled false —
approval.serviceAccount — —
approval.serviceAccount.create true —
approval.serviceAccount.name "" —
approval.serviceAccount.annotations {} —
approval.serviceAccount.automountServiceAccountToken false Enable only when the selected workload-identity mechanism needs a projected token.
approval.storage — —
approval.storage.type memory —
approval.storage.postgres — —
approval.storage.postgres.existingSecret "" —
approval.storage.postgres.dsnKey dsn —
approval.storage.postgres.autoMigrate true —
approval.ttl — —
approval.ttl.default "1h" —
approval.ttl.maximum "720h" —
approval.ttl.claim "5m" —
approval.allowSelfApproval false —
approval.auth — —
approval.auth.mode token —
approval.auth.existingSecret "" —
approval.auth.workflowTokenKey workflow-token —
approval.auth.approverTokenKey approver-token —
approval.audit — —
approval.audit.mode async —
approval.audit.sinkType stdout —
approval.notification — —
approval.notification.webhook — —
approval.notification.webhook.url "" —
approval.notification.webhook.existingSecret "" —
approval.notification.webhook.tokenKey token —
approval.notification.webhook.timeout "5s" —
approval.sweeper — —
approval.sweeper.interval "30s" —
approval.sweeper.batchSize 100 —
approval.terminationGracePeriodSeconds 30 —
approval.resources {} —
approval.podLabels {} —
approval.podAnnotations {} —

gateway#

85 keys

KeyDefaultDescription
gateway — —
gateway.enabled true —
gateway.replicaCount 1 —
gateway.image — —
gateway.image.repository ghcr.io/bitfid-inc/agentfleet-gateway —
gateway.image.tag v0.1.0 —
gateway.image.digest "" —
gateway.service — —
gateway.service.type ClusterIP —
gateway.service.port 8080 —
gateway.config — —
gateway.config.agentEndpoint — The registry supplies each agent's endpoint URL and the gateway POSTs caller input to it, so a registry record naming an untrusted host would exfiltrate that input. enforceInClusterDNS requires a kubernetes-hosted agent's endpoint to be a Service DNS name in the agent's own namespace, which is the documented internal networking contract. Off by default so plain and local installs can use port-forwarded or non-cluster endpoints; enabled by the enterprise-baseline and secure-production profiles. Scheme, cloud-metadata, and link-local checks always apply and are not optional.
gateway.config.agentEndpoint.enforceInClusterDNS false —
gateway.config.metrics — —
gateway.config.metrics.enabled true —
gateway.config.metrics.path "" Empty inherits observability.metrics.path.
gateway.config.registryURL "" —
gateway.config.scheduler — —
gateway.config.scheduler.enabled false —
gateway.config.scheduler.url "" —
gateway.config.scheduler.failurePolicy fallback —
gateway.config.policy — —
gateway.config.policy.mode "off" —
gateway.config.policy.url "" —
gateway.config.policy.failurePolicy fail —
gateway.config.context — —
gateway.config.context.mode "off" —
gateway.config.context.url "" —
gateway.config.context.appendFailurePolicy continue —
gateway.config.audit — —
gateway.config.audit.mode "off" —
gateway.config.audit.sinkType "noop" —
gateway.config.a2a — —
gateway.config.a2a.delegationEnabled false —
gateway.config.a2a.delegationTimeout "10s" —
gateway.config.tenantAuthz — —
gateway.config.tenantAuthz.mode "off" —
gateway.config.tenantAuthz.source "static_config" —
gateway.config.tenantAuthz.static — —
gateway.config.tenantAuthz.static.memberships [] —
gateway.config.tenantAuthz.idpGroups — —
gateway.config.tenantAuthz.idpGroups.enabled false —
gateway.config.tenantAuthz.idpGroups.groupClaim "groups" —
gateway.config.tenantAuthz.idpGroups.stringClaimDelimiter " " —
gateway.config.tenantAuthz.idpGroups.includeMatchedGroups false —
gateway.config.tenantAuthz.idpGroups.mappings [] —
gateway.config.auth — —
gateway.config.auth.mode "off" —
gateway.config.auth.allowUnregisteredCallers false Every mode but entra_app admits a principal with no Registry caller record, so the caller is not checked against the Registry: off and header_dev trust the tenant and actor the request states, and oidc_jwt verifies the token and takes the tenant and actor from its claims. off and header_dev are for LOCAL DEVELOPMENT ONLY and oidc_jwt may run in production (the owner's decisions, issue #654, relayed in chat and not verifiable from the repository). The chart refuses to render the Gateway in any of the three unless this is true, which acknowledges that (issue #656). It changes nothing about how the mode behaves. The chart sets the Gateway's environment variable AGENTFLEET_GATEWAY_ALLOW_UNREGISTERED_CALLERS=true from it, which the binary itself requires in those three modes. It is a variable and not an argument because gateway.image.tag can name an image older than the chart: that image does not define the flag and would exit on it, but ignores the variable (and then has no startup refusal of its own). The other pairing crashes: on a chart older than the startup refusal (issue #709), bumping only gateway.image.tag to an image built from it, in off, header_dev or oidc_jwt with this set to true, is a crash loop (the older chart renders no such variable and the Gateway refuses to start; not run in a cluster); upgrade the chart first. The default mode above is "off", so a default install is refused until it chooses entra_app, or sets this (a local install with off or header_dev, or an oidc_jwt install that accepts that its callers are not checked against the Registry): the demo and local profiles and Make targets set it. An existing install in off, header_dev or oidc_jwt must set this (or move to entra_app) BEFORE upgrading, or the upgrade fails to render. entra_app needs no acknowledgement.
gateway.config.auth.diagnosticsEnabled true —
gateway.config.auth.requireTenant false —
gateway.config.auth.requireActor false —
gateway.config.auth.headerDev — —
gateway.config.auth.headerDev.tenantHeader "X-AgentFleet-Tenant" —
gateway.config.auth.headerDev.actorHeader "X-AgentFleet-Actor" —
gateway.config.auth.headerDev.actorTypeHeader "X-AgentFleet-Actor-Type" —
gateway.config.auth.headerDev.allowBodyFallback true —
gateway.config.auth.oidcJwt — —
gateway.config.auth.oidcJwt.issuer "" —
gateway.config.auth.oidcJwt.audience [] —
gateway.config.auth.oidcJwt.jwksURL "" —
gateway.config.auth.oidcJwt.allowInsecureHTTP false —
gateway.config.auth.oidcJwt.tenantClaim "tenant_id" —
gateway.config.auth.oidcJwt.actorClaim "sub" —
gateway.config.auth.oidcJwt.actorTypeClaim "actor_type" —
gateway.config.auth.oidcJwt.allowedAlgorithms — —
gateway.config.auth.oidcJwt.clockSkew "60s" —
gateway.config.auth.oidcJwt.jwksCacheTTL "5m" —
gateway.config.auth.oidcJwt.jwksRefreshTimeout "5s" —
gateway.config.auth.oidcJwt.jwksRefreshMinInterval "60s" —
gateway.config.auth.entraApp — Settings of auth.mode=entra_app: an Entra application token is verified with the oidcJwt issuer, audience and jwksURL above, the application must hold an approved binding, and its agent's Registry record gives the tenant.
gateway.config.auth.entraApp.directoryID "" —
gateway.config.auth.entraApp.requiredRole "" —
gateway.config.auth.entraApp.registryRefreshSeconds 5 How long a binding lookup may be cached, in whole seconds: it bounds how long a deleted binding keeps admitting its caller. It is the one value the chart derives the Registry's pending-caller approval grace from (twice this, at least 60 seconds), so the two cannot drift apart.
gateway.config.auth.entraApp.bindings — The identity binding database; the Gateway needs SELECT on identity_bindings only.
gateway.config.auth.entraApp.bindings.existingSecret "" —
gateway.config.auth.entraApp.bindings.dsnKey dsn —
gateway.config.auth.entraApp.auditDigest — The keyed digest that names a caller in audit events and in the pending table. The key must be the one the Model Router and the Console use.
gateway.config.auth.entraApp.auditDigest.existingSecret "" —
gateway.config.auth.entraApp.auditDigest.keyKey key —
gateway.config.auth.entraApp.auditDigest.keyIDKey keyId —
gateway.config.auth.entraApp.recordPending false Record a verified caller with no binding as pending in the Registry. The caller is denied either way. An Admin or Owner lists and decides a pending caller through the Console when console.registration.pendingAdmin is enabled; otherwise a recorded row ends only by expiry. How long a row lives is set under registry.pendingCallers (retention, maxAge, rejectionRetention, sweepInterval); see exception 7 of docs/known-limitations.md. Needs registry.pendingCallers.enabled.
gateway.resources {} —
gateway.podLabels {} —
gateway.podAnnotations {} —

registry#

43 keys

KeyDefaultDescription
registry — —
registry.enabled true —
registry.replicaCount 1 —
registry.image — —
registry.image.repository ghcr.io/bitfid-inc/agentfleet-registry —
registry.image.tag v0.1.0 —
registry.image.digest "" —
registry.service — —
registry.service.type ClusterIP —
registry.service.port 8082 —
registry.security — The registry is a root of trust: the Gateway invokes whatever endpoint URL a record carries and the Model Router derives an agent's tenant from its record. Token mode authenticates the internal write API so only AgentFleet components (the operator) can create, change, or delete agent records. Defaults to off for local installs; the registry logs a startup warning while writes are unauthenticated. Enabled in the enterprise-baseline and secure-production profiles, which require distinct registry-token and registry-read-token keys in the shared internal-auth Secret — see "Registry Authentication" in the chart README before upgrading.
registry.security.auth — —
registry.security.auth.mode "off" —
registry.security.auth.existingSecret "" —
registry.security.auth.tokenKey token tokenKey remains the write credential for backward compatibility. It is mounted only into the Registry and Operator.
registry.security.auth.readMode "off" Read authentication can be rolled out safely in two releases: permissive distributes the read-only credential while reads stay open; token enforces it after every reader has restarted.
registry.security.auth.readTokenKey read-token —
registry.operatorWritesAreApprovals false Transitional (issue #726; rules 2 to 4 of docs/decisions/2026-11-08-the-registry-is-the-one-inventory-and-only-approved-agents-route.md). Off by default: a record made through the Registry's write API (the Operator's Agent records included) is pending and does not route, because only a recorded decision may make one registered or managed, and the chart enables no approval route (the Registry's first-slice approval routes, issue #727, are off unless it is started with -approval-mode=token, which nothing in this chart sets). On, a write that states source operator is taken as the approval of its record. It records no person's decision: with Registry write authentication off any caller that can reach the write API is an approver, with it on (security.auth.mode: token) the holder of the write token. It is removed when the approval service exists. Memory storage (the default and enterprise-baseline, local-light) loses its records when the pod is rolled, which an upgrade does, and then no agent routes until the setting is on; on PostgreSQL storage existing registered and managed agents keep routing and only Agents applied after the upgrade are pending. A release upgraded with --reuse-values keeps the values it was installed with, which lack this key, whatever its profile: if it depends on operator writes approving, pass --set registry.operatorWritesAreApprovals=true (or the profile with -f). Accepted by the owner, 2026-11-11; read from Helm's --reuse-values semantics and the template, not run on a cluster; the chart README has the details, and the rules for revoked and refused writes. The local and demo profiles turn it on when their file is passed; the enterprise and secure-production profiles do not.
registry.discoveryWrite — The discovery write token (issue #593). Off by default. When on, the Registry also accepts a fifth bearer token, the key `tokenKey` of registry.security.auth.existingSecret, for one thing: a PUT of an observed discovery record at a discovery/obs-<32 lower-case hex> placement. The body must be exactly the shape the reconciler writes: source discovery and trust level observed stated, runtime and hosting mode "unknown", a display name and description that are the reconciler's fixed words for a known collector, a tenant from `tenants` below, and no other field (no cluster name, agentId, workspace, replacesAgentId, endpoint, capability, model, tool, owner or health field). It opens no other route and cannot delete a record or write a registered or managed one; the standing write token (tokenKey above) is unchanged and is not given to the job that holds this one. It is how discovery.reconcile writes without the standing token. Needs registry.security.auth.mode=token and a key that differs from tokenKey, readTokenKey, pendingCallers.tokenKey and console.registration.pendingAdmin.tokenKey. The Secret itself is created by you. tenants is the list of tenants the token may write records for, compared with the record's tenant exactly. Required when enabled, with no default: the Registry refuses to start with the token and no tenant, and the render fails. The token cannot store a tenant it was not configured with, so a value that a person chose cannot be written through it. List the tenant that discovery.reconcile.tenant names (the render checks that it is listed).
registry.discoveryWrite.enabled false —
registry.discoveryWrite.tokenKey discovery-write-token —
registry.discoveryWrite.tenants [] —
registry.pendingCallers — Verified but unbound entra_app callers, kept in a table of their own. Off by default. A row is listed and decided through the Console when console.registration.pendingAdmin is enabled, and otherwise ends only by expiry; a row holds the caller's raw directory and object IDs while it is pending (exception 7 of docs/known-limitations.md). How long is the operator's to set, with the values below; the defaults are the bounds that document calls proposed. A row's raw identifiers can outlive its expiry by up to sweepInterval. The route that writes the table is opened by a third credential, which only the Gateway and the Registry receive; it can write that table and nothing else. Needs PostgreSQL storage. The Registry's approval grace is derived from gateway.config.auth.entraApp.registryRefreshSeconds, which the Gateway uses only in entra_app mode.
registry.pendingCallers.enabled false —
registry.pendingCallers.tokenKey pending-token Key of the pending-caller token in registry.security.auth.existingSecret. It must differ from tokenKey and readTokenKey.
registry.pendingCallers.rowLimit 1000 The most rows the table holds before new callers are counted and not recorded. Service-account rows (modelRouter.workloadIdentity.recordPending) may hold at most half of it, so they cannot fill the table and leave Entra callers capped.
registry.pendingCallers.retention 720h How long a pending row lives after its last attempt (each attempt moves it).
registry.pendingCallers.maxAge 2160h The absolute bound on a pending row, from its first attempt. Nothing moves it. An application that keeps calling is recorded again after it.
registry.pendingCallers.rejectionRetention 168h How long a rejected row lives after the decision (used by #312).
registry.pendingCallers.sweepInterval 1h How often the Registry deletes expired rows (it also sweeps once at startup).
registry.pendingCallers.decisionHistory "" How long an approved or rejected Entra application is kept WITH its raw directory, object and client IDs, so the Console can list decisions. A service account's decision is never kept: its namespace and name are free text people chose. Empty keeps no new history, and a decided row then holds no identifier (docs/pending-caller-design-spec.md Section 5). A value such as 720h is the operator's choice to hold those identifiers that long (exception 7), and they can stay up to one sweepInterval longer, until the next sweep deletes the expired row (the sweep runs only while pending callers are enabled). Rows already written keep the expiry they were given: emptying or shortening this does not remove them, and the Console still lists them until they expire. Any administrator of some tenant sees every recorded decision across tenants, and the table has no row bound.
registry.audit — Agent records are a root of trust, so every write and every rejected write is recorded. Audit defaults on because registry writes are low volume (the operator republishes on its sync interval) and a stdout sink adds no dependency, which is what makes a forged or unexpected record detectable after the fact.
registry.audit.mode async —
registry.audit.sinkType stdout —
registry.storage — —
registry.storage.type memory —
registry.storage.postgres — —
registry.storage.postgres.existingSecret "" —
registry.storage.postgres.dsnKey dsn —
registry.storage.postgres.autoMigrate true —
registry.resources {} —
registry.podLabels {} —
registry.podAnnotations {} —

scheduler#

18 keys

KeyDefaultDescription
scheduler — —
scheduler.enabled true —
scheduler.replicaCount 1 —
scheduler.image — —
scheduler.image.repository ghcr.io/bitfid-inc/agentfleet-scheduler —
scheduler.image.tag v0.1.0 —
scheduler.image.digest "" —
scheduler.service — —
scheduler.service.type ClusterIP —
scheduler.service.port 8083 —
scheduler.security — Optional bearer token on the Scheduler's /internal/v1/* routes (#550). Off by default, which leaves them unauthenticated as before; /healthz and /readyz never need the token. With mode "token" the chart gives the Scheduler and the Gateway (the Scheduler's only client) AGENTFLEET_SCHEDULER_TOKEN from the Secret named here, and the Scheduler starts with -auth-mode=token.
scheduler.security.auth — —
scheduler.security.auth.mode "off" —
scheduler.security.auth.existingSecret "" —
scheduler.security.auth.tokenKey token —
scheduler.resources {} —
scheduler.podLabels {} —
scheduler.podAnnotations {} —

discovery#

101 keys

KeyDefaultDescription
discovery — Discovery collects observations about the agents, credentials and MCP servers an estate already has. It writes nothing: no Registry records, no provider mutations, no enforcement. That is a deployment property as well as a code property, which is why nothing below names a write endpoint and a chart test asserts it. The one exception is store.enabled, which gives the scan a PostgreSQL DSN and INSERTs each run there; it is off by default and the test asserts the default render names none. Disabled by default. A scan reports what it was pointed at, and pointing it at nothing by default is better than a chart that quietly scans whatever happens to be mounted.
discovery.enabled false —
discovery.salt — The installation salt. agentfleet-discovery writes the names people chose (checkout and directory names, MCP server names and config paths, variable names) as digests under a key derived from it, and refuses to run without one, so an enabled job needs a Secret. Exceptions: what an option you turn on stores in clear (--builder-include-names and the like), and some diagnostics; docs/known-limitations.md lists them. The chart does not create the Secret: a salt generated by a template would change on every upgrade and make earlier runs' digests incomparable. At least 16 bytes.
discovery.salt.existingSecret "" —
discovery.salt.key salt —
discovery.image — —
discovery.image.repository ghcr.io/bitfid-inc/agentfleet-discovery —
discovery.image.tag v0.1.0 —
discovery.image.digest "" —
discovery.schedule "0 3 * * *" Daily at 03:00. A scan is scheduled work rather than a service: the command collects, reports and exits.
discovery.suspend false —
discovery.startingDeadlineSeconds 300 —
discovery.backoffLimit 0 One attempt. A run that could not read the estate is a finding to look at, not a transient to retry: retrying hides the first failure behind the second and the report is the product.
discovery.ttlSecondsAfterFinished "" No TTL by default, because a TTL and a history limit are not the same control and the TTL wins. Kubernetes deletes a finished Job and its Pod once ttlSecondsAfterFinished elapses whatever the CronJob history says, so asking for ten failures beside a 24-hour TTL on a daily schedule kept one - the history limit read as a promise the TTL quietly broke. Empty means the history limits below are the only retention rule, which is the one an operator reading this chart thinks they are setting. Set a TTL only together with a history limit that it cannot outlive.
discovery.successfulJobsHistoryLimit 3 —
discovery.failedJobsHistoryLimit 10 Failed runs are kept deeper than successful ones. An incomplete run is the finding, and a finding nobody retained is the absence discovery exists to prevent.
discovery.scanPath /scan Where the scan looks. The repository collector walks the working directory and the MCP scanner reads configuration paths under HOME, both of which are set to this path, so what is not mounted here is not scanned - and the run reports the roots it was given either way.
discovery.scanVolume — —
discovery.scanVolume.emptyDir {} —
discovery.egressPolicy — Egress policy for the two Jobs that reach the store: the scan CronJob and the retention sweep CronJob. OFF by default; the secure-production profile turns it on. With it on (and discovery.store.enabled) the chart renders one Egress NetworkPolicy per Job, selecting that Job's pods by the same labels the pod template carries, that allows only DNS and the PostgreSQL destination named below, on TCP postgres.port. Selecting the pods isolates them for egress, so anything not listed is dropped, including a PostgreSQL host this block does not name. The chart cannot read the DSN in the Secret, so it cannot check that the destination named here is the one the DSN points at. A mismatch fails closed: the Job cannot connect. Name at least one destination: with the policy enabled and none named the render fails instead of allowing all. A CIDR with a prefix shorter than /8 (IPv4) or /16 (IPv6) is refused for the same reason; that is a floor against "every address", not a check that what passes is narrow. What the scan Job needs: for the Job this chart renders, DNS and PostgreSQL (the store) and nothing else; see "Discovery egress policy" in the chart README for the trace. A scan you extend outside the chart to call a provider or the Kubernetes API is blocked by this policy. Not covered: the adoption lapse, reconciler and migration Jobs (they also reach PostgreSQL) and the PostgreSQL pods' ingress. The chart adds no policy that isolates PostgreSQL, because that would cut off every caller not listed in it. If the destination cannot be expressed here, leave this off and apply your own policy: see "Discovery egress policy" in the chart README.
discovery.egressPolicy.enabled false —
discovery.egressPolicy.postgres — —
discovery.egressPolicy.postgres.port 5432 The PostgreSQL pod's port (TCP). A NetworkPolicy matches the destination pod's port after a Service has rewritten it, so for an in-cluster PostgreSQL behind a Service whose port differs from its targetPort, give the targetPort. For `bundled` the bundled pod's container port is used (postgresql.bundled.service.port, which also sets that container port).
discovery.egressPolicy.postgres.cidrs [] External PostgreSQL: the database's real endpoint addresses, after any Service translation (a NetworkPolicy matches IPs, not a hostname, and not a Service address the traffic is rewritten from). Example: ["10.20.30.0/24"]. Prefixes shorter than /8 (IPv4) or /16 (IPv6) are refused, and so are IPv6 ranges that are IPv4-mapped (::ffff:...) or that start with zero groups and are no longer than /96 (they can contain the IPv4-mapped space).
discovery.egressPolicy.postgres.bundled false The chart's bundled PostgreSQL (dev only; requires postgresql.bundled.enabled).
discovery.egressPolicy.postgres.inCluster — PostgreSQL running in the cluster that this chart did not install. podSelector is required when this is used. Each selector needs at least one matchLabels entry or one matchExpressions entry: an empty one (including `matchLabels: {}` or `matchExpressions: []`) would match every pod or namespace and is refused. namespaceSelector left out means this release's namespace, which the rendered policy names explicitly.
discovery.egressPolicy.postgres.inCluster.podSelector {} —
discovery.egressPolicy.postgres.inCluster.namespaceSelector {} —
discovery.egressPolicy.dns — Cluster DNS. The defaults are CoreDNS/kube-dns in kube-system on port 53, UDP and TCP. Override them if your DNS runs elsewhere; `cidrs` adds ranges (for example a NodeLocal DNSCache address, 169.254.20.10/32).
discovery.egressPolicy.dns.port 53 —
discovery.egressPolicy.dns.podSelector — —
discovery.egressPolicy.dns.podSelector.matchLabels — —
discovery.egressPolicy.dns.podSelector.matchLabels.k8s-app kube-dns —
discovery.egressPolicy.dns.namespaceSelector — —
discovery.egressPolicy.dns.namespaceSelector.matchLabels — —
discovery.egressPolicy.dns.cidrs [] —
discovery.store — Saves each run to the PostgreSQL observation store (internal/discovery/ postgresstore) in addition to the run output below. Off by default. Nothing here has a default that would keep runs indefinitely or digest them under a shared key: when enabled, an empty retention window, Secret name or key name fails the render, and the command itself refuses to start without a window, DSN or key. The table is created by the migration Job, so this also needs migrations.enabled (or a schema you applied yourself); the render does not check that. The render only checks the shape of the window and schedule, and cannot see whether the Secret or its keys exist.
discovery.store.enabled false —
discovery.store.existingSecret "" A Secret in this namespace holding the connection string and the store key. The store key is NOT the installation salt (AGENTFLEET_DISCOVERY_SALT): every collector host holds the salt, and a table dump plus the salt would let a reader guess file names and paths back from their digests. Generate it separately, at least 16 bytes, and give it to no other workload: kubectl create secret generic discovery-store \ --from-literal=dsn='postgres://...' --from-literal=store-key="$(openssl rand -hex 32)" Rotating it makes old and new digests incomparable (design spec section 11, the salt lifecycle, is still open); stored runs are not rewritten.
discovery.store.dsnKey dsn —
discovery.store.keyKey store-key —
discovery.store.retention "" How long a saved run is kept, a quoted Go duration such as "720h" (days are not a Go unit). Required when store.enabled: there is deliberately no default, because a default is a decision to keep data nobody chose. The same value goes to the writer (which refuses a run already older than it) and to the retention job.
discovery.store.sweep — The job that removes runs stored longer ago than the window. It runs the migrations image's -sweep-discovery mode and needs DELETE and SELECT on the table (its WHERE reads stored_at; DELETE alone fails with permission denied).
discovery.store.sweep.schedule "37 * * * *" —
discovery.store.sweep.timeout "2m" —
discovery.store.sweep.concurrencyPolicy Forbid —
discovery.store.sweep.startingDeadlineSeconds 300 —
discovery.store.sweep.successfulJobsHistoryLimit 1 —
discovery.store.sweep.failedJobsHistoryLimit 3 —
discovery.store.sweep.backoffLimit 1 —
discovery.store.sweep.resources {} —
discovery.store.sweep.podLabels {} —
discovery.store.sweep.podAnnotations {} —
discovery.store.adoptionLapse — The job that purges an adoption record's raw provider key ID at its bound (issue #616; docs/decisions/2026-11-06-adoption-record-raw-key-id-bound-90-days.md: the raw ID is purged at close or 90 days after adoption, whichever comes first). It runs the migrations image's -lapse-adoption mode: every open record whose raw_key_id_until has passed is marked lapsed and loses its raw key ID, which raises it as a finding for a person. It never calls a provider and never deletes a key. It is idempotent, and by PostgreSQL's documented READ COMMITTED behaviour safe on more than one replica (one UPDATE statement; not run against concurrent replicas). OFF by default, and nothing calls Adopt yet, so no raw key ID is stored today. Where Adopt is wired, turn this on: until Lapse runs, the raw key ID stays in adoption_records.key_id past its bound and only read-time refusal limits it. When on, a raw key ID outlives its bound by at most about intervalMinutes while the job keeps succeeding, and longer if a run starts late, a tick is skipped or the job fails (Kubernetes starts a CronJob approximately). Needs discovery.store.enabled (the table is created by the discovery migration component), and a Secret of its own: its role needs SELECT on the columns id, status and raw_key_id_until only, and UPDATE on status, key_id, purged_at and lapsed_at. Not SELECT on key_id: that is the raw provider key ID, which a role able to read it would get without the retry authorizer. SQL in the chart README (its column grants were exercised by a test on PostgreSQL 16 only; the rest was not run). The render refuses the store's own Secret and key and the schema owner's, and cannot see the role. Turning it on also renders a watchdog CronJob (-check-adoption-lapse, same schedule, same Secret, grace twice intervalMinutes) that fails when an open record is past its bound by more than the grace, so a lapse job that is not running or is failing shows as a failed Job (not a cause that stops both CronJobs alike). No alert is shipped: something must watch failed Jobs (chart README, "The watchdog", which also lists what it does not detect).
discovery.store.adoptionLapse.enabled false —
discovery.store.adoptionLapse.existingSecret "" —
discovery.store.adoptionLapse.dsnKey dsn —
discovery.store.adoptionLapse.intervalMinutes 15 How often it runs, in minutes, 1 to 59, rendered as "*/N * * * *": the longest gap between two runs is then under an hour, always shorter than a day. A schedule you write yourself is not offered, so a value that waits longer than the bound allows cannot be set.
discovery.store.adoptionLapse.timeout "2m" —
discovery.store.adoptionLapse.activeDeadlineSeconds 600 Ends a Job (the lapse job and its watchdog) that has run this long, so one stuck on an image pull or never scheduled becomes a failed Job and does not sit in the way of the next under Forbid. The render refuses more than twice intervalMinutes in seconds (the watchdog's grace), a sanity bound that makes a false alarm less likely, not impossible: at the default 600 it means an interval of 5 minutes or more.
discovery.store.adoptionLapse.concurrencyPolicy Forbid —
discovery.store.adoptionLapse.startingDeadlineSeconds 300 —
discovery.store.adoptionLapse.successfulJobsHistoryLimit 1 —
discovery.store.adoptionLapse.failedJobsHistoryLimit 3 —
discovery.store.adoptionLapse.backoffLimit 1 —
discovery.store.adoptionLapse.resources {} —
discovery.store.adoptionLapse.podLabels {} —
discovery.store.adoptionLapse.podAnnotations {} —
discovery.reconcile — The reconciler job (issue #593): the separate, privileged step that writes the agents of the latest stored discovery run to the Registry as observed records (source discovery, trust level observed; meant not to be routable: docs/known-limitations.md says where that is enforced today and where it is not). It runs the migrations image's -reconcile-discovery mode. The collector job above has no Registry credential and none of this reaches it. OFF by default. When on, the render refuses unless: discovery.enabled, discovery.store.enabled, registry.enabled, registry.discoveryWrite.enabled and registry.security.auth.mode=token; a Secret of its own is named (not any Secret another value of this chart names, so not the store's, the Registry's shared one or the Console's); maxObserved and window are set (they have no default). The Secret holds three keys and nothing else: dsnKey the connection string of a database role meant to SELECT on discovery_runs and nothing else (the same grants as console.discoveryView; SQL in the chart README; the chart cannot check the grant). It needs no store key. registryTokenKey the same value as the key registry.discoveryWrite.tokenKey of registry.security.auth.existingSecret: the Registry's discovery write token. Never the standing write token: the job refuses to start if it finds that in its environment. registryReadTokenKey the Registry's read token, only used (and then required) when registry.security.auth.readMode is permissive or token. The job reads the Registry to count the workspace's observed records and to ask whether a placement is held. The render cannot see whether the Secret, its keys or the role exist.
discovery.reconcile.enabled false —
discovery.reconcile.existingSecret "" —
discovery.reconcile.dsnKey dsn —
discovery.reconcile.registryTokenKey discovery-write-token —
discovery.reconcile.registryReadTokenKey read-token —
discovery.reconcile.registryURL "" The Registry's base URL; empty is this chart's Registry.
discovery.reconcile.maxObserved 0 The most observed discovery records the tenant's workspace may hold. Required, no default (decision record 2026-11-08, rule 10 says "the per-tenant cap" and gives none). A new finding past it is not recorded and the job fails (a failed Job is the alert; nothing else alerts). At most 10000.
discovery.reconcile.window "" How far back a stored run may have started and still be reconciled, a quoted Go duration such as "48h". Required, no default. A store whose newest run is older fails the job: a collector that stopped is a failed Job, not a replay of an old estate.
discovery.reconcile.tenant "" The tenant the records are written under. Required, with no default, and it must be one of registry.discoveryWrite.tenants: the Registry accepts the job's token only for the tenants it was configured with, and the default workspace (an empty tenant) is not reachable with it. The job takes it from here and not from the stored run, which holds no tenant.
discovery.reconcile.schedule "0 4 * * *" An hour after the scan's default schedule.
discovery.reconcile.suspend false —
discovery.reconcile.timeout "2m" —
discovery.reconcile.activeDeadlineSeconds 600 Ends a Job that has run this long, so one stuck on an image pull becomes a failed Job and does not sit in the way of the next under Forbid.
discovery.reconcile.concurrencyPolicy Forbid The check is read-then-write, so two reconcilers running at once can each admit records and pass maxObserved. Forbid is the only value the render accepts.
discovery.reconcile.startingDeadlineSeconds 300 —
discovery.reconcile.successfulJobsHistoryLimit 1 —
discovery.reconcile.failedJobsHistoryLimit 3 —
discovery.reconcile.backoffLimit 0 —
discovery.reconcile.resources {} —
discovery.reconcile.podLabels {} —
discovery.reconcile.podAnnotations {} —
discovery.allowEmpty [] Collectors expected to find nothing, by name. An unacknowledged empty leaves the run incomplete on purpose: the reader cannot tell an empty estate from an unasked question.
discovery.outOfScope [] Paths outside this scan's profile, matched against a path relative to its root and against the base name. Every pattern is printed in the report with the number of paths it excluded.
discovery.json — The machine-readable run, which is the only output an observation claim may cite: the printed summary carries counts rather than records, on purpose, so a claim cannot rest on it. On by default, because a scheduled scan whose evidence exists only in pod logs produces nothing citable and the operator finds that out when they try to cite it. The default volume is deliberately NOT durable, and deliberately not a silent emptyDir either. A chart cannot guess whether an estate wants a PersistentVolumeClaim, an object-store sidecar or a host path, and defaulting to emptyDir would write every run to storage that leaves with the pod while looking configured. So the default is emptyDir with this written next to it: runs survive only as long as the pod unless volume is replaced, and the chart test asserts the warning is here rather than trusting that an adopter read it.
discovery.json.enabled true —
discovery.json.path "-" "-" writes the run to stdout, where `kubectl logs` retrieves it from a Job that has already finished. That is the default because it is the only channel needing no storage to configure: once a scan exits its container is terminated, and both `kubectl cp` and `kubectl exec` require a running one. An earlier default wrote the run to a file on an emptyDir, which produced evidence no adopter could retrieve by any means - worse than writing none, because it looked like retention. The printed summary goes to stderr when the run goes to stdout, so both reach the log, and the document is delimited within it: kubectl logs job/<job> | sed -n '/^{/,/^}/p' > run.json Both ends, not "to the end of the log". An incomplete run exits non-zero and prints why, so that line lands after the closing brace and an open-ended range swallows it - failing to parse on exactly the runs an adopter most needs to read. The document's own braces are the only ones at column zero, because it is rendered indented. Any other value is a path inside the container and needs a real volume below. The chart refuses to render a file path onto an empty volume rather than writing somewhere nothing can read.
discovery.json.volume {} Required when path is not "-". A chart cannot guess whether an estate wants a PersistentVolumeClaim, an object-store sidecar or a host path, so it asks rather than defaulting to storage that leaves with the pod. Name the file per run, or each run overwrites the last: POD_NAME is in the environment for that, and Kubernetes expands $(POD_NAME) in args. path: /runs/discovery-$(POD_NAME).json volume: persistentVolumeClaim: claimName: discovery-runs
discovery.resources {} —
discovery.podLabels {} —
discovery.podAnnotations {} —
discovery.extraVolumes [] —
discovery.extraVolumeMounts [] —

policy#

20 keys

KeyDefaultDescription
policy — —
policy.enabled true —
policy.replicaCount 1 —
policy.image — —
policy.image.repository ghcr.io/bitfid-inc/agentfleet-policy —
policy.image.tag v0.1.0 —
policy.image.digest "" —
policy.service — —
policy.service.type ClusterIP —
policy.service.port 8084 —
policy.security — Optional bearer token on the Policy service's /internal/v1/* routes (#550). Off by default, which leaves them unauthenticated as before; /healthz and /readyz never need the token. With mode "token" the chart gives the Policy service and its clients (the Gateway, the Model Router and the MCP router) AGENTFLEET_POLICY_TOKEN from the Secret named here, and the Policy service starts with -auth-mode=token.
policy.security.auth — —
policy.security.auth.mode "off" —
policy.security.auth.existingSecret "" —
policy.security.auth.tokenKey token —
policy.mode enforce —
policy.rules {} Operator-authored governance rules, rendered into the chart ConfigMap and mounted read-only. Leaving this empty means the policy service denies every request, so gateway.config.policy.mode=enforce needs rules here before any traffic is allowed through. Unknown keys fail startup rather than being ignored, so a typo cannot silently leave a restriction unenforced. Note: an allow list is only checked when the request carries that field. A request with no tenant is not matched by allowedTenants; defaultDecision is what stops it. Keep defaultDecision set to deny. rules: defaultDecision: deny allowedTenants: - enterprise-it allowedCapabilities: - it-triage
policy.resources {} —
policy.podLabels {} —
policy.podAnnotations {} —

guard#

26 keys

KeyDefaultDescription
guard — Optional local guardrail service. Disabled by default. When enabled it runs the deterministic secret and encoded-content detectors configured below; no model traffic passes through it until the Model Router is configured to call it.
guard.enabled false —
guard.replicaCount 1 —
guard.image — —
guard.image.repository ghcr.io/bitfid-inc/agentfleet-guard —
guard.image.tag v0.1.0 —
guard.image.digest "" —
guard.service — —
guard.service.type ClusterIP —
guard.service.port 8090 —
guard.serviceAccount — —
guard.serviceAccount.create true —
guard.serviceAccount.name "" —
guard.serviceAccount.annotations {} —
guard.serviceAccount.automountServiceAccountToken false —
guard.auth — Callers authenticate with a shared bearer token read from existingSecret. mode=off is for local development only and is rejected by the secure production profile.
guard.auth.mode token —
guard.auth.existingSecret "" —
guard.auth.tokenKey token —
guard.config — Detector manifests and tenant profiles, rendered into a GuardConfig file and mounted read-only. Guard refuses to start without at least one profile, and unknown keys fail startup. A profile's namespace must be the namespace of the agents it covers, and its mode (off, audit, or enforce) decides whether findings change a request. Start new profiles in audit. See examples/guard/values-secret-detection.yaml for a complete, tested example.
guard.config.detectors [] —
guard.config.profiles [] —
guard.terminationGracePeriodSeconds 30 —
guard.resources {} —
guard.podLabels {} —
guard.podAnnotations {} —

modelRouter#

131 keys

KeyDefaultDescription
modelRouter — —
modelRouter.enabled true —
modelRouter.replicaCount 1 —
modelRouter.image — —
modelRouter.image.repository ghcr.io/bitfid-inc/agentfleet-model-router —
modelRouter.image.tag v0.1.0 —
modelRouter.image.digest "" —
modelRouter.service — —
modelRouter.service.type ClusterIP —
modelRouter.service.port 8085 —
modelRouter.serviceAccount — —
modelRouter.serviceAccount.create true —
modelRouter.serviceAccount.name "" —
modelRouter.serviceAccount.annotations {} —
modelRouter.serviceAccount.automountServiceAccountToken false Enable only when provider or Kubernetes workload identity needs a projected token.
modelRouter.credentials — —
modelRouter.credentials.mode static static uses installation-wide Secret references; kubernetes resolves by tenant; azure-key-vault reads each mapped provider key from the customer's Azure Key Vault by reference, for every request, using an Azure workload identity (see azureKeyVault below). AgentFleet's own credential vault mode is not selectable here.
modelRouter.credentials.kubernetes — —
modelRouter.credentials.kubernetes.secretNamespace "" Empty uses the Helm release namespace.
modelRouter.credentials.kubernetes.cacheTTL "5m" —
modelRouter.credentials.kubernetes.mappings [] —
modelRouter.credentials.azureKeyVault — Only with mode=azure-key-vault. In another mode the render refuses any setting here that is not at its default (a value equal to the default cannot be told from unset), and in every mode it refuses a key under azureKeyVault, identity, auditSalt or a mapping that the chart does not read, such as a misspelt vaultUrl. It is meant to hold references and identifiers only, never a provider key: in the mapping the tenant, provider, vault URI, secret name and field, and the identity's client and tenant IDs on the ServiceAccount. The render shape-checks secretName and field (names, not starting with sk-), the vault URI, the identity IDs, the scope prefix and the salt Secret's name and key, and refuses the chart's other provider-key Secret references in this mode (the OpenAI and Anthropic existingSecret, bedrock.credentialsSecret). It checks tenant only for being non-empty, so a key pasted into tenant would not be caught. The router reads the latest version of each mapped secret with Get Secret on every request and caches nothing (internal/modelrouter/azkv_credentials.go). The chart creates no Azure resource and no role assignment. The identity needs read access to the mapped secrets and nothing else: see docs/azure-key-vault-least-privilege.md. Not run against a real vault yet (#455).
modelRouter.credentials.azureKeyVault.vaultUri "" https URL of the customer's vault, the host only: for example https://acme-keys.vault.azure.net. No path, port or trailing dot.
modelRouter.credentials.azureKeyVault.identity — The Microsoft Entra application or user-assigned managed identity the router signs in as, through Azure Workload Identity. The chart writes both onto the router's ServiceAccount (azure.workload.identity/client-id and /tenant-id) and labels the router's pods azure.workload.identity/use for the Azure Workload Identity webhook, which is installed by the operator, not by this chart. Both are GUIDs. Requires modelRouter.serviceAccount.create=true.
modelRouter.credentials.azureKeyVault.identity.clientId "" —
modelRouter.credentials.azureKeyVault.identity.tenantId "" —
modelRouter.credentials.azureKeyVault.scope "" Access-token scope for Key Vault. Empty uses the router's default, the public cloud's https://vault.azure.net/.default; a sovereign cloud sets its own.
modelRouter.credentials.azureKeyVault.auditSalt — A Secret holding the installation salt (standard base64, at least 16 bytes) under key, injected as AGENTFLEET_CREDENTIAL_SALT. The router keys the digests in its credential audit events with it and refuses to start in this mode without it. Create the Secret yourself: a salt generated by a template would change on every render. Give every router replica the same one.
modelRouter.credentials.azureKeyVault.auditSalt.existingSecret "" —
modelRouter.credentials.azureKeyVault.auditSalt.key salt —
modelRouter.credentials.azureKeyVault.mappings [] One entry per tenant and provider. secretName is the secret's name in the vault (letters, digits and hyphens, at most 127). field is the JSON key that holds the provider key when the secret's value is a JSON object (letters, digits, underscore, hyphen or dot, at most 64, not starting with sk-); empty means the whole secret value is the key. Every enabled OpenAI or Anthropic provider needs at least one entry. - tenant: retail provider: openai secretName: retail-openai field: api_key
modelRouter.config — —
modelRouter.config.policyURL "" —
modelRouter.security — —
modelRouter.security.auth — —
modelRouter.security.auth.mode "off" —
modelRouter.security.auth.existingSecret "" —
modelRouter.security.auth.tokenKey token —
modelRouter.security.allowPolicyOff true —
modelRouter.security.allowPolicyAudit false mode=audit records a deny or approval_required decision without applying it, so it is a policy bypass that arrives in the request body. Keep it false unless a deliberate rollout needs it.
modelRouter.security.allowUnauthenticatedPaidExecution false Explicit escape hatch for controlled local paid-provider fixtures.
modelRouter.security.limits — —
modelRouter.security.limits.requestsPerMinute 60 —
modelRouter.security.limits.modelCallsPerMinute 180 —
modelRouter.security.limits.maxConcurrentPerTenant 8 —
modelRouter.security.limits.maxModelsPerRequest 4 —
modelRouter.security.limits.maxOutputTokens 8192 —
modelRouter.security.limits.maxTrackedTenants 1000 —
modelRouter.metrics — —
modelRouter.metrics.enabled true —
modelRouter.metrics.path "" Empty inherits observability.metrics.path.
modelRouter.audit — —
modelRouter.audit.mode async off, async, or sync_required. sync_required fails the request if audit delivery fails.
modelRouter.audit.sinkType stdout stdout, spool or noop. spool writes events to a local directory for a log shipper (see spool below); the optional shipper sidecar (spool.shipper, off by default, never run in a cluster) is meant to deliver and delete them.
modelRouter.audit.spool — The spool lives in a per-pod emptyDir: it survives a container restart but not the pod. Each replica has its own, which the single-writer design needs.
modelRouter.audit.spool.path /var/lib/agentfleet/audit Mount path of the spool directory when sinkType=spool.
modelRouter.audit.spool.maxBytes 1073741824 Most bytes the spool may hold before it refuses writes. The volume's size limit is derived from it with some headroom.
modelRouter.audit.spool.shipper — —
modelRouter.audit.spool.shipper.enabled false Segments that are sealed but not yet shipped are lost with the pod, except what the shipper's final pass delivers while the pod is stopping. A sidecar meant to deliver sealed segments to ClickHouse and delete them (cmd/agentfleet-audit-shipper; tested only against a fake ClickHouse, and never run in a cluster). Needs sinkType=spool. Off by default: a spool nobody drains fills up and then refuses writes. Turning it on ties the Model Router pod's readiness to the sidecar's: a missing Secret, a wrong Secret key or a bad setting keeps the router out of its Service. It copies each spooled line, unfiltered, into a ClickHouse table with no retention: the raw tenant, the caller-supplied actor.id, the agent name and, for a caller that is not a workload, the agent namespace. The record docs/decisions/2026-10-25-audit-shipper-table-covered.md names these.
modelRouter.audit.spool.shipper.image — NOTE: no shipper image is published yet (the release workflow has no entry for it). With enabled=true and these defaults the container cannot pull, and a pod is Ready only when all its containers are, so the Model Router would not become Ready. Set repository (and tag) to an image you built, for example with `make docker-build-local`, until one is published.
modelRouter.audit.spool.shipper.image.repository ghcr.io/bitfid-inc/agentfleet-audit-shipper —
modelRouter.audit.spool.shipper.image.tag v0.1.0 —
modelRouter.audit.spool.shipper.image.digest "" —
modelRouter.audit.spool.shipper.clickhouse — —
modelRouter.audit.spool.shipper.clickhouse.url "" ClickHouse HTTP endpoint, such as http://agentfleet-audit-clickhouse.agentfleet-observability.svc:8123.
modelRouter.audit.spool.shipper.clickhouse.database agentfleet_audit —
modelRouter.audit.spool.shipper.clickhouse.table audit_spool_events —
modelRouter.audit.spool.shipper.clickhouse.existingSecret "" Secret with the ClickHouse credentials, read into CLICKHOUSE_USER and CLICKHOUSE_PASSWORD. Required.
modelRouter.audit.spool.shipper.clickhouse.usernameKey username —
modelRouter.audit.spool.shipper.clickhouse.passwordKey password —
modelRouter.audit.spool.shipper.interval 10s —
modelRouter.audit.spool.shipper.batchLines 1000 —
modelRouter.audit.spool.shipper.drainDelay 7s Durations are strings such as "10s"; quote a bare number such as "0". On stop, wait drainDelay (on a clean shutdown the router seals its last segment within its 5 second shutdown; if its shutdown fails or times out the leftover segment is sealed only at the router's next start, which a per-pod emptyDir does not survive), then deliver what is sealed within drainTimeout. Both containers get the stop signal together, so drainDelay plus drainTimeout must fit the pod's termination grace period (30 seconds unless changed).
modelRouter.audit.spool.shipper.drainTimeout 15s —
modelRouter.audit.spool.shipper.resources {} —
modelRouter.comparison — —
modelRouter.comparison.enabled true —
modelRouter.comparison.contextURL "" Empty uses the in-chart Context service when context.enabled=true.
modelRouter.comparison.storageRequired false —
modelRouter.pricing — —
modelRouter.pricing.enabled false Rates are operator-managed so provider price changes do not require a release.
modelRouter.pricing.rates [] —
modelRouter.guard — Guard inspection of model input and output. Each binding selects the Guard profile for one tenant's agents in one namespace; callers cannot choose a profile. The profile's own mode decides whether findings change requests. failurePolicy (fail_closed or fail_open) decides what happens when Guard cannot answer. streaming (reject or uninspected) decides whether covered agents may stream, since streamed replies cannot be inspected first. The secure production profile requires fail_closed and reject.
modelRouter.guard.enabled false —
modelRouter.guard.url "" Defaults to the in-chart Guard Service when guard.enabled=true.
modelRouter.guard.existingSecret "" Token Secret for calling Guard. Defaults to guard.auth.existingSecret and guard.auth.tokenKey.
modelRouter.guard.tokenKey "" —
modelRouter.guard.bindings [] —
modelRouter.workloadIdentity — Accept agent ServiceAccount tokens from protected namespaces. The Model Router verifies each token with the Kubernetes TokenReview API and takes tenant and agent from the Registry, rejecting requests that claim different values. The shared token keeps working for Console and Gateway. Requires modelRouter.serviceAccount.automountServiceAccountToken=true so the Model Router can call TokenReview.
modelRouter.workloadIdentity.enabled false —
modelRouter.workloadIdentity.audience agentfleet-model-router —
modelRouter.workloadIdentity.cacheTTL "30s" How long a TokenReview result for an agent token is reused.
modelRouter.workloadIdentity.registryRefresh "5s" How long an agent's Registry tenant and phase are reused. This bounds how quickly a suspension reaches the Model Router; keep it well under 30s and no longer than cacheTTL.
modelRouter.workloadIdentity.registryURL "" Defaults to the in-chart Registry.
modelRouter.workloadIdentity.recordPending false Record a verified agent-named ServiceAccount (agentfleet-agent-<name>) that the Registry has no agent for as a pending caller, so an Admin can see it in the Console. The caller is denied either way, and a token that fails review or a ServiceAccount not named like an agent records nothing. The row holds the raw namespace, ServiceAccount name and global.clusterName while it is pending (exception 7 of docs/known-limitations.md). Needs registry.pendingCallers.enabled, a global.clusterName the Registry accepts (letters, digits, ".", "_" and "-", starting and ending with a letter or digit, at most 253 characters) and modelRouter.audit.sinkType stdout or spool. The Model Router then receives the pending-caller token. Keep registryRefresh under the Registry's pending approval grace (the larger of 60 seconds and twice gateway.config.auth.entraApp.registryRefreshSeconds), or an approved account can be recorded as pending again; the chart does not check it.
modelRouter.workloadIdentity.bindings — Admit a ServiceAccount that has an IdentityBinding whatever it is called, as the agent and tenant the binding names. An Admin or Owner creates one by approving the pending service account in the Console (needs modelRouter.workloadIdentity.recordPending and console.registration, see above), and can disable or delete it there. The path needs console.registration.enabled, console.registration.pendingAdmin.enabled, recordPending, a Router database role and one audit digest key shared by the Console, Gateway and Router. The chart does not check that they agree and no test installs the chart and runs the whole path, so leave this off unless you have checked it in your cluster. With this off, approving a pending service account in the Console creates a binding that admits nothing, so turn it on before approving one. The agentfleet-agent-<name> naming rule stays as the fallback for an account with no binding. A revoked binding stops admitting within registryRefresh. Reads the identity binding database, the one the Gateway and the Console use; the role needs SELECT on identity_bindings only. The audit digest key must be the one the Console and the Gateway use. Any pod that can mint a token for the Router audience reaches the database (an account with no binding is remembered for one registryRefresh, and the Router holds at most four connections), and a database that cannot answer refuses every workload call, agent named or not (503), by design. The chart ships ingress policies only, so it neither opens nor checks the Router's route to that database.
modelRouter.workloadIdentity.bindings.enabled false —
modelRouter.workloadIdentity.bindings.existingSecret "" —
modelRouter.workloadIdentity.bindings.dsnKey dsn —
modelRouter.workloadIdentity.auditDigest — The audit record names an agent caller by a keyed digest, never by its ServiceAccount, so the Model Router needs a secret key and a key id. Required when workload identity is enabled; the Model Router refuses to start without them. The Secret holds a key of at least 32 bytes with at least 8 distinct byte values (surrounding whitespace trimmed, not decoded; generate 32 random bytes and hex-encode them) and a key id that is a counter or a date: k and a number of up to six digits (k1), or YYYY-MM or YYYY-MM-DD (2026-10). The id appears in every digest, so a rotation is visible in evidence, and a label such as a person's or an environment's name is refused at startup. Rotating the key breaks correlation of digests across the rotation, which is accepted.
modelRouter.workloadIdentity.auditDigest.existingSecret "" —
modelRouter.workloadIdentity.auditDigest.keyKey key —
modelRouter.workloadIdentity.auditDigest.keyIDKey keyId —
modelRouter.providers — —
modelRouter.providers.mock — —
modelRouter.providers.mock.enabled true —
modelRouter.providers.openai — —
modelRouter.providers.openai.enabled false —
modelRouter.providers.openai.existingSecret "" —
modelRouter.providers.openai.secretKey api-key —
modelRouter.providers.openai.baseURL "" —
modelRouter.providers.openai.organization "" —
modelRouter.providers.openai.project "" —
modelRouter.providers.openai.timeout "30s" —
modelRouter.providers.openai.allowInsecureHTTP false —
modelRouter.providers.anthropic — —
modelRouter.providers.anthropic.enabled false —
modelRouter.providers.anthropic.existingSecret "" —
modelRouter.providers.anthropic.secretKey api-key —
modelRouter.providers.anthropic.baseURL "" —
modelRouter.providers.anthropic.version "2023-06-01" —
modelRouter.providers.anthropic.timeout "30s" —
modelRouter.providers.anthropic.allowInsecureHTTP false —
modelRouter.providers.bedrock — —
modelRouter.providers.bedrock.enabled false —
modelRouter.providers.bedrock.region "" —
modelRouter.providers.bedrock.timeout "30s" —
modelRouter.providers.bedrock.credentialsSecret "" Name of an existing Secret that holds AWS credentials for the AWS SDK default chain, for a cluster with no cloud role binding (for example AKS). Keys: accessKeyId, secretAccessKey, and optionally sessionToken. Prefer short-lived credentials with a session token. Leave empty where the platform supplies an identity (on EKS, the role annotation on modelRouter.serviceAccount.annotations).
modelRouter.resources {} —
modelRouter.podLabels {} —
modelRouter.podAnnotations {} —

context#

34 keys

KeyDefaultDescription
context — —
context.enabled false —
context.replicaCount 1 —
context.image — —
context.image.repository ghcr.io/bitfid-inc/agentfleet-context —
context.image.tag v0.1.0 —
context.image.digest "" —
context.service — —
context.service.type ClusterIP —
context.service.port 8086 —
context.mode "off" —
context.security — —
context.security.auth — —
context.security.auth.mode "off" —
context.security.auth.existingSecret "" —
context.security.auth.tokenKey token —
context.storage — —
context.storage.type memory —
context.storage.postgres — —
context.storage.postgres.existingSecret "" —
context.storage.postgres.dsnKey dsn —
context.storage.postgres.autoMigrate true —
context.retention — —
context.retention.days 7 —
context.cleanupInterval "5m" —
context.memory — —
context.memory.maxConversations 1000 —
context.memory.maxConversationsPerTenant 100 —
context.memory.maxTurnsPerConversation 100 —
context.memory.maxComparisonsPerConversation 20 —
context.memory.maxBytesPerConversation 262144 —
context.resources {} —
context.podLabels {} —
context.podAnnotations {} —

demoAgents#

48 keys

KeyDefaultDescription
demoAgents — —
demoAgents.enabled false —
demoAgents.namespace agentfleet-demo —
demoAgents.refund — —
demoAgents.refund.enabled true —
demoAgents.refund.replicaCount 1 —
demoAgents.refund.image — —
demoAgents.refund.image.repository ghcr.io/bitfid-inc/examples/refund-agent —
demoAgents.refund.image.tag v0.1.0 —
demoAgents.refund.image.digest "" —
demoAgents.refund.service — —
demoAgents.refund.service.port 8080 —
demoAgents.order — —
demoAgents.order.enabled true —
demoAgents.order.replicaCount 1 —
demoAgents.order.image — —
demoAgents.order.image.repository ghcr.io/bitfid-inc/examples/order-agent —
demoAgents.order.image.tag v0.1.0 —
demoAgents.order.image.digest "" —
demoAgents.order.service — —
demoAgents.order.service.port 8080 —
demoAgents.it — —
demoAgents.it.enabled true —
demoAgents.it.replicaCount 1 —
demoAgents.it.image — —
demoAgents.it.image.repository ghcr.io/bitfid-inc/examples/it-support-agent —
demoAgents.it.image.tag v0.1.0 —
demoAgents.it.image.digest "" —
demoAgents.it.service — —
demoAgents.it.service.port 8080 —
demoAgents.customerProfileMCP — —
demoAgents.customerProfileMCP.enabled false —
demoAgents.customerProfileMCP.replicaCount 1 —
demoAgents.customerProfileMCP.image — —
demoAgents.customerProfileMCP.image.repository ghcr.io/bitfid-inc/examples/customer-profile-mcp —
demoAgents.customerProfileMCP.image.tag v0.1.0 —
demoAgents.customerProfileMCP.image.digest "" —
demoAgents.customerProfileMCP.service — —
demoAgents.customerProfileMCP.service.port 8088 —
demoAgents.a2aPeer — —
demoAgents.a2aPeer.enabled false —
demoAgents.a2aPeer.replicaCount 1 —
demoAgents.a2aPeer.image — —
demoAgents.a2aPeer.image.repository ghcr.io/bitfid-inc/examples/a2a-peer —
demoAgents.a2aPeer.image.tag v0.1.0 —
demoAgents.a2aPeer.image.digest "" —
demoAgents.a2aPeer.service — —
demoAgents.a2aPeer.service.port 18090 —

mesh#

7 keys

KeyDefaultDescription
mesh — —
mesh.enabled false —
mesh.provider istio —
mesh.mode sidecar —
mesh.mtls permissive —
mesh.labels {} —
mesh.annotations {} —

observability#

71 keys

KeyDefaultDescription
observability — —
observability.enabled true —
observability.metrics — —
observability.metrics.enabled true —
observability.metrics.path /metrics —
observability.metrics.serviceMonitor — —
observability.metrics.serviceMonitor.enabled false —
observability.tracing — —
observability.tracing.enabled false —
observability.tracing.endpoint "" —
observability.logs — —
observability.logs.level info —
observability.audit — —
observability.audit.enabled true —
observability.audit.sink — —
observability.audit.sink.type noop —
observability.audit.sink.existingSecret "" —
observability.demo — —
observability.demo.enabled false Demo observability is intentionally opt-in. Production installs should usually connect AgentFleet to an existing metrics and audit platform.
observability.demo.prometheus — —
observability.demo.prometheus.enabled true —
observability.demo.prometheus.image — —
observability.demo.prometheus.image.repository prom/prometheus —
observability.demo.prometheus.image.tag v3.5.0 —
observability.demo.prometheus.image.digest "" —
observability.demo.prometheus.service — —
observability.demo.prometheus.service.type ClusterIP —
observability.demo.prometheus.service.port 9090 —
observability.demo.prometheus.resources {} —
observability.demo.prometheus.retention 6h —
observability.demo.grafana — —
observability.demo.grafana.enabled true —
observability.demo.grafana.image — —
observability.demo.grafana.image.repository grafana/grafana —
observability.demo.grafana.image.tag 12.1.1 —
observability.demo.grafana.image.digest "" —
observability.demo.grafana.service — —
observability.demo.grafana.service.type ClusterIP —
observability.demo.grafana.service.port 3000 —
observability.demo.grafana.adminUser admin —
observability.demo.grafana.adminPassword agentfleet-demo —
observability.demo.grafana.installClickHousePlugin true —
observability.demo.grafana.resources {} —
observability.demo.audit — —
observability.demo.audit.enabled false —
observability.demo.audit.clickhouse — —
observability.demo.audit.clickhouse.enabled true —
observability.demo.audit.clickhouse.image — —
observability.demo.audit.clickhouse.image.repository clickhouse/clickhouse-server —
observability.demo.audit.clickhouse.image.tag "25.7" —
observability.demo.audit.clickhouse.image.digest "" —
observability.demo.audit.clickhouse.service — —
observability.demo.audit.clickhouse.service.type ClusterIP —
observability.demo.audit.clickhouse.service.httpPort 8123 —
observability.demo.audit.clickhouse.service.nativePort 9000 —
observability.demo.audit.clickhouse.database agentfleet_audit —
observability.demo.audit.clickhouse.table otel_logs —
observability.demo.audit.clickhouse.username agentfleet —
observability.demo.audit.clickhouse.password agentfleet-audit-change-me —
observability.demo.audit.clickhouse.persistence — —
observability.demo.audit.clickhouse.persistence.enabled true —
observability.demo.audit.clickhouse.persistence.size 5Gi —
observability.demo.audit.clickhouse.persistence.storageClass "" —
observability.demo.audit.clickhouse.resources {} —
observability.demo.audit.collector — —
observability.demo.audit.collector.enabled true —
observability.demo.audit.collector.image — —
observability.demo.audit.collector.image.repository otel/opentelemetry-collector-contrib —
observability.demo.audit.collector.image.tag 0.155.0 —
observability.demo.audit.collector.image.digest "" —
observability.demo.audit.collector.resources {} —

postgresql#

27 keys

KeyDefaultDescription
postgresql — —
postgresql.enabled false —
postgresql.external — —
postgresql.external.host "" —
postgresql.external.port 5432 —
postgresql.external.database agentfleet —
postgresql.external.existingSecret "" —
postgresql.bundled — —
postgresql.bundled.enabled false Dev/local only. Production profiles must use an externally managed PostgreSQL service and caller-managed DSN Secrets.
postgresql.bundled.replicaCount 1 —
postgresql.bundled.image — —
postgresql.bundled.image.repository postgres —
postgresql.bundled.image.tag 16-alpine —
postgresql.bundled.image.digest "" —
postgresql.bundled.service — —
postgresql.bundled.service.port 5432 —
postgresql.bundled.auth — —
postgresql.bundled.auth.username agentfleet —
postgresql.bundled.auth.database agentfleet —
postgresql.bundled.auth.password "" Empty generates or reuses a chart-managed Secret password.
postgresql.bundled.persistence — —
postgresql.bundled.persistence.enabled true —
postgresql.bundled.persistence.size 1Gi —
postgresql.bundled.persistence.storageClass "" —
postgresql.bundled.resources {} —
postgresql.bundled.podLabels {} —
postgresql.bundled.podAnnotations {} —

redis#

6 keys

KeyDefaultDescription
redis — —
redis.enabled false —
redis.external — —
redis.external.host "" —
redis.external.port 6379 —
redis.external.existingSecret "" —

eventBus#

9 keys

KeyDefaultDescription
eventBus — —
eventBus.enabled false —
eventBus.type "" —
eventBus.kafka — —
eventBus.kafka.brokers [] —
eventBus.kafka.existingSecret "" —
eventBus.nats — —
eventBus.nats.url "" —
eventBus.nats.existingSecret "" —

integrations#

65 keys

KeyDefaultDescription
integrations — —
integrations.google — —
integrations.google.enabled false —
integrations.google.project "" —
integrations.google.region "" —
integrations.google.existingSecret "" —
integrations.databricks — —
integrations.databricks.enabled false —
integrations.databricks.workspaceURL "" —
integrations.databricks.existingSecret "" —
integrations.mcp — —
integrations.mcp.enabled false —
integrations.mcp.router — —
integrations.mcp.router.enabled false —
integrations.mcp.router.replicaCount 1 —
integrations.mcp.router.image — —
integrations.mcp.router.image.repository ghcr.io/bitfid-inc/agentfleet-mcp-router —
integrations.mcp.router.image.tag v0.1.0 —
integrations.mcp.router.image.digest "" —
integrations.mcp.router.service — —
integrations.mcp.router.service.type ClusterIP —
integrations.mcp.router.service.port 8087 —
integrations.mcp.router.metrics — —
integrations.mcp.router.metrics.enabled true —
integrations.mcp.router.metrics.path "" Empty inherits observability.metrics.path.
integrations.mcp.router.security — Bearer token every caller of /internal/v1/mcp/ must present (the Console presents it when it lists tools). The router refuses to start in audit or enforce mode without one, unless allowUnauthenticated is set; the chart refuses to render that combination too. /healthz, /readyz and metrics stay open for probes and scrapes. The Secret is yours to create.
integrations.mcp.router.security.auth — —
integrations.mcp.router.security.auth.existingSecret "" —
integrations.mcp.router.security.auth.tokenKey token —
integrations.mcp.router.security.auth.allowUnauthenticated false Local development only: audit or enforce mode with no token.
integrations.mcp.router.serviceAccount — The router's own ServiceAccount, used only when workloadIdentity.enabled; without it the router runs as the chart's shared ServiceAccount, as before. It is separate from the shared one so that the TokenReview permission below does not go to every component that uses the shared one.
integrations.mcp.router.serviceAccount.create true —
integrations.mcp.router.serviceAccount.name "" —
integrations.mcp.router.serviceAccount.annotations {} —
integrations.mcp.router.serviceAccount.automountServiceAccountToken false Needs true when workloadIdentity.enabled, so the router can call TokenReview.
integrations.mcp.router.workloadIdentity — Check the caller and the MCP server against the Registry before a request is forwarded (issue #688, rule 7 of docs/decisions/2026-11-08-the-registry-is-the-one-inventory-and-only-approved-agents-route.md). A caller presents its projected ServiceAccount token for the audience below; the router verifies it with TokenReview, maps agentfleet-agent-<name> in namespace <ns> to the Registry agent <ns>/<name>, and forwards only when that record and the MCP server's record are both registered or managed and name the server's tenant. Every entry in integrations.mcp.servers then needs registry.workspace and registry.agentId, the immutable identity of the server's Registry record (the Registry has no MCP server record kind: it is an ordinary agent record that you register and an administrator approves). With this on, the shared token above is neither required nor accepted, so the Console's tool catalog, which presents only that token, is refused until the Console is given a workload identity of its own (it is not, here). The Registry address and its read-only token come from the chart's Registry and registry.security.auth, as for the Model Router. Off by default, as the Model Router's is; the router then makes no Registry call.
integrations.mcp.router.workloadIdentity.enabled false —
integrations.mcp.router.workloadIdentity.audience agentfleet-mcp-router —
integrations.mcp.router.workloadIdentity.cacheTTL "30s" How long a TokenReview result is reused. The Registry answer is reused for at most 5 seconds (60 while the Registry cannot be reached), whatever this is.
integrations.mcp.router.workloadIdentity.registryURL "" Defaults to the in-chart Registry.
integrations.mcp.router.workloadIdentity.auditDigest — The audit record names a caller and a server by keyed digests, never by name. The Secret holds the same kind of key and key id as modelRouter.workloadIdentity.auditDigest, and the same ones if the digests of the two routers should correlate.
integrations.mcp.router.workloadIdentity.auditDigest.existingSecret "" —
integrations.mcp.router.workloadIdentity.auditDigest.keyKey key —
integrations.mcp.router.workloadIdentity.auditDigest.keyIDKey keyId —
integrations.mcp.router.config — —
integrations.mcp.router.config.mode "off" —
integrations.mcp.router.config.discovery — —
integrations.mcp.router.config.discovery.refreshInterval "5m" —
integrations.mcp.router.config.call — —
integrations.mcp.router.config.call.timeout "15s" —
integrations.mcp.router.config.call.maxResponseBytes 1048576 —
integrations.mcp.router.policy — The Policy service the router asks before every tools/call. Empty url uses this chart's Policy service when policy.enabled. In audit or enforce mode the chart refuses to render without one, unless allowNoPolicy is set (local development only: every call is then allowed). failurePolicy is what a call does when the Policy service is unavailable (it cannot be reached, times out, answers 5xx or answers something that is not a decision): fail refuses it, fallback forwards it and records that it did. Any other non-2xx below 500 (a 4xx, or a redirect, which is not followed) refuses the call under both. allowNoPolicy=true also leaves the router without a Policy URL even when policy.enabled, so it allows every call; setting it together with url is refused.
integrations.mcp.router.policy.url "" —
integrations.mcp.router.policy.failurePolicy fail —
integrations.mcp.router.policy.allowNoPolicy false —
integrations.mcp.router.audit — One event per tools/call policy step. mode is off, async or sync_required (sync_required refuses a call whose event cannot be delivered). sinkType is stdout or noop: the router binary also has a spool sink, which this chart does not give a volume, so it cannot be selected here.
integrations.mcp.router.audit.mode async —
integrations.mcp.router.audit.sinkType stdout —
integrations.mcp.router.resources {} —
integrations.mcp.router.podLabels {} —
integrations.mcp.router.podAnnotations {} —
integrations.mcp.servers [] —
integrations.a2a — —
integrations.a2a.enabled false —
integrations.a2a.agentCards [] —

console#

113 keys

KeyDefaultDescription
console — —
console.enabled false —
console.replicaCount 1 —
console.image — —
console.image.repository ghcr.io/bitfid-inc/agentfleet-console —
console.image.tag v0.1.0 —
console.image.digest "" —
console.service — —
console.service.type ClusterIP —
console.service.port 8089 —
console.serviceAccount — —
console.serviceAccount.create true —
console.serviceAccount.name "" —
console.serviceAccount.annotations {} —
console.registration — Agent registration and Admin or Owner approval of identity bindings (#312). Off by default. When enabled the Console writes the identity binding store, so it needs a database role of its own (SELECT, INSERT, UPDATE and DELETE on identity_bindings, identity_binding_tombstones, identity_binding_releases and identity_registration_requests; an UPDATE or DELETE with a WHERE clause also reads the column) that is not the Gateway's read-only role and not the prune job's. The chart can only compare Secret references: existingSecret and dsnKey below must differ from both, and that the role behind them is a separate one is yours to set up. The identity audit key is the Gateway's (gateway.config.auth.entraApp.auditDigest), so the Console names a caller in audit events with the same digest. The binding store's quiescence interval is the Gateway's registryRefreshSeconds: the chart passes that one value to both, so they cannot drift apart. The bound is exact only when the Gateway's last read and a disable are seen together: a read already in flight when a disable commits can keep a binding admitted for its own latency past the refresh. Needs the Gateway in entra_app mode, OIDC Console sessions (console.config.auth.mode=oidc_session; the chart refuses header_dev, whose actor and roles are request headers), tenant authorization set to enforce, and identityPrune.enabled (a deleted binding's tombstone is kept only for tombstoneRetention). A registration request and a pending-caller decision hold raw directory and object IDs (exception 7 of docs/known-limitations.md).
console.registration.enabled false —
console.registration.postgres — —
console.registration.postgres.existingSecret "" —
console.registration.postgres.dsnKey dsn —
console.registration.pendingAdmin — Lets the Console list, approve and reject the Registry's pending callers. The token is the key `tokenKey` of registry.security.auth.existingSecret; it must differ from the Registry's write, read and pending tokenKeys. The Registry mounts its Admin routes only when it holds it, and the Console is its only intended holder. Needs registry.pendingCallers.enabled.
console.registration.pendingAdmin.enabled false —
console.registration.pendingAdmin.tokenKey pending-admin-token —
console.registration.tombstoneRetention "720h" How long the tombstone of a deleted binding is kept (an audit retention period the operator sets). identityPrune deletes it once it has expired.
console.registration.expiry "" How long a registration request stays pending; empty takes the Console's default of 168h. A pending request holds its raw values for this plus one sweepInterval, on a running Console whose sweep succeeds.
console.registration.purgeAfter "" How long a decided request (which holds no identifier) is kept; empty takes the Console's default of 24h.
console.registration.sweepInterval "15m" How often the Console expires and purges requests.
console.usage — Usage Center is an optional tenant-scoped projection over model.call audit evidence. It does not store prompts or model replies.
console.usage.enabled false —
console.usage.clickhouse — —
console.usage.clickhouse.url "" —
console.usage.clickhouse.database agentfleet_audit —
console.usage.clickhouse.table otel_logs —
console.usage.clickhouse.username default —
console.usage.clickhouse.existingSecret "" —
console.usage.clickhouse.passwordKey password —
console.usage.clickhouse.allowInsecureHTTP false —
console.usage.queryTimeout "5s" —
console.usage.defaultWindow "168h" —
console.usage.maximumWindow "2160h" —
console.discoveryView — The read-only discovery view (#594, #627): the latest stored discovery run's coverage and scope rules, and the Registry's observed records, on the Console's discovery page. Off by default; with it off the page says the Console has no read access to the store and shows no run. When enabled the Console starts with -discovery-view and receives one variable, AGENTFLEET_DISCOVERY_POSTGRES_DSN, from the Secret named below, and the chart mounts nothing else for the view. existingSecret is required (there is no default) and the render fails without it. The render refuses a Secret that holds another credential, as far as this check knows: any Secret that another existingSecret or credentialsSecret value in this file names (the salt, the store key, every connection string, the service and provider tokens, the audit keys, the Console's own credentials), and the four Secrets the chart generates that this check knows (the bundled PostgreSQL's, the Console local demo's, the demo Grafana's and ClickHouse's). It cannot know about a Secret that no value here names, such as one you created by hand for something else: use a Secret made for this alone. The role behind it should get SELECT on discovery_runs and nothing else inside its database; the chart cannot see inside a Secret, so that is yours to set up. The SQL is in the chart README ("Console Discovery View"). PostgreSQL grants PUBLIC CONNECT and TEMPORARY on every database, and CREATE on schema public in a cluster initialised before version 15, and the README's REVOKEs act only on the one database, so the role can still create temporary objects (and on a cluster initialised before version 15, tables in public) in databases where PUBLIC keeps those grants; REVOKE CONNECT ... FROM PUBLIC on the others, or restrict the role in pg_hba.conf, to close that. The SQL has not been run. kubectl create secret generic console-discovery-view \ --from-literal=dsn='postgres://agentfleet_console_discovery:...@host/db'
console.discoveryView.enabled false —
console.discoveryView.existingSecret "" —
console.discoveryView.dsnKey dsn —
console.config — —
console.config.registryURL "" —
console.config.gatewayURL "" —
console.config.schedulerURL "" —
console.config.policyURL "" —
console.config.contextURL "" —
console.config.modelRouterURL "" —
console.config.mcpRouterURL "" —
console.config.a2aCardBaseURL "https://agentfleet.example.invalid/a2a/v1" —
console.config.defaultTenant "" —
console.config.auth — —
console.config.auth.mode "off" Console sign-in: off, header_dev, oidc_session, or local_demo (a sandbox-only username and password for one account).
console.config.auth.localDemo — Settings for local_demo. The chart refuses local_demo in the production profile and needs an explicit password here; the demo profile sets one, so no install gets a default. The password is stored in the agentfleet-console-local-demo Secret. The account name is fixed to demo-admin, so no install can make it a person's name.
console.config.auth.localDemo.password "" —
console.config.auth.localDemo.tenant retail —
console.config.auth.requireTenant false —
console.config.auth.requireActor false —
console.config.auth.headerDev — —
console.config.auth.headerDev.tenantHeader "X-AgentFleet-Tenant" —
console.config.auth.headerDev.actorHeader "X-AgentFleet-Actor" —
console.config.auth.headerDev.actorTypeHeader "X-AgentFleet-Actor-Type" —
console.config.auth.oidc — —
console.config.auth.oidc.issuerURL "" —
console.config.auth.oidc.clientID "" —
console.config.auth.oidc.existingSecret "" —
console.config.auth.oidc.clientSecretKey client-secret —
console.config.auth.oidc.publicBaseURL "" —
console.config.auth.oidc.scopes — —
console.config.auth.oidc.tenantClaim tenant_id —
console.config.auth.oidc.groupsClaim groups —
console.config.auth.oidc.allowedAlgorithms — —
console.config.auth.oidc.allowInsecureHTTP false —
console.config.auth.oidc.discoveryTimeout "10s" —
console.config.auth.oidc.session — —
console.config.auth.oidc.session.store memory —
console.config.auth.oidc.session.cookieName agentfleet_console_session —
console.config.auth.oidc.session.secureCookie true —
console.config.auth.oidc.session.sameSite lax —
console.config.auth.oidc.session.ttl "8h" —
console.config.auth.oidc.session.loginStateTTL "10m" —
console.config.auth.oidc.session.maxSessions 10000 —
console.config.auth.oidc.session.maxLoginAttempts 1000 —
console.config.auth.oidc.session.postgres — —
console.config.auth.oidc.session.postgres.existingSecret "" —
console.config.auth.oidc.session.postgres.dsnKey dsn —
console.config.auth.oidc.session.postgres.autoMigrate true —
console.config.auth.oidc.session.postgres.sweepInterval "5m" —
console.config.tenantAuthz — —
console.config.tenantAuthz.mode "off" —
console.config.tenantAuthz.readRoles — —
console.config.tenantAuthz.static — —
console.config.tenantAuthz.static.memberships [] —
console.config.dependencyTimeout "2s" —
console.config.comparisonTimeout "45s" Keep above the largest of modelRouter.providers.{openai,anthropic,bedrock}.timeout (30s by default; the chart renders them as -provider-*-timeout). The router calls the models in parallel, one bounded call each, so this is an approximation of the slowest leg plus overhead.
console.inventory — —
console.inventory.kubernetes — —
console.inventory.kubernetes.enabled true —
console.inventory.kubernetes.namespace "" —
console.inventory.kubernetes.kubeconfig "" —
console.inventory.kubernetes.context "" —
console.inventory.kubernetes.fallbackToFiles true —
console.catalog — —
console.catalog.routes [] —
console.catalog.policies [] —
console.catalog.useMCPRouterConfig true —
console.demoCatalog — —
console.demoCatalog.enabled false —
console.resources {} —
console.podLabels {} —
console.podAnnotations {} —

profiles#

9 keys

KeyDefaultDescription
profiles — —
profiles.demo — —
profiles.demo.enabled false —
profiles.mesh — —
profiles.mesh.enabled false —
profiles.observability — —
profiles.observability.enabled false —
profiles.production — —
profiles.production.enabled false —

tunnel#

20 keys

KeyDefaultDescription
tunnel — Cloudflare Tunnel connector: an outbound-only cloudflared Deployment that carries a Cloudflare-hosted hostname to the Console. Experimental and not verified on a cluster: it has only been rendered, not run. Its container drops privilege escalation and every capability, runs under the RuntimeDefault seccomp profile and does not mount a ServiceAccount token. It does not yet run as non-root or with a read-only root filesystem (both wait on a cluster run that shows what the image needs), it ignores the global.* pod settings and sets no serviceAccountName, and its image is pinned by tag, not digest (#388). The token is read from the named Secret key; the public hostname and its route are set in Cloudflare. NetworkPolicy: networkPolicy.enabled renders ingress policies for the Registry, Model Router, Context and Guard only. None selects the Console or this connector, and the chart renders no egress policy for either, so with networkPolicy.enabled the connector can still reach the Console Service and the Cloudflare edge. A default-deny policy added outside the chart would need its own rules for both.
tunnel.enabled false —
tunnel.replicaCount 1 —
tunnel.image — —
tunnel.image.repository cloudflare/cloudflared —
tunnel.image.tag "2025.8.1" —
tunnel.existingSecret cloudflared-tunnel —
tunnel.tokenKey token —
tunnel.resources — Conservative starting values for one connector carrying the Console only. They are an estimate, not a measurement: no cluster run has shown the connector's real usage yet. There is no CPU limit, so a burst is slowed by contention rather than throttled.
tunnel.resources.requests — —
tunnel.resources.requests.cpu 10m —
tunnel.resources.requests.memory 32Mi —
tunnel.resources.limits — —
tunnel.resources.limits.memory 128Mi —
tunnel.readinessProbe — cloudflared's metrics listener, which serves its documented /ready endpoint, on this port. The chart always passes --metrics: left unset, the container build of cloudflared binds the listener on 0.0.0.0 (ports 20241-20245), and the same listener also serves /metrics, /config, Go's /debug/pprof handlers and cloudflared's diagnostic endpoints. Both points are read from cloudflared 2025.8.1's metrics/metrics.go and Makefile, not observed on a running connector. Off (the default), the listener binds 127.0.0.1 only and there is no probe. On, it binds every pod address, because the kubelet probes the pod IP, and adds a readiness probe on /ready. No chart NetworkPolicy selects this pod, so with the probe on, any pod that can reach the pod IP can read those endpoints. Turn it on only where something else restricts ingress to the connector to the kubelet. The probe is unverified on a cluster: the endpoint and the flag are taken from Cloudflare's documentation.
tunnel.readinessProbe.enabled false —
tunnel.readinessProbe.port 2000 —
tunnel.readinessProbe.initialDelaySeconds 5 —
tunnel.readinessProbe.periodSeconds 10 —
tunnel.readinessProbe.failureThreshold 3 —